yepsua/filament-range-field
A Filament form field for range inputs and sliders, letting Laravel admin panels capture single values or min/max ranges with a simple, native-feeling component that integrates directly into Filament forms.
Install and register. The package ships a single Filament form component, so there is nothing to configure beyond Composer:
composer require yepsua/filament-range-field
If your project publishes Filament assets (typical for non-Vite-published installs), re-run the asset command after installing so the slider JS/CSS lands in public/:
php artisan filament:assets
php artisan view:clear
Look here first. Open vendor/yepsua/filament-range-field/README.md and the class at Yepsua\FilamentRangeField\Forms\Components\Range. That class is the entire public API — read its setUp() and its minValue()/maxValue()/step()-style setters before writing anything, because the surface area is tiny and the README is the only docs.
Check the changelog before you pin. The latest tag is v0.3.4, a patch release whose notes are just "Issues fixed: Issue #1" with a compare link. Read that link (and the closed issue) before upgrading — a one-line changelog on a low-traffic package can hide a behavioural fix you were unknowingly relying on. Confirm the filament/filament constraint in composer.json still matches your panel version at the same time.
First use case. A bounded numeric setting is the natural fit — a discount percentage, a rating, an opacity/zoom level, a "notify me X days before" threshold:
use Yepsua\FilamentRangeField\Forms\Components\Range;
public static function form(Form $form): Form
{
return $form->schema([
Range::make('satisfaction')
->label('Satisfaction score')
->minValue(0)
->maxValue(10)
->step(1)
->default(5)
->required(),
]);
}
Because it extends Filament's Field, everything you already know (default(), required(), disabled(), helperText(), live(), columnSpanFull()) works unchanged. Assume you will read the source rather than the docs for anything beyond that.
Pair it with a number input instead of replacing one. Sliders are bad at precise entry. The workable pattern is a slider for feel plus a numeric field for exactness, wired through reactive state:
Range::make('threshold')
->minValue(0)->maxValue(100)->step(1)
->live(debounce: 300)
->afterStateUpdated(fn ($state, Set $set) => $set('threshold_input', $state)),
TextInput::make('threshold_input')
->numeric()
->live(onBlur: true)
->afterStateUpdated(fn ($state, Set $set) => $set('threshold', $state)),
Note the debounce: — a slider that fires live() on every pixel of drag will hammer the Livewire endpoint.
Decide the hydration/save shape deliberately. A range field hydrates as whatever the slider emits (often a string or float from JS). Pin it down so your column type matches:
Range::make('weight')
->minValue(0)->maxValue(1)->step(0.01)
->rule('numeric') // UI bounds are NOT validation
If the value must be an int, either cast with ->integer() (if the field supports it via the parent) or normalise in mutateFormDataBeforeSave() on the page/resource.
Validation is your job, not the field's. minValue()/maxValue() constrain the widget; they do not constrain a crafted Livewire request or fillForm() from an importer. Always add explicit rules:
->rules(['required', 'numeric', 'min:0', 'max:100'])
Layout. Sliders need width to be usable. Set ->columnSpanFull() or place the field in its own Section; a half-column slider on a 3-column grid becomes a 120px control.
Reactive visibility. It composes cleanly with conditional UI, which is where it earns its keep — e.g. only show the "custom interval" slider when a select is set to custom:
Range::make('custom_days')
->visible(fn (Get $get) => $get('interval') === 'custom')
Settings pages and actions. It works in any Filament form context — resource create/edit, SettingsPage, and modal action forms (Action::make()->form([...])). It does not work as a table filter directly; wrap it in a custom filter form if you need range filtering, or use the standard Filter with query() and two numeric inputs.
Version compatibility is still the headline risk. The package shipped v0.3.4 after a long quiet spell, but a patch bump on a small 0.x package does not necessarily add Filament v4 support — verify the filament/filament constraint in composer.json rather than assuming the release modernised it. Check before you plan around it, and pin the package exactly ("0.3.4" or the tag you tested) — 36 stars, 0 dependents and a release cadence measured in years means you are effectively vendoring it, and a patch landing now is no promise of the next one.
Upgrade deliberately, not automatically. If you are already on v0.3.3, read Issue #1 and the compare diff before bumping. A fix to shared slider behaviour (initialisation, hydration, value coercion) is exactly the kind of change that alters what $state looks like on save — re-run your own form round-trip (create, edit, save, reload) after the bump rather than trusting a green suite, since Livewire tests bypass the widget entirely.
Assets are the usual first bug. Slider renders as a bare input or not at all → run php artisan filament:assets, hard-refresh, and confirm the package's JS is in public/. If you deploy with a build step, make sure the asset command is in your deploy script; a stale public/ between deploys is the most common "it worked locally" report. This is doubly true after a version bump — a cached old asset against new markup produces the classic "handle snaps to 0" symptom.
Alpine morphing. Sliders initialised by third-party JS frequently die after Livewire re-renders (conditional fields, repeaters, modals). Symptoms: the handle snaps back to 0, or dragging does nothing. Test explicitly inside a Repeater and inside a modal Action form. If it breaks, the fix is re-initialising on livewire:navigated / morph.updated — hook it from a small @push('scripts') in your panel rather than patching vendor.
Tests will not catch bound violations. Livewire tests fillForm(['x' => 500]) and bypass the widget entirely. Anything relying on the slider's min/max for correctness needs a real validation rule, or your test suite will happily commit out-of-range data.
Floats and step precision. step(0.1) will produce values like 0.30000000000000004 depending on the underlying library. Round in mutateFormDataBeforeSave() and make sure the DB column is decimal, not float, if the value matters.
Multiple instances on one page. Two range fields in the same form can collide if the JS selects by a shared class or ID. Give each field a distinct name/state path and verify both drag independently before shipping — worth re-checking specifically after v0.3.4 if Issue #1 touched element selection.
Accessibility and mobile. Custom slider JS often drops keyboard arrow support and touch handling. Check tab-focus + arrow keys, and test on a touch device — this is the kind of thing that gets caught in UAT and is annoying to patch in a low-maintenance dependency.
Extension points. You get Filament's ->extraAttributes() and ->extraInputAttributes() for passing data attributes to the underlying element, and ->view() (or publishing the Blade view, if the package supports it) for a full override. If you find yourself needing more than that — dual-handle range, pips, custom formatting — budget for writing your own component instead of fighting this one; it is small enough that a fork is cheaper than a workaround. Given the release cadence, forking is also the sane answer if you need a fix and cannot wait for the next patch.
When not to use it. For a value that is typed far more often than dragged (prices, quantities above ~50), a TextInput::make()->numeric() is simpler, testable, and has zero asset risk. Reserve the slider for genuinely exploratory input.
How can I help you explore Laravel packages today?