DatePicker / RangePicker
- Component overview: Date selection components —
DatePickerfor single date,RangePickerfor 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
| Prop | Type | Default | Description |
|---|---|---|---|
value | Date | — | Selected date |
onChange | (date: Date | undefined) => void | — | Selection callback |
placeholder | string | 'Select date' | Trigger placeholder text |
formatDate | (date: Date) => string | YYYY/MM/DD | Date format function |
disabled | boolean | false | Disable the picker |
allowClear | boolean | true | Show clear button on hover |
open | boolean | — | Controlled open state |
defaultOpen | boolean | false | Initial open state |
onOpenChange | (open: boolean) => void | — | Open state change callback |
disabledDate | Matcher | Matcher[] | — | Calendar disabled condition |
fromDate | Date | — | Earliest selectable date |
toDate | Date | — | Latest selectable date |
defaultMonth | Date | — | Initial displayed month |
className | string | — | Trigger 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
| Prop | Type | Default | Description |
|---|---|---|---|
value | DateRange | — | Selected range { from: Date; to?: Date } |
onChange | (range: DateRange | undefined) => void | — | Selection callback |
startPlaceholder | string | 'Start date' | Start input placeholder |
endPlaceholder | string | 'End date' | End input placeholder |
formatDate | (date: Date) => string | YYYY/MM/DD | Date format function |
separator | ReactNode | '–' | Separator between start and end |
disabled | boolean | false | Disable the picker |
allowClear | boolean | true | Show clear button on hover |
open | boolean | — | Controlled open state |
defaultOpen | boolean | false | Initial open state |
onOpenChange | (open: boolean) => void | — | Open state change callback |
disabledDate | Matcher | Matcher[] | — | Calendar disabled condition |
fromDate | Date | — | Earliest selectable date |
toDate | Date | — | Latest selectable date |
defaultMonth | Date | — | Initial displayed month |
className | string | — | Trigger custom class |
Token Table
| Element | Property | Token / Value |
|---|---|---|
| Trigger | Border (default) | Separators/Emphasized#CCCCCC |
| Trigger | Border (active) | Grays/Black#000000 |
| Trigger | Background | Backgrounds/Primary#FFFFFF |
| Trigger (disabled) | Background | Grays/Gray-1#EBEBEB |
| Value text | Color | Labels/Primary#000000 |
| Placeholder | Color | Labels/Tertiary#757575 |
| Separator (RangePicker) | Color | Labels/Tertiary#757575 |
| Icon | Color | Labels/Secondary#3D3D3D |
| Panel | Background | Backgrounds/Primary#FFFFFF |
| Panel | Shadow | Effects/Shadow/Defaultrgba(0,0,0,0.1) |
| Panel | Border | Separators/Default#EBEBEB |
| Range middle | Background | Grays/Gray-1#EBEBEB |
Size Spec
| Dimension | Value |
|---|---|
| Trigger height | 32px |
| Trigger horizontal padding | Spacing_16 (16px) |
| Trigger border radius | Radius_5 (5px) |
| Trigger font | Body/Regular (14px) |
| Calendar panel width (DatePicker) | 332px |
| Calendar panel (RangePicker) | Two months side-by-side |
| Day cell size | 36×36px |
| Popover offset | 4px |
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.