CalendarPreview
One subcomposed date component that owns date state and popover state explicitly.1<CalendarPreview defaultMonth={new Date(2024, 3, 1)}>2 <CalendarPreview.Nav />3 <CalendarPreview.Grid />4</CalendarPreview>
CalendarPreview replaces Calendar, DatePicker and RangePicker with a
single root and dot-notation parts. Every piece of state is owned explicitly —
selection, visible month, open, granularity — so nothing is private and no part
needs to reach around another.
It ships alongside the current calendar family; those exports are removed a release after this one is documented.
Anatomy
1import { CalendarPreview } from '@raystack/apsara'23<CalendarPreview>4 <CalendarPreview.Trigger>5 <CalendarPreview.Input />6 </CalendarPreview.Trigger>7 <CalendarPreview.Content initialFocus={false}>8 <CalendarPreview.GranularityTabs />9 <CalendarPreview.Nav />10 <CalendarPreview.Grid />11 <CalendarPreview.MonthGrid />12 <CalendarPreview.Footer>13 <CalendarPreview.Cancel />14 <CalendarPreview.Apply />15 </CalendarPreview.Footer>16 </CalendarPreview.Content>17</CalendarPreview>
Drop any part you do not need. Grid renders for the day granularity and
MonthGrid for the rest, so a picker offering both keeps both in the tree.
API Reference
Root
Owns every piece of state and provides it to the parts.
Prop
Type
Trigger
Anchors the popover. Renders a div, never a <button>, because it may
contain a typed input.
Prop
Type
Content
The portaled surface. Positioning props are passed here directly.
Prop
Type
Input
The typed single-date field.
Prop
Type
RangeInput
Paired start and end fields. Both are typable.
Prop
Type
Nav
Caption, a revert-to-default button, and previous / next. Renders for the day granularity only — the other granularities scroll rather than page.
The revert button appears only when the root was given a defaultValue and the
current value differs from it; pressing it restores that default. It is absent
otherwise rather than disabled, because a control that can never do anything is
noise.
Prop
Type
Grid
The day grid.
Prop
Type
MonthGrid
Month, quarter, half-year and year selection, as a scrolling list of years.
Prop
Type
GranularityTabs
Day, Month, Quarter, Half-year and Year. Renders only when the root offers more than one granularity.
Prop
Type
Footer
Action row for Apply and Cancel.
Prop
Type
Examples
State
Open, visible month, and bounds are all ordinary props. The visible month is independent of the value but initialises from it, so a picker holding a date in another year opens on that year rather than today.
1<CalendarPreview defaultOpen onOpenChange={(open) => console.log(open)}>2 <CalendarPreview.Trigger>3 <CalendarPreview.Input />4 </CalendarPreview.Trigger>5 <CalendarPreview.Content initialFocus={false}>6 <CalendarPreview.Nav />7 <CalendarPreview.Grid />8 </CalendarPreview.Content>9</CalendarPreview>
Granularity
granularities lists what the user may switch between; the tabs appear only
when there is more than one.
1<CalendarPreview2 defaultMonth={new Date(2024, 3, 1)}3 granularities={["day", "month", "quarter", "half-year", "year"]}4>5 <CalendarPreview.GranularityTabs />6 <CalendarPreview.Nav />7 <CalendarPreview.Grid />8 <CalendarPreview.MonthGrid />9</CalendarPreview>
MonthGrid emits the first day of the chosen period — a quarter pick in
2024 Q3 yields 1 July 2024 — and onValueChange's second argument names the
granularity that produced it. The pair is what makes the value unambiguous: a
Date alone cannot distinguish 1 June picked as a day from June picked as a
month.
The typed field follows the active granularity too, reading Jun 2026,
Q3 2026, H1 2026 or 2026 rather than a full date.
It also reads across granularities: typing Q4 2027 into a day field switches
to Quarter and commits in one go, and a bare Q4 resolves against the year on
screen. The active granularity is always tried first, and only granularities the
picker actually offers are considered — so a day-only picker rejects Q4 rather
than switching to a tab that is not there.
Commit and locking
commit="explicit" buffers edits until Apply, so a popover can be abandoned
without the parent seeing intermediate states. lock holds one endpoint of a
range read-only while the other stays pickable.
1<CalendarPreview2 commit="explicit"3 defaultMonth={new Date(2024, 3, 1)}4 defaultOpen5>6 <CalendarPreview.Trigger>7 <CalendarPreview.Input />8 </CalendarPreview.Trigger>9 <CalendarPreview.Content initialFocus={false}>10 <CalendarPreview.Nav />11 <CalendarPreview.Grid />12 <CalendarPreview.Footer>13 <CalendarPreview.Cancel />14 <CalendarPreview.Apply />15 </CalendarPreview.Footer>
Inside a Field
Input reads field context, so the label association, required and
aria-invalid all wire up by composition. The component renders no error text
itself — report through onValidityChange and let Field.Error present it.
1<Field>2 <Field.Label>Starts</Field.Label>3 <CalendarPreview>4 <CalendarPreview.Trigger>5 <CalendarPreview.Input />6 </CalendarPreview.Trigger>7 <CalendarPreview.Content initialFocus={false}>8 <CalendarPreview.Nav />9 <CalendarPreview.Grid />10 </CalendarPreview.Content>11 </CalendarPreview>12 <Field.Error />13</Field>
Accessibility
- The day grid is react-day-picker's, which supplies the grid roles, roving tabindex and arrow-key navigation.
Triggerrenders a non-button element with button semantics supplied by Base UI: it carriesrole,tabindex,aria-haspopup,aria-expanded, andaria-disabledrather than adisabledattribute.- Pass
initialFocus={false}toContentwhenever the trigger contains a typed field. Without it the popup takes focus on open and keystrokes never reach the field. - The
Navcaption is anaria-live="polite"region, so changing month is announced. MonthGridcells are buttons witharia-pressed, not tabs — the design reuses the standalone tab visual, but tab semantics without tabpanels would be wrong.readOnlyleaves days legible and focusable while refusing edits;disabledremoves them from interaction and prevents the popover opening at all.