> ## Documentation Index
> Fetch the complete documentation index at: https://super-calendar.afonsojramos.me/llms.txt
> Use this file to discover all available pages before exploring further.

# react-dom renderer

> A pure react-dom calendar with no React Native runtime, from @super-calendar/dom.

`@super-calendar/dom` renders the calendar with real DOM elements. There is no
React Native and no react-native-web in the tree: it builds on the same headless
core as the native package and on Legend List's DOM renderer. Reach for it when
your app is web-only, or web-first, and you don't want to pull a React Native
runtime into the bundle.

If you already ship a React Native app and want one codebase that also runs in
the browser, use [`@super-calendar/native` on react-native-web](/guides/web)
instead.

## Install

<CodeGroup>
  ```bash npm theme={null}
  npm install @super-calendar/dom react react-dom @legendapp/list date-fns
  ```

  ```bash pnpm theme={null}
  pnpm add @super-calendar/dom react react-dom @legendapp/list date-fns
  ```
</CodeGroup>

`react`, `react-dom`, `@legendapp/list`, and `date-fns` are peer dependencies, so
they resolve to the single copy your app already uses.

## What it renders

| Export      | Renders                                                                   |
| ----------- | ------------------------------------------------------------------------- |
| `Calendar`  | Batteries-included: picks the view from `mode` (month, schedule, grid).   |
| `TimeGrid`  | A day/week/N-day time grid with an all-day lane and a now line.           |
| `Agenda`    | A day-grouped, scrolling list of events: the schedule view.               |
| `MonthList` | A scrolling, virtualized month: a date picker, or a calendar with events. |
| `MonthView` | A single month grid: compact picker, or a calendar with event chips.      |

`Calendar` mirrors the React Native component's API across every view, including
`mode="schedule"` (the `Agenda` list). You can also drop down to the
individual views. The selection hook (`useDateRange`) and grid helpers
(`useMonthGrid`, `buildMonthGrid`, `layoutDayEvents`) are re-exported from the
core, so you import everything from one place.

The month views take an optional `events` prop: pass it (even `[]`) and the grid
switches from the compact picker look to a calendar layout with event chips and a
"+N more" overflow; omit it for the picker. `TimeGrid` supports `businessHours`
shading, `timeslots`, a bounded hour window (`minHour`/`maxHour`, plus `hideHours`
to drop the hour axis), `showWeekNumber` (with `weekNumberPrefix`), `onPressCell`,
drag-to-create (`onCreateEvent`), and drag-to-move/resize (`onDragEvent`, which
can return `false` to reject a drop).
Creating on empty space is pointer-only (drag to sweep out a range); see
[Keyboard navigation](#keyboard-navigation) for how focus moves.

## The Calendar wrapper

The quickest start mirrors the native component: pick a `mode` and pass events.

```tsx theme={null}
import { Calendar, type CalendarEvent } from "@super-calendar/dom";

const events: CalendarEvent[] = [
  { title: "Standup", start: new Date(2026, 5, 23, 9, 0), end: new Date(2026, 5, 23, 9, 30) },
];

export function App() {
  return (
    <Calendar
      mode="week"
      date={new Date()}
      events={events}
      weekStartsOn={1}
      height={520}
      onPressEvent={(event) => console.log(event.title)}
    />
  );
}
```

Switch `mode` to `"month"` for a month grid with event chips, `"schedule"` for a
day-grouped agenda list, or `"day"` / `"3days"` / `"custom"` (with `numberOfDays`)
for the time grid. Custom rendering splits by view: `renderTimeEvent` for the time
grid, `renderMonthEvent` for month chips, and `renderScheduleEvent` for agenda
rows.

## Schedule (agenda)

`mode="schedule"` (or the `Agenda` component directly) renders a scrolling list of
events grouped under a date header per day, sorted by start. It does not window
by `date`: it shows exactly the `events` you pass, so you control the range. Each
row defaults to a time + title; override it with `renderScheduleEvent`.

```tsx theme={null}
import { Agenda, type CalendarEvent } from "@super-calendar/dom";

const events: CalendarEvent[] = [
  { title: "Standup", start: new Date(2026, 5, 23, 9, 0), end: new Date(2026, 5, 23, 9, 30) },
  { title: "Lunch", start: new Date(2026, 5, 23, 12, 0), end: new Date(2026, 5, 23, 13, 0) },
];

<Agenda
  events={events}
  height={520}
  onPressEvent={(event) => console.log(event.title)}
  onPressDay={(day) => console.log("header", day)}
/>;
```

## A time grid and a range picker

```tsx theme={null}
import { useMemo } from "react";
import { type CalendarEvent, MonthList, TimeGrid, useDateRange } from "@super-calendar/dom";

const events: CalendarEvent[] = [
  { title: "Standup", start: new Date(2026, 5, 23, 9, 0), end: new Date(2026, 5, 23, 9, 30) },
  {
    title: "Design review",
    start: new Date(2026, 5, 23, 9, 15),
    end: new Date(2026, 5, 23, 10, 30),
  },
];

export function App() {
  const today = useMemo(() => new Date(), []);
  const { range, onPressDate, reset } = useDateRange({ minDate: today });

  return (
    <>
      <TimeGrid date={today} mode="week" weekStartsOn={1} events={events} height={480} />

      <MonthList
        date={today}
        minDate={today}
        weekStartsOn={1}
        selectedRange={range ?? undefined}
        onPressDay={onPressDate}
        height={420}
      />
      <button type="button" onClick={reset}>
        Clear
      </button>
    </>
  );
}
```

`TimeGrid` and `MonthList` take an explicit `height` because they virtualize and
scroll inside that box. Everything else (events, selection, week start) matches
the native API, since both renderers share the core types.

## Selection

Selection works as in the native [Date selection](/guides/selection) guide:
`useDateRange` returns `range` and an `onPressDate` handler, and you pass
`selectedRange` (or `selectedDates`) plus `minDate` / `maxDate` /
`isDateDisabled` to `MonthList`. Selection is driven by clicking days; the
react-dom renderer does not implement pointer drag-select.

## Keyboard navigation

Tab moves through **events only**. Empty day columns (time grid) and empty day
cells (month) are not tab stops, so keyboard focus lands on each event in turn
rather than stopping on every blank cell. Events are buttons: Enter or Space
activates them (`onPressEvent`).

This is a deliberate default. The alternative, making every empty cell focusable,
turns a week into seven tab stops before the first event and a month into dozens.
Creating on empty space stays pointer-only (drag to sweep out a range); if you
want a keyboard way to add an event, render your own "New event" button.

Three cases add richer keyboard navigation:

* The **date picker** (`MonthList` or `MonthView` rendered without `events`) is
  always keyboard-navigable: one roving tab stop per month, arrow keys move the
  focus, Enter selects the day. Date selection is the whole point there.
* **Month with events**, opt in with `keyboardDayNavigation` to add the same
  roving day focus on top of the event chips, so Enter on a focused day opens it
  (`onPressDay`). It is off by default.
* **Time grid with events**, opt in with `keyboardEventNavigation` to add arrow-key
  movement between events: Up/Down step through a day by time, Left/Right jump to
  the nearest event in the adjacent day, Home/End go to the day's first/last event,
  and Enter/Space activate. This is additive: every event stays individually
  tabbable, so screen-reader users keep full access and the arrow keys are a
  convenience for sighted keyboard users. Off by default. The day-column headers
  always carry a full-date label for screen readers, whether or not they're
  interactive.

```tsx theme={null}
import { Calendar } from "@super-calendar/dom";

// Time grid where arrow keys move a roving focus between events.
<Calendar mode="week" date={new Date()} events={events} keyboardEventNavigation />;
```

```tsx theme={null}
import { Calendar, type CalendarEvent } from "@super-calendar/dom";

const events: CalendarEvent[] = [
  { title: "Standup", start: new Date(2026, 5, 23, 9, 0), end: new Date(2026, 5, 23, 9, 30) },
];

// Month view where arrow keys also move a roving day focus (Enter opens the day).
<Calendar
  mode="month"
  date={new Date()}
  events={events}
  keyboardDayNavigation
  onPressDay={(day) => console.log("open day", day)}
/>;
```

`keyboardDayNavigation` and `keyboardEventNavigation` are web-only concerns, so
they exist on the react-dom renderer only. The React Native renderer pages with
the arrow keys on react-native-web instead.

### Paging with the keyboard

Pass `onChangeDate` and the focused calendar pages with **PageDown** / **PageUp**:
a month in `month`, a week in `schedule`, otherwise the view's day span. It's
controlled, so update `date` in response (the same handler you'd wire to your own
prev/next buttons). Omit `onChangeDate` and the keys fall through untouched.

```tsx theme={null}
<Calendar mode="week" date={date} onChangeDate={setDate} events={events} />
```

## Theming

The DOM theme is a flat object of colors plus a few metrics, separate from the
React Native `CalendarTheme` (which carries `TextStyle`/`ViewStyle`). Both derive
their colors from the same core tokens, so the palettes match and the
[color-token list](/guides/theming#color-tokens) applies here too (just flat,
not nested under `colors`).

```tsx theme={null}
import { darkDomTheme, mergeDomTheme, TimeGrid } from "@super-calendar/dom";

// mergeDomTheme(overrides, base?) — base defaults to defaultDomTheme.
const brand = mergeDomTheme({ selectedBackground: "#7c3aed", cellHeight: 56 });

<TimeGrid date={new Date()} mode="day" theme={brand} height={480} />;
// or pass `theme={darkDomTheme}` for dark mode
```

The theme recolours everything at once. To hand a **specific part** of a view to
Tailwind or your own CSS — with `data-*` state variants like `data-[today]:` — use
the per-slot `classNames` / `styles` props. See
[Styling with Tailwind & CSS](/guides/styling).

See it running on the [live demo](/demo) and in the
[`examples/web`](https://github.com/afonsojramos/super-calendar/tree/main/examples/web)
Vite app.
