# Agenda **Category**: react **URL**: https://ui.darkcode.dev/en/docs/react/components/agenda **Source**: https://raw.githubusercontent.com/DarkCode-Developers/darkcode-ui/refs/heads/main/apps/docs/content/docs/en/react/components/(date-and-time)/agenda.mdx > Composable calendar with day, week, and month views for displaying and managing events with drag interactions *** ## Import ```tsx import { Agenda, useAgenda } from '@darkcode-ui/react'; ``` ### Usage Pass an `events` array and the mutation callbacks you want to enable. Drag-to-create, drag-to-move, drag-to-resize, and keyboard deletion are each activated only when the matching callback is provided — omit a callback and its interaction is disabled. ```tsx "use client"; import type {AgendaEvent} from "@darkcode-ui/react"; import type {CalendarDateTime} from "@internationalized/date"; import {Agenda} 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 initialEvents: 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"}, {color: "warning", end: at(0, 11, 30), id: "pairing", start: at(0, 10, 30), title: "Pairing"}, {end: at(1, 11), id: "1on1", start: at(1, 10), title: "1:1 with manager"}, {color: "danger", end: at(-1, 16), id: "retro", start: at(-1, 15), title: "Sprint retro"}, {end: at(0, 0), id: "release", isAllDay: true, start: at(0, 0), title: "Release day"}, ]; let counter = 0; export function Basic() { const [events, setEvents] = useState(initialEvents); const moveEvent = (id: string, start: CalendarDateTime, end: CalendarDateTime) => setEvents((prev) => prev.map((event) => (event.id === id ? {...event, end, start} : event))); return (
setEvents((prev) => prev.filter((event) => event.id !== id))} onEventMove={moveEvent} onEventResize={moveEvent} onEventCreate={({end, start}) => { counter += 1; setEvents((prev) => [...prev, {end, id: `new-${counter}`, start, title: "New event"}]); }} />
); } ``` ### 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 (
view: {view} · date: {date.toString()} · selected: {selectedEventId ?? "none"}
); } ``` ### Read Only Omit the mutation callbacks for a display-only agenda, or set `isReadOnly: true` on individual events to keep them selectable while disabling move/resize and hiding the resize handle. ```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(0, 15), id: "lunch", isReadOnly: true, start: at(0, 13), title: "Lunch & learn"}, {color: "danger", end: at(1, 11), id: "incident", start: at(1, 10), title: "Incident review"}, ]; // Without mutation callbacks the agenda is display-only: events can be // selected but never created, moved, resized, or deleted. export function ReadOnly() { return (
); } ``` ### Unconfirmed Events `status: "unconfirmed"` renders an event with a dashed border and transparent background to indicate a tentative booking. ```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, 11), id: "planning", start: at(0, 9), status: "unconfirmed", title: "Planning (tentative)", }, { color: "danger", end: at(0, 14), id: "vendor", start: at(0, 12, 30), status: "unconfirmed", title: "Vendor call (tentative)", }, {end: at(0, 16), id: "review", start: at(0, 15), title: "Review (confirmed)"}, ]; export function UnconfirmedEvents() { return (
); } ``` ### Custom Hours and Slot Duration `startHour` and `endHour` clip the visible time range; `slotDuration` controls drag snapping granularity in minutes. ```tsx "use client"; import type {AgendaEvent} from "@darkcode-ui/react"; import type {CalendarDateTime} from "@internationalized/date"; import {Agenda} 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 initialEvents: AgendaEvent[] = [ {end: at(0, 9, 30), id: "standup", start: at(0, 9), title: "Standup"}, {color: "success", end: at(0, 11, 30), id: "review", start: at(0, 10), title: "Design review"}, {end: at(1, 14, 30), id: "demo", start: at(1, 13, 30), title: "Demo"}, ]; let counter = 0; // The grid shows business hours only, and dragging snaps to 30-minute slots. export function CustomHours() { const [events, setEvents] = useState(initialEvents); const moveEvent = (id: string, start: CalendarDateTime, end: CalendarDateTime) => setEvents((prev) => prev.map((event) => (event.id === id ? {...event, end, start} : event))); return (
{ counter += 1; setEvents((prev) => [...prev, {end, id: `new-${counter}`, start, title: "New event"}]); }} />
); } ``` ### 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