Design System

Date, time & calendar

One calendar surface powers every date control. Each trigger keeps the V2 field chrome, exactly one right-side icon, and the standard FormField label / helper / error hierarchy.

Family rules

Applies to Date, Date & time, Time, Month, Week and Range pickers alike.

What is this?

A single date & time family — DatePicker, DateTimePicker, TimePicker, MonthPicker, WeekPicker, DateRangePicker and the shared Calendar — composed from the existing V2 Button, Select, Popover, FormField and Feedback components.

When to use it

  • Capturing a service date, resident birth date or maintenance schedule in a form
  • Booking an appointment time or a recurring job slot
  • Filtering a list or report by a date range

How to use it

  • Exactly one supporting icon per trigger, always right-aligned: Calendar for dates, Clock for time
  • All calendars render the shared Calendar component — never a bespoke grid
  • Deferred pickers (date & time, presets, ranges) commit only on Apply
  • Validation uses the existing FieldMessage error treatment
  • Overlays use the V2 Popover: aligned to trigger, collision-padded, viewport-safe

What to avoid

  • A custom icon alongside a native browser picker icon
  • Communicating today, selected, range or disabled state through colour alone
  • New overlay, focus or spacing treatments specific to calendars

One icon, on the right

Date, Date & time, Month and Week triggers show a single Calendar icon; Time shows a single Clock icon. Native date inputs keep their picker functional but never render a second visible indicator.

Date picker

The default single-date control.

DatePicker

Pick one calendar date. A field-shaped trigger opens the shared Calendar in a V2 popover.

import { DatePicker } from "@/design-system";

When to use

  • A single date such as a service date or resident birth date
  • Dates where a calendar helps the user orient by weekday or month
  • Forms already using FormField label and helper conventions

When not to use

  • A date plus a time — use DateTimePicker
  • A start and end date — use DateRangePicker
  • Whole months or weeks — use MonthPicker or WeekPicker

Service date, with today and month navigation

The date the service is delivered.

Month and year are selectable for distant dates.

Disabled dates, required, error and read-only

Past dates cannot be scheduled.

Set by the recurrence rule.

Do

  • Keep the calendar icon as the only trigger icon
  • Disable dates that are genuinely unavailable rather than validating after the fact

Don't

  • Show a native picker icon next to the V2 icon
  • Style today and selected identically

Accessibility

  • The trigger is a real button labelled by its FieldLabel; the popover traps focus and closes on Escape.
  • Arrow keys move by day, PageUp / PageDown by month, Home / End within the week; Enter or Space selects.
  • Today carries a border and semibold weight in addition to any colour, so it never reads as selected.
  • Disabled dates are non-interactive, dimmed and skipped by keyboard navigation.
  • Helper text and errors are wired through aria-describedby; errors also carry role=alert.

Date & time picker

Date and time in one deferred overlay.

DateTimePicker

Pick a date and a time together. The calendar and the hour / minute / AM-PM segments live in one popover and commit on Apply.

import { DateTimePicker } from "@/design-system";

When to use

  • Appointments and visits
  • Recurring job start moments
  • Anything where the time of day changes meaning

When not to use

  • Date-only values — use DatePicker
  • Time-only values — use TimePicker

Appointment time

Confirm the date and the arrival time.

Validation and read-only

Managed by the schedule.

Do

  • Default the time to a sensible working hour
  • Commit only when the user applies

Don't

  • Show both a calendar and a clock icon on the trigger
  • Close the overlay before the time is chosen

Accessibility

  • One Calendar icon on the trigger — never a second clock icon.
  • Time segments are V2 Selects, so they inherit keyboard support, typeahead and correct ARIA.
  • Apply and Cancel are real buttons; Escape cancels and returns focus to the trigger.
  • The value is announced as a single formatted string on the trigger.

Time picker

Hour, minute and AM / PM.

TimePicker

Pick a time of day, either by segment selects or from a list of slots.

import { TimePicker } from "@/design-system";

When to use

  • Service times and appointment slots
  • Shift start and end times

When not to use

  • Durations — use a NumberInput with a unit addon
  • Date plus time — use DateTimePicker

Service time, 12-hour and 24-hour

Visits are scheduled in 15-minute slots.

24-hour clock, 30-minute slots.

Validation and disabled

Do

  • Match the step to how the business actually books time

Don't

  • Offer minute-level precision where 15-minute slots are the reality

Accessibility

  • One Clock icon, right-aligned on the trigger.
  • The trigger exposes role=combobox with expanded state and controls the slot listbox.
  • Segments are Selects: arrow keys, typeahead, Home / End and Escape all work.
  • The selected slot is marked with aria-selected and semibold weight, not colour alone.

Month & week pickers

Coarser periods, same chrome.

MonthPicker

Pick a whole month, with year navigation and a 12-cell grid.

import { MonthPicker } from "@/design-system";

When to use

  • Reporting periods
  • Billing months

When not to use

  • Specific dates — use DatePicker

Reporting month

Reports are generated per calendar month.

Future months are not billable yet.

Accessibility

  • Year navigation uses the standard IconButton with explicit labels; the year is announced via aria-live.
  • The current month carries a border and semibold weight; the selected month is filled and pressed.
  • Disabled months are struck through as well as dimmed.

WeekPicker

Pick a whole week. Clicking any day selects that week and shows it as a connected range.

import { WeekPicker } from "@/design-system";

When to use

  • Rota and roster periods
  • Weekly maintenance schedules

When not to use

  • A free-form range — use DateRangePicker

Rota week

Weeks run Monday to Sunday.

Accessibility

  • Week numbers are rendered so the selected period is identifiable without relying on the range fill.
  • Keyboard navigation is day-based; selecting any day commits the whole week.
  • Range start and end carry semibold weight in addition to the range surface.

Date strip

A horizontal day selector with prev / next stepping.

DateStrip

Select a single day from a compact horizontal strip; the arrows step the visible window without changing the selection.

import { DateStrip } from "@/design-system";

When to use

  • Booking and appointment day bars
  • Roster or schedule day navigation

When not to use

  • Picking a far-off date — use DatePicker
  • A date range — use DateRangePicker
  • A whole week as one value — use WeekPicker

Booking day, with month label and event dots

September 2026

Selected: Friday 25 September

Disabled

September 2026

Accessibility

  • Every day cell is a real button with an aria-pressed state and a full spoken date label.
  • The arrows are labelled IconButtons; keyboard users can tab through the whole strip.
  • Selection is never carried by colour alone — the pressed state is announced.

Date range picker

Start, range and end — plus optional presets.

DateRangePicker

Pick a start and end date. Optional quick presets and an explicit Clear / Cancel / Apply footer keep reporting filters predictable.

import { DateRangePicker, DATE_RANGE_PRESETS } from "@/design-system";

When to use

  • Date range filters on lists and reports
  • Booking or availability windows

When not to use

  • A single date — use DatePicker
  • A calendar week — use WeekPicker

Filter with presets and Apply

Filters the maintenance list by completion date.

Immediate selection, and validation

Closes as soon as both ends are chosen.

Do

  • Add presets only where a relative range is genuinely useful
  • Keep the label visible so the range is never ambiguous

Don't

  • Force presets into every range picker
  • Rely on the range fill alone to convey start and end

Accessibility

  • The trigger always carries a visible label; its accessible name includes the current range.
  • Range start and end are semibold and filled; the middle uses a subtle connected surface — never colour alone.
  • An inverted range reports the standard field error instead of silently reordering.
  • Presets are grouped and labelled; Clear, Cancel and Apply are ordinary V2 buttons.

Date range filter

A compact preset dropdown for toolbar and table filters.

DateRangeFilter

A single compact trigger that opens a preset list — All time, Today, Yesterday, Last 7 / 30 days, This / Last month, Last 6 months — with an optional Custom range… entry that swaps to the shared range calendar. Presets commit immediately; a custom range commits on Apply.

import { DateRangeFilter, DATE_RANGE_FILTER_PRESETS } from "@/design-system";

When to use

  • Date filters in table filter bars, dashboards and report toolbars
  • Anywhere a quick relative period matters more than exact dates

When not to use

  • Capturing a deliberate start and end date in a form — use DateRangePicker
  • A single date — use DatePicker

Toolbar filter with presets and custom range

Compact size, and without custom range

Do

  • Keep All time as the neutral default
  • Use the resolved DateRange from onValueChange to filter the data

Don't

  • Duplicate these presets inside DateRangePicker
  • Hide the active period behind an icon-only trigger

Accessibility

  • The trigger's accessible name always announces the current period.
  • Options are a real listbox with aria-selected; the active period is also semibold, never colour alone.
  • Custom range requires Apply — closing the calendar discards the draft.

Calendar

The shared surface, usable on its own.

Calendar

The one calendar surface of the system: month grid, header navigation, day states and an optional Today shortcut.

import { Calendar } from "@/design-system";

When to use

  • Inline scheduling views
  • Building a new date interaction that still needs the standard grid

When not to use

  • Anywhere a picker already exists — compose the picker instead

Inline calendar with month / year selection and Today

September 2026

Disabled dates and outside-month days

September 2026

Do

  • Keep cells light — only today, selected and range carry weight

Don't

  • Give every cell a border or shadow
  • Re-style react-day-picker outside this component

Accessibility

  • Header navigation uses IconButton with explicit Previous / Next month labels.
  • Day states — default, hover, focus, selected, today, disabled, outside month, range start / middle / end — are each distinguishable without colour.
  • Focus is always visible via the shared focus ring; Escape and Tab behave natively.

Event calendar

A month grid of scheduled activities, with a toolbar, colour key and per-day events.

EventCalendar

Reviewing a schedule: month navigation, a tone-coded colour key, weekday headers and six week rows of day cells carrying their events.

import { EventCalendar } from "@/design-system";

When to use

  • Activity, booking and appointment calendars
  • Any month overview where events belong to days rather than to rows

When not to use

  • Picking a date in a form — use DatePicker
  • A list of events with no month context — use List or DataTable

Month view with a colour key and view controls

January 1970
  • Note
  • Email
  • Call
  • Meeting
  • Task
  • Site tour

Do

  • Keep the grid quiet — meaning comes from the event chips, not from cell chrome
  • Always pass a legend when more than one tone is in play

Don't

  • Show more than a few events per cell — use maxEventsPerDay and a day view
  • Encode a category with colour alone