konekt/html
Konekt HTML is a fluent tag and form builder for Laravel. Generate HTML elements, Bootstrap-friendly forms, and reusable macros directly from PHP, with clean chainable APIs that keep markup out of your templates.
Install and confirm the framework constraint first. konekt/html is a community continuation of the old laravelcollective/html package (itself the package removed from Laravel core in 5.0), so the single most important first step is checking the illuminate/* constraints in composer.json against your Laravel version:
composer require konekt/html
composer show konekt/html # read the illuminate/* requirements
If composer update resolves but your app is on a newer Laravel major than the package declares, expect silent breakage in edge cases (session/old-input handling, Blade directives) — verify on a branch before adopting.
Register the provider/facades. Modern releases use package auto-discovery; on older apps or after a Laravel upgrade you may still need:
// config/app.php
'providers' => [Konekt\Html\HtmlServiceProvider::class],
'aliases' => ['Form' => ..., 'Html' => ...],
Check the README for the exact class names in your installed version — the facade paths have moved between the Collective\Html and Konekt\Html namespaces.
Publish the config (class injection defaults, component defaults) if you're not on Bootstrap:
php artisan vendor:publish --provider="Konekt\Html\HtmlServiceProvider"
First use case: replace a hand-written <form> in a resource controller's create/edit view with a model-bound form. That single change gives you CSRF, method spoofing, old-input repopulation and model hydration for free, which is where 90% of the package's value lives.
{!! Form::model($post, ['route' => ['posts.update', $post], 'method' => 'PUT']) !!}
{!! Form::text('title', null, ['class' => 'form-control']) !!}
{!! Form::textarea('body', null, ['rows' => 8]) !!}
{!! Form::submit('Save', ['class' => 'btn btn-primary']) !!}
{!! Form::close() !!}
Model-bound CRUD forms. Form::model() is the canonical pattern: pass the Eloquent model as the second argument and the builder fills every field from the attribute (or old() on a validation redirect) unless you explicitly pass a non-null value. Pair with a FormRequest for validation and a resourceful controller — the view needs almost no glue code.
Error-aware field partials. Rather than repeating Bootstrap/Tailwind error classes, create one partial per control and drive it from $errors:
{{-- resources/views/components/form/text.blade.php --}}
<div class="form-group {{ $errors->has($name) ? 'has-error' : '' }}">
{!! Form::label($name, $label) !!}
{!! Form::text($name, $value, array_merge(['class' => 'form-control'], $attributes)) !!}
@if($errors->has($name)) <span class="help-block">{{ $errors->first($name) }}</span> @endif
</div>
Register custom components for repeated markup. The builder has a component() method that binds a partial to a macro name, so {!! Form::bsText('title', 'Title') !!} expands to your view:
// AppServiceProvider::boot()
Form::component('bsText', 'components.form.text', ['name', 'label', 'value' => null, 'attributes' => []]);
The argument list order must match how the partial consumes variables; add defaults for optional args or every call site gets brittle.
Macros for everything the component system can't express. Form::macro('money', fn($name) => ...) is the escape hatch for one-off controls and for wrapping JS-driven widgets. Register them in a dedicated FormServiceProvider rather than AppServiceProvider once you have more than two or three.
Selects from Eloquent. Form::select('author_id', User::pluck('name', 'id'), null, ['placeholder' => 'Choose…']) works well; for many-to-many relationships pass the plucked array and an array of selected IDs — model binding will not resolve pivot relations for you.
Static assets stay out of this. Html::script/Html::style exist for parity, but on any app using Vite or Mix, keep the asset pipeline in Blade's @vite/mix() helpers and reserve the HTML builder for structural helpers (Html::link, Html::ul, Html::attributes) where it actually saves typing.
Testing. Assert against rendered attribute strings (assertSee('name="title"', false)); note that any test touching the facades needs a booted application, so keep form assertions in Feature tests, not plain Unit tests.
{{ Form::text(...) }} will print escaped markup. Always {!! !!}. Newer releases ship shorthand Blade directives (@form … @endform) that hide this; check your version's docs before using them, since mixed Blade versions in one codebase are a debugging hazard.old() and model binding both see them as "missing" rather than false. Emit {!! Form::hidden('active', 0) !!} immediately before the checkbox to get deterministic booleans.Form::text('name', $value) — a non-null $value wins over both the model attribute and old() input. Passing '' instead of null silently disables repopulation after a failed validation; this is the most common "why is my form blank after validation?" bug.Form/Html aliases are the first thing to break after a Laravel major bump. Symptom: Class "Form" not found in Blade only.'form' and 'html' singletons; to swap in a subclass, use $this->app->extend('form', fn($builder, $app) => new CustomFormBuilder(...)) from a provider that registers after HtmlServiceProvider.composer.json, run your full suite against a new Laravel release on a branch, and keep a fork ready — the package surface is small (two builders plus facades), so vendoring is cheap if you need a same-day fix.dd(Form::getModel()) inside the form block confirms what the builder is bound to; if fields are unexpectedly empty, that's the fastest way to distinguish "wrong model" from "value-precedence bug".Form::open() embeds a token by default; for stateless or signed-URL forms pass ['url' => ..., 'method' => 'POST'] and consider disabling the token explicitly rather than stripping it after the fact.How can I help you explore Laravel packages today?