);
}
```
### Anatomy
Rendering `` without children produces the complete default layout. Every part is also exported for full composition:
```tsx
import {Agenda} from '@darkcode-ui/react';
export default () => (
{/* Day / Week views */}
{/* Month view */}
)
```
For state shared with your own UI, build the state with `useAgenda` and pass it through the `state` prop:
```tsx
const agenda = useAgenda({events, defaultView: "week"});
return (
<>
{agenda.visibleDays.length} day(s) visible
>
);
```
### Views
The agenda supports three views — `day`, `week`, and `month` — switched through `Agenda.ViewSelector` or programmatically via `setView`. Clicking a date number in the month grid navigates to that date in day view, and the first day of each month shows the month name (e.g. "May 1").
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
const base = today(getLocalTimeZone());
const at = (days: number, hour: number, minute = 0) =>
toCalendarDateTime(base.add({days})).add({minutes: hour * 60 + minute});
const events: AgendaEvent[] = [
{
color: "success",
end: at(3, 0),
id: "conference",
isAllDay: true,
start: at(1, 0),
title: "Conference",
},
{end: at(0, 0), id: "sprint", isAllDay: true, start: at(-2, 0), title: "Sprint 42"},
{end: at(0, 10), id: "standup", start: at(0, 9, 30), title: "Daily standup"},
{color: "warning", end: at(0, 12), id: "review", start: at(0, 11), title: "Design review"},
{color: "danger", end: at(0, 15), id: "incident", start: at(0, 14), title: "Incident review"},
{end: at(7, 11), id: "sync", start: at(7, 10), title: "Weekly sync"},
];
export function MonthView() {
return (
);
}
```
### Events
Events are plain objects using `CalendarDateTime` from [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/):
```tsx
interface AgendaEvent {
id: string;
title: string;
start: CalendarDateTime;
end: CalendarDateTime;
color?: string;
isAllDay?: boolean;
isReadOnly?: boolean;
status?: "confirmed" | "unconfirmed";
}
```
Events whose start and end fall on different dates, or with `isAllDay: true`, render as spanning bars in the all-day section (day/week views) or across month rows.
### Drag Interactions
All drag interactions activate after an 8px pointer threshold, so plain clicks never trigger previews:
- **Drag to create** — drag on an empty time slot; a dashed preview snaps to `slotDuration`
- **Drag to move** — drag an event vertically to change its time, or horizontally to move it to another day
- **Drag to resize** — drag the handle at the bottom edge of an event
- **Month move** — drag a month-view event onto another cell; the target highlights via `data-drop-target`
Pressing Escape cancels an in-flight drag. On touch devices, event cards set `touch-action: none` only when draggable, while day columns keep `pan-y` so scrolling always wins over drag-to-create.
### Selection and Deletion
Clicking an event selects it (`data-selected`), clicking empty space deselects, and Escape clears the selection. When `onEventDelete` is provided, Delete or Backspace removes the selected event. Selection state can be controlled via `selectedEventId` and `onEventSelect`.
```tsx
"use client";
import type {AgendaEvent, AgendaView} from "@darkcode-ui/react";
import type {CalendarDate} from "@internationalized/date";
import {Agenda, Button, Description} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
import {useState} from "react";
const base = today(getLocalTimeZone());
const at = (days: number, hour: number, minute = 0) =>
toCalendarDateTime(base.add({days})).add({minutes: hour * 60 + minute});
const events: AgendaEvent[] = [
{end: at(0, 10), id: "standup", start: at(0, 9, 30), title: "Daily standup"},
{color: "success", end: at(0, 12), id: "review", start: at(0, 10), title: "Design review"},
{end: at(1, 11), id: "1on1", start: at(1, 10), title: "1:1 with manager"},
];
export function Controlled() {
const [view, setView] = useState("week");
const [date, setDate] = useState(base);
const [selectedEventId, setSelectedEventId] = useState(null);
return (
);
}
```
### All-Day Events
All-day and multi-day events stack in a collapsible section pinned above the time grid. When collapsed, per-day event counts are shown — customize them with `collapsedLabel` on `Agenda.AllDaySection`.
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
const base = today(getLocalTimeZone());
const allDay = (
id: string,
title: string,
startDays: number,
endDays: number,
color?: string,
): AgendaEvent => ({
color,
end: toCalendarDateTime(base.add({days: endDays})),
id,
isAllDay: true,
start: toCalendarDateTime(base.add({days: startDays})),
title,
});
const events: AgendaEvent[] = [
allDay("sprint", "Sprint 42", -2, 2),
allDay("travel", "Travel", 0, 1, "warning"),
allDay("holiday", "Public holiday", 2, 2, "danger"),
allDay("workshop", "Workshop", 0, 0, "success"),
];
// The all-day section is collapsible; when collapsed it shows per-day counts
// using the customizable `collapsedLabel`.
export function AllDayEvents() {
return (
`+${count}`} />
);
}
```
### Event Colors
The `color` prop accepts semantic token names (`accent`, `success`, `warning`, `danger`, `default`) or any raw CSS color. Backgrounds are derived with `color-mix`, so a single value themes the whole card.
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
const base = today(getLocalTimeZone());
const at = (hour: number, minute = 0) =>
toCalendarDateTime(base).add({minutes: hour * 60 + minute});
// Semantic token names map to theme colors; any raw CSS color also works.
const colors = ["accent", "success", "warning", "danger", "#8b5cf6", "oklch(0.7 0.15 200)"];
const events: AgendaEvent[] = colors.map((color, index) => ({
color,
end: at(9 + index, 45),
id: `color-${index}`,
start: at(9 + index),
title: color,
}));
export function EventColors() {
return (
);
}
```
### Current Time Indicator
When today is visible, a live indicator marks the current time: a badge in the time column, a faded line across all columns, and a solid segment over today's column. Hour labels near the indicator hide automatically to avoid overlap, and the position updates every minute.
### Weekend Highlighting
Saturday and Sunday columns and month cells receive a subtle tint via the `data-weekend` attribute, computed from the active locale.
### Custom Styles
```tsx
"use client";
import type {AgendaEvent} from "@darkcode-ui/react";
import {Agenda} from "@darkcode-ui/react";
import {getLocalTimeZone, toCalendarDateTime, today} from "@internationalized/date";
const base = today(getLocalTimeZone());
const at = (days: number, hour: number, minute = 0) =>
toCalendarDateTime(base.add({days})).add({minutes: hour * 60 + minute});
const events: AgendaEvent[] = [
{end: at(0, 10), id: "standup", start: at(0, 9, 30), title: "Daily standup"},
{color: "success", end: at(0, 12), id: "review", start: at(0, 10), title: "Design review"},
{end: at(1, 11), id: "1on1", start: at(1, 10), title: "1:1 with manager"},
];
// Sizing is driven by CSS custom properties on the root element.
export function CustomStyles() {
return (
);
}
```
## Styling
### CSS Variables
Sizing is controlled with CSS custom properties on `.agenda`:
| Variable | Default | Description |
|----------|---------|-------------|
| `--agenda-slot-height` | `60px` | Height of one hour in the time grid. |
| `--agenda-time-column-width` | `58px` | Width of the hour labels column. |
| `--agenda-current-time-color` | `var(--color-danger)` | Color of the current time indicator. |
| `--agenda-event-radius` | `var(--radius-md)` | Border radius of event cards. |
| `--agenda-week-header-height` | `3.5rem` | Height of the sticky day headers row. |
| `--agenda-all-day-row-height` | `1.625rem` | Height of one all-day/spanning event row. |
### Customizing the component classes
```css
@layer components {
.agenda {
--agenda-slot-height: 90px;
--agenda-current-time-color: var(--color-accent);
}
.agenda__event[data-selected="true"] {
@apply shadow-lg;
}
.agenda__day-column[data-today="true"] {
@apply bg-accent-soft/20;
}
}
```
### CSS Classes
Agenda uses these classes in `packages/styles/components/agenda.css`:
- `.agenda` - Root container; defines the CSS variables above.
- `.agenda__header` - Flex container for heading, view selector, and navigation.
- `.agenda__heading` - Month/year title.
- `.agenda__view-selector` - View switcher (built on ToggleButtonGroup).
- `.agenda__navigation` / `.agenda__nav-button` / `.agenda__today-button` - Navigation controls.
- `.agenda__body` - Bordered content area below the header.
- `.agenda__week-header` / `.agenda__day-header` - Sticky day headers with name and date.
- `.agenda__time-grid` - Scrollable grid container.
- `.agenda__time-labels` / `.agenda__time-label` - Hour labels column.
- `.agenda__day-column` / `.agenda__time-slot` - Day columns and hour rows.
- `.agenda__event` / `.agenda__event-title` / `.agenda__event-time` - Timed event card.
- `.agenda__resize-handle` - Bottom resize handle.
- `.agenda__all-day-section` / `.agenda__all-day-toggle` / `.agenda__all-day-label` / `.agenda__all-day-event` / `.agenda__all-day-summary` - All-day section parts.
- `.agenda__current-time-indicator` / `.agenda__current-time-badge` / `.agenda__current-time-line` / `.agenda__current-time-today-line` - Current time indicator parts.
- `.agenda__month-grid` / `.agenda__month-weekday-header` / `.agenda__month-row` / `.agenda__month-cell` - Month view structure.
- `.agenda__month-cell-date` / `.agenda__month-cell-more` / `.agenda__month-event` / `.agenda__month-spanning-event` - Month view content.
- `.agenda__create-preview` / `.agenda__drop-preview` - Drag previews.
### Interactive States
- **Selected**: `[data-selected="true"]` on event elements
- **Unconfirmed**: `[data-status="unconfirmed"]` — dashed border, transparent background
- **Read only**: `[data-readonly="true"]`
- **Dragging**: `[data-dragging="true"]` on the source event and the root
- **Resizing**: `[data-resizing="true"]`
- **Today**: `[data-today="true"]` on day headers, columns, and month cells
- **Weekend**: `[data-weekend="true"]` on columns and month cells
- **Outside month**: `[data-outside-month="true"]` on month cells
- **Drop target**: `[data-drop-target="true"]` on the hovered month cell during a drag
## API Reference
### useAgenda
The headless hook that owns all agenda state. `Agenda` accepts the same options directly as props, or a prebuilt state via `state`.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `events` | `AgendaEvent[]` | - | Events to display. Required. |
| `defaultView` | `"day" \| "week" \| "month"` | `"week"` | Initial view (uncontrolled). |
| `view` | `"day" \| "week" \| "month"` | - | Controlled view state. |
| `onViewChange` | `(view: AgendaView) => void` | - | Called when the view changes. |
| `defaultDate` | `CalendarDate` | today | Initial focused date (uncontrolled). |
| `date` | `CalendarDate` | - | Controlled date state. |
| `onDateChange` | `(date: CalendarDate) => void` | - | Called when the date changes. |
| `startHour` | `number` | `0` | First visible hour in the time grid. |
| `endHour` | `number` | `24` | Last visible hour in the time grid. |
| `slotDuration` | `number` | `60` | Slot snapping granularity in minutes. |
| `onEventCreate` | `({start, end}) => void` | - | Enables drag-to-create. |
| `onEventMove` | `(id, start, end) => void` | - | Enables drag-to-move (including cross-day and month moves). |
| `onEventResize` | `(id, start, end) => void` | - | Enables drag-to-resize. |
| `onEventDelete` | `(id: string) => void` | - | Enables Delete/Backspace removal of the selected event. |
| `onEventSelect` | `(id: string \| null) => void` | - | Called when selection changes. |
| `selectedEventId` | `string \| null` | - | Controlled selection state. |
The returned state includes `view`, `date`, `setView`, `setDate`, `navigateNext`, `navigatePrevious`, `navigateToday`, `visibleDays`, `visibleWeeks`, `getEventsForDay(day)`, `allDayLayout`, `getMonthRowLayout(week)`, `getPerCellEvents(day)`, `selectEvent`, `deleteSelectedEvent`, and `dragState`.
### Composition Parts
| Component | Description |
|-----------|-------------|
| `Agenda.Header` | Container for heading, view selector, and navigation. Renders all three by default. |
| `Agenda.Heading` | Localized month/year title (e.g. "May 2026"). |
| `Agenda.ViewSelector` | Day/Week/Month segmented control; accepts a `labels` map for localization. |
| `Agenda.Navigation` | Wrapper for nav and today buttons. |
| `Agenda.NavButton` | Previous/next button (`slot="previous"` or `slot="next"`). |
| `Agenda.TodayButton` | Navigates to today's date. |
| `Agenda.Body` | Renders the time grid or month grid based on the active view. |
| `Agenda.TimeGrid` | Scrollable day/week grid; owns the drag interactions. |
| `Agenda.WeekHeader` | Sticky row of day headers. |
| `Agenda.AllDaySection` | Collapsible all-day events section (`collapsedLabel` prop). |
| `Agenda.AllDayLabel` | "all-day" label text. |
| `Agenda.AllDayEvent` | Spanning bar (`event`, `colStart`, `colSpan`, `row`). |
| `Agenda.TimeLabels` | Hour labels column. |
| `Agenda.CurrentTimeIndicator` | Live time marker; updates every minute. |
| `Agenda.DayColumn` | One day's column (`date` prop); renders that day's events by default. |
| `Agenda.Event` | Timed event card (`event` prop) with resize handle. |
| `Agenda.MonthGrid` | Month view container; renders weeks by default. |
| `Agenda.MonthWeekdayHeader` | Weekday names row. |
| `Agenda.MonthRow` | One week row (`week` prop for default rendering). |
| `Agenda.MonthSpanningEvent` | Multi-day bar (`event`, `colStart`, `colSpan`, `row`). |
| `Agenda.MonthCell` | Day cell (`date`, `maxEvents`, `moreLabel`, `spanningRowCount`). |
| `Agenda.MonthEvent` | Per-cell event chip with drag-to-move support. |
### Agenda.MonthCell Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `date` | `CalendarDate` | - | The date for this cell. Required. |
| `maxEvents` | `number` | `2` | Visible events before overflow; spanning rows above the cell consume this budget. |
| `moreLabel` | `(count: number) => string` | `"N more"` | Label for the overflow link, which navigates to day view. |
| `spanningRowCount` | `number` | `0` | Spanning event rows above this cell (reserves space). |
### Accessibility
- Events are focusable with descriptive labels (title, date, time range, and status); Enter/Space selects.
- Shift+ArrowUp/ArrowDown resizes a focused event by one slot when resizing is enabled.
- Month date numbers and overflow links are buttons that announce the full date.
- Week start, weekday names, weekend detection, and all time formats follow the active locale via `I18nProvider`.
### Related packages
- [`@internationalized/date`](https://react-aria.adobe.com/internationalized/date/) — `CalendarDate` and `CalendarDateTime` types used by all agenda props
- [`I18nProvider`](https://react-aria.adobe.com/I18nProvider) — override locale for a subtree