tardigrades/sexy-field-form
Sexy Field Form is a Laravel/PHP package for building expressive, reusable form fields with a clean API, helping you define, render, and customize forms quickly in Laravel applications.
Before you install anything, read the source. This package has zero stars and zero dependents, which means there is no community surface area telling you it works on your Laravel version. Budget 15 minutes to read composer.json, src/, and the service provider before committing it to a project.
Install via Composer:
composer require tardigrades/sexy-field-form
If Packagist resolution fails (common with low-traffic packages), pin it as a VCS repository in your root composer.json:
"repositories": [
{ "type": "vcs", "url": "https://github.com/dionsnoeijen/sexy-field-form" }
]
First place to look: composer.json for illuminate/support and php constraints. A form-rendering package written against Laravel 5.x/6.x will usually still load on 10/11 but may break on things like Htmlable return types, FormRequest internals, or Blade directive registration. Then check src/ for a ServiceProvider — confirm it is listed under extra.laravel.providers for auto-discovery, or register it manually in config/app.php.
First use case: replace a hand-written Blade input block with a field object. In a typical setup you get a field instance from a facade or helper, configure it fluently, then echo it:
{{-- before --}}
<div class="form-group @error('email') has-error @enderror">
<label for="email">Email</label>
<input type="email" name="email" id="email"
value="{{ old('email') }}" class="form-control">
@error('email') <span class="help-block">{{ $message }}</span> @enderror
</div>
{{-- after (verify exact method names against src/) --}}
{!! Field::email('email')->label('Email') !!}
The win is that old(), the error bag, and the wrapper markup are handled once inside the package instead of in every form. Start with a single field on a single form — not a whole CRUD screen — so you can diff the output against your existing markup and confirm the package's opinions match your CSS framework (Bootstrap 3 vs 4 vs Tailwind markup assumptions are a very common mismatch in packages of this vintage).
Wrap it, don't sprinkle it. The highest-value move with an unmaintained dependency is to put a seam between it and your views. Create one Blade component and let everything else depend on that:
// app/View/Components/Field.php
class Field extends Component
{
public function __construct(
public string $name,
public string $type = 'text',
public ?string $label = null,
) {}
public function render()
{
return view('components.field');
}
}
{{-- resources/views/components/field.blade.php --}}
{!! Field::$type($name)->label($label ?? Str::headline($name)) !!}
Now if the package dies, or you migrate to Livewire/Blade components, you rewrite one file instead of 200 templates.
Form object pattern. Group a related set of fields into a small class so controllers and views stay dumb:
class ProfileForm
{
public function fields(): array
{
return [
Field::text('name')->required(),
Field::email('email')->required(),
Field::textarea('bio')->rows(4),
];
}
}
Loop them in the view. This also gives you one place to assert against in tests.
Pair with FormRequest for validation. The package renders error state; it does not validate. Keep validation in a FormRequest and let the field objects read from the shared error bag. If errors do not appear, the cause is almost always that the field name does not exactly match the validation key (including array notation like addresses.0.city).
Macros for house style. If the package exposes a macroable base (check for Macroable in src/), register your own field types in a service provider's boot():
Field::macro('currency', function ($name) {
return Field::text($name)->attr(['inputmode' => 'decimal', 'step' => '0.01']);
});
Incremental adoption. Do not refactor an entire admin area. Convert one form, ship it, then convert the next. Form markup is high-blast-radius; a broken old() path on a settings page is a support ticket, not a compile error.
Testing integration. Because the output is HTML, assert on it rather than on the object:
$response = $this->get('/profile');
$response->assertSee('name="email"', false);
That gives you a regression net independent of the package's internal API, which matters a lot when the package has no test suite of its own to lean on.
Escape everything by default. Form builders return Htmlable objects that must be echoed raw ({!! !!}). Any method that accepts a label, placeholder, or help text and does not escape it internally becomes an XSS vector the moment that string is user-controlled. Audit the escaping in src/ before passing anything from the database or a request into a label. When in doubt, e() it yourself.
Version drift is the main risk. Zero dependents means nobody has hit the Laravel 10/11 compatibility wall for you. Watch for: missing return types on methods implementing Htmlable/Arrayable, $dates-era date handling, str_*/array_* helpers that moved to Str::/Arr::, and Blade directives registered via the old Blade::directive signature. Fixes are usually 5–10 lines — fork it, tag it, point Composer at your fork.
Config caching hides registration problems. If the provider or a directive "works locally but not in staging", run php artisan config:clear && php artisan view:clear before debugging anything else. A stale bootstrap/cache/config.php will silently omit a package's config if it was added after the last cache warm.
old() and model binding conflict. Many builders of this era default a field's value from a bound model after checking old(), or vice versa. Confirm the precedence: the correct order is always old() → explicit ->value() → model attribute → default. Test a failed-validation round-trip explicitly; it is the single most common bug in form packages.
Debug by dumping the object, not the page. When a field renders wrong, dump($field) (or dd($field->toArray()) if it implements Arrayable) to see the attributes it actually built. Reading the generated HTML tells you what is wrong; the object tells you why.
Extension points to check for, in priority order: macroability, a config file you can publish (php artisan vendor:publish --tag=...), view overrides (a resources/views/vendor/tardigrades/... path), and finally a custom class extending the base field. If none of those exist, stop and wrap it instead of forking.
Treat it as a reference implementation. Given the adoption numbers, the realistic best use of this package is as a pattern: read how it structures a field object, how it resolves error state, how it composes attributes, then write a 200-line internal equivalent tuned to your CSS framework. You get the ergonomics with none of the supply-chain risk. If you need something maintained in this space for production, look at spatie/laravel-html or Blade components instead.
How can I help you explore Laravel packages today?