devletes / filament-time-picker
A clock-face time and date-time picker for Filament — hour and minute dials, drag or type, natively in Livewire and Alpine. No jQuery, no date library, no build step.
Requires
- php: ^8.2
- filament/filament: ^5.0
Requires (Dev)
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0 || ^11.0
- phpunit/phpunit: ^11.0 || ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A clock face for Filament's time and date-time fields — hour and minute dials, drag or tap or type.
- Drop-in replacements.
TimePickerandDateTimePickerextend Filament's own, so swapping the import is the whole migration - No dependencies. No jQuery, no date library, no build step — vanilla JavaScript on the Alpine you already have
- The dial follows the field.
g:i Agets a 12-hour face with AM/PM,H:igets a 24-hour face with an inner ring - Every minute is reachable, not every fifth, and the steps still coarsen it when you want that
- Bounds that hold.
minTime()/maxTime()dim the dial, clamp typing, and are re-checked on the server - A calendar to match, with min and max dates, disabled dates and your locale's month and weekday names
- Themed off Filament's palette — follows your panel's colour, light and dark
![]() |
![]() |
Requirements
- PHP
^8.2 - Filament
^5.0on Laravel 12 or 13
Installation
composer require devletes/filament-time-picker
Then publish Filament's assets so the stylesheet and script are served on your panels:
php artisan filament:assets
The package registers its own stylesheet and script with Filament's asset manager — there is nothing to add to your custom Tailwind theme.
Quick start
use Devletes\TimePicker\Forms\Components\DateTimePicker; use Devletes\TimePicker\Forms\Components\TimePicker; TimePicker::make('deliver_at') ->seconds(false) ->displayFormat('g:i A'); DateTimePicker::make('ships_at') ->seconds(false) ->displayFormat('M j, Y g:i A');
A drop-in replacement
Both components extend Filament's own, so swapping the import is the whole migration:
-use Filament\Forms\Components\TimePicker; +use Devletes\TimePicker\Forms\Components\TimePicker; -use Filament\Forms\Components\DateTimePicker; +use Devletes\TimePicker\Forms\Components\DateTimePicker;
Everything you already use still works and still means the same thing — seconds(), the steps, format(), displayFormat(), timezone(), live(), disabled(), the affixes and the validation. The state cast is Filament's own, so nothing about what is stored changes.
The dial follows the field
Say nothing, and the face is read off the display format. A field that writes its hour as 1–12 gets the 12-hour face with an AM/PM switch; anything else gets the 24-hour face, with 13–23 and midnight on an inner ring.
TimePicker::make('deliver_at')->displayFormat('g:i A'); // 12-hour face TimePicker::make('cutoff')->displayFormat('H:i'); // 24-hour face TimePicker::make('cutoff')->hoursCycle(24); // say so yourself
![]() |
![]() |
![]() |
![]() |
Set time-picker.hours_cycle in the config to make one face the rule everywhere.
Minutes and seconds
Picking an hour moves to the minute dial. The dial takes every minute: the numerals are drawn at the fives, but dragging between them lands on 9:07 the way you would expect. A small label above the pin says which unit you are setting.
TimePicker::make('slot')->minutesStep(15); // quarter hours only TimePicker::make('slot')->hoursStep(2);
![]() |
![]() |
seconds() adds a third dial, and secondsStep() coarsens it the same way.
TimePicker::make('loaded_at') ->seconds() ->displayFormat('H:i:s');
![]() |
![]() |
Bounds
TimePicker::make('pickup') ->minTime('09:00') ->maxTime('17:00');
Numerals outside the range are dimmed and refuse the pointer, typing is pulled back inside, and the same bound is checked again on the server — the panel is not the only way a value arrives. The message names the bound in the field's own format ("must be no earlier than 9:00 AM"). Both accept a string, a Carbon instance, or a closure.
![]() |
![]() |
Date and time
DateTimePicker pairs the same clock with a calendar. Picking a day moves straight to the clock, and the switch at the top of the panel moves between the two.
DateTimePicker::make('ships_at') ->seconds(false) ->displayFormat('M j, Y g:i A') ->minDate(now()->startOfDay()) ->maxDate(now()->addMonths(3)) ->disabledDates(['2026-12-25', '2026-12-26']) ->minTime('08:00') ->maxTime('20:00');
![]() |
![]() |
![]() |
![]() |
minDate() and maxDate() disable the days outside them, and on the first and last day their times narrow the clock too. minTime() / maxTime() stay a daily window, applied on every day. Month and weekday names follow the field's locale.
Filament's other date options apply as they do there:
DateTimePicker::make('ships_at') ->weekStartsOnSunday() // or firstDayOfWeek(0–7) ->defaultFocusedDate(now()->addWeek()) // the month an empty field opens on ->closeOnDateSelection(); // pick a day and close, without the clock
Without a time, it is a calendar only:
DateTimePicker::make('delivered_on')->time(false);
Typing and the keyboard
The big digits in the panel are inputs. Click one and type; it moves to the minute on its own when the hour cannot be extended. The arrow keys nudge by the field's own step, Escape closes the panel, and Backspace on the closed field clears it.
Closing and the footer
By default the panel waits for OK. To save and close as soon as the last dial is set (the minute, or the second when seconds are on), and hide OK:
TimePicker::make('call_back_at')->closeOnComplete();
The footer's buttons are Filament's own. Relabel them, or drop the "Now" shortcut:
TimePicker::make('deliver_at') ->okLabel('Set time') ->cancelLabel('Back') ->nowLabel('Right now') ->nowButton(false);
On phones
native() hands rendering back to Filament, which gives the browser's own control — on a phone the platform's picker is the better choice:
TimePicker::make('deliver_at') ->native(fn (): bool => str_contains((string) request()->userAgent(), 'Mobile'));
Configuration
config/time-picker.php sets the defaults every field starts from:
php artisan vendor:publish --tag=devletes-time-picker-config
return [ 'hours_cycle' => null, // 12, 24, or null to read it off each field's display format 'close_on_complete' => false, // save and close once the last dial is set 'now_button' => true, // show the "Now" shortcut ];
Theming
Every colour resolves through Filament's palette variables, so the picker follows your panel's primary colour and its light and dark themes without being told. To change it further, override the --fi-dtp-* variables in your theme stylesheet:
html .fi-dtp { --fi-dtp-accent: var(--success-600); --fi-dtp-plate: var(--gray-50); } html.dark .fi-dtp { --fi-dtp-accent: var(--success-400); }
The variables are surface, plate (the dial), border, text, muted, faded, accent, on-accent and hover.
Or publish the views to change the markup:
php artisan vendor:publish --tag=devletes-time-picker-views
Translations
Every user-facing string goes through devletes-time-picker::time-picker.*. Publish the language files to reword them or add a locale:
php artisan vendor:publish --tag=devletes-time-picker-translations
Then edit or add files under lang/vendor/devletes-time-picker/.
API reference
Everything Filament's TimePicker and DateTimePicker offer, plus:
| Method | What it does |
|---|---|
hoursCycle(12|24) |
Fixes the face rather than reading it off the display format |
minTime($time) |
The earliest time of day the field will take. String, Carbon, or closure |
maxTime($time) |
The latest time of day the field will take. String, Carbon, or closure |
closeOnComplete() |
Save and close once the last dial is set, and hide OK |
nowButton(false) |
Drops the "Now" shortcut from the footer |
okLabel($label) |
Relabels the confirm button |
cancelLabel($label) |
Relabels the cancel button |
nowLabel($label) |
Relabels the "Now" shortcut |
native() |
Hands rendering back to Filament for the browser's own control |
On DateTimePicker, Filament's minDate(), maxDate(), disabledDates(), firstDayOfWeek(), weekStartsOnMonday(), weekStartsOnSunday(), defaultFocusedDate(), closeOnDateSelection(), time() and seconds() all apply to the calendar and the clock.
Testing
composer install
composer test
The suite runs on Testbench with the real Filament stack, against PHP 8.2–8.4 on Laravel 12 and 13.
Need something custom?
We build production Filament panels and plugins for teams that want to ship fast without compromising on polish. If you need a custom feature, an extended variant of this package, or a fully bespoke component built for your stack, we can help.
- Browse the rest of our Filament work: filament.devletes.com
- Get in touch: salman@devletes.com
Typical engagements: new Filament plugins, custom resources/widgets/actions, theme + UX work, integrations with your existing services, and one-off tailored forks of our open-source packages.
Have you seen our Orbit theme?
Orbit is our dark-first theme for Filament 5. Every panel surface is restyled — sidebar, topbar, forms, tables, modals, notifications.
Credits
License
MIT. See LICENSE.md.















