cr0w/phml

Composable HTML builder for PHP.

Maintainers

Package info

github.com/cr0w-digital/phml

pkg:composer/cr0w/phml

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-03-20 22:28 UTC

This package is auto-updated.

Last update: 2026-04-20 22:43:12 UTC


README

A lightweight PHP library for building HTML with functions and arrays.

Installation

composer require cr0w/phml

Quick start

require 'vendor/autoload.php';

echo \phml\render(
    h('.card#main',
        h('h1', 'Hello, world!'),
        h('p', 'Built with phml.')
    )
);
<div class="card" id="main">
  <h1>Hello, world!</h1>
  <p>Built with phml.</p>
</div>

API

h(string $selector, mixed ...$args): array

Builds an HTML node as a plain PHP array. The selector supports Emmet-style shorthand for tag, id, and classes.

h('div')                          // <div></div>
h('p.lead', 'Hello')              // <p class="lead">Hello</p>
h('section.full.dark#hero')       // <section class="full dark" id="hero"></section>
h('.card#main')                   // <div class="card" id="main"></div>  (tag defaults to div)

An optional associative array as the first argument sets attributes:

h('a', ['href' => '/about', 'class' => 'nav-link'], 'About')
// <a href="/about" class="nav-link">About</a>

Children can be strings, other nodes, arrays of nodes, or false/null (ignored):

$items = array_map(fn($i) => h('li', $i), ['One', 'Two', 'Three']);

h('ul', $items)
// <ul><li>One</li><li>Two</li><li>Three</li></ul>

h('div', $isAdmin ? h('button', 'Delete') : null)
// <div></div>

render(mixed $node): string

Serializes a node (or array of nodes) to an HTML string. Text content and attribute values are escaped automatically.

render(h('p', 'Hello'))           // <p>Hello</p>
render(h('br'))                   // <br>  (void tags have no closing tag)
render(null)                      // ''
render(false)                     // ''
render([h('li', 'A'), h('li', 'B')]) // <li>A</li><li>B</li>  (fragment)

c(mixed ...$parts): string

Composes a class string from mixed inputs. Useful for conditional classes.

c('btn', 'btn-lg')
// 'btn btn-lg'

c('btn', $isLarge && 'btn-lg')
// 'btn' or 'btn btn-lg'

c('btn', ['btn-primary' => $isPrimary, 'btn-disabled' => $isDisabled])
// 'btn btn-primary'

c(['px-4', ['text-lg' => $big]])
// 'px-4' or 'px-4 text-lg'

e(mixed $value): string

Escapes a value for safe HTML output. Called automatically by render() on all text content and attribute values. Use this when interpolating values outside of render().

echo '<title>' . e($pageTitle) . '</title>';

raw(string $html): RawHtml

Marks a string as trusted HTML, bypassing escaping in render(). Use only for content you control.

render(h('div', raw('<strong>trusted</strong>')))
// <div><strong>trusted</strong></div>

Attributes

Boolean attributes

true renders the attribute name only. false and null omit the attribute entirely.

h('input', ['type' => 'checkbox', 'checked' => true, 'disabled' => false])
// <input type="checkbox" checked>

Style array

An array value for style is converted to an inline style string. camelCase keys are kebab-cased automatically.

h('div', ['style' => ['marginTop' => '1rem', 'color' => 'red']])
// <div style="margin-top: 1rem; color: red"></div>

Prefix shorthands

Any attribute key ending in - with an array value is expanded as a prefix shorthand. This covers data-, aria-, hx- (HTMX), x- (Alpine), v- (Vue), or any other framework with no special casing required.

h('div', ['data-' => ['userId' => 1, 'role' => 'admin']])
// <div data-user-id="1" data-role="admin"></div>

h('button', ['aria-' => ['label' => 'Close', 'expanded' => 'false']])
// <button aria-label="Close" aria-expanded="false"></button>

h('form', ['hx-' => ['post' => '/submit', 'swap' => 'outerHTML']])
// <form hx-post="/submit" hx-swap="outerHTML"></form>

h('div', ['x-' => ['data' => '{ open: false }', 'show' => 'open']])
// <div x-data="{ open: false }" x-show="open"></div>

camelCase subkeys are kebab-cased:

h('div', ['hx-' => ['swapOob' => 'true']])
// <div hx-swap-oob="true"></div>

Testing

composer install
composer test