laravellux/html
LaravelLux HTML is a Laravel package for building HTML through a fluent, composable API. It provides helpers for elements, attributes, forms, and Blade-friendly rendering, helping keep views clean, reusable, and easy to maintain.
The package re-establishes the classic Form and Html builders for Laravel — the same API the abandoned laravelcollective/html provided. Treat it as a drop-in replacement for hand-written <form> boilerplate: CSRF tokens, method spoofing, old() repopulation, and model-backed inputs.
Install and confirm discovery.
composer require laravellux/html
Laravel's package auto-discovery should register the service provider and the Form / Html aliases. If Form::open() throws "Class 'Form' not found," add them manually in config/app.php:
'aliases' => [
'Form' => Laravellux\Html\FormFacade::class,
'Html' => Laravellux\Html\HtmlFacade::class,
],
Verify the exact namespaces in the package's composer.json / src/ before assuming — the repository details are not published.
Publish the config (optional but recommended early).
php artisan vendor:publish --tag=html
This gives you the default form/select classes, label_format, and error_bag. Without it you're running on package defaults, which is fine but harder to reason about when markup surprises you.
First real use case — replace a manual create form.
{!! Form::open(['route' => 'posts.store', 'files' => true]) !!}
{!! Form::label('title', 'Title') !!}
{!! Form::text('title') !!}
{!! Form::submit('Save') !!}
{!! Form::close() !!}
Compare against what you wrote before: no @csrf, no old('title'), no enctype, no manual value= attribute. That delta is the entire value proposition — everything else is variations on it.
Where to look first: the README for the facade method list, then src/FormBuilder.php for the exact escaping and old()-resolution behaviour. Because this is a low-star, single-maintainer package, read the source for anything you're relying on in production rather than assuming parity with laravelcollective.
Model binding for edit forms. This is the pattern you'll use most:
{!! Form::model($post, ['route' => ['posts.update', $post], 'method' => 'PUT']) !!}
{!! Form::text('title') !!}
{!! Form::textarea('body') !!}
{!! Form::select('status', $statuses) !!}
{!! Form::close() !!}
Form::model() seeds every field from the model, with old() taking precedence after a validation failure. Form::close() resets that context — always pair them.
Action-based vs. route-based opening. Prefer 'route' => [...] or 'action' => [...] over a raw 'url'; you get URL generation and correct method spoofing in one array. Add 'files' => true whenever any input is type="file".
Validation-failure round trip. The builder reads from the flashed input automatically. Keep your error display standard:
@error('title') <span class="text-red-600">{{ $message }}</span> @enderror
If you validate with a named bag ($request->validateWithBag('login', ...)), pass 'error_bag' => 'login' to Form::open() so the builder highlights the right fields, or set it globally in config.
Custom field components. Don't sprinkle Form:: calls across fifty views. Wrap them:
{{-- resources/views/components/field.blade.php --}}
<div>
{!! Form::label($name, $label) !!}
{!! Form::$type($name, $value, $attributes) !!}
@error($name) <p>{{ $message }}</p> @enderror
</div>
Call via <x-field name="title" type="text" label="Title" />. This keeps the builder's old()/model logic while giving you one place to change markup.
Macros for domain-specific inputs. Register once in a service provider's boot():
Form::macro('money', function ($name, $value = null) {
return Form::number($name, $value, ['step' => '0.01', 'min' => 0]);
});
Then Form::money('price') works everywhere. The same mechanism exists on Html::macro() for things like Html::cdnScript($path).
Collection-backed selects. Always reduce to a key/value shape:
{!! Form::select('author_id', $authors->pluck('name', 'id'), null, ['placeholder' => 'Choose…']) !!}
Passing Eloquent models directly to Form::select is a common source of __toString errors or [object Object]-style labels.
Escaping and layout. For anything the builder doesn't own — wrappers, grid classes, error summaries — stay in Blade. The builders are for inputs and forms, not page structure. Use Html::script() / Html::style() only if you're not on Vite; if you are, they add nothing.
Testing. The output is deterministic HTML, so assertSee('name="title"', false) and route-based action="..." assertions are cheap regression tests. Assert the _token field exists in one feature test per form to catch a broken provider registration early.
{!! !!}, not {{ }}. The builders return HTML strings. Blade's escaped echo will render <input ...> as literal text. This is the single most common mistake and it looks like a broken package rather than a broken template.Form::model() leaks state. If you open a model form and forget Form::close(), the next plain Form::open() inherits the model and prefills unexpectedly. close() is not cosmetic.old() beats the model. On a failed update, fields show the submitted (invalid) value, not the database value — that's correct behaviour, but it confuses people debugging "stale" form data. Check the session's _old_input before assuming a bug.Form::checkbox('tos', 1) on an existing model is checked when the model attribute is truthy, but an unchecked box sends nothing. For boolean updates, add Form::hidden('tos', 0) before the checkbox or validate with sometimes|boolean.Form::text('address[city]') produces name="address[city]", but validation errors are keyed address.city. Your @error('address.city') must use dot notation while the input name uses brackets.<option> markup yourself or accept plain text. Passing HTML through labels renders it literally._method are injected for you. Don't hand-add @csrf inside a Form::open() — you'll send two _token fields. If you're getting TokenMismatchException, the likely cause is a partial rendered outside the builder (e.g. an AJAX-replaced fragment) or a form rendered before the session started.class="form-control" or class="form-group" attributes come from the package config. Publish it and set default_form_class / default_select_class to match your CSS framework instead of overriding per-call.$request or auth()->user() inside a macro closure at boot time — resolve them inside the closure body on each call.Form / Html aliases. Do not install this alongside laravelcollective/html or another builder. If Form::open() resolves to an unexpected class, inspect config/app.php aliases and the extra.laravel.aliases entry in composer.json — later packages silently win.FormBuilder and rebind it in a service provider rather than editing vendor files. Confirm the binding key by reading the package's provider.@csrf, old(), @method) or to spatie/laravel-html is mechanical — keep the Form:: calls concentrated in your components so that swap touches a handful of files, not every view.illuminate/support constraint in the package's composer.json against your app before adopting; recent release dates imply current Laravel, not universal compatibility.How can I help you explore Laravel packages today?