arcanedev/php-html
A lightweight PHP package for building HTML elements programmatically with a clean, fluent API. Create tags, attributes, and nested content without messy string concatenation, keeping markup generation readable, reusable, and easy to test in any PHP or Laravel project.
Install with composer require arcanedev/php-html, then confirm what you actually got before designing around it:
composer show arcanedev/php-html
Check the installed version, PHP constraint, and whether it pulls in illuminate/support. If a service provider/facade is auto-discovered in your Laravel version, you can use the facade; otherwise the static builder (Arcanedev\Html\Html) and element classes work standalone. The first place to look is the package src/ directory—particularly the element factory and the base Element class—because the README is thin and the available tag methods are easiest to enumerate from the code.
Your first use case should be a dynamic fragment that is awkward in Blade: a list built from a collection, a status badge, or an email digest. Smoke-test it in php artisan tinker or a throwaway route:
use Arcanedev\Html\Html;
return Html::div('Hello')->class('p-4');
Verify two things immediately: that text is escaped by default, and how the output renders when returned from a controller versus echoed in a Blade view. Once that behaves predictably, move on to composing fragments from arrays/collections, which is where this package earns its place.
Wrap it in your own presenter/component layer. The package is a thin fluent builder, not an application architecture. Create small classes or methods such as Alert::success(string $message): string or MenuItem::render(array $item): Element that return rendered HTML. This gives you one place to swap the implementation later and keeps tag-building calls out of controllers.
Compose fragments from collections. The natural fit is data-driven markup:
$list = Html::ul()->children(
collect($items)->map(fn ($item) => Html::li($item->label)->class('list-item'))
);
This is the pattern to use for menus, breadcrumbs, notification digests, invoice line items, and PDF templates generated with Dompdf—contexts where you are producing markup from PHP data rather than from a Blade template.
Use it for mailables and notifications. Build the body fragment in a mailable or a queued job and pass the rendered string into the view data. It is especially useful when the same fragment must be produced in a console command, a scheduled report, or a test without booting a view.
Blade interop. If the element implements Htmlable/Stringable, returning it directly from a view-model method avoids double escaping. If you echo it manually, use {!! $html !!}. For fragments, prefer passing a rendered string into the template and echoing it once, rather than sprinkling builder calls through Blade.
Conditional attributes. Build attribute arrays and pass them in one call where possible, and use explicit ternaries for classes rather than relying on null handling:
Html::a($url, $label)->attributes([
'class' => $active ? 'nav-link active' : 'nav-link',
'aria-current' => $active ? 'page' : null,
]);
Testing. Assert against rendered strings with assertSee(..., escape: false) or direct string comparison. Snapshot-style assertions are useful here because whitespace and self-closing tag style can shift between versions.
Integration tip: pick one concern for this package—typically dynamic fragments and emails—and leave page layout to Blade components. Mixing both for the same UI element creates two sources of truth.
raw, html, or equivalent in your version) bypass it. Never feed request data or stored user content into those methods. Audit every call site that emits unescaped markup.{{ }} can be escaped into visible tags unless it implements Htmlable. When output looks like <div>, that is the cause—render to string and use {!! !!}, or return an HtmlString.img, br, input, hr, and meta should not receive content. Also check whether output is <br> or <br />; this breaks brittle snapshot tests and can matter for XML/XHTML pipelines.null/false values, array-valued classes, and numeric attribute names are common sources of surprises. Verify each in tinker before relying on them, and prefer constructing the final attribute array yourself.Html::tag($name)), validate against an allowlist. Tag and attribute injection is still injection.composer.json and keep all usage behind your own wrapper. Compare against spatie/laravel-html or native Blade components before committing broadly; migration is trivial if the calls are centralized.(string) $element) or call the render method explicitly; dd() on the object shows the fluent state, not the final markup. Decode entities when comparing test output.How can I help you explore Laravel packages today?