Pho Design System

Calendar

A month of days, laid out to read or to choose from. Built on React DayPicker with Pho’s own class map, so the library’s stylesheet is never imported and no rdp-* class reaches the page. Every DayPicker prop passes through. For a calendar behind a field — a trigger that shows the chosen date and opens this in a popover — use DatePicker.

Import

import { Calendar } from "@photon-ai/pho-ui/components/calendar";

Basic

mode="single" picks one day. Clicking the chosen day again clears it, which is how an optional date gets unset. Between the month arrows sits a dot: the way back to today’s month, whatever the viewer has wandered into.

const [selected, setSelected] = useState<Date>();

<Calendar mode="single" selected={selected} onSelect={setSelected} />;

Selection follows DayPicker’s own contract: hand it onSelect and the calendar is controlled, so selected has to come with it. Leave both off and the calendar keeps the selection itself.

Range

mode="range" picks a span. The days between both ends carry a band; the ends themselves take the action fill. numberOfMonths shows more than one month at a time, side by side once there is room and stacked below sm.

const [range, setRange] = useState<DateRange>();

<Calendar
  mode="range"
  numberOfMonths={2}
  selected={range}
  onSelect={setRange}
/>;

A range needs two clicks. The first already reads as a complete one-day span (from and to both set), so watch the clicks rather than the value if you need to know when the viewer is done.

Multiple

mode="multiple" collects days that need not touch. min and max bound how many.

<Calendar mode="multiple" max={5} selected={days} onSelect={setDays} />

Bounds

disabled takes a matcher, or a list of them: a Date, a { before } / { after } interval, a { dayOfWeek } set, or a predicate. startMonth and endMonth stop the arrows and set how far the year list reaches; left unsaid, it runs ten years either side of today.

<Calendar
  mode="single"
  startMonth={new Date(2026, 0)}
  endMonth={new Date(2026, 11)}
  disabled={[{ dayOfWeek: [0, 6] }, { before: new Date() }]}
  selected={selected}
  onSelect={setSelected}
/>

The caption jumps

The month and the year in the caption are each a button into their own view, so a date two years out is two taps rather than twenty-four. The month opens the twelve months and lands straight back on the days. The year opens the years, and because a year alone does not name a day, it asks for the month next. The panel keeps its shape through all three, and the arrows step aside while a list is up.

Ten years either side of today are on offer unless startMonth / endMonth say otherwise.

captionLayout="label" prints the month and year as words instead, with no way in. Use it where the dates in play are within a month or two of today and the arrows are enough. Naming any other captionLayout hands the caption back to DayPicker and its native select dropdowns.

September 2026
<Calendar mode="single" captionLayout="label" />

footer prints a line under the grid in a live region, which is where a consequence belongs — what the choice means, not what to do next.

Pick the day it renews.

Anatomy

Props

Every DayPicker prop passes through. The ones that come up most:

Accessibility

Best practices