calendar-tsx

Accessibility

Roles, labels and the locale for the built-in labels.

The parts render semantic elements with roles and labels. Events, triggers and overflow are native buttons, so they are focusable and work with the keyboard. There is no roving focus between days.

PartSemantics
DayGridViewregion, labelled "Calendar"
DayGridViewSectiongrid, with row, columnheader and gridcell
TimeGridViewregion, labelled "Time grid"
ListViewregion, labelled "Event list"
TimelineViewregion, labelled "Timeline"
ListViewDayHeaderh3
Unavailable and now partsaria-hidden

The built-in labels are in English, except the triggers' dates, which use the provider's locale. Pass aria-label to any part to replace its label.

Under forced-colors: active, the themes keep today's marker, the now line and unavailable time visible with system colors. See customizing.

Editing controls

Pointer bindings do not add keyboard movement or resizing automatically. Provide labeled controls that call the same operation APIs, and announce completion or rejection in a live region. Keep preview overlays aria-hidden and noninteractive. Escape cancels active pointer gestures. See selection and event editing and the playground.

Locale

Pass locale to CalendarProvider, or to Calendar. It defaults to 'en-US'.

export const GermanCalendar = (props: { calendar: AppCalendar; children: JSX.Element }) => (
  // trigger labels read "Go to Mittwoch, 11. März 2026"
  <CalendarProvider value={props.calendar} locale='de-DE'>
    {props.children}
  </CalendarProvider>
);

Labels

Give events a label that includes their time, which the visible title often leaves out:

export const Event = (props: { segment: Calendar_TimeGridSegment<Booking> }) => {
  const label = () => {
    const event = props.segment.event;
    const start = event.kind === 'allDay' ? '' : event.start.toLocaleString('en-US', { timeStyle: 'short' });

    return `${event.data.title}, ${start}`;
  };

  return (
    <TimeGridViewEvent segment={props.segment} aria-label={label()}>
      {props.segment.event.data.title}
    </TimeGridViewEvent>
  );
};