Skip to main content

DatePicker / RangePicker

  • Component overview: Date selection components — DatePicker for single date, RangePicker for date range. Both wrap the internal Calendar with a Popover trigger.
  • Size baseline: Trigger 32px tall, calendar panel 332px wide (RangePicker shows two months side-by-side), day cell 36×36px, border radius 5px.
  • Implementation note: Trigger simulates an input using a <button>. Calendar panel opens in a Popover with auto-close on selection. Zero external runtime dependencies.
  • DatePicker Figma spec / RangePicker Figma spec

DatePicker

Basic Usage

Click the trigger to open the calendar panel. Selecting a date closes the panel and shows the formatted date.

Result
Loading...
Live Editor
const Demo = () => {
  const [date, setDate] = useState(undefined)
  return (
    <div style={{ padding: '40px 0' }}>
      <DatePicker
        className="w-62"
        value={date}
        onChange={setDate}
        placeholder="Select date"
      />
    </div>
  )
}
render(<Demo />)

States

Disabled

Result
Loading...
Live Editor
const Demo = () => {
  return (
    <div style={{ display: 'flex', gap: 16, padding: '40px 0' }}>
      <DatePicker className="w-62" disabled placeholder="Disabled empty" />
      <DatePicker className="w-62" disabled value={new Date(2026, 3, 20)} />
    </div>
  )
}
render(<Demo />)

With Value (Clearable)

Hover the trigger to reveal the clear button.

Result
Loading...
Live Editor
const Demo = () => {
  const [date, setDate] = useState(new Date(2026, 3, 20))
  return (
    <div style={{ padding: '40px 0' }}>
      <DatePicker
        className="w-62"
        value={date}
        onChange={setDate}
      />
    </div>
  )
}
render(<Demo />)

Disabled Dates

Use disabledDate to restrict selectable dates. For example, disable all past dates:

Result
Loading...
Live Editor
const Demo = () => {
  const [date, setDate] = useState(undefined)
  return (
    <div style={{ padding: '40px 0' }}>
      <DatePicker
        className="w-62"
        value={date}
        onChange={setDate}
        disabledDate={{ before: new Date() }}
        placeholder="No past dates"
      />
    </div>
  )
}
render(<Demo />)

Custom Format

Result
Loading...
Live Editor
const Demo = () => {
  const [date, setDate] = useState(new Date(2026, 3, 20))
  const format = (d) => d.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })
  return (
    <div style={{ padding: '40px 0' }}>
      <DatePicker
        className="w-62"
        value={date}
        onChange={setDate}
        formatDate={format}
      />
    </div>
  )
}
render(<Demo />)

DatePicker Props

PropTypeDefaultDescription
valueDateSelected date
onChange(date: Date | undefined) => voidSelection callback
placeholderstring'Select date'Trigger placeholder text
formatDate(date: Date) => stringYYYY/MM/DDDate format function
disabledbooleanfalseDisable the picker
allowClearbooleantrueShow clear button on hover
openbooleanControlled open state
defaultOpenbooleanfalseInitial open state
onOpenChange(open: boolean) => voidOpen state change callback
disabledDateMatcher | Matcher[]Calendar disabled condition
fromDateDateEarliest selectable date
toDateDateLatest selectable date
defaultMonthDateInitial displayed month
classNamestringTrigger custom class

RangePicker

Basic Usage

Click the trigger to open the dual-month calendar. Select a start date, then an end date. The panel closes after the range is complete.

Result
Loading...
Live Editor
const Demo = () => {
  const [range, setRange] = useState(undefined)
  return (
    <div style={{ padding: '40px 0' }}>
      <RangePicker
        className="w-85"
        value={range}
        onChange={setRange}
      />
    </div>
  )
}
render(<Demo />)

States

Disabled

Result
Loading...
Live Editor
const Demo = () => {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16, padding: '40px 0' }}>
      <RangePicker className="w-85" disabled />
      <RangePicker
        className="w-85"
        disabled
        value={{ from: new Date(2026, 3, 17), to: new Date(2026, 4, 1) }}
      />
    </div>
  )
}
render(<Demo />)

With Value (Clearable)

Hover the trigger to reveal the clear button.

Result
Loading...
Live Editor
const Demo = () => {
  const [range, setRange] = useState({ from: new Date(2026, 3, 17), to: new Date(2026, 4, 1) })
  return (
    <div style={{ padding: '40px 0' }}>
      <RangePicker
        className="w-85"
        value={range}
        onChange={setRange}
      />
    </div>
  )
}
render(<Demo />)

Disabled Dates

Result
Loading...
Live Editor
const Demo = () => {
  const [range, setRange] = useState(undefined)
  return (
    <div style={{ padding: '40px 0' }}>
      <RangePicker
        className="w-85"
        value={range}
        onChange={setRange}
        disabledDate={{ before: new Date() }}
        startPlaceholder="From today"
        endPlaceholder="To..."
      />
    </div>
  )
}
render(<Demo />)

Custom Separator

Result
Loading...
Live Editor
const Demo = () => {
  const [range, setRange] = useState(undefined)
  return (
    <div style={{ padding: '40px 0' }}>
      <RangePicker
        className="w-85"
        value={range}
        onChange={setRange}
        separator="~"
      />
    </div>
  )
}
render(<Demo />)

RangePicker Props

PropTypeDefaultDescription
valueDateRangeSelected range { from: Date; to?: Date }
onChange(range: DateRange | undefined) => voidSelection callback
startPlaceholderstring'Start date'Start input placeholder
endPlaceholderstring'End date'End input placeholder
formatDate(date: Date) => stringYYYY/MM/DDDate format function
separatorReactNode'–'Separator between start and end
disabledbooleanfalseDisable the picker
allowClearbooleantrueShow clear button on hover
openbooleanControlled open state
defaultOpenbooleanfalseInitial open state
onOpenChange(open: boolean) => voidOpen state change callback
disabledDateMatcher | Matcher[]Calendar disabled condition
fromDateDateEarliest selectable date
toDateDateLatest selectable date
defaultMonthDateInitial displayed month
classNamestringTrigger custom class

Token Table

ElementPropertyToken / Value
TriggerBorder (default)Separators/Emphasized#CCCCCC
TriggerBorder (active)Grays/Black#000000
TriggerBackgroundBackgrounds/Primary#FFFFFF
Trigger (disabled)BackgroundGrays/Gray-1#EBEBEB
Value textColorLabels/Primary#000000
PlaceholderColorLabels/Tertiary#757575
Separator (RangePicker)ColorLabels/Tertiary#757575
IconColorLabels/Secondary#3D3D3D
PanelBackgroundBackgrounds/Primary#FFFFFF
PanelShadowEffects/Shadow/Defaultrgba(0,0,0,0.1)
PanelBorderSeparators/Default#EBEBEB
Range middleBackgroundGrays/Gray-1#EBEBEB

Size Spec

DimensionValue
Trigger height32px
Trigger horizontal paddingSpacing_16 (16px)
Trigger border radiusRadius_5 (5px)
Trigger fontBody/Regular (14px)
Calendar panel width (DatePicker)332px
Calendar panel (RangePicker)Two months side-by-side
Day cell size36×36px
Popover offset4px

Changelog

2026-04-14 — Initial release

New components: DatePicker (single date) and RangePicker (date range with dual-month calendar). Both support clearable value, disabled state, controlled open mode, and custom date formatting.