Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Timepicker Laravel Package

harvirsidhu/filament-timepicker

A Filament form field that adds a customizable time picker to Laravel admin panels, making it easy to select and store time values with a clean, user-friendly input.

View on GitHub
Deep Wiki
Context7

Getting Started

Pull it in with Composer; there is no plugin class to register and nothing to add to a panel provider:

composer install harvirsidhu/filament-timepicker
php artisan filament:assets

The filament:assets step is not optional. Since v1.1.0 the suggestion panel ships its own plain-CSS asset (resources/dist/filament-timepicker.css) registered by FilamentTimepickerServiceProvider; skip the command and you get a correctly-behaving but visually naked dropdown. Re-run it after every composer update of this package.

Note the dependency is filament/forms, not filament/filament. If your app is a plain Livewire + Forms setup (no panel), it installs fine; panel users see no difference.

Smallest useful field:

use Harvirsidhu\FilamentTimepicker\SmartTimePicker;

SmartTimePicker::make('start_time')
    ->interval(15)
    ->minTime('08:00')
    ->maxTime('18:00')
    ->required(),

That is already the first use case worth wiring up: a booking/scheduling form where the operator types 330 or 3p and the canonical 15:30 lands in the column. Where to look first: src/SmartTimePicker.php for the whole public surface (one small class, readable end-to-end), then resources/views/time-picker.blade.php and resources/js/components/smart-time-picker.js when you need to understand why the UX behaves a certain way. The lang file at resources/lang/<locale>/time-picker.php is short and tells you every string you can override.

Implementation Patterns

Relative bounds driven by a sibling field. minTime()/maxTime() accept closures and are genuinely reactive (the v1.1.0 data-config bridge). A common booking shape:

SmartTimePicker::make('start_time')
    ->minTime(fn (Get $get) => $get('opening_time') ?? '08:00')
    ->maxTime(fn (Get $get) => $get('closing_time') ?? '18:00'),

SmartTimePicker::make('end_time')
    ->minTime(fn (Get $get) => $get('start_time'))
    ->maxTime(fn (Get $get) => $get('closing_time') ?? '18:00'),

Duration derivation. durationFrom() points at a sibling holding a duration in minutes and both labels the suggestions (09:00 – 09:30) and floors the picker at the start time; defaultDuration() auto-fills the field on change, now clamped into [minTime, maxTime] rather than only capped at the ceiling:

SmartTimePicker::make('ends_at')
    ->durationFrom('duration')
    ->defaultDuration(fn (Get $get) => $get('duration') ?? 30),

Reactivity that actually fires. ->live() and afterStateUpdated() work on commit — the field binds via applyStateBindingModifiers() so a ->live() field emits a real $entangle(path, true). Use it for summary panels, price calculation, conflict checks:

SmartTimePicker::make('start_time')
    ->live()
    ->afterStateUpdated(fn (Set $set, $state) => $set('end_time', ...)),

Be aware the hook fires on commit, not on every keystroke — that is the intended behaviour, since intermediate strings like 3 or 33 are not times.

Strictness is two layers. minTime()/maxTime() are enforced unconditionally; strict(true) adds only the interval-grid check on top. Bounds are evaluated first, so an out-of-window value gets the useful min_time/max_time message rather than a confusing "off grid" one. Reach for strict() when slot alignment matters (appointments), not when you just want opening hours respected.

Migration from Filament's stock TimePicker. Swap the class and keep your calls: native() and timezone() exist as deliberate no-ops for drop-in parity. Do not expect ->timezone('America/New_York') to shift anything — the storage contract is wall-clock H:i, and a 3 PM slot stores as 15:00 regardless.

Custom themes. If you previously had an @source line pointing at this package's views, you can delete it — it is a no-op now that the panel is a published asset reading Filament's runtime --gray-* variables. Your panel's colours still apply.

Testing your own form. Assert the stored value as H:i, not as a Carbon instance in some zone. A regression test along the lines of "stores appointment slot times as wall-clock" is cheap insurance and matches the package's own guarantee.

Gotchas and Tips

  • Upgrading to v1.1.0 changes behaviour silently in one respect: minTime()/maxTime() are now enforced. Values that previously slipped through via paste, $set, or an importer are rejected both client-side (the box snaps back to the last good value) and server-side (a validation rule with its own message). Audit existing data and import paths before rolling out.
  • Tabbing through a form is safe now (v1.1.0): Tab only commits after you have typed or arrowed. The flip side — pressing Enter in a closed picker still commits typed text but then submits the form. If you have a multi-field form where users habitually hit Enter, make sure your submit handling is intentional.
  • When a reactive interval()/minTime() looks stale, inspect the data-config sibling <div> in DevTools. The component root carries wire:ignore (load-bearing — it stops Livewire re-initialising the teleported <ul> outside Alpine scope after a roundtrip), which also freezes x-data. Config therefore arrives through that sibling attribute, which Livewire can morph. If the attribute is right but the dropdown is wrong, the bug is in RECONFIGURABLE/watchConfig() in the JS; if the attribute itself is stale, the problem is in your closure.
  • Do not remove wire:ignore, and avoid Blade conditionals that tear down and recreate the field's DOM. Filament's hidden()/disabled() are fine — disabled and aria-invalid are kept current across roundtrips by explicit code, precisely because the root is ignored.
  • Panel positioning is imperative on purpose. If the dropdown lands in the wrong place, look at the inline left/top/width written onto the panel element — not at a :style binding. Moving it to a string :style binding would wipe the display/opacity that x-show/x-transition set on the same node. It re-measures every frame while open (Floating-UI-style autoUpdate), so a field that moves during a modal animation or a late font load should self-correct; if it doesn't, that loop is the place to look.
  • Forking? Two parsers, one truth. Support\TimeParser is authoritative; parse()/toDisplay() in the Alpine component mirror it for instant feedback. Change parse rules in both and add a row to tests/Fixtures/parse-cases.json, which both the PHP and JS suites iterate — that fixture is what makes drift fail CI. dehydrateStateUsing stays a pure parse normalizer; don't push validation into it.
  • Adding a reactive option requires two edits: SmartTimePicker::getAlpineConfig() and RECONFIGURABLE in the JS, or the new option will stay pinned to its first-render value.
  • Translations live under harvirsidhu-filament-timepicker::, not the Spatie shortName — use that prefix everywhere (PHP __(), the Blade view, and the durationLabels passed into x-data). Publish the lang file to localise duration words; there is no hardcoded English in the JS.
  • displayFormat() uses PHP date() tokens with backslash-escape support, and it is honoured on both sides — including in the min_time/max_time validation messages. Pick something unambiguous ('H:i' or 'g:i a') if your users skew international, and keep seconds() off unless your column is actually H:i:s.
  • Maturity check: 3 stars, 0 dependents, one author. It is well-tested (PHP + JS suites, architecture and Livewire feature tests) and the release notes are unusually candid, but pin the version in composer.json and keep your own wall-clock regression test. The package is domain-agnostic by design — it knows nothing about availability, clinics, or appointments — so all business rules belong in your form, not in the field.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo