DatePicker

A field that opens a calendar to choose a date, or a date and time, and the calendar on its own.

StableSince 1.0.0
On this page

Import

import { Calendar, DatePicker } from '@envsemble/envui';

Usage

A DatePicker is a field with its label, description and error, whose button opens the calendar.

  • Its value is a day as YYYY-MM-DD, or null for none.
  • Leave it uncontrolled, with defaultValue, or control it with value and onValueChange, which receives null once the field is cleared.
  • clearable adds a clear button to the field and the calendar.
PublishDate.tsx
import { useState } from 'react';
import { DatePicker } from '@envsemble/envui';

export default function PublishDate() {
  const [publishOn, setPublishOn] = useState<string | null>('2026-10-20');
  return (
    <div style={{ inlineSize: 'min(100%, 20rem)' }}>
      <DatePicker label="Publish on" value={publishOn} onValueChange={setPublishOn} clearable />
    </div>
  );
}

A date and a time

granularity is day, the default, or minute, for a local date and time as YYYY-MM-DDTHH:mm.

  • The time is chosen on wheels of hours, minutes every minuteStep minutes, 15 by default, and, on a 12-hour clock, day periods.
  • placeholderTime, as HH:mm, is the time a newly chosen day starts with, 00:00 by default.
  • hourCycle sets the time's clock whatever the locale's: h23 for 18:00 with no AM/PM wheel, h12 for 6:00 PM. The value stays on the 24-hour clock.
<DatePicker
  label="Meeting"
  granularity="minute"
  placeholderTime="09:00"
  minuteStep={30}
  hourCycle="h23"
  value={meeting}
  onValueChange={setMeeting}
  clearable
/>

Range and format

min and max, in the value's format, make earlier and later days unavailable, and the month buttons stop at them. Check the order of two dates yourself, and set error.

  • locale, EnvUIProvider's by default, sets the shown value and the calendar's names.
  • weekStart sets the first day of the week, from 0 for Sunday.
  • formatOptions sets the Intl.DateTimeFormat options of the shown value.

In a form

name submits the value as YYYY-MM-DD, or YYYY-MM-DDTHH:mm, a local date without a zone; converting it to a moment belongs to the application.

  • required keeps an empty field from submitting, and a reset restores defaultValue.
  • Use readOnly, not disabled, for a date people can't change.
  • Values are YYYY-MM-DD, so two dates compare as text: on submit, set the last field's error where last < first.
<form onSubmit={request}>
  <DatePicker label="First day" name="first" defaultValue="2026-11-02" required />
  <DatePicker label="Last day" name="last" defaultValue="2026-11-06" required error={error} />
  <Button type="submit">Request leave</Button>
</form>

Sizes

  • size is sm, md or lg; without it, the field takes its container's control height.
  • With visibleLabel={false}, placeholder shows in place of the label; name the field with aria-label.
  • The field is 12 rem wide at least, also in a container sized to its content, and in a narrower container it takes the container's width.
<DatePicker
  size="sm"
  visibleLabel={false}
  aria-label="Due"
  placeholder="Due"
  granularity="minute"
/>

Calendar

Calendar is the month on its own, for a page or a panel: it takes value, defaultValue and onValueChange, min, max, weekStart and locale, as the field does.

<Calendar aria-label="Planning" value={day} onValueChange={setDay} />

Composition

open, defaultOpen and onOpenChange control the calendar; onOpenChange also receives why it opens or closes, PickerOpenChangeDetails, such as item-press for a chosen day. clearLabel, todayLabel, nowLabel, timeLabel, previousMonthLabel and nextMonthLabel name its buttons, from EnvUIProvider's messages by default. className and style reach the field's root, and ref the button.

The field's parts are the root, env-field with env-date-picker; the control, env-date-picker-control, with the label, env-date-picker-label, the value, env-date-picker-value, the clear button, env-date-picker-clear, and the calendar icon, env-date-picker-icon; then the description, env-field-description, and the error, env-field-error. The popup is the picker card TimePicker and MonthPicker open too, env-picker-popup with env-date-picker-popup. Its body, env-picker-popup-body, holds the calendar, with env-calendar-previous, env-calendar-title, env-calendar-next and env-calendar-day, and the times, env-date-picker-time, beside it with granularity="minute"; its footer, env-picker-popup-footer with env-date-picker-actions, holds the actions, Clear in env-picker-popup-footer-end with env-date-picker-actions-end.

Customize

Its popup is frosted, as every popup is: --env-popup-opacity, --env-popup-blur and --env-popup-shadow, set on a theme, change it, as Popover describes.

Accessibility

You provide

  • A label, or aria-label on a field without one.
  • Validation, such as the order of two dates, and its error.
  • The labels in the application's language.

envUI provides

  • The field as a combobox button named by its label, with its description and error.
  • The calendar as a grid of days, each named by its full date, with one tab stop.
  • Time wheels as spinbuttons named Hours, Minutes and AM/PM.
  • The month heading as a live region, announced as it changes.

Props

DatePicker

A field for a date, or a date and time, that shows the value in the reader's locale and opens a calendar to choose it, with keyboard access following the WAI-ARIA date picker.

PropTypeDescription
ref
Ref<HTMLButtonElement>
The field's button, which shows the value and opens the calendar.
className
string
The field's class, on its root element, beside env-field and env-date-picker.
style
CSSProperties
Styles of the field's root element.
value
stringnull
The value: a day as YYYY-MM-DD, or with the minute granularity a local date and time as YYYY-MM-DDTHH:mm. null, or an empty string, for none.
defaultValueDefault null
stringnull
The value an uncontrolled field starts with, and that a form reset restores; none by default.
onValueChange
(value: string | null) => void
Receives the new value, or null once the field is cleared.
granularityDefault 'day'
'day''minute'
What the value holds: day, a date as YYYY-MM-DD, or minute, a date and a time as YYYY-MM-DDTHH:mm, chosen in a list of times beside the calendar.
min
string
The earliest value, as YYYY-MM-DD or YYYY-MM-DDTHH:mm; earlier days are unavailable.
max
string
The latest value, as YYYY-MM-DD or YYYY-MM-DDTHH:mm; later days are unavailable.
placeholderTime
string
The time a newly chosen day starts with, as HH:mm; 00:00 by default.
minuteStepDefault 15
number
With the minute granularity, the minutes between the times the list offers, from 1 to 60. A value between them still shows in the list, in its place.
locale
string
The locale of the shown value and the calendar's names and numbers: EnvUIProvider's, else the browser's. Dates are Gregorian, also in a locale with a calendar of its own.
hourCycleDefault the locale's
'h12''h23'
With the minute granularity, the clock of the shown time and the wheels, whatever the locale's: h23 for 18:00, with no AM/PM wheel, or h12 for 6:00 PM. The value stays on the 24-hour clock.
weekStartDefault the locale's, or Monday where the browser cannot tell
0123456
The calendar's first day of the week, from 0 for Sunday to 6 for Saturday.
formatOptions
Intl.DateTimeFormatOptions
How the field shows the value: Intl.DateTimeFormat options, by default the medium date style and, with the minute granularity, the short time style, in the Gregorian calendar unless they name another.
name
string
The name the value is submitted under with a form, as YYYY-MM-DD or YYYY-MM-DDTHH:mm.
requiredDefault false
boolean
A value is needed: an empty field keeps its form from submitting and takes focus.
readOnlyDefault false
boolean
The value can be read and focused but not changed.
placeholder
string
Shown while the field is empty and its label floats or is hidden.
clearableDefault false
boolean
A clear button in the field and in the calendar removes the value.
open
boolean
Whether the calendar is open; with `onOpenChange` it controls it.
defaultOpenDefault false
boolean
Whether an uncontrolled calendar starts open; closed by default.
onOpenChange
(open: boolean, details: PickerOpenChangeDetails) => void
Called with the new state, and why, when the calendar opens or closes.
clearLabelDefault 'Clear', or EnvUIProvider's messages.datePicker.clear
string
Accessible name of the field's clear button and text of the calendar's.
todayLabelDefault 'Today', or EnvUIProvider's messages.datePicker.today
string
The calendar's shortcut to today.
nowLabelDefault 'Now', or EnvUIProvider's messages.datePicker.now
string
With the minute granularity, the calendar's shortcut to the current date and time.
timeLabelDefault 'Time', or EnvUIProvider's messages.datePicker.time
string
With the minute granularity, the heading and accessible name of the list of times.
previousMonthLabelDefault 'Previous month', or EnvUIProvider's messages.calendar.previousMonth
string
Accessible name of the calendar's previous month button.
nextMonthLabelDefault 'Next month', or EnvUIProvider's messages.calendar.nextMonth
string
Accessible name of the calendar's next month button.
label
ReactNode
The field's visible name, such as Project name; it also names the control for assistive technology.
description
ReactNode
Lasting guidance under the field, such as a format or a limit, linked to the control as its description.
error
ReactNode
A message under the field that says how to correct the value. While it is set, the control is invalid (aria-invalid) and the message joins its description, after the description.
size
'sm''md''lg'
The control height, sm, md or lg; without it, the container's, and else md. sm is for a field with its label hidden.
visibleLabelDefault true
boolean
The label shows in the field, where it rests while the field is empty and floats into the outline while the field is in use or has a value. false hides it, for a field whose purpose is clear without it, such as a search in a toolbar: the label still names the field for assistive technology, and a placeholder gives the visible guidance.

Also accepts the native props of <button>, such as ref, className and aria-* attributes.

Calendar

A month of days to choose one from, following the WAI-ARIA date picker grid: the arrow keys move by day and week, Page Up and Page Down by month (with Shift, by year), Home and End to the week's ends, and Enter or Space chooses the day. Named with aria-label or aria-labelledby, it is a group around the month's grid.

PropTypeDescription
value
stringnull
The chosen day as YYYY-MM-DD, or null for none.
defaultValueDefault null
stringnull
The day an uncontrolled calendar starts with; none by default, which shows today's month.
onValueChange
(value: string) => void
Receives the chosen day as YYYY-MM-DD.
min
string
The earliest day that can be chosen, as YYYY-MM-DD; earlier days are unavailable.
max
string
The latest day that can be chosen, as YYYY-MM-DD; later days are unavailable.
weekStartDefault the locale's, or Monday where the browser cannot tell
0123456
The first day of each week, from 0 for Sunday to 6 for Saturday.
locale
string
The locale of the month and weekday names and the day numbers: EnvUIProvider's, else the browser's. The days are always Gregorian, also in a locale with a calendar of its own.
previousMonthLabelDefault 'Previous month', or EnvUIProvider's messages.calendar.previousMonth
string
Accessible name of the previous month button.
nextMonthLabelDefault 'Next month', or EnvUIProvider's messages.calendar.nextMonth
string
Accessible name of the next month button.

Also accepts the native props of <div>, such as ref, className and aria-* attributes.

Part names

Each part's class and state attributes are public and versioned, for an application's CSS to style.

DatePicker

env-date-picker
The date field.
env-date-picker-actions
The row of actions under the calendar, the picker card's footer.
env-date-picker-actions-end
The actions at the end of that row.
env-date-picker-clear
The button that clears the date.
env-date-picker-control
The field's well.
env-date-picker-end
The buttons at the end of the well.
env-date-picker-icon
The calendar icon.
env-date-picker-label
The label.
env-date-picker-native
The native input that holds the value for forms.
env-date-picker-panes
The calendar and the times side by side.
env-date-picker-placeholder
The text shown without a date.
env-date-picker-popup
The picker card with the calendar, also on TimePicker and MonthPicker.
env-date-picker-time
The times beside the calendar.
env-date-picker-time-title
The title over the times.
env-date-picker-trigger
The button that opens the calendar.
env-date-picker-value
The chosen date as text.
data-action-count
1 · 2 on env-date-picker-control
data-label-floating
Present on env-date-picker-control
data-label-hidden
Any value, or present
data-popup-open
Present on env-date-picker-control

Calendar

env-calendar
The calendar of one month.
env-calendar-day
One day.
env-calendar-day-mark
The mark under a day, such as today's.
env-calendar-grid
The grid of days.
env-calendar-header
The row with the month and the previous and next buttons.
env-calendar-next
The button for the next month.
env-calendar-pad
An empty cell before the first day or after the last.
env-calendar-previous
The button for the previous month.
env-calendar-title
The month and year.
data-date
Any value, or present on env-calendar-day

Strings

EnvUIProvider's messages overrides each, and a component's own label prop wins over it.

messages.calendar.previousMonth
"Previous month" The previous-month button's name.
messages.calendar.nextMonth
"Next month" The next-month button's name.
messages.datePicker.clear
"Clear" The clear button's name.
messages.datePicker.today
"Today" Picks today.
messages.datePicker.now
"Now" Picks the current time.
messages.datePicker.time
"Time" Names the time field.
messages.timePicker.clear
"Clear" The clear button's name.
messages.timePicker.now
"Now" Picks the current time.
messages.timePicker.hours
"Hours" Names the column of hours.
messages.timePicker.minutes
"Minutes" Names the column of minutes.
messages.timePicker.dayPeriod
"AM/PM" Names the column of the day's halves, on a 12-hour clock.

Changelog

1.0.0

showLabel is visibleLabel, named after the label that visibleLabel={false} hides.

The field keeps its minimum width, 12 rem, in a container sized to its content, such as an inline-flex toolbar, rather than growing after the container measured it and pushing its neighbours onto another row; in a narrower container it still takes the container's width. The calendar's month and year, env-calendar-heading, are env-calendar-title.

With the minute granularity, the time is chosen on wheels of hours, minutes every minuteStep minutes (15) and, on a 12-hour clock, day periods, beside the calendar, in place of the time field under it; turning a wheel sets the time at once. The calendar has no Done button, and doneLabel is gone: it closes as a date-only one does. The field and popup are shared with TimePicker and MonthPicker.

withTime is granularity, "day" or "minute"; hint is description; variant="outlined" is variant="outline". className and style style the field's root rather than the button, which takes ref. The calendar opens on the shared popup, its month buttons are ghost icon buttons, and its labels come from EnvUIProvider's messages, as its locale does: env-date-time-well is env-date-picker-control.

DateTimeField is DatePicker, with the env-date-picker-* classes; the page is date-picker.

1.0.0-prerelease.6

Colors under the pointer and while pressed change with --env-motion-ease-enter, rather than easing in slowly, so hover and press show from the first frame.

The calendar's popup opens over --env-motion-duration-medium, fading in and growing from 98 % with --env-motion-ease-enter, rather than growing from 96 % over the slow duration, so it shows from its first frames rather than late.

The field keeps its focused frame and label until the calendar has closed, rather than fading and coming back as focus returns.

With the minute granularity, hourCycle, h12 or h23, sets the time's clock whatever the locale's, such as 18:00 with no AM/PM wheel in English; the value stays YYYY-MM-DDTHH:mm.

The calendar's days, on their own and in the popup, are a little less than a row height wide, 24 px at the compact scale and 32 px at the comfortable one, so the columns stand close around the numbers; they still touch, and touch screens keep touch-sized days.

A clearable field's clear button and its calendar icon stand a step apart, so they don't crowd each other in a small field.

While its calendar is open, the field keeps its focused frame, the accent edge and label, also when focus is in the popup, as when it opens under the pointer.

The calendar names the weekdays by their first two letters at every width and scale, Mo Tu We in English, where those tell the seven apart, so the names never run into each other in the popup's narrow days.

The calendar's month buttons show larger arrows, 1.25 rem, on slightly smaller buttons, so the month's name stays on one line, and the header keeps a little room under it before the weekdays, so it reads at the compact scale too.

The chosen hour and minute take the chosen day's solid accent fill rather than the selection's tint, and are round and as tall as a day, as the chosen day is, so a chosen day and time look alike and the wheels stay narrow; a wheel with fewer values than it shows rows, such as minutes every 15, no longer loops and shows a value twice, while its arrow keys still wrap around.

As a time wheel turns to a row pressed or scrolled to, the fill stays on that row rather than passing over the rows on the way, and a row's text takes the fill's color only while it is on the fill, so a value it leaves is never white on the popup.

With a time, the wheels move under the calendar where the card has no room for them beside it, rather than running past its edge.

Days outside min and max take the disabled text color, faded, so they can't be taken for days that can be chosen; today keeps its accent color.

The floating variant and the variant prop are gone: the field is outlined, and takes size="lg" in a long form on a page. The field takes a text area's rounded corners rather than round ends. A small one with a visible label takes the medium height, so its floated label and its value each have room, and says so in the console while developing: sm is for a field with its label hidden.

The calendar card takes the width of a field a little wider than it, up to 1 rem, and in a narrow window it is at least as wide as its field, so it ends with the field; Today and Clear follow the calendar's six weeks closely. Beside the calendar, the Time heading shares the line of the month's name.

1.0.0-prerelease.5

defaultTime is placeholderTime, as React Aria names it: default names an uncontrolled starting value elsewhere.

The calendar popup is frosted: the popup's color lets a little of what is behind through, blurred, with a soft shadow and no outline edge but a faint hairline in dark, and is solid with its edge at high contrast and with reduced transparency. Its inset is a panel's, as wide as a popup row's corner: 14 px, 11.2 px at the compact scale. The calendar leads with the month's name, its buttons side by side at the end; the days of a week touch, a row height wide; today is in the accent color rather than ringed, and a day under the pointer takes the neutral fill.