PHPackages                             epic-64/elem - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. epic-64/elem

ActiveLibrary[Templating &amp; Views](/categories/templating)

epic-64/elem
============

A fluent, type-safe PHP library for building HTML documents using the DOM

0.6.0(2mo ago)272.9k↑3550%MITPHPPHP &gt;=8.4CI passing

Since Feb 14Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/epic-64/elem)[ Packagist](https://packagist.org/packages/epic-64/elem)[ Docs](https://github.com/epic-64/elem)[ RSS](/packages/epic-64-elem/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (2)Versions (7)Used By (0)

Elem
====

[](#elem)

[![Tests](https://github.com/epic-64/elem/actions/workflows/tests.yml/badge.svg)](https://github.com/epic-64/elem/actions/workflows/tests.yml)[![Coverage](https://camo.githubusercontent.com/c9f095a84c0b241a35f4b6996930940d4f96e7f93857abe3879715fb08531031/68747470733a2f2f657069632d36342e6769746875622e696f2f656c656d2f6261646765732f636f7665726167652e737667)](https://epic-64.github.io/elem/coverage/index.html)[![Lib Lines](https://camo.githubusercontent.com/94090fa897af0f990cbfb30f07c61726f1e44d79cf35390d226b9a3c7e58d12f/68747470733a2f2f657069632d36342e6769746875622e696f2f656c656d2f6261646765732f6c6f632d7372632e737667)](https://github.com/epic-64/elem/tree/main/src)[![Test Lines](https://camo.githubusercontent.com/04f4ac5904c9b43ba2920b7dcbd9cfe670b3707502ce84927718bae8bc442fee/68747470733a2f2f657069632d36342e6769746875622e696f2f656c656d2f6261646765732f6c6f632d746573742e737667)](https://github.com/epic-64/elem/tree/main/tests)[![PHP](https://camo.githubusercontent.com/96704ae292bc7bb735a02d44cb09a331c2709cbf68f1ba39ea54af4693a6c3b5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e342b2d3737374242342e737667)](https://www.php.net/)[![PHPStan](https://camo.githubusercontent.com/70237ca9cbbd2749f729e2c3cd1c3446ffa316f600984bafcbe197d8e9a11d9b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d4c6576656c253230392d627269676874677265656e2e737667)](https://phpstan.org/)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)[![Packagist](https://camo.githubusercontent.com/abd481999fb59710e5e38fd58e664a3a5f0c70389d8aef2b831fa1c19ae3574d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f657069632d36342f656c656d2e737667)](https://packagist.org/packages/epic-64/elem)

> **Finally, you can be an HTML programmer.**
> Put it on your resume and I will take you for a beer.

A fluent, type-safe PHP library for building HTML documents using the DOM.

```
composer require epic-64/elem
```

Showcase
--------

[](#showcase)

### It reads like HTML, but it's PHP

[](#it-reads-like-html-but-its-php)

```
div(id: 'hero', class: 'container')(
    h(1, text: 'Welcome'),
    p(text: 'Build HTML with pure PHP.'),
    div(class: 'actions')(
        a(href: '/start', class: 'btn btn-primary', text: 'Get Started'),
        a(href: '/docs', class: 'btn', text: 'Learn More')
    )
)
```

Output:

```

    Welcome
    Build HTML with pure PHP.

        Get Started
        Learn More

```

### Components are just functions

[](#components-are-just-functions)

```
function card(string $title, string $body): Element {
    return div(class: 'card')(
        h(3, text: $title),
        p(text: $body)
    );
}

// Use it anywhere
div(class: 'grid')(
    card('Fast', 'No template parsing overhead.'),
    card('Safe', 'XSS protection built-in.'),
    card('Smart', 'Full IDE support.')
)
```

### Full power of PHP - not a crippled template language

[](#full-power-of-php---not-a-crippled-template-language)

```
div(class: 'user-list')(
    list_of($users)
        ->filter(fn(User $u) => $u->isActive())
        ->map(fn(User $u) => userCard($u))
)
```

### Type-safe - your IDE and PHPStan catch mistakes

[](#type-safe---your-ide-and-phpstan-catch-mistakes)

```
// ❌ Blade: Typo? Runtime surprise!
Click

// ✅ Elem: Caught before you save
a(hfer: $url)  // Error: Unknown parameter "hfer"
```

### XSS-safe by default

[](#xss-safe-by-default)

```
$evil = 'alert("xss")';
echo div(text: $evil);
// Output: &lt;script&gt;alert("xss")&lt;/script&gt;
```

### Easy conditional modifications with `when()`

[](#easy-conditional-modifications-with-when)

```
$isAdmin = false;
$isActive = true;
div(class: 'card')
    ->when($isAdmin, fn($el) => $el->class('admin'))
    ->when($isActive, fn($el) => $el->class('active'))
// Output:
```

### Layouts with slots

[](#layouts-with-slots)

```
function page(string $title, array $head = [], array $body = []): Element {
    return html(lang: 'en')(
        head()(
            title(text: $title),
            meta(charset: 'UTF-8'),
            meta(name: 'viewport', content: 'width=device-width, initial-scale=1.0'),
            ...$head
        ),
        body()(...$body)
    );
}

page('Home',
    head: [stylesheet('/css/app.css')],
    body: [h(1, text: 'Welcome'), p(text: 'Hello!')]
);
```

Output:

```
>

    Home

    Welcome
    Hello!

```

---

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Why Elem?](#why-elem)
- [Examples](#examples)
- [Extending Elem](#extending-elem)
- [How It Works](#how-it-works)
- [API Reference](#api-reference)
- [Demo Server](#demo-server)
- [Development](#development)
- [License](#license)

Installation
------------

[](#installation)

**Requirements:** PHP 8.4+, ext-dom

```
composer require epic-64/elem
```

Quick Start
-----------

[](#quick-start)

```
use function Epic64\Elem\{div, p, a, span, html, head, body, title, meta, h};

// Simple elements
echo div(id: 'container', class: 'wrapper')(
    p(text: 'Hello, World!'),
    a(href: 'https://example.com', text: 'Click me')->blank(),
    span(class: 'highlight', text: 'Important')
);

// Complete HTML document
echo html(lang: 'en')(
    head()(
        meta(charset: 'UTF-8'),
        title(text: 'My Page')
    ),
    body()(
        div(id: 'app')(
            h(1, text: 'Welcome'),
            p(text: 'This is my page.')
        )
    )
);
```

Why Elem?
---------

[](#why-elem)

- **Type-safe** - Your IDE knows what's happening. Autocomplete, refactoring, and PHPStan just work.
- **Composable** - Build reusable components as plain functions. No magic, no framework lock-in.
- **Pure PHP** - Full power of the language: loops, conditionals, functions, type hints.
- **XSS-safe** - Text is automatically escaped through the DOM.
- **LLM-friendly** - Named parameters and type checking catch AI-generated mistakes.

📖 **[Full documentation: Why Elem?](docs/why-elem.md)** *(coming soon)*

Examples
--------

[](#examples)

### Basic Elements

[](#basic-elements)

```
// Forms
form(action: '/login')(
    input(type: 'email', name: 'email')->required()->placeholder('Email'),
    input(type: 'password', name: 'password')->required(),
    button(text: 'Login', type: 'submit')
);

// Lists
ul(class: 'nav')(
    li(text: 'Home'),
    li(text: 'About')
);

// Tables
table()(
    tr()(th(text: 'Name'), th(text: 'Age')),
    tr()(td(text: 'Alice'), td(text: '30'))
);
```

📖 **[Full documentation: Basic Examples](docs/basic-examples.md)**

### Composition &amp; Dynamism

[](#composition--dynamism)

Use PHP's full power: enums, typed classes, functions, and native control flow.

```
function userCard(User $user): Element
{
    return div(class: 'user-card')(
        avatar($user->name),
        badge($user->role->value, $user->role->badge()),
        $user->active ? badge('Active', BadgeVariant::Success) : null,
    );
}

// Filter and map with full type safety
div(class: 'user-list')(
    list_of($users)
        ->filter(fn(User $u) => $u->active)
        ->map(fn(User $u) => userCard($u))
);
```

📖 **[Full documentation: Composition &amp; Dynamism](docs/composition-and-dynamism.md)**

### Templating &amp; Layouts

[](#templating--layouts)

Build reusable page layouts with multiple "slots" for content injection:

```
function dashboardLayout(
    string $pageTitle,
    array $headerSlot = [],
    array $mainSlot = [],
): Element {
    return pageLayout(
        pageTitle: $pageTitle,
        bodySlot: [
            div(class: 'dashboard')(
                el('header')(...$headerSlot),
                el('main')(...$mainSlot),
            ),
        ],
    );
}

// Fill only the slots you need
dashboardLayout(
    pageTitle: 'My Dashboard',
    headerSlot: [h(1, text: '🚀 My App')],
    mainSlot: [card('Stats', $statsContent)],
);
```

📖 **[Full documentation: Templating &amp; Layouts](docs/templating-and-layouts.md)**

### Imperative Style

[](#imperative-style)

While Elem encourages functional style, sometimes imperative code is clearer. Use `when()` for simple conditionals:

```
div(class: 'card')
    ->when($isAdmin, fn($el) => $el->class('admin'))
    ->when($isActive, fn($el) => $el->class('active'))
```

Use `tap()` for more complex logic:

```
div(class: 'user-card')
    ->tap(function ($el) use ($isAdmin, $permissions) {
        if ($isAdmin) {
            $el->class('admin');
        }
        foreach ($permissions as $perm) {
            $el->data("can-$perm", 'true');
        }
    })
```

📖 **[Full documentation: Imperative Style](docs/imperative-style.md)**

### HTMX Integration

[](#htmx-integration)

Return HTML fragments directly from your endpoints - no JSON serialization needed:

```
// Add HTMX attributes
button(text: 'Load More')
    ->attr('hx-get', '/api/items')
    ->attr('hx-target', '#results')
    ->attr('hx-swap', 'beforeend')

// Return HTML from your API
function handleSearch(string $query): void {
    $users = searchUsers($query);
    echo ul(class: 'search-results')(
        list_of($users)->map(fn($user) =>
            li(text: $user->name)
        )
    );
}
```

### Linking External Resources

[](#linking-external-resources)

```
head()(
    stylesheet('/css/style.css'),
    icon('/favicon.ico'),
    font('/fonts/custom.woff2', 'font/woff2'),
    link(href: '/manifest.json', rel: 'manifest')
)
```

How It Works
------------

[](#how-it-works)

Elem is built on PHP's native [DOM extension](https://www.php.net/manual/en/book.dom.php). Each element wraps a `DOMElement`, and the `__invoke` magic method lets you add children by calling the element as a function:

```
// This fluent syntax...
div(class: 'card')(
    h(1, text: 'Title'),
    p(text: 'Content')
);

// ...uses __invoke to append children to the DOM
```

📖 **[Full documentation: How It Works](docs/how-it-works.md)**

Extending Elem
--------------

[](#extending-elem)

### Custom Elements with `el()`

[](#custom-elements-with-el)

Use `el()` to create any element by tag name:

```
use function Epic64\Elem\el;

el('article', class: 'post')(...);
el('nav', class: 'main-nav')(...);
el('my-custom-component')->attr('some-prop', 'value');
```

### Custom Attributes with `->attr()`

[](#custom-attributes-with--attr)

```
// ARIA attributes
button(text: 'Menu')
    ->attr('aria-expanded', 'false')
    ->attr('aria-controls', 'menu-panel');

// Data attributes (or use ->data())
div()->data('controller', 'dropdown');

// HTMX, Alpine.js, or any other library
div()
    ->attr('hx-get', '/api/data')
    ->attr('x-data', '{ open: false }');
```

### Raw HTML with `raw()`

[](#raw-html-with-raw)

When you have trusted HTML from an external source (Markdown parser, CMS, sanitizer):

```
use function Epic64\Elem\raw;

$html = $markdownParser->convert($markdown);
div(class: 'prose')(raw($html));
```

> ⚠️ **Never use `raw()` with user input** - it bypasses XSS protection.

### Adding Text to Elements

[](#adding-text-to-elements)

There are three ways to add text content:

```
use function Epic64\Elem\text;

// 1. Using the text: parameter
p(text: 'Hello, World!');

// 2. Using plain strings as children
p()('Hello, World!');

// 3. Using text() for explicit text nodes
p()(text('Hello, World!'));
```

All three methods automatically escape content for XSS protection.

API Reference
-------------

[](#api-reference)

### Element Classes

[](#element-classes)

All element classes extend the base `Element` class and provide fluent interfaces:

- **Structure**: `Html`, `Head`, `Body`, `Title`, `Meta`, `Link`, `Style`, `Script`
- **Text**: `Div`, `Span`, `Paragraph`, `Heading`
- **Links &amp; Media**: `Anchor`, `Image`
- **Forms**: `Form`, `Input`, `Button`, `Label`, `Textarea`, `Select`, `Option`
- **Lists**: `UnorderedList`, `OrderedList`, `ListItem`
- **Tables**: `Table`, `TableRow`, `TableCell`, `TableHeader`
- **Special**: `RawHtml` - Holds unescaped HTML content (use via `raw()` function)

### Common Methods

[](#common-methods)

All elements support:

- `->id(string $id)` - Set the id attribute
- `->class(string ...$classes)` - Add CSS classes
- `->attr(string $name, string $value)` - Set any attribute
- `->style(string $style)` - Set inline styles
- `->data(string $name, string $value)` - Set data-\* attributes
- `->tap(callable $callback)` - Tap into the element for imperative modifications
- `->when(bool $condition, callable $callback)` - Conditionally apply modifications
- `->toHtml(bool $pretty = false)` - Output HTML
- `->toPrettyHtml()` - Output formatted HTML (called automatically in \_\_toString)

### Helper Functions

[](#helper-functions)

- `el(string $tag)` - Create a generic element with any tag name
- `raw(string $html)` - Create a `RawHtml` instance for injecting unescaped HTML
- `list_of(iterable $items)` - Create a fluent collection for mapping/filtering

Demo Server
-----------

[](#demo-server)

The `examples/` directory contains interactive demos showcasing the library's features.

### Running the Demo Server

[](#running-the-demo-server)

```
# From the project root
php -S localhost:8080 -t examples examples/server.php
```

Then open  in your browser.

### Available Demos

[](#available-demos)

- **Index** (`/`) - Overview and navigation
- **Layout Demo** (`/layout-demo`) - Complex templates with multiple slots: page layouts, dashboard layouts, cards, and modals
- **Dynamic Content Demo** (`/dynamic-content-demo`) - Showcases enums, reusable components, data transformation, and conditional rendering
- **Template Demo** (`/template-demo`) - Building complete HTML pages
- **HTMX Demo** (`/htmx-demo`) - Interactive components with HTMX integration

Development
-----------

[](#development)

### Running Tests

[](#running-tests)

```
# Run tests
vendor/bin/pest

# Run tests with coverage
vendor/bin/pest --coverage

# Run tests with coverage and enforce minimum threshold
vendor/bin/pest --coverage --min=80
```

### Static Analysis

[](#static-analysis)

```
vendor/bin/phpstan analyze
```

License
-------

[](#license)

MIT

###  Health Score

46

—

FairBetter than 93% of packages

Maintenance83

Actively maintained with recent releases

Popularity33

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

6

Last Release

87d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/69f51ba0f546c8f9aeb29ee0e151199b418b5e257b77e9216ac81139dcdd63a2?d=identicon)[epic-64](/maintainers/epic-64)

---

Top Contributors

[![epic-64](https://avatars.githubusercontent.com/u/105014007?v=4)](https://github.com/epic-64 "epic-64 (132 commits)")

---

Tags

phphtmldombuildertemplatefluenttype-safeelements

###  Code Quality

TestsPest

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/epic-64-elem/health.svg)

```
[![Health](https://phpackages.com/badges/epic-64-elem/health.svg)](https://phpackages.com/packages/epic-64-elem)
```

###  Alternatives

[phug/phug

Pug (ex-Jade) facade engine for PHP, HTML template engine structured by indentation

67292.2k13](/packages/phug-phug)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
