Expand the date/time component family with TimePicker, DateTimePicker, MonthPicker, and RelativeDateSelect. These components complete the temporal input surface by building on the existing DatePicker, DateRangePicker, and InlineCalendar primitives. All new components must be fully keyboard accessible, support time zone handling, honor reduced-motion preferences, and ship with Storybook stories including controls.
Introduction
Overview
The design system provides DatePicker, DateRangePicker, and InlineCalendar for date selection but lacks dedicated components for time-only input, combined date-time input, month-level granularity, and human-friendly relative date selection (e.g. “Last 7 days”). SaaS dashboards, scheduling UIs, and reporting filters all require these patterns.
Goals
Provide a TimePicker component for standalone time selection with configurable step intervals.
Provide a DateTimePicker that composes DatePicker and TimePicker into a single field.
Provide a MonthPicker for year-month selection without day granularity.
Provide a RelativeDateSelect for preset ranges (“Today”, “Last 30 days”, “This quarter”) with optional custom-range escape hatch.
Maintain API consistency with the existing DatePicker props contract (value as ISO string, onChange callback, label, error, hint, disabled, required).
Deliver complete time zone support via an optional timeZone prop using IANA identifiers.
Non-Goals
Building a full scheduling/calendar widget (event placement, drag-to-resize).
Server-side date parsing or validation libraries.
Replacing the existing DatePicker or DateRangePicker APIs; those remain stable.
Scope
In Scope
Item
Description
TimePicker
Dropdown or scroll-wheel time selector with hour/minute/optional-second inputs and AM/PM toggle.
DateTimePicker
Combined date + time field composing DatePicker and TimePicker.
MonthPicker
Year-month selector with calendar-grid or dropdown-based navigation.
RelativeDateSelect
Preset-based select with configurable presets and optional custom DateRangePicker fallback.
Time zone support
Optional timeZone prop (IANA string) on TimePicker and DateTimePicker; display offset label.
Keyboard navigation
Arrow keys, Enter, Escape, Tab for all new components per WAI-ARIA date/time patterns.
Reduced-motion
All open/close animations respect prefers-reduced-motion via existing motion tokens.
Storybook stories
One story file per component with controls, dark mode, RTL, and disabled/error states.
InlineCalendar (src/components/ui/inline-calendar.tsx): Always-visible calendar grid for embedding in panels.
No TimePicker, DateTimePicker, MonthPicker, or RelativeDateSelect exist.
Motion tokens in src/styles/motion.css define --motion-overlay (220ms) and reduced-motion overrides.
Form field utilities (useFieldId, joinDescribedBy, getFieldShellClassName) standardize field chrome.
Proposed Solution
Build four new components in src/components/ui/:
TimePicker — A controlled input displaying formatted time with a popover dropdown listing selectable time slots at the configured step interval. Uses React Aria’s time field primitives for accessibility. Emits HH:mm or HH:mm:ss strings.
DateTimePicker — Composes DatePicker (for the date portion) and TimePicker (for the time portion) in a single field group. Value is an ISO 8601 datetime string (YYYY-MM-DDTHH:mm). The popover shows the calendar with a time selector below it.
MonthPicker — A button trigger opening a popover grid of months within a navigable year. Value is YYYY-MM string. Year navigation via chevron buttons.
RelativeDateSelect — A Select-like component whose options are developer-defined presets (each with a label and a resolve function returning { start: Date; end: Date }). An optional allowCustom prop appends a “Custom range…” option that opens a DateRangePicker inline.
All four use the established field shell (getFieldShellClassName), label (FieldLabelContent), and field-id conventions. Time zone display is handled by an optional trailing badge showing the UTC offset.
Requirements
Requirement Priorities
Must Have: TimePicker, DateTimePicker, MonthPicker, RelativeDateSelect with keyboard navigation and Storybook stories.
Should Have: Time zone display, 12h/24h toggle, RelativeDateSelect custom range escape hatch.
Could Have: Scroll-wheel time selection mode, voice-over optimized announcements.
Won’t Have (this release): Recurring schedule patterns, NLP parsing.
DateTimePicker combines date and time selection into a single ISO datetime string output.
AC-03
MonthPicker renders a 4x3 grid, navigates years, and emits YYYY-MM strings.
AC-04
RelativeDateSelect resolves presets to date ranges and optionally opens a custom range picker.
AC-05
All components render correctly in dark mode and with prefers-reduced-motion: reduce.
AC-06
All components pass axe-core checks with zero violations.
AC-07
Storybook stories exist for each component with controls for all props.
AC-08
Unit tests cover happy path, keyboard navigation, edge cases (midnight, DST), and disabled/error states.
AC-09
pnpm typecheck and pnpm vitest run --project unit pass with zero errors.
LLM Handoff Instructions
When implementing this FRD:
Start with TimePicker — it is the foundational primitive. Build it in src/components/ui/time-picker.tsx following the DatePicker pattern: React Aria primitives, getFieldShellClassName for the trigger, FieldLabelContent for the label, popover with dropdownOverlayPanelClassName.
Then DateTimePicker — compose DatePicker + TimePicker. Put in src/components/ui/date-time-picker.tsx. The popover should render the calendar above and time selector below.
Then MonthPicker — src/components/ui/month-picker.tsx. Use a similar popover pattern but replace the day grid with a 4x3 month grid.
Then RelativeDateSelect — src/components/ui/relative-date-select.tsx. Wrap the existing Select component; the custom range mode should conditionally render DateRangePicker.
Stories go in sibling .stories.tsx files. Follow the DatePicker stories structure (Default, WithError, Disabled, DarkMode, Controls).
Tests go in sibling .test.tsx files. Test keyboard flows with @testing-library/react and userEvent.
Read ADR-004 (motion) before adding any animation; use existing motion tokens from motion.css.
Read ADR-002 (date/time conventions) for value format decisions.
Key files to reference:
src/components/ui/date-picker.tsx — prop pattern, field shell usage, popover structure.
src/components/ui/date-range-picker.tsx — range value contract.
src/lib/calendar-date.ts — date parsing/formatting utilities.