Kirby Template Attributes

Better attribute API for snippets and templates

This plugin brings Vue.js/Laravel-Blade-like attribute composition to the templates of your Kirby project. This is an exploration in search of better HTML attribute handling for nested snippets and components.

Requirements

  • Kirby 4.0+ (use version 1.x for Kirby 3 installations)
  • PHP 8.1 (because of Enumeration support)

Installation

The recommended is installation via composer.

composer require fabianmichael/kirby-template-attributes

Alternatively, you can also download the plugin and install it manually by copying it to the site/plugins/ folder of your website.

Usage

Basic usage

Use the attributes() helper for generating a string of attributes:

<button <?= attributes([
  'role' => 'button',
  'aria-expanded' => 'false',
]) ?>>[…]</button>

You can also use named arguments if you prefer a leaner syntax. Be aware, that this only works as long as you don’t have dashes in your attribute names:

<img <?= attributes(
  class: 'icon',
  width: 16,
  height: 16,
  src: $image->url(),
	alt: 'The funniest donkey ever!',
) ?>>

Or if all you have is an attributes string, you can also feed the that to the attributes() helper:

<?php

// get image dimensions as height="yyy" width="xxx" 
$src  = 'img.png';
$size = getimagesize($src)[3];

?>

<img <?= attributes($size)->merge([
	'src' => $src,
	'alt' => '',
]) ?>>

⚠️ If you need XML-compatible attributes, always call $attributes->toXml() instead of just echoing the Attributes object, because otherwise all attributes will be converted to lower-case.

In many cases, you need to set different classes. The classes() helper is a nice shortcut for improved readability:

<button <?= classes([
  'button',
  'button--red' => $color === 'red', // class will only appear in class attribute, if condition is true
]) ?>>[…]</button>

The classes() helper is pretty flexible and also accepts multiple paramaters, each of those can eithe be a string or array (but please ensure to write readible code anyways):

<button <?= classes('button', [
  'button--red' => $color === 'red',
], 'absolute', 'top-0 left-0') ?>>[…]</button>

Merging attributes

# site/snippets/button.php

<button <?= attributes([
  'class' => 'button',
  'role' => 'button',
  'aria-expanded' => 'false',
  'style' => '--foo: bar',
])->merge($attr ?? []) ?>>[…]</button>

# site/templates/default.php

<?php snippet('button', [
  'attr' => [
    'role' => 'unicorn', // attributes can be overridden
    'onclick' => 'alert("everyone likes alerts!!!")',
    'class' => 'absolute top-0 left-0
      md:left-4
      xl:left-8', // classes are automatically appended to the existing attribute value and surplus whitespace is trimmed
    'style' => '--bar: foo', // style attribute value is also appended to the original value
  ],
]) ?>

Before/After

You can set $before and $after, just like for Kirby’s Html::attr() helper by using the corresponding methods:

attributes(class: 'foo')->before(' ');
attributes(class: 'foo')->after(' ');

Examples

A button component exists as a snippet in site/snippets/button.php:

<button class="button"><?= html($text ?? 'Button text') ?></button>

A common situation would be the requirement to add attributes when calling the snippet('button') helper class, e.g. class, data-*, title, aria-* etc. Developers cannot handly every possible attribute for each component. The previous attributes() helper could help here:

<button <?= attributes($attr ?? []) ?> class="button"><?= html($text ?? 'Button text') ?></button>

This works better, but we still cannot extend the class attribute easily. Enter the new attributes() helper:

<button <?= attributes([
    'class' => 'button',
])->merge($attr ?? []) ?>><?= html($text ?? 'Button text') ?></button>

Even shorter:

<button <?= classes('button')->merge($attr ?? []) ?>><?= html($text ?? 'Button text') ?></button>

This becomes even cooler, because the classes can be assigned conditionally as an array:

<?php

$text ??= 'Button text';
$size ??= 'normal';
$theme ??= null;
$attr ??= [];

?>
<button <?= attributes([
    'role' => 'button',
    'style' => [
      'font-size: 2rem;' => ($size === 'large'),
    ],
])->class([
    'button',
    "button--{$size}",
    "button--{$theme}" => $theme, // will only be merged, if $theme is trueish
])->merge($attr) ?>><?= html($text) ?></button>

This is already cool and makes working with attributes for snippets much easier, e.g. is we use the button in site/snippets/menu.php:

<nav class="menu">
    […]
    <?php snippet('button', [
        'text' => 'Toggle Menu',
        'attr' => [
            'class' => 'menu__button',
            'aria-controls' => 'menu-popup',
            'aria-expanded' => false,
            'role' => 'teapot', // overrrides the default attribute
        ],
    ]) ?>
</nav>

License

MIT (but you are highly encouraged to ❤️ sponsor me, if this piece of software helps you to pay your bills).