Date Picker
A date picker lets you enter a date through text input or pick one from a calendar.
Good to know: The date picker is built on top of the
@internationalized/datelibrary.
Features
- Displays a calendar view for date selection
- Supports
single,multiple, andrangeselection modes - Supports disabling specific dates
- Supports date range presets
- Supports week numbers
- Supports custom format and parse logic
- Works with localization, timezone, and custom calendar systems
- Provides keyboard accessibility for navigating the calendar.
Installation
Install the date-picker package:
npm install @zag-js/date-picker @zag-js/react # or yarn add @zag-js/date-picker @zag-js/react
npm install @zag-js/date-picker @zag-js/solid # or yarn add @zag-js/date-picker @zag-js/solid
npm install @zag-js/date-picker @zag-js/vue # or yarn add @zag-js/date-picker @zag-js/vue
npm install @zag-js/date-picker @zag-js/svelte # or yarn add @zag-js/date-picker @zag-js/svelte
Anatomy
Check the date-picker anatomy and part names.
Each part includes a
data-partattribute to help identify them in the DOM.
Usage
Import the package:
import * as datepicker from "@zag-js/date-picker"
These are the key exports:
machine- Behavior logic.connect- Maps behavior to JSX props and event handlers.parse- Parses an ISO 8601 date string.
You'll also need to provide a unique
idto theuseMachinehook. This is used to ensure that every part has a unique identifier.
Then use the framework integration helpers:
import * as datepicker from "@zag-js/date-picker" import { useMachine, normalizeProps, Portal } from "@zag-js/react" import { useId } from "react" function DatePicker() { const service = useMachine(datepicker.machine, { id: useId() }) const api = datepicker.connect(service, normalizeProps) return ( <> <div {...api.getControlProps()}> <input {...api.getInputProps()} /> <button {...api.getTriggerProps()}>🗓</button> </div> <Portal> <div {...api.getPositionerProps()}> <div {...api.getContentProps()}> {/* Day View */} <div hidden={api.view !== "day"}> <div {...api.getViewControlProps({ view: "year" })}> <button {...api.getPrevTriggerProps()}>Prev</button> <button {...api.getViewTriggerProps()}> {api.visibleRangeText.start} </button> <button {...api.getNextTriggerProps()}>Next</button> </div> <table {...api.getTableProps({ view: "day" })}> <thead {...api.getTableHeaderProps({ view: "day" })}> <tr {...api.getTableRowProps({ view: "day" })}> {api.weekDays.map((day, i) => ( <th scope="col" key={i} aria-label={day.long}> {day.narrow} </th> ))} </tr> </thead> <tbody {...api.getTableBodyProps({ view: "day" })}> {api.weeks.map((week, i) => ( <tr key={i} {...api.getTableRowProps({ view: "day" })}> {week.map((value, i) => ( <td key={i} {...api.getDayTableCellProps({ value })}> <div {...api.getDayTableCellTriggerProps({ value })}> {value.day} </div> </td> ))} </tr> ))} </tbody> </table> </div> {/* Month View */} <div hidden={api.view !== "month"}> <div {...api.getViewControlProps({ view: "month" })}> <button {...api.getPrevTriggerProps({ view: "month" })}> Prev </button> <button {...api.getViewTriggerProps({ view: "month" })}> {api.visibleRange.start.year} </button> <button {...api.getNextTriggerProps({ view: "month" })}> Next </button> </div> <table {...api.getTableProps({ view: "month", columns: 4 })}> <tbody {...api.getTableBodyProps({ view: "month" })}> {api .getMonthsGrid({ columns: 4, format: "short" }) .map((months, row) => ( <tr key={row} {...api.getTableRowProps()}> {months.map((month, index) => ( <td key={index} {...api.getMonthTableCellProps({ ...month, columns: 4, })} > <div {...api.getMonthTableCellTriggerProps({ ...month, columns: 4, })} > {month.label} </div> </td> ))} </tr> ))} </tbody> </table> </div> {/* Year View */} <div hidden={api.view !== "year"}> <div {...api.getViewControlProps({ view: "year" })}> <button {...api.getPrevTriggerProps({ view: "year" })}> Prev </button> <span> {api.getDecade().start} - {api.getDecade().end} </span> <button {...api.getNextTriggerProps({ view: "year" })}> Next </button> </div> <table {...api.getTableProps({ view: "year", columns: 4 })}> <tbody {...api.getTableBodyProps()}> {api.getYearsGrid({ columns: 4 }).map((years, row) => ( <tr key={row} {...api.getTableRowProps({ view: "year" })}> {years.map((year, index) => ( <td key={index} {...api.getYearTableCellProps({ ...year, columns: 4, })} > <div {...api.getYearTableCellTriggerProps({ ...year, columns: 4, })} > {year.label} </div> </td> ))} </tr> ))} </tbody> </table> </div> </div> </div> </Portal> </> ) }
import * as datepicker from "@zag-js/date-picker" import { useMachine, normalizeProps } from "@zag-js/solid" import { createMemo, createUniqueId, Portal } from "solid-js" function DatePicker() { const service = useMachine(datepicker.machine, { id: createUniqueId() }) const api = createMemo(() => datepicker.connect(service, normalizeProps)) return ( <> <div {...api().getControlProps()}> <input {...api().getInputProps()} /> <button {...api().getTriggerProps()}>🗓</button> </div> <Portal> <div {...api().getPositionerProps()}> <div {...api().getContentProps()}> {/* Day View */} <div hidden={api().view !== "day"}> <div {...api().getViewControlProps()}> <button {...api().getPrevTriggerProps()}>Prev</button> <button {...api().getViewTriggerProps()}> {api().visibleRangeText.start} </button> <button {...api().getNextTriggerProps()}>Next</button> </div> <table {...api().getTableProps()}> <thead {...api().getTableHeaderProps()}> <tr {...api().getTableBodyProps()}> <Index each={api().weekDays}> {(day) => ( <th scope="col" aria-label={day().long}> {day().narrow} </th> )} </Index> </tr> </thead> <tbody {...api().getTableBodyProps()}> <Index each={api().weeks}> {(week) => ( <tr {...api().getTableRowProps()}> <Index each={week()}> {(value) => ( <td {...api().getDayTableCellProps({ value: value(), })} > <div {...api().getDayTableCellTriggerProps({ value: value(), })} > {value().day} </div> </td> )} </Index> </tr> )} </Index> </tbody> </table> </div> {/* Month View */} <div hidden={api().view !== "month"}> <div {...api().getViewControlProps({ view: "month" })}> <button {...api().getPrevTriggerProps({ view: "month" })}> Prev </button> <button {...api().getViewTriggerProps({ view: "month" })}> {api().visibleRange.start.year} </button> <button {...api().getNextTriggerProps({ view: "month" })}> Next </button> </div> <table {...api().getTableProps({ view: "month", columns: 4 })}> <tbody> <Index each={api().getMonthsGrid({ columns: 4, format: "short" })} > {(months) => ( <tr {...api().getTableBodyProps({ view: "month" })}> <Index each={months()}> {(month) => ( <td {...api().getMonthTableCellProps(month())}> <div {...api().getMonthTableCellTriggerProps( month(), )} > {month().label} </div> </td> )} </Index> </tr> )} </Index> </tbody> </table> </div> {/* Year View */} <div hidden={api().view !== "year"}> <div {...api().getViewControlProps({ view: "year" })}> <button {...api().getPrevTriggerProps({ view: "year" })}> Prev </button> <span> {api().getDecade().start} - {api().getDecade().end} </span> <button {...api().getNextTriggerProps({ view: "year" })}> Next </button> </div> <table {...api().getTableProps({ view: "year", columns: 4 })}> <tbody> <Index each={api().getYearsGrid({ columns: 4 })}> {(years) => ( <tr {...api().getTableBodyProps({ view: "year" })}> <Index each={years()}> {(year) => ( <td {...api().getYearTableCellProps({ ...year(), columns: 4, })} > <div {...api().getYearTableCellTriggerProps({ ...year(), columns: 4, })} > {year().label} </div> </td> )} </Index> </tr> )} </Index> </tbody> </table> </div> </div> </div> </Portal> </> ) }
<script setup lang="ts"> import * as datepicker from "@zag-js/date-picker" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed, Fragment, Teleport } from "vue" const service = useMachine(datepicker.machine, { id: "1", }) const api = computed(() => datepicker.connect(service, normalizeProps)) </script> <template> <> <div v-bind="api.getControlProps()"> <input v-bind="api.getInputProps()" /> <button v-bind="api.getTriggerProps()">🗓</button> </div> <Teleport to="body"> <div v-bind="api.getPositionerProps()"> <div v-bind="api.getContentProps()"> <!-- Day View --> <div v-show="api.view !== 'day'"> <div v-bind="api.getViewControlProps({ view: 'year' })"> <button v-bind="api.getPrevTriggerProps()">Prev</button> <button v-bind="api.getViewTriggerProps()"> {{api.visibleRangeText.start}} </button> <button v-bind="api.getNextTriggerProps()">Next</button> </div> <table v-bind="api.getTableProps({ view: 'day' })"> <thead v-bind="api.getTableHeaderProps({ view: 'day' })"> <tr v-bind="api.getTableRowProps({ view: 'day' })"> <template v-for="(day) in api.weekDays"> <th scope="col"> {{day.narrow}} </th> </template> </tr> </thead> <tbody v-bind="api.getTableBodyProps({ view: 'day' })"> <template v-for="(week) in api.weeks"> <tr v-bind="api.getTableRowProps({ view: 'day' })"> <template v-for="(value) in week"> <td v-bind="api.getDayTableCellProps({ value })"> <div v-bind="api.getDayTableCellTriggerProps({ value })"> {{value.day}} </div> </td> </template> </tr> </template> </tbody> </table> </div> <!-- Month View --> <div v-show="api.view !== 'month'"> <div v-bind="api.getViewControlProps({ view: 'month' })"> <button v-bind="api.getPrevTriggerProps({ view: 'month' })"> Prev </button> <button v-bind="api.getViewTriggerProps({ view: 'month' })"> {{ api.visibleRange.start.year }} </button> <button v-bind="api.getNextTriggerProps({ view: 'month' })"> Next </button> </div> <table v-bind="api.getTableProps({ view: 'month', columns: 4 })"> <tbody v-bind="api.getTableBodyProps({ view: 'month' })"> <template v-for="(months) in api.getMonthsGrid({ columns: 4, format: 'short' })"> <tr v-bind="api.getTableRowProps()"> <template v-for="(month) in months"> <td v-bind="api.getMonthTableCellProps({ ...month, columns: 4, })"> <div v-bind="api.getMonthTableCellTriggerProps({ ...month, columns: 4, })"> {{ month.label }} </div> </td> </template> </tr> </template> </tbody> </table> </div> <!-- Year View --> <div v-show="api.view !== 'year'"> <div v-bind="api.getViewControlProps({ view: 'year' })"> <button v-bind="api.getPrevTriggerProps({ view: 'year' })"> Prev </button> <span> {{ api.getDecade().start }} - {{ api.getDecade().end }} </span> <button v-bind="api.getNextTriggerProps({ view: 'year' })"> Next </button> </div> <table v-bind="api.getTableProps({ view: 'year', columns: 4 })"> <tbody v-bind="api.getTableBodyProps()"> <template v-for="(years) in api.getYearsGrid({ columns: 4 })"> <tr v-bind="api.getTableRowProps({ view: 'year' })"> <template v-for="(year) in years"> <td v-bind="api.getYearTableCellProps({ ...year, columns: 4, })"> <div v-bind="api.getYearTableCellTriggerProps({ ...year, columns: 4, })"> {{ year.label }} </div> </td> </template> </tr> </template> </tbody> </table> </div> </div> </div> </Teleport> </> </template>
<script lang="ts"> import * as datepicker from "@zag-js/date-picker" import { portal, useMachine, normalizeProps } from "@zag-js/svelte" const id = $props.id() const service = useMachine(datepicker.machine, ({ id })) const api = $derived(datepicker.connect(service, normalizeProps)) </script> <div {...api.getControlProps()}> <input {...api.getInputProps()} /> <button {...api.getTriggerProps()}>🗓</button> </div> <div use:portal {...api.getPositionerProps()}> <div {...api.getContentProps()}> <!-- Day View --> <div hidden={api.view !== "day"}> <div {...api.getViewControlProps({ view: "year" })}> <button {...api.getPrevTriggerProps()}>Prev</button> <button {...api.getViewTriggerProps()}> {api.visibleRangeText.start} </button> <button {...api.getNextTriggerProps()}>Next</button> </div> <table {...api.getTableProps({ view: "day" })}> <thead {...api.getTableHeaderProps({ view: "day" })}> <tr {...api.getTableRowProps({ view: "day" })}> {#each api.weekDays as day, i (i)} <th scope="col" aria-label={day.long}>{day.narrow}</th> {/each} </tr> </thead> <tbody {...api.getTableBodyProps({ view: "day" })}> {#each api.weeks as week, i (i)} <tr {...api.getTableRowProps({ view: "day" })}> {#each week as value, i (i)} <td {...api.getDayTableCellProps({ value })}> <div {...api.getDayTableCellTriggerProps({ value })}>{value.day}</div> </td> {/each} </tr> {/each} </tbody> </table> </div> <!-- Month View --> <div hidden={api.view !== "month"}> <div {...api.getViewControlProps({ view: "month" })}> <button {...api.getPrevTriggerProps({ view: "month" })}> Prev </button> <button {...api.getViewTriggerProps({ view: "month" })}> {api.visibleRange.start.year} </button> <button {...api.getNextTriggerProps({ view: "month" })}> Next </button> </div> <table {...api.getTableProps({ view: "month", columns: 4 })}> <tbody {...api.getTableBodyProps({ view: "month" })}> {#each api.getMonthsGrid({ columns: 4, format: "short" }) as months, row (row)} <tr {...api.getTableRowProps()}> {#each months as month, index (index)} <td {...api.getMonthTableCellProps({ ...month, columns: 4 })}> <div {...api.getMonthTableCellTriggerProps({ ...month, columns: 4 })}>{month.label}</div> </td> {/each} </tr> {/each} </tbody> </table> </div> <!-- Year View --> <div hidden={api.view !== "year"}> <div {...api.getViewControlProps({ view: "year" })}> <button {...api.getPrevTriggerProps({ view: "year" })}> Prev </button> <span> {api.getDecade().start} - {api.getDecade().end} </span> <button {...api.getNextTriggerProps({ view: "year" })}> Next </button> </div> <table {...api.getTableProps({ view: "year", columns: 4 })}> <tbody {...api.getTableBodyProps()}> {#each api.getYearsGrid({ columns: 4 }) as years, row (row)} <tr {...api.getTableRowProps({ view: "year" })}> {#each years as year, index (index)} <td {...api.getYearTableCellProps({ ...year, columns: 4 })}> <div {...api.getYearTableCellTriggerProps({ ...year, columns: 4 })}>{year.label}</div> </td> {/each} </tr> {/each} </tbody> </table> </div> </div> </div>
Setting the initial date
To set the initial value rendered by the date picker, set defaultValue.
const service = useMachine(datepicker.machine, { defaultValue: [datepicker.parse("2022-01-01")], })
Controlling the selected date
Use the value and onValueChange properties to programmatically control the
selected date.
const service = useMachine(datepicker.machine, { value: [datepicker.parse("2022-01-01")], onValueChange(details) { // details => { value: DateValue[], valueAsString: string[], view: "day" | "month" | "year" } console.log("selected date:", details.valueAsString) }, })
You can also set it with api.setValue.
const nextValue = datepicker.parse("2022-01-01") api.setValue([nextValue])
Controlling the open state
Use open and onOpenChange to control the popup state.
const service = useMachine(datepicker.machine, { open: true, onOpenChange(details) { // details => { open: boolean, value: DateValue[] } console.log("open state changed to:", details.open) }, })
You can also manage it with api.setOpen.
// open the date picker api.setOpen(true) // close the date picker api.setOpen(false)
Setting the min and max dates
To constrain the date range that can be selected by the user, set the min and
max properties.
const service = useMachine(datepicker.machine, { min: datepicker.parse("2022-01-01"), max: datepicker.parse("2022-12-31"), })
When min or max is reached, previous/next triggers are disabled.
Changing the start of the week
Set startOfWeek from 0 to 6 (0 is Sunday, 6 is Saturday).
const service = useMachine(datepicker.machine, { startOfWeek: 1, // Monday })
Disabling the date picker
Set disabled to true to disable the picker.
const service = useMachine(datepicker.machine, { disabled: true, })
Rendering month and year pickers
Render month and year selects with api.getMonthSelectProps and
api.getYearSelectProps.
<div> <select {...api.getMonthSelectProps()}> {api.getMonths().map((month, i) => ( <option key={i} value={month.value}> {month.label} </option> ))} </select> <select {...api.getYearSelectProps()}> {getYearsRange({ from: 1_000, to: 4_000 }).map((year, i) => ( <option key={i} value={year}> {year} </option> ))} </select> </div>
Marking unavailable dates
Set isDateUnavailable and return true for blocked dates.
import { isWeekend } from "@internationalized/date" const service = useMachine(datepicker.machine, { isDateUnavailable: (date, locale) => { // mark weekends as unavailable return isWeekend(date, locale) }, })
Setting the calendar starting view
The default view is day. Set defaultView to day, month, or year to
change it.
const service = useMachine(datepicker.machine, { defaultView: "month", })
Setting the read-only mode
Set readOnly to true to prevent value changes.
const service = useMachine(datepicker.machine, { readOnly: true, })
Required and invalid state
Use required and invalid for form validation and UI state.
const service = useMachine(datepicker.machine, { required: true, invalid: false, })
Setting the focused date
By default, focused date is the first selected date or today. Set
defaultFocusedValue to override it.
const service = useMachine(datepicker.machine, { defaultFocusedValue: datepicker.parse("2022-01-01"), })
Rendering the calendar inline
To render the calendar inline, set inline to true.
const service = useMachine(datepicker.machine, { inline: true, })
Usage within a form
Set name to include the picker in form data.
const service = useMachine(datepicker.machine, { name: "date", })
Rendering fixed number of weeks
Set fixedWeeks to true to always render 6 weeks and avoid layout jumps
between months.
const service = useMachine(datepicker.machine, { fixedWeeks: true, })
Showing week numbers
Set showWeekNumbers to true to enable a week-number column in day view.
const service = useMachine(datepicker.machine, { showWeekNumbers: true, })
Then render the week-number header and cells in your day table.
<table {...api.getTableProps({ view: "day" })}> <thead {...api.getTableHeaderProps({ view: "day" })}> <tr {...api.getTableRowProps({ view: "day" })}> {api.showWeekNumbers && ( <th {...api.getWeekNumberHeaderCellProps({ view: "day" })}>Wk</th> )} {api.weekDays.map((day, i) => ( <th scope="col" key={i} aria-label={day.long}> {day.narrow} </th> ))} </tr> </thead> <tbody {...api.getTableBodyProps({ view: "day" })}> {api.weeks.map((week, weekIndex) => ( <tr key={weekIndex} {...api.getTableRowProps({ view: "day" })}> {api.showWeekNumbers && ( <td {...api.getWeekNumberCellProps({ weekIndex, week })}> {api.getWeekNumber(week)} </td> )} {week.map((value, i) => ( <td key={i} {...api.getDayTableCellProps({ value })}> <div {...api.getDayTableCellTriggerProps({ value })}> {value.day} </div> </td> ))} </tr> ))} </tbody> </table>
Choosing a selection mode
Use selectionMode to switch between single, multiple, and range.
const service = useMachine(datepicker.machine, { selectionMode: "multiple", })
If you use multiple, you can cap selections with maxSelectedDates.
const service = useMachine(datepicker.machine, { selectionMode: "multiple", maxSelectedDates: 3, })
Using range presets
In range mode, api.getPresetTriggerProps accepts a preset key like
"last7Days" or a custom DateValue[].
const service = useMachine(datepicker.machine, { selectionMode: "range", })
<button {...api.getPresetTriggerProps({ value: "last7Days" })}> Last 7 days </button> <button {...api.getPresetTriggerProps({ value: "last30Days" })}> Last 30 days </button>
<button {...api.getPresetTriggerProps({ value: [datepicker.parse("2024-01-01"), datepicker.parse("2024-01-15")], })} > First half of Jan </button>
Customizing format and parse behavior
Use format and parse to control how input text is displayed and parsed.
const service = useMachine(datepicker.machine, { format: (date, details) => date .toDate(details.timeZone) .toLocaleDateString(details.locale, { dateStyle: "short" }), parse: (value) => datepicker.parse(value), })
You can also build specialized pickers:
- Month-year picker: use
minView: "month"andplaceholder: "mm/yyyy" - Year picker: use
minView: "year",defaultView: "year", andplaceholder: "yyyy"
import { CalendarDate } from "@internationalized/date" const service = useMachine(datepicker.machine, { minView: "month", view: "month", placeholder: "mm/yyyy", format: (date) => `${String(date.month).padStart(2, "0")}/${date.year}`, parse: (value) => { const match = value.match(/^(\d{1,2})\/(\d{4})$/) if (!match) return undefined const [_, month, year] = match.map(Number) return new CalendarDate(year, month, 1) }, })
import { CalendarDate } from "@internationalized/date" const service = useMachine(datepicker.machine, { selectionMode: "range", minView: "year", defaultView: "year", placeholder: "yyyy", format: (date) => String(date.year), parse: (value) => { const match = value.match(/^(\d{4})$/) if (!match) return undefined const [_, year] = match.map(Number) return new CalendarDate(year, 1, 1) }, })
Setting locale and timezone
Set locale and timeZone to control date rendering.
const service = useMachine(datepicker.machine, { locale: "en-GB", timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, })
Using custom calendar systems
Pass a dedicated createCalendar function so you only ship the calendar your
locale needs.
import { PersianCalendar } from "@internationalized/date" function createPersianCalendar(identifier: string) { if (identifier === "persian") return new PersianCalendar() throw new Error(`Unsupported calendar: ${identifier}`) } const service = useMachine(datepicker.machine, { locale: "fa-IR", createCalendar: createPersianCalendar, })
import { BuddhistCalendar } from "@internationalized/date" function createBuddhistCalendar(identifier: string) { if (identifier === "buddhist") return new BuddhistCalendar() throw new Error(`Unsupported calendar: ${identifier}`) } const service = useMachine(datepicker.machine, { locale: "th-TH", createCalendar: createBuddhistCalendar, })
Restricting available views
Use minView and maxView to limit view navigation.
const service = useMachine(datepicker.machine, { minView: "month", maxView: "year", })
Controlling open behavior
By default, the picker closes after selection and does not open on input click. You can change both behaviors.
const service = useMachine(datepicker.machine, { openOnClick: true, closeOnSelect: false, })
Use defaultOpen if you want the calendar to start open in uncontrolled mode.
const service = useMachine(datepicker.machine, { defaultOpen: true, })
Allowing outside-day selection
Days outside the visible month are disabled by default. Set
outsideDaySelectable to true if you want them selectable.
const service = useMachine(datepicker.machine, { outsideDaySelectable: true, })
Listening to date changes
Use onValueChange to listen for date changes.
const service = useMachine(datepicker.machine, { onValueChange(details) { // details => { value: DateValue[], valueAsString: string[], view: "day" | "month" | "year" } console.log("selected date:", details.valueAsString) }, })
Listening to view changes
Use onViewChange to listen for view changes.
const service = useMachine(datepicker.machine, { onViewChange(details) { // details => { view: "day" | "month" | "year" } console.log("view changed to:", details.view) }, })
Controlling the focused date
Use focusedValue and onFocusChange to control keyboard focus in the
calendar.
const service = useMachine(datepicker.machine, { focusedValue: datepicker.parse("2022-01-01"), onFocusChange(details) { console.log("focused date:", details.focusedValue) }, })
Listening to focus and visible range changes
Use onFocusChange for focused-date changes and onVisibleRangeChange for
visible-range changes.
const service = useMachine(datepicker.machine, { onFocusChange(details) { console.log("focused date:", details.focusedValue) }, onVisibleRangeChange(details) { console.log("visible range:", details.visibleRange) }, })
Rendering multiple months
To display multiple months:
- set the
numOfMonthsprop to the desired number of months - generate the weeks for the offset months using
api.getOffset({ months: 1 })
const service = useMachine(datepicker.machine, { // ... numOfMonths: 2, }) const offset = api.getOffset({ months: 1 })
Then render the offset months.
<tbody {...api.getTableBodyProps({ view: "day" })}> {offset.weeks.map((week, i) => ( <tr key={i} {...api.getTableRowProps({ view: "day" })}> {week.map((value, i) => ( <td key={i} {...api.getDayTableCellProps({ value, visibleRange: offset.visibleRange, })} > <div {...api.getDayTableCellTriggerProps({ value, visibleRange: offset.visibleRange, })} > {value.day} </div> </td> ))} </tr> ))} </tbody>
Positioning
Use positioning to customize popup placement.
const service = useMachine(datepicker.machine, { positioning: { placement: "bottom-start" }, })
Localization
Use translations to customize accessibility labels and messages.
const service = useMachine(datepicker.machine, { translations: { clearTrigger: "Clear date", }, })
Styling guide
Each date-picker part includes a data-part attribute you can target in CSS.
[data-scope="date-picker"][data-part="root"] { /* styles for the root part */ } [data-scope="date-picker"][data-part="input"] { /* styles for the input part */ } [data-scope="date-picker"][data-part="trigger"] { /* styles for the trigger part */ } [data-scope="date-picker"][data-part="content"] { /* styles for the input part */ }
Open State
[data-scope="date-picker"][data-part="trigger"] { &[data-state="open"] { /* styles for the open state */ } &[data-state="closed"] { /* styles for the closed state */ } }
Cell States
[data-scope="date-picker"][data-part="table-cell-trigger"] { /* styles for the cell */ &[data-selected] { /* styles for the selected date */ } &[data-focus] { /* styles for the focused date */ } &[data-disabled] { /* styles for the disabled date */ } &[data-unavailable] { /* styles for the unavailable date */ } &[data-today] { /* styles for the today date */ } &[data-weekend] { /* styles for the weekend date */ } }
Methods and Properties
Machine Context
The date picker machine exposes the following context properties:
localestringThe locale (BCP 47 language tag) to use when formatting the date.createCalendar(identifier: CalendarIdentifier) => CalendarA function that creates a Calendar object for a given calendar identifier. Enables non-Gregorian calendar support (Persian, Buddhist, Islamic, etc.) without bundling all calendars by default.translationsIntlTranslationsThe localized messages to use.idsPartial<{ root: string; label: (index: number) => string; table: (id: string) => string; tableHeader: (id: string) => string; tableBody: (id: string) => string; tableRow: (id: string) => string; content: string; cellTrigger: (id: string) => string; prevTrigger: (view: DateView) => string; nextTrigger: (view: DateView) => string; viewTrigger: (view: DateView) => string; clearTrigger: string; control: string; input: (index: number) => string; trigger: string; monthSelect: string; yearSelect: string; positioner: string; }>The ids of the elements in the date picker. Useful for composition.namestringThe `name` attribute of the input element.timeZonestringThe time zone to usedisabledbooleanWhether the calendar is disabled.readOnlybooleanWhether the calendar is read-only.requiredbooleanWhether the date picker is requiredinvalidbooleanWhether the date picker is invalidoutsideDaySelectablebooleanWhether day outside the visible range can be selected.minDateValueThe minimum date that can be selected.maxDateValueThe maximum date that can be selected.closeOnSelectbooleanWhether the calendar should close after the date selection is complete. This is ignored when the selection mode is `multiple`.openOnClickbooleanWhether to open the calendar when the input is clicked.valueDateValue[]The controlled selected date(s).defaultValueDateValue[]The initial selected date(s) when rendered. Use when you don't need to control the selected date(s) of the date picker.focusedValueDateValueThe controlled focused date.defaultFocusedValueDateValueThe initial focused date when rendered. Use when you don't need to control the focused date of the date picker.numOfMonthsnumberThe number of months to display.startOfWeeknumberThe first day of the week. `0` - Sunday `1` - Monday `2` - Tuesday `3` - Wednesday `4` - Thursday `5` - Friday `6` - SaturdayfixedWeeksbooleanWhether the calendar should have a fixed number of weeks. This renders the calendar with 6 weeks instead of 5 or 6.showWeekNumbersbooleanWhether to show the week number column in the day view.onValueChange(details: ValueChangeDetails) => voidFunction called when the value changes.onFocusChange(details: FocusChangeDetails) => voidFunction called when the focused date changes.onViewChange(details: ViewChangeDetails) => voidFunction called when the view changes.onVisibleRangeChange(details: VisibleRangeChangeDetails) => voidFunction called when the visible range changes.onOpenChange(details: OpenChangeDetails) => voidFunction called when the calendar opens or closes.isDateUnavailable(date: DateValue, locale: string) => booleanReturns whether a date of the calendar is available.selectionModeSelectionModeThe selection mode of the calendar. - `single` - only one date can be selected - `multiple` - multiple dates can be selected - `range` - a range of dates can be selectedmaxSelectedDatesnumberThe maximum number of dates that can be selected. This is only applicable when `selectionMode` is `multiple`.format(date: LocaleDetails) => stringThe format of the date to display in the input.parse(value: string, details: LocaleDetails) => DateValueFunction to parse the date from the input back to a DateValue.placeholderstringThe placeholder text to display in the input.viewDateViewThe view of the calendardefaultViewDateViewThe default view of the calendarminViewDateViewThe minimum view of the calendarmaxViewDateViewThe maximum view of the calendarpositioningPositioningOptionsThe user provided options used to position the date picker contentopenbooleanThe controlled open state of the date pickerdefaultOpenbooleanThe initial open state of the date picker when rendered. Use when you don't need to control the open state of the date picker.inlinebooleanWhether to render the date picker inlinedir"ltr" | "rtl"The document's text/writing direction.idstringThe unique identifier of the machine.getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The date picker api exposes the following methods:
focusedbooleanWhether the input is focusedopenbooleanWhether the date picker is opendisabledbooleanWhether the date picker is disabledinvalidbooleanWhether the date picker is invalidreadOnlybooleanWhether the date picker is read-onlyinlinebooleanWhether the date picker is rendered inlinenumOfMonthsnumberThe number of months to displayshowWeekNumbersbooleanWhether the week number column is shown in the day viewselectionModeSelectionModeThe selection mode (single, multiple, or range)maxSelectedDatesnumberThe maximum number of dates that can be selected (only for multiple selection mode).isMaxSelectedbooleanWhether the maximum number of selected dates has been reached.viewDateViewThe current view of the date pickergetWeekNumber(week: DateValue[]) => numberReturns the ISO 8601 week number (1-53) for the given week (array of dates).getDaysInWeek(week: number, from?: DateValue) => DateValue[]Returns an array of days in the week index counted from the provided start date, or the first visible date if not given.getOffset(duration: DateDuration) => DateValueOffsetReturns the offset of the month based on the provided number of months.getRangePresetValue(value: DateRangePreset) => DateValue[]Returns the range of dates based on the provided date range preset.getMonthWeeks(from?: DateValue) => DateValue[][]Returns the weeks of the month from the provided date. Represented as an array of arrays of dates.isUnavailable(date: DateValue) => booleanReturns whether the provided date is available (or can be selected)weeksDateValue[][]The weeks of the month. Represented as an array of arrays of dates.weekDaysWeekDay[]The days of the week. Represented as an array of strings.visibleRangeVisibleRangeThe visible range of dates.visibleRangeTextVisibleRangeTextThe human readable text for the visible range of dates.valueDateValue[]The selected date.valueAsDateDate[]The selected date as a Date object.valueAsStringstring[]The selected date as a string.focusedValueDateValueThe focused date.focusedValueAsDateDateThe focused date as a Date object.focusedValueAsStringstringThe focused date as a string.selectTodayVoidFunctionSets the selected date to today.setValue(values: DateValue[]) => voidSets the selected date to the given date.setTime(time: Time, index?: number) => voidSets the time for a specific date value. Converts CalendarDate to CalendarDateTime if needed.setFocusedValue(value: DateValue) => voidSets the focused date to the given date.clearValue(options?: { focus?: boolean; }) => voidClears the selected date(s).setOpen(open: boolean) => voidFunction to open or close the calendar.focusMonth(month: number) => voidFunction to set the selected month.focusYear(year: number) => voidFunction to set the selected year.getYears() => Cell[]Returns the months of the yeargetYearsGrid(props?: YearGridProps) => YearGridValueReturns the years of the decade based on the columns. Represented as an array of arrays of years.getDecade() => Range<number>Returns the start and end years of the decade.getMonths(props?: MonthFormatOptions) => Cell[]Returns the months of the yeargetMonthsGrid(props?: MonthGridProps) => MonthGridValueReturns the months of the year based on the columns. Represented as an array of arrays of months.format(value: DateValue, opts?: Intl.DateTimeFormatOptions) => stringFormats the given date value based on the provided options.setView(view: DateView) => voidSets the view of the date picker.goToNextVoidFunctionGoes to the next month/year/decade.goToPrevVoidFunctionGoes to the previous month/year/decade.getDayTableCellState(props: DayTableCellProps) => DayTableCellStateReturns the state details for a given cell.getMonthTableCellState(props: TableCellProps) => TableCellStateReturns the state details for a given month cell.getYearTableCellState(props: TableCellProps) => TableCellStateReturns the state details for a given year cell.