Date Picker
The <x-wirekit::date-picker> component is a styled wrapper around the native <input type="date">. It uses the browser's built-in calendar popup for zero dependencies and automatic localization to the user's OS locale.
Usage
Width
Like all WireKit form components, the date picker fills its container (w-full). Date fields are typically narrow — constrain the width via the parent element:
<div class="max-w-[12rem]">
<x-wirekit::date-picker label="Start date" name="start_date" />
</div>
See Input — Width for more layout examples (grid columns, mixed widths).
Date Range
Add the range flag to render a linked start + end pair. Each field is a native
date input, and the two are reactively linked — the end can't be set before the
start, and the start can't be set after the end. They submit as name[start]
and name[end]. Pass an initial range as an array or a YYYY-MM-DD/YYYY-MM-DD
string.
Range Constraints
Must be in 2024
Error State
Please enter a valid date
Why Native?
The native <input type="date"> gives us:
- Zero dependencies — no JavaScript calendar library to ship
- Automatic localization — calendar renders in the user's OS language and date format
- Full accessibility — arrow keys, PageUp/PageDown for months, Home/End for week boundaries, all work out of the box
- Mobile keyboards — native date wheel on iOS, spinner on Android
- Browser-native parsing — handles timezone and format edge cases
The value is always submitted in YYYY-MM-DD format regardless of the display format.
Optimistic UI
Pass the name of the Livewire method the picker should call and the date lands immediately, then confirms or undoes itself when the server answers:
<x-wirekit::date-picker
name="due"
label="Due date"
:value="$due"
optimistic="saveDue"
/>
Load wirekit-optimistic.js alongside whichever bundle you already use — below it, in your layout:
@wirekitScripts
<script src="{{ asset('vendor/wirekit/wirekit-optimistic.js') }}"></script>
Try it
The demo below runs the real path: the change shows immediately, the outline says it is provisional, and the server's answer either confirms it silently or takes it back.
The <livewire:demos.…> wrapper above exists only on this site — it supplies the demo
methods so the page can show a real round trip. The block under it is what you write.
The prop takes a method name rather than true because the component cannot know the action otherwise: server actions reach a WireKit component through the attribute bag, so it never sees your wire:change. optimistic replaces wire:model.live here rather than joining it — the method you name is what changes the value on the server; pass the current one with :value so the picker knows where it started.
range mode is not covered. A range is two values, so an undo would have to answer what it restores when only one end moved — and the two inputs constrain each other, so rolling one back can leave the other holding a bound that no longer applies. Passing both props gives you the plain range picker rather than a layer applied to half of it.
What a screen reader hears Picking a date announces once, hedged — "Saving" — so the new value is audible as provisional. Confirmation is silent: what was announced is what happened. Only a deviation speaks a second time, which is what makes an undo recognizable as an undo.
Where the picker shows a validation message, the undo stays silent and leaves that message to speak: it tells you what to do, "could not save" does not.
An aborted request announces nothing at all — nothing was refused.
Focus stays exactly where you put it. An undo arrives on the server's schedule, and moving focus then would take you out of your place for a reason you could not predict.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string|null |
null |
Form field name (range mode submits name[start] / name[end]) |
id |
string|null |
auto-generated | Element id |
value |
string|array|null |
null |
Initial date YYYY-MM-DD; in range mode an array ['start' => .., 'end' => ..] or a YYYY-MM-DD/YYYY-MM-DD string |
optimistic |
string|null |
null |
Livewire method to call, showing the new date before the server confirms it. Single-date mode only; ignored with range or disabled. See Optimistic UI. |
optimisticArgs |
array |
[] |
Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to. |
range |
bool |
false |
When true, renders a linked start + end date pair |
min |
string|null |
null |
Earliest selectable date (YYYY-MM-DD) |
max |
string|null |
null |
Latest selectable date (YYYY-MM-DD) |
size |
string |
'md' |
'sm', 'md', 'lg' |
disabled |
bool |
false |
Disabled state |
required |
bool |
false |
Required field |
placeholder |
string|null |
null |
Placeholder (browser-dependent) |
hint |
string|null |
null |
Helper text below input |
error |
string|null |
null |
Error message (also reads from $errors) |
scope |
string|null |
null |
Scoped personalization key |
Accessibility
- Native
<input type="date">— full browser a11y and keyboard support aria-invalid="true"andaria-describedbyon erroraria-required="true"when required- Focus ring and border color tokens adapt to light/dark mode automatically
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Move focus into the date input |
Type characters matching YYYY-MM-DD (or the configured format) |
Set the value directly |
ArrowUp / ArrowDown (in supporting browsers) |
Step the focused field by ±1 |
Pitfalls
- Don't pass a
Carbon\Carboninstance directly. The underlying<input type="date">expects an ISO string. Use->format('Y-m-d')before binding, or rely on Livewire's automatic Carbon ↔ string casting. - Don't disable the dropdown. deliberately ships an HTML5 native picker for accessibility — disabling it sacrifices keyboard-only date entry that screen-reader users depend on.
Design Tokens
| Token | Used for |
|---|---|
--text-wk-xs / --text-wk-sm / --text-wk-md / --text-wk-lg |
Font size per size prop |
--color-wk-text |
Selected date text |
--color-wk-text-muted |
Calendar icon + hint |
--color-wk-text-placeholder |
Empty-state text |
--color-wk-bg-input |
Input background |
--color-wk-accent |
Selected date highlight |
--color-wk-border-strong |
Default border |
--color-wk-border-error |
Error-state border |
--color-wk-danger-text |
Error message |
--color-wk-ring |
Focus ring |
--ring-wk-width |
Focus ring width |
--border-wk-width |
Border width |
--radius-wk-md |
Border radius |
--size-wk-sm / --size-wk-md / --size-wk-lg |
Input height per size prop |
--padding-wk-x-md / --padding-wk-y-xs |
Input padding |
--opacity-wk-disabled |
Disabled visual weight |
--transition-wk-duration |
Hover / focus transition |
Config Defaults
The defaults live in config/wirekit.php under components.date-picker. Override them globally:
'components' => [
'date-picker' => ['size' => 'md', 'format' => 'Y-m-d'],
],
Further Reading
- MDN:
<input type="date"> - Can I Use:
input type=date— browser support matrix - MDN: Date and time input types
- WAI-ARIA: Date Picker Dialog Pattern — for custom calendar UIs (not used here)