Skip to main content
Copy for LLM

Datetime Picker

The <x-wirekit::datetime-picker> component picks a moment: a date and a time of day. Its trigger looks like a field and shows the moment it holds, "Oct 27, 2026 at 10:30 AM". It opens a dialog with a calendar and a list of times beside it. The value is written YYYY-MM-DDTHH:MM, in the shape a datetime-local field submits, and a form receives it under the field's name.

Usage

Datetime picker

A value takes both halves. A day picked first shows on the trigger, and the focus moves to the list of times, but the field stays empty until a time is picked too: half a moment cannot be written as one, and a quiet midnight would be wrong. Once there is a value, another day changes it at once and the dialog stays open; a time completes it and closes the dialog.

With a value

A date object works as the value too, such as a model attribute with a datetime cast. It is read as the wall-clock time it names in your application's time zone.

Confirmation

With with-confirmation the dialog keeps the day and the time it is given until Select is pressed. Cancel, Escape or a press outside leave the value as it was, and the trigger shows the old value until then.

Picked, then confirmed

Default time

default-time gives a day picked without a time that time at once, so a single pick in the calendar makes a value.

Nine o'clock unless changed

Times

interval sets the minutes between two times in the list, 30 by default. min-time and max-time set the first and the last time of every day, and a time outside them is not in the list.

Every 15 minutes within office hours

time-format shows the times on a 12-hour or a 24-hour clock. auto, the default, follows your application's locale.

On a 24-hour clock

The time list is a list of times on the wall clock. A day on which the clocks change offers the same times as any other day, and the value is the time it shows.

Limits

min and max take a moment, a day, a date object, today or now. The calendar closes the days before min and after max, and on those two days the list closes the times outside them. max written as a day leaves the whole of that day open.

From now until noon on 20 December

unavailable closes days, times and spans: a day (2026-10-24), a time or a span on every day (12:00, 12:00-13:00), and a moment or a span on one day (2026-10-23T10:00, 2026-10-23T15:00-16:00), both ends of a span included. A date object closes the moment it names, as an existing booking does. Write a list, or the entries separated by commas.

Lunch and two bookings closed

A closed day or time stays in reach of the keyboard and is named as unavailable, and nothing picks it. The limits steer what a reader can pick and nothing more: validate the value on the server as well.

Typing the time

time="input" puts a field under the calendar where the time is typed, instead of the list beside it. The field holds the hour, the minute and, on a 12-hour clock, AM or PM, each a segment of its own, in the order and with the separators of your application's locale. The button at the end of the field opens the list of times as suggestions, with the same limits, and a time picked there fills the segments.

A typed time

A digit sets a segment and moves on once no other digit fits: on a 12-hour clock 1 waits for 10, 11 or 12, and 3 moves to the minutes at once. The arrow keys step a segment round its range, from 59 back to 00 without changing the hour, Home and End go to its first and its last value, a and p set AM or PM, and Backspace empties it. On a touch screen the keyboard opens with digits.

A typed time outside min-time and max-time, or closed by unavailable, min or max on the day picked, is not taken, and a sentence under the field says why: type 7:00 AM above, and it names the times from 8:00 AM to 6:00 PM. A typed time waits for Select, as with with-confirmation, so the field never receives a time that is half typed.

Typing the whole moment

type="input" makes the trigger a field where the whole moment is typed: the day, the month, the year, the hour, the minute and, on a 12-hour clock, AM or PM, each a segment of its own, in the order and with the separators of the locale. A German page asks 27.10.2026, 14:45, an American one 10/27/2026, 2:45 PM. The button at the end of the field opens the dialog, and the field and the dialog show each other's moment.

A typed moment

A segment takes digits as a typed time does, and the year takes four. A moment typed whole and open is taken at once, as a native field takes it, so wire:model.live hears each one. A day its month does not have, a day closed by min, max or unavailable, and a closed time are not taken: the field holds no moment, and a sentence under it says why. A moment pasted into any segment fills the field, written as a timestamp (2026-10-27T14:45, an offset dropped) or as the locale writes it.

With range the start and the end are typed in two fields, named Start and End, with one button beside them that opens the dialog for both. The two stand side by side, and on a narrow screen one above the other. A range typed whole is taken at once when the end comes after the start and the range covers as many days as min-range and max-range allow, both ends counted; until then the field of the end says why. The dialog opens on what the two fields hold, a start typed without its end among it.

A typed stay of up to two weeks
Start

End

With a custom trigger the trigger stays the caller's button.

Range

range picks two moments, a start and an end. The calendar shows two months and picks the two days, and below it a row for each end takes its time: a field that opens the list of times, or with time="input" the time typed in segments. A range always waits for Select range, which stays closed until both ends have a day and a time and the end comes after the start. On one day the times decide, and the sentence under the times says why.

A trip

The trigger writes the range from its start to its end, "Oct 27, 2026 at 9:00 AM – Nov 1, 2026 at 5:00 PM", and the end's time alone when both ends fall on one day. default-time gives days picked by hand their times, one for both ends or 09:00/17:00 for each, and min-range and max-range limit the days a range covers, as on the calendar.

A shift on one day, typed, nine to five unless changed

With presets the calendar lists ranges such as "Last 7 days" beside it, as on the calendar. A preset takes its whole days, from the first time of a day to the last, unless default-time names the two.

A report period with presets

Binding a range

The form receives name[start] and name[end], each YYYY-MM-DDTHH:MM, and name[preset] with presets. wire:model binds the two ends as {property}.start and {property}.end, so the property is an array with the two keys or a Pushery\WireKit\Values\DateTimeRange, which keeps the time of each end and the preset:

use Pushery\WireKit\Values\DateTimeRange;

public DateTimeRange $trip;

public function mount(): void
{
    $this->trip = DateTimeRange::from('2026-10-27T09:00/2026-11-01T17:00');
}
<x-wirekit::datetime-picker name="trip" label="Trip" range :value="$trip" wire:model.live="trip" />

Validation reads the ends as trip.start and trip.end. :value takes start/end as text, an array with start and end, the two as a list, or a range object.

Opening month and time

Without a value the calendar opens on the current month and the list of times on the current time. open-to names another moment, or a day, for both.

Opens on 2 November at 14:00

Clearing

clearable adds a button to the trigger that empties the field, while it holds a value.

With a clear button

Hint and error

A hint stands under the field and describes the trigger. An error takes its place, marks the trigger invalid and is announced. After a failed validation the error is read from the error bag by name, so a form needs no :error of its own.

With a hint

Local time at the airport.

Error state

The arrival must come after the departure.

Calendar options

The calendar in the dialog takes the options of the calendar component: months, with-today, selectable-header, week-numbers, week-starts-on and fixed-weeks. See the calendar for each.

Today button, week numbers and six weeks

Sizes

size sets the height of the trigger: sm, md, the default, or lg, as on the other fields.

Small and large triggers

Custom trigger

The trigger slot replaces the field with a trigger of your own, a button that says what it does. The picker wires it to the dialog, and its content names it.

A Schedule button
Post

On a phone

Below the kit's sm width, 640 pixels, and on a screen less than 480 pixels tall, as a phone held sideways, the dialog opens as a modal over the page rather than below the trigger: centered over a backdrop, inside the safe area of a phone with a notch, with the page behind it held still. The days grow to the size of a touch target as far as the width allows, a range shows one month with its presets as a select above it, and the list of times gives way to a select, which the phone opens as its own wheel; a typed time keeps its segments. A pick always waits for Select there, with or without with-confirmation, and a tap on the backdrop closes the dialog as Escape does. A dialog taller than the screen scrolls in itself with Cancel and Select in sight. The screen is read when the dialog opens, and a tablet keeps the panel below the trigger whichever way it is held.

A meeting picked on a phone

Set the preview to 390 pixels and open the picker to see the modal.

Inside a form

The picker renders a hidden field with the given name, which a form submits as YYYY-MM-DDTHH:MM. required stops an empty submit as a native field does, with the browser's own message under the field. A form reset returns the picker to the value it started with. disabled takes the field out of the form, and readonly shows the value and opens nothing.

<form method="POST" action="/meetings">
    @csrf

    <x-wirekit::datetime-picker name="starts_at" label="Starts at" required />

    <x-wirekit::button type="submit">Save</x-wirekit::button>
</form>

With Livewire

wire:model binds the moment, written YYYY-MM-DDTHH:MM. Pass the bound property as :value as well: the picker shows the moment it is given, and a moment the server sets reaches it.

<x-wirekit::datetime-picker name="starts_at" label="Starts at" :value="$startsAt" wire:model.live="startsAt" />

The answer to a pick of the reader's own changes nothing on the screen, so an open dialog stays open while the server answers.

Localization

The picker writes its moment in your application's locale, the one config('app.locale') carries: the day, the time and the word between them on the trigger, and the months and weekdays of the calendar. The server writes the trigger the way the script does, so its text does not change when the page starts, and the language of the reader's browser plays no part.

locale writes the moment in another locale, a BCP 47 tag such as fr or en-US: for a schedule that belongs to another region than the page, or for a reader whose language your application keeps in the session.

A moment written in French
<x-wirekit::datetime-picker name="departure" label="Departure" :locale="session('locale')" />

The other texts of the picker, such as Cancel, Select and the placeholder, stay in your application's language and come from WireKit's translations. time-format sets a 12-hour or a 24-hour clock where the locale's own does not suit, and week-starts-on="locale" starts the calendar's week on the locale's first day.

Timezones

The picker works with wall-clock time, as a datetime-local field does: the moment the reader picks is the moment the field submits, 2026-10-27T14:00, and nothing is converted in the browser. A date object, or a moment written with its offset as toIso8601String() writes one, is read in your application's time zone, config('app.timezone'), and Livewire reads the moment that comes back in that zone too.

When your readers live in other time zones, keep the bound property as text in the reader's zone and convert it when you load and store it:

use App\Models\Meeting;
use Carbon\CarbonImmutable;
use Livewire\Component;

class EditMeeting extends Component
{
    public Meeting $meeting;

    // The reader's time zone, from their profile.
    public string $zone = 'America/New_York';

    public string $startsAt = '';

    public function mount(): void
    {
        $this->startsAt = $this->meeting->starts_at->setTimezone($this->zone)->format('Y-m-d\TH:i');
    }

    public function save(): void
    {
        $this->meeting->update([
            'starts_at' => CarbonImmutable::createFromFormat('Y-m-d\TH:i', $this->startsAt, $this->zone)->utc(),
        ]);
    }
}
<x-wirekit::datetime-picker name="starts_at" label="Starts" :value="$startsAt" wire:model="startsAt" />

Props

Prop Type Default Description
name string|null null Name of the hidden form field. Ids derive from it
id string|null null Id of the trigger
label string|null null The field's label
value string|DateTimeInterface|array|null null The moment, YYYY-MM-DDTHH:MM, or a date object, read in the application's time zone. With range: start/end, an array with start and end, or a DateTimeRange. See Binding a range
range bool false Two moments, a start and an end, taken together with Select range. See Range
type string 'button' button, a trigger that opens the dialog, or input, a field where the whole moment is typed, two with range, with a button that opens the dialog. Not with a custom trigger. See Typing the whole moment
placeholder string|null null What the trigger shows without a value; "Select a date and time" by default, "Select a date and time range" with range
hint string|null null A hint below the field
help string|null null An explanation in a tooltip beside the label
error string|null null An error message; read from the error bag by name as well
announceError bool|null null Whether an error is announced when it appears
size string 'md' The height of the trigger: sm, md or lg
disabled bool false Disables the picker and leaves it out of the form
readonly bool false Shows the value and opens nothing
required bool false Stops an empty submit with the browser's message
clearable bool false A button that empties the field
withConfirmation bool false The day and the time wait for Select, as a range always does. See Confirmation
defaultTime string|null null The time a day picked without one takes, HH:MM; with range one for both ends or HH:MM/HH:MM
time string 'list' list, a list of times beside the calendar, or input, a time typed in segments with the list as suggestions. See Typing the time
interval int 30 Minutes between two times in the list
timeFormat string 'auto' auto, 12-hour or 24-hour
minTime string|null null The first time of every day, HH:MM
maxTime string|null null The last time of every day, HH:MM
min string|DateTimeInterface|null null The first moment a reader can pick. See Limits
max string|DateTimeInterface|null null The last moment a reader can pick
unavailable array|string|null null Days, times and spans a reader cannot pick
openTo string|DateTimeInterface|null null Where the dialog opens without a value
months int|null null Months the calendar shows: 1, or 2 with range
minRange int|null null With range: the fewest days a range may cover, both ends counted
maxRange int|null null With range: the most days a range may cover, both ends counted
presets bool|string|array|null null With range: ranges such as "Last 7 days" beside the calendar, as the calendar's presets takes them
withToday bool false The calendar's today button
selectableHeader bool false The calendar's month and year selects
weekNumbers bool false The calendar's week numbers
weekStartsOn int|string 1 The first day of the calendar's week, from 0 to 6, or locale
fixedWeeks bool false Six rows in every month
locale string|null null BCP 47 locale for the moment on the trigger, the calendar and the times. null uses your application's locale. See Localization
scope string|null null Scoped personalization key

Events

The picker dispatches these on its root, and they bubble:

Event When
wirekit:datetime-picker-open The dialog opens
wirekit:datetime-picker-close The dialog closes, however it closes
wirekit:datetime-picker-clear The reader empties the field with the clear button

The root carries data-open while the dialog is open. The hidden field fires input and change for each moment picked, as a native field does, so wire:model.change and wire:model.blur work as on any field.

Accessibility

  • The trigger is a button with aria-haspopup="dialog", aria-expanded and aria-controls. Its name is the field's label and the moment it shows, "Meeting Oct 27, 2026 at 10:30 AM", and "(required)" for a required field.
  • The dialog is a role="dialog" with aria-modal="true", named by the field's label and what it is for, "Meeting, Select a date and time". Enter, Space and Down Arrow on the trigger open it and move the focus to the day of the value, or without one to today or the next day that can be picked, on every opening; Tab stays inside it, and Escape closes it and returns the focus to the trigger.
  • The calendar is the calendar component, with its grid, its keyboard and its names.
  • On a phone the dialog is a modal: the page behind it is inert and does not scroll, the focus stays in the dialog, and a tap on the backdrop or Escape closes it and returns the focus to the trigger. The select that stands in for the list of times is named "Time", or by the row of a range's end, and offers the same times, a closed one disabled.
  • The list of times is a listbox named "Time". The arrow keys move between open times, Home and End go to the first and the last, Page Up and Page Down an hour, and typing goes to the time typed: 9 to nine o'clock, 14 to two in the afternoon, 9:3 to half past nine. Enter or Space picks. A closed time is passed over and named as unavailable.
  • A day picked without a time moves the focus to the list of times.
  • With range the calendar picks the two days as the calendar does with range. The time of each end stands in a row named "Start" or "End": a button named by the row and the time it shows, which opens the list of times as a listbox named by the row, or with time="input" the typed time named by the row. Select range is described by the sentence that says the end must come after the start, which stands in a live region (aria-live="polite") and is announced as it appears, as a field's error is, unless announce-error is off.
  • With time="input" the segments are spin buttons named "Hour", "Minute" and "AM/PM", in a group named "Time", each with its range and its value. Up and Down change a segment, Left and Right move between them, and Tab goes on to the button at the end of the field, then to Cancel and Select. The button is named "Suggested times" and opens the list of times; Escape closes the list first, and the dialog with the next press. A day picked without a time it can take moves the focus to the first segment. The sentence that says why a typed time is not taken is announced, and describes each segment.
  • With type="input" the field is a role="group" named by the label. Its segments are spin buttons named "Day", "Month", "Year", "Hour", "Minute" and "AM/PM", each with its range and its value, and the label points at the first one. The hint and the error describe each segment and the button at the end of the field, which is named "Select a date and time" and opens the dialog; Alt+Down Arrow opens it from a segment, as on a native field. The clear button empties the field and moves the focus to its first segment. The sentence that says why a typed moment is not taken describes each segment and stands in a live region (aria-live="polite"), announced as it appears unless announce-error is off. With range the two fields are groups named by the label and their word, "Stay Start" and "Stay End", in a group named by the label, each with a sentence of its own; the sentence of the end also says when the end does not come after the start or the range covers too few or too many days. The button beside them is named "Select a date and time range".

Keyboard Interaction

Key Where Action
Enter, Space, Down Arrow Trigger Opens the dialog, with the focus on the day of the value, or on today or the next day that can be picked
Escape Dialog Closes the dialog and returns the focus to the trigger; with the suggestions of a typed time or the list of a range's time open, it closes them first
Tab, Shift+Tab Dialog Move between the calendar, the times and the buttons; the focus stays in the dialog
Arrow keys, Home, End, Page Up, Page Down Calendar As on the calendar; with Shift, Page Up and Page Down move a year
Up, Down List of times Move between the open times
Home, End List of times The first and the last open time
Page Up, Page Down List of times An hour back or ahead
A digit List of times Goes to the time typed: 9 to nine o'clock, 14 to two in the afternoon, 9:3 to half past nine
Enter, Space List of times Picks the time
Enter, Space Time of a range's end Opens its list of times, with the focus on the time it shows or the nearest open one
Up, Down Typed time Steps the segment round its range
Left, Right Typed time Moves between the segments
Home, End Typed time The first and the last value of the segment
a, p Typed time Sets AM or PM
Backspace Typed time Empties the segment
A digit Typed moment Sets the segment and moves on once no other digit fits, the year at its fourth digit
Up, Down Typed moment Steps the segment round its range; an empty year starts at the current one
Left, Right Typed moment Moves between the segments
Home, End Typed moment The first and the last value of the segment
a, p Typed moment Sets AM or PM
Backspace Typed moment Empties the segment, or moves back from an empty one
Alt+Down Arrow Typed moment Opens the dialog

Design Tokens

Token Used for
--size-wk-time-list Width of the list of times beside the calendar
--size-wk-sm / --size-wk-md / --size-wk-lg Height of the trigger at each size, and of a time in the list
--size-wk-xl The least height of the list of times
--color-wk-bg-input / --color-wk-border-strong The trigger's surface and border
--color-wk-bg-elevated / --color-wk-border / --shadow-wk-lg The dialog's surface, border and shadow
--color-wk-bg-hover / --color-wk-bg-active A time under the pointer, and the time picked
--color-wk-text-muted / --color-wk-text-placeholder The word "at" and the icons, and the placeholder
--radius-wk-md / --radius-wk-lg / --radius-wk-sm The trigger, the dialog and a time
--color-wk-accent / --color-wk-accent-fg The segment of a typed time that has the focus
--color-wk-border-error / --color-wk-danger-text The field of a typed time that is not taken, and the sentence that says why
--color-wk-overlay / --z-wk-modal The backdrop behind the dialog on a phone, and the layer the two stand on
--size-wk-modal-sm The width of the dialog on a phone, less the edges of a narrower screen
--size-wk-touch-target The size the days of the calendar grow to on a phone

Personalization

Override classes globally via WireKit::personalize():

use Pushery\WireKit\WireKit;

WireKit::personalize('datetime-picker', [
    'trigger' => 'rounded-[var(--radius-wk-lg)]',
    'panel' => 'shadow-[var(--shadow-wk-xl)]',
]);

Further Reading

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store