Calendar
Compound components for building accessible calendars, powered by @gentleduck/calendar.
import * as Calendar from '@gentleduck/primitives/calendar'import * as Calendar from '@gentleduck/primitives/calendar'Anatomy
<Calendar.Root>
<Calendar.Header />
<Calendar.Nav>
<Calendar.PrevButton />
<Calendar.NextButton />
</Calendar.Nav>
<Calendar.Grid>
<Calendar.Weekdays />
{/* Map over state.weeks to render Calendar.Day */}
</Calendar.Grid>
<Calendar.MonthView />
<Calendar.YearView />
</Calendar.Root><Calendar.Root>
<Calendar.Header />
<Calendar.Nav>
<Calendar.PrevButton />
<Calendar.NextButton />
</Calendar.Nav>
<Calendar.Grid>
<Calendar.Weekdays />
{/* Map over state.weeks to render Calendar.Day */}
</Calendar.Grid>
<Calendar.MonthView />
<Calendar.YearView />
</Calendar.Root>Example
import { NativeAdapter } from '@gentleduck/calendar'
import * as Calendar from '@gentleduck/primitives/calendar'
const adapter = new NativeAdapter()
function DatePicker() {
return (
<Calendar.Root adapter={adapter} mode="single" className="p-4 border rounded-lg">
<div className="flex items-center justify-between mb-4">
<Calendar.Header className="text-sm font-medium" />
<Calendar.Nav className="flex gap-1">
<Calendar.PrevButton className="h-7 w-7 rounded hover:bg-accent">
<-
</Calendar.PrevButton>
<Calendar.NextButton className="h-7 w-7 rounded hover:bg-accent">
->
</Calendar.NextButton>
</Calendar.Nav>
</div>
<Calendar.Grid className="grid grid-cols-7 gap-1">
<Calendar.Weekdays className="contents text-muted-foreground text-xs" />
{/* Day cells rendered via context */}
</Calendar.Grid>
</Calendar.Root>
)
}import { NativeAdapter } from '@gentleduck/calendar'
import * as Calendar from '@gentleduck/primitives/calendar'
const adapter = new NativeAdapter()
function DatePicker() {
return (
<Calendar.Root adapter={adapter} mode="single" className="p-4 border rounded-lg">
<div className="flex items-center justify-between mb-4">
<Calendar.Header className="text-sm font-medium" />
<Calendar.Nav className="flex gap-1">
<Calendar.PrevButton className="h-7 w-7 rounded hover:bg-accent">
<-
</Calendar.PrevButton>
<Calendar.NextButton className="h-7 w-7 rounded hover:bg-accent">
->
</Calendar.NextButton>
</Calendar.Nav>
</div>
<Calendar.Grid className="grid grid-cols-7 gap-1">
<Calendar.Weekdays className="contents text-muted-foreground text-xs" />
{/* Day cells rendered via context */}
</Calendar.Grid>
</Calendar.Root>
)
}API
Calendar.Root
The root component. Wraps useCalendar and provides context to all children. Renders a div with role="application".
| Prop | Type | Default | Description |
|---|---|---|---|
adapter | Adapter.IDateAdapter<Date> | required | Date adapter instance |
mode | Selection.SelectionMode | required | 'single', 'range', 'multi', or 'multi-range' |
locale | CalendarLocaleConfig | - | Locale, week start day, and direction |
month | Date | - | Controlled displayed month |
defaultMonth | Date | - | Default month (uncontrolled) |
selected | CalendarValue<Date, M> | - | Controlled selection |
defaultSelected | CalendarValue<Date, M> | - | Default selection (uncontrolled) |
onSelect | (value) => void | - | Called when selection changes |
onMonthChange | (month: Date) => void | - | Called when displayed month changes |
onDismiss | () => void | - | Called on Escape |
numberOfMonths | number | 1 | Months to show side by side |
showOutsideDays | boolean | true | Show days from adjacent months |
fixedWeeks | boolean | false | Always show 6 weeks |
disabled | Date[] | (date) => boolean | - | Disabled dates |
fromDate | Date | - | Earliest selectable date |
toDate | Date | - | Latest selectable date |
Data attributes:
| Attribute | Value |
|---|---|
data-slot | "calendar" |
data-view | Current ViewMode ("days", "months", or "years") |
Calendar.Header
Displays the current month and year. Renders a div with aria-live="polite".
| Prop | Type | Default | Description |
|---|---|---|---|
formatMonth | (month: Date, adapter: Adapter.IDateAdapter<Date>) => string | - | Custom formatter for the title. Defaults to "March 2026" format |
Data attributes:
| Attribute | Value |
|---|---|
data-slot | "calendar-header" |
Calendar.Nav
Navigation wrapper. Renders a div with role="navigation". When no children are provided, it renders PrevButton and NextButton automatically.
Data attributes:
| Attribute | Value |
|---|---|
data-slot | "calendar-nav" |
Calendar.PrevButton
Navigates to the previous month. Renders a button.
| Prop | Type | Default | Description |
|---|---|---|---|
All button props | - | - | Standard button HTML attributes |
Automatically applies aria-label="Go to previous month" and disabled from context.
Data attributes:
| Attribute | Value |
|---|---|
data-slot | "calendar-nav-button" |
data-direction | "prev" |
Calendar.NextButton
Navigates to the next month. Renders a button.
| Prop | Type | Default | Description |
|---|---|---|---|
All button props | - | - | Standard button HTML attributes |
Automatically applies aria-label="Go to next month" and disabled from context.
Data attributes:
| Attribute | Value |
|---|---|
data-slot | "calendar-nav-button" |
data-direction | "next" |
Calendar.Grid
The day grid container. Renders a div with role="grid", aria-roledescription="calendar", and aria-labelledby pointing to the Header element (set automatically by getGridProps()).
Data attributes:
| Attribute | Value |
|---|---|
data-slot | "calendar-grid" |
Calendar.Weekdays
Renders the weekday header row as a div with role="row". Each weekday renders as an abbr with role="columnheader".
| Prop | Type | Default | Description |
|---|---|---|---|
renderWeekday | (weekday: string, index: number) => ReactNode | - | Custom render function for each weekday cell |
Data attributes:
| Attribute | Value |
|---|---|
data-slot | "calendar-weekdays" |
data-slot (each cell) | "calendar-weekday" |
Calendar.Day
A single day cell. Renders a button with full ARIA attributes and data attributes for styling.
| Prop | Type | Default | Description |
|---|---|---|---|
day | Grid.ICalendarDay<Date> | required | Day data from the calendar grid |
Automatically applies all day props from context: aria-label, aria-selected, aria-disabled, aria-current, tabIndex, onClick, onKeyDown, onMouseEnter, and all data-* styling attributes.
Data attributes:
| Attribute | Value |
|---|---|
data-slot | "calendar-day" |
data-calendar-day | "" - marker attribute used internally for focus queries |
data-selected | "true" when selected |
data-today | "true" for today |
data-disabled | "true" when disabled |
data-outside-month | "true" for adjacent month days |
data-hidden | "true" when showOutsideDays is false and day is outside |
data-range-start | "true" for range start |
data-range-middle | "true" for range middle |
data-range-end | "true" for range end |
data-focused | "true" when keyboard focused |
data-weekend | "true" for weekends |
Calendar.MonthView
A 12-month grid for month picking. Renders a div with role="grid". Clicking a month navigates to it and switches back to the day view.
Data attributes:
| Attribute | Value |
|---|---|
data-slot | "calendar-month-view" |
data-slot (each cell) | "calendar-month" |
data-current | "true" on the current month |
data-month (each cell) | Zero-based month index (e.g. 0 for January) |
Calendar.YearView
A decade year grid for year picking. Renders a div with role="grid". Clicking a year navigates to it and switches to the month view.
Data attributes:
| Attribute | Value |
|---|---|
data-slot | "calendar-year-view" |
data-slot (each cell) | "calendar-year" |
data-current | "true" on the current year |
data-year (each cell) | Four-digit year (e.g. 2026) |
Accessibility
Calendar.Rootrendersrole="application"witharia-label="Calendar"Calendar.Gridrendersrole="grid"witharia-roledescription="calendar"andaria-labelledbylinked to the HeaderCalendar.Weekdaysrendersrole="row"with each weekday cell asrole="columnheader"Calendar.Dayrendersrole="gridcell"with full ARIA labelingCalendar.MonthViewandCalendar.YearViewrender each button cell withrole="gridcell"Calendar.Navrendersrole="navigation"witharia-label="Calendar navigation"- Screen reader announcements are rendered automatically via
AnnouncerPortal - Keyboard navigation follows the WAI-ARIA grid pattern (ArrowLeft/ArrowRight, PageUp/PageDown, Home/End, Enter/Space, Escape)