Logo VitNode
Documentation

Date Picker

A button that opens a calendar in a popover, for picking a single date in forms and filters.

The Date Picker is a Calendar tucked inside a popover. It takes little space until someone needs it, then closes as soon as they pick a day.

Preview

Usage

import { z } from 'zod'
import { AutoForm } from '@vitnode/core/components/form/auto-form'
import { AutoFormDatePicker } from '@vitnode/core/components/form/fields/date-picker'
const formSchema = z.object({
  birthday: z.iso.date('Pick your birthday'),
  anniversary: z.iso.date().optional(),
})

<AutoForm
  formSchema={formSchema}
  fields={[
    {
      id: 'birthday',
      component: props => (
        <AutoFormDatePicker {...props} label="Birthday" />
      ),
    },
    {
      id: 'anniversary',
      component: props => (
        <AutoFormDatePicker {...props} label="Community anniversary" />
      ),
    },
  ]}
/>

The value is a YYYY-MM-DD string, so use z.iso.date() in the schema. It is the user's local day, with no time zone surprises on the way to your API. Optional fields get a Clear button.

z.date() does not work in AutoForm: the schema is turned into JSON Schema, which has no date type. Use z.iso.date() and convert on the server if you need a Date.

Calendar options

calendarProps passes any Calendar prop through, except the selection ones. A birthday field with year dropdowns and no future dates:

<AutoFormDatePicker
  {...props}
  calendarProps={{
    captionLayout: 'dropdown',
    disabled: { after: new Date() },
    endMonth: new Date(),
    startMonth: new Date(1926, 0),
  }}
  label="Birthday"
/>

Date format

The button shows the date in the active VitNode language, October 7, 2026 in English. Pass Intl.DateTimeFormat options to change it.

<DatePicker
  dateFormat={{ dateStyle: 'short' }}
  onChange={setDate}
  value={date}
/>

Date range

DateRangePicker picks a start and an end day. Quick ranges on the left apply in one click; a range picked on the calendar waits for Apply range, so a half-finished pick never fires a request. The button shows the preset's name, or the dates like Oct 7 – 12, 2026.

import { DateRangePicker } from '@vitnode/core/components/ui/date-range-picker'
const [range, setRange] = React.useState({ from: weekAgo, to: today })
const [preset, setPreset] = React.useState<'7d' | '30d' | null>('7d')

<DateRangePicker
  activePreset={preset}
  disabledDays={{ after: today }}
  endMonth={today}
  onChange={next => {
    setPreset(null)
    setRange(next)
  }}
  onPresetSelect={next => {
    setPreset(next)
    setRange(resolve(next))
  }}
  presets={[
    { key: '7d', label: 'Last 7 days' },
    { key: '30d', label: 'Last 30 days' },
  ]}
  value={range}
/>

You own the presets: onPresetSelect hands you the key and you work out the dates, so "Last 7 days" can mean the site's time zone instead of the browser's. The calendar shows two months on wide screens and one on phones.

Accessibility

  • AutoFormDatePicker wires the label, description and error to the button.
  • With DatePicker alone, give it an id and a <Label htmlFor>.
  • Opening the popover moves focus into the calendar. Escape closes it and focus returns to the button.
  • A validation message waits until focus leaves the field, not when the popover opens.

Props

DatePicker

DatePicker also takes every <button> prop.

Prop

Type

AutoFormDatePicker takes the same props, except its onChange receives the YYYY-MM-DD string.

DateRangePicker

DateRangePicker also takes every <button> prop.

Prop

Type