PHP all the way.
Skip the template.

h('.card', h('p', 'Hello')) — no templates, no compilation, no string interpolation. Nodes are plain PHP arrays. Map them, filter them, pass them around. Render when you need a string.

$ composer require cr0w/phml

Build arrays.
Render HTML.

Building nodes
// Tag, optional attrs, children
h('.card', h('p', 'Hello'));

// CSS selector shorthand
h('.card.featured#user', [
    'data-'    => ['userId' => $id],
    'aria-'    => ['label' => 'Card'],
    'style'    => ['marginTop' => '1rem'],
]);

// Void elements — self-closing
h('input', [
    'type'     => 'text',
    'required' => true,
    'disabled' => false, // omitted
]);
Rendering & escaping
// Render node tree to string
echo \phml\render(
    h('ul',
        array_map(
            fn($item) => h('li', $item),
            $items
        )
    )
);

// e() escapes for safe output
e('<script>'); // &lt;script&gt;

// raw() bypasses escaping
h('div', raw($trustedHtml));

Conditional classes
without string juggling.

c() accepts strings, booleans, associative arrays, and nested arrays in any combination. Falsy values are silently dropped. The result is a clean space-separated class string.

// Strings — always included
c('btn', 'btn-lg');
// → 'btn btn-lg'

// Conditional — include when truthy
c('btn', $active && 'is-active', $disabled && 'is-disabled');
// → 'btn is-active'  (when $active, !$disabled)

// Associative — class => condition
c('btn', [
    'btn-primary' => $primary,
    'btn-block'   => $block,
]);
// → 'btn btn-primary'  (when $primary, !$block)

// Nested arrays — flattened
c(['px-4', ['py-2', ['rounded' => true]]]);
// → 'px-4 py-2 rounded'

c() is also used internally by h() — passing a class attribute accepts the same mixed inputs directly.

Nodes are arrays.
Compose freely.

A node is a plain PHP array: [$tag, $attrs, ...$children]. Because they are data, they can be passed around, filtered, mapped, and merged before rendering. No objects, no builder chains, no state.

Composing nodes
$badge = h('span.badge', $count);

$card = h('.card',
    h('h2', $title),
    h('p', $body),
    $showBadge ? $badge : null,
);

// null and false children are silently dropped
echo \phml\render($card);
Fragments & lists
// A flat array of nodes renders as a fragment
$items = array_map(
    fn($row) => h('tr',
        h('td', $row['name']),
        h('td', $row['value']),
    ),
    $rows
);

echo \phml\render(
    h('table', $items)
);

Public surface.

Function Description
h($selector, ...$args) Build a node. Selector supports tag, #id, and .class shorthand. Optional associative array of attributes, followed by any number of children.
e($value) Escape a value for HTML output via htmlspecialchars. Called automatically on all string children in render().
c(...$parts) Compose a class string from mixed inputs — strings, conditionals, associative arrays, nested arrays.
raw($html) Wrap a string as trusted HTML. Bypasses escaping in render(). Use only with content you control.
\phml\render($node) Render a node or tree to an HTML string.