> ## 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.

# Custom events

> Type your events and render them your way.

## The event type

Events are plain objects. The only required fields are `start`, `end`, and
`title`; everything else is yours. `CalendarEvent<T>` is generic, so you can
attach your own fields and read them back in callbacks and renderers.

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

type Meta = { id: string; calendarId: string; color: string };

const events: CalendarEvent<Meta>[] = [
  {
    title: "Standup",
    start: new Date(2026, 5, 23, 9, 0),
    end: new Date(2026, 5, 23, 9, 30),
    id: "1",
    calendarId: "work",
    color: "#1F6FEB",
  },
];
```

* `allDay` lays the event out in the all-day lane above the grid instead of in
  the columns. It's also inferred for midnight-to-midnight spans. Pass
  `showAllDayEventCell={false}` to hide the lane entirely (its events won't show).
  In the `schedule` view (which has no lane) an all-day event reads "All day"
  instead of a time range; override the wording with `allDayLabel` (e.g. for a
  different language).
* `disabled` opts an event out of drag interactions.
* `draggable`, `startEditable`, and `durationEditable` control per-event drag and
  resize (see the [drag guide](/guides/dragging)).
* Multi-day events draw as one continuous bar across the days they span in the
  month view, and as a per-day clipped segment on the week/day time grid.

## Render your own event

Pass `renderEvent` — a component (so it can use hooks) that receives
`RenderEventArgs`. It's used in every mode and for every event shape (timed,
all-day, multi-day), so you only write it once.

```tsx theme={null}
import type { RenderEventArgs } from "@super-calendar/native";
import { Pressable, Text } from "react-native";

function MyEvent({ event, onPress }: RenderEventArgs<Meta>) {
  return (
    <Pressable onPress={onPress} style={{ flex: 1, backgroundColor: event.color, borderRadius: 6 }}>
      <Text numberOfLines={1}>{event.title}</Text>
    </Pressable>
  );
}

<Calendar /* ... */ renderEvent={MyEvent} />;
```

<Tip>
  Wrapping events in a portaling overlay (a context menu, popover) on the web? Portal it into your
  app's React root, not `document.body` — see the [Web guide](/guides/web#portaling-overlays).
</Tip>

On the day/week grid a timed event's box is sized to its duration and given to your
renderer as `boxHeight`, so it tracks the grid's hour scale as the grid zooms or
resizes. Fit your content to `boxHeight`: the built-in renderer clamps its own
content, and a custom renderer should adapt (show less, or scroll) rather than
assume a fixed size, since content taller than the slot is clipped. `boxHeight`
never drops below the grid's `minEventHeight` (default 32 on native, 14 on the
dom renderer); set it to `0` if your renderer should get the exact duration
height. The box is inset from its slot by `eventGap` (see
[Event boxes](/guides/time-grid#event-boxes)).

## Lighter touches

If you only want to tweak the built-in event box, you don't need a full
`renderEvent`:

* `eventCellStyle` — a style (or a function of the event) merged onto the
  built-in box.
* `keyExtractor` — a stable key per event; defaults to start-time + index.
* `showTime` shows the time range under the title (default true). The title comes
  first: on the day/week grid it fills the box with as many whole lines as fit
  (never a half-cut line), and the time only appears once a full line is free
  beneath it.
* `ellipsizeTitle` ends a single-line title (the all-day lane, month cells) with a
  trailing ellipsis when it overflows, instead of a hard clip (default false).

## Screen-reader labels

Each event announces a built-in label: its title plus the time range (or "all
day"), which the grid otherwise only shows visually. Override it per event with
`eventAccessibilityLabel`. It receives the event and a `{ mode, isAllDay, ampm }`
context, and its return value replaces the default label across every view.

```tsx theme={null}
<Calendar
  mode="week"
  date={date}
  events={events}
  eventAccessibilityLabel={(event, { isAllDay }) =>
    isAllDay
      ? `${event.title}, all day`
      : `${event.title}, from ${format(event.start, "h:mm a")} to ${format(event.end, "h:mm a")}`
  }
/>
```

The same prop is on the standalone `MonthView`, `MonthList`, and `TimeGrid`, so a
custom renderer and its label stay in sync.

## Background events

Set `display: "background"` to paint an event's time range as a shaded,
non-interactive band behind the grid instead of an event box — blocked time,
maintenance windows, public holidays:

```tsx theme={null}
const events: CalendarEvent[] = [
  { title: "Maintenance", start: at(9), end: at(12), display: "background" },
  { title: "Standup", start: at(9), end: at(10) }, // renders normally, on top
];
```

Background events shade the time grid and the resource timeline's lanes (an
all-day or multi-day one covers each day's full window). They take no overlap
column, render no chip in the month grid or agenda, and produce no year-view
dot. Restyle the band with the `backgroundEvent` slot or the theme's
`backgroundEvent` colour token.

To draw a band yourself, or make it pressable, pass a component as
`renderBackgroundEvent` to `Calendar` or `TimeGrid` on either renderer (the
resource timeline keeps the shade). It is rendered like `renderEvent`, so it may
use hooks, and receives the `event`, the `mode`, the band's `boxHeight`, the
`ampm` flag, `continuesBefore` / `continuesAfter` for a multi-day event, and
`onPress`, which fires `onPressEvent`; React Native adds `onLongPress` and makes
`boxHeight` a live shared value that follows a pinch. The themed tint is
dropped, the band lets touches or pointer events through to what you render, and
it is no longer hidden from assistive tech. On the web the band itself takes no
focus, so render a focusable control such as a button for keyboard users. Cell
presses and drag-to-create are unreachable underneath such a band. Define the
component outside your render so it keeps its identity (on the web the args
type is
`DomRenderEventArgs` from `@super-calendar/dom`):

```tsx theme={null}
import { Pressable, Text } from "react-native";
import { Calendar, type RenderEventArgs } from "@super-calendar/native";

function BlockedBand({ event, onPress }: RenderEventArgs) {
  return (
    <Pressable onPress={onPress} style={{ flex: 1, backgroundColor: "#fde68a" }}>
      <Text>{event.title}</Text>
    </Pressable>
  );
}

<Calendar
  mode="week"
  date={date}
  events={events}
  renderBackgroundEvent={BlockedBand}
  onChangeDate={setDate}
  onPressEvent={(event) => console.log(event.title)}
/>;
```

## Loading events from a feed

`useEventSource` (from `@super-calendar/core`) owns the fetching for you: point
it at a JSON feed, an iCalendar feed (`.ics` URLs are parsed automatically), or
your own async function, and hand the result to any view. Set
`refetchIntervalMs` for a live feed; `refetch` reloads on demand, and a failed
refetch keeps the previous events while reporting `error`.

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

const { events, loading, error, refetch } = useEventSource("https://example.com/rooms.ics", {
  refetchIntervalMs: 5 * 60_000,
});

<Calendar mode="week" date={date} events={events} onChangeDate={setDate} />;
```

JSON feeds default to items with ISO `start`/`end` strings; pass `map` to
reshape anything else (rename fields, attach a `resourceId`, filter).

One nuance for function sources: swapping the function itself doesn't trigger a
refetch (inline functions change identity every render, which would loop).
The next interval tick or a manual `refetch()` always calls the latest
function, so change what the function reads, or call `refetch()` after
swapping it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.