spatie/html-element
Generate dynamic HTML in PHP with a hyperscript-style API plus Emmet-like selectors. Build elements and attributes with a simple render helper (often wrapped as el()) to produce nested markup cleanly and programmatically, ideal for small view components.
Installation:
composer require spatie/html-element
No additional configuration is required—just autoload.
First Render:
Use the static HtmlElement::render() method or create a helper function:
use Spatie\HtmlElement\HtmlElement;
// Direct usage
echo HtmlElement::render('div', ['class' => 'container'], 'Hello');
// Recommended helper (add to a service provider or helper file)
function el(...$args) {
return HtmlElement::render(...$args);
}
First Use Case: Render a Bootstrap card with nested elements:
echo el('div.card',
el('div.card-header', 'Title'),
el('div.card-body', 'Content goes here')
);
Element Creation:
el('tag', ['attr' => 'value'], 'Content')el('div.container',
el('h1', 'Title'),
el('p', 'Body')
)
> for nesting or + for siblings:
el('div.container > div.row + div.footer')
Dynamic Attributes:
$attrs = ['class' => 'btn', 'data-id' => $id];
el('button', $attrs, 'Click Me');
el('div', [], function($el) {
$el->setAttribute('data-user', auth()->id());
});
Event Handling:
Attach JavaScript events via onclick, onchange, etc.:
el('button', ['onclick' => 'alert("Clicked!")'], 'Submit');
Integration with Blade: Create a Blade directive for reusable components:
// In a service provider
Blade::directive('element', function ($expression) {
return "<?php echo \\Spatie\\HtmlElement\\HtmlElement::render($expression); ?>";
});
Usage:
@element('div.alert', ['class' => 'success'], 'Success!')
Component-Based Rendering: Encapsulate complex elements in functions:
function card($title, $content) {
return el('div.card',
el('div.card-header', $title),
el('div.card-body', $content)
);
}
Attribute Escaping:
el('div', [], 'Raw <script>alert("XSS")</script>', false);
Emmet Syntax Limitations:
>, +) only works for the first argument. Nested elements must use explicit chaining:
// Works
el('div.container > div.row')
// Fails (use chaining instead)
el('div.container', 'div.row > div.col')
Closure Scope:
setAttribute or setContent run after the element is created. Avoid relying on parent scope variables:
$user = auth()->user();
el('div', [], function($el) use ($user) {
$el->setAttribute('data-id', $user->id); // Correct
});
Performance:
el() calls in loops. Pre-render static HTML and concatenate:
// Bad (nested calls in loop)
foreach ($items as $item) {
el('div.item', [], $item);
}
// Good (pre-render)
$html = '';
foreach ($items as $item) {
$html .= HtmlElement::render('div.item', [], $item);
}
Inspect Rendered HTML:
Use HtmlElement::renderToString() to debug output:
$html = HtmlElement::renderToString('div', [], 'Test');
dd($html); // Inspect raw HTML
Attribute Conflicts:
If attributes are ignored, check for typos or reserved keywords (e.g., class vs className).
Closure Debugging:
Add dd($el) inside closures to inspect the element state:
el('div', [], function($el) {
dd($el->attributes); // Debug attributes
});
Custom Elements:
Extend Spatie\HtmlElement\HtmlElement to add methods:
class CustomElement extends HtmlElement {
public static function button($text, $callback) {
return static::render('button', [
'onclick' => "{$callback}()"
], $text);
}
}
Attribute Modifiers:
Use the modifyAttributes method to transform attributes:
el('input', ['type' => 'text'], '', function($attrs) {
$attrs['data-processed'] = 'true';
return $attrs;
});
Event Delegation: For complex event handling, attach data attributes and use JavaScript:
el('div', [
'data-on-click' => 'handleClick',
'data-id' => $id
], 'Clickable');
Then use JavaScript to listen for data-on-click events.
Integration with Laravel Collectives:
Combine with HTML facade for seamless Blade integration:
use Collective\Html\Html as HtmlHelper;
echo HtmlHelper::script(HtmlElement::renderToString('script', [], 'alert("Hello")'));
How can I help you explore Laravel packages today?