27.7k

Agenda

Composable calendar with day, week, and month views for displaying and managing events with drag interactions

Import

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.

"use client";

import type {AgendaEvent} from "@darkcode-ui/react";
import type {CalendarDateTime} from "@internationalized/date";

Anatomy

Rendering <Agenda events={...} /> without children produces the complete default layout. Every part is also exported for full composition:

import {Agenda} from '@darkcode-ui/react';

export default () => (
  <Agenda events={events}>
    <Agenda.Header>
      <Agenda.Heading />
      <Agenda.ViewSelector />
      <Agenda.Navigation>
        <Agenda.NavButton slot="previous" />
        <Agenda.TodayButton />
        <Agenda.NavButton slot="next" />
      </Agenda.Navigation>
    </Agenda.Header>
    <Agenda.Body>
      {/* Day / Week views */}
      <Agenda.TimeGrid>
        <Agenda.WeekHeader />
        <Agenda.AllDaySection />
      </Agenda.TimeGrid>

      {/* Month view */}
      <Agenda.MonthGrid />
    </Agenda.Body>
  </Agenda>
)

For state shared with your own UI, build the state with useAgenda and pass it through the state prop:

const agenda = useAgenda({events, defaultView: "week"});

return (
  <>
    <span>{agenda.visibleDays.length} day(s) visible</span>
    <Agenda state={agenda} />
  </>
);

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").

"use client";

import type {AgendaEvent} from "@darkcode-ui/react";

import {Agenda} from "@darkcode-ui/react";

Events

Events are plain objects using CalendarDateTime from @internationalized/date:

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.

"use client";

import type {AgendaEvent, AgendaView} from "@darkcode-ui/react";
import type {CalendarDate} from "@internationalized/date";

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.

"use client";

import type {AgendaEvent} from "@darkcode-ui/react";

import {Agenda} from "@darkcode-ui/react";

Unconfirmed Events

status: "unconfirmed" renders an event with a dashed border and transparent background to indicate a tentative booking.

"use client";

import type {AgendaEvent} from "@darkcode-ui/react";

import {Agenda} from "@darkcode-ui/react";

Custom Hours and Slot Duration

startHour and endHour clip the visible time range; slotDuration controls drag snapping granularity in minutes.

"use client";

import type {AgendaEvent} from "@darkcode-ui/react";
import type {CalendarDateTime} from "@internationalized/date";

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.

"use client";

import type {AgendaEvent} from "@darkcode-ui/react";

import {Agenda} from "@darkcode-ui/react";

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.

"use client";

import type {AgendaEvent} from "@darkcode-ui/react";

import {Agenda} from "@darkcode-ui/react";

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

"use client";

import type {AgendaEvent} from "@darkcode-ui/react";

import {Agenda} from "@darkcode-ui/react";

Styling

CSS Variables

Sizing is controlled with CSS custom properties on .agenda:

VariableDefaultDescription
--agenda-slot-height60pxHeight of one hour in the time grid.
--agenda-time-column-width58pxWidth of the hour labels column.
--agenda-current-time-colorvar(--color-danger)Color of the current time indicator.
--agenda-event-radiusvar(--radius-md)Border radius of event cards.
--agenda-week-header-height3.5remHeight of the sticky day headers row.
--agenda-all-day-row-height1.625remHeight of one all-day/spanning event row.

Customizing the component classes

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

OptionTypeDefaultDescription
eventsAgendaEvent[]-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.
defaultDateCalendarDatetodayInitial focused date (uncontrolled).
dateCalendarDate-Controlled date state.
onDateChange(date: CalendarDate) => void-Called when the date changes.
startHournumber0First visible hour in the time grid.
endHournumber24Last visible hour in the time grid.
slotDurationnumber60Slot 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.
selectedEventIdstring | 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

ComponentDescription
Agenda.HeaderContainer for heading, view selector, and navigation. Renders all three by default.
Agenda.HeadingLocalized month/year title (e.g. "May 2026").
Agenda.ViewSelectorDay/Week/Month segmented control; accepts a labels map for localization.
Agenda.NavigationWrapper for nav and today buttons.
Agenda.NavButtonPrevious/next button (slot="previous" or slot="next").
Agenda.TodayButtonNavigates to today's date.
Agenda.BodyRenders the time grid or month grid based on the active view.
Agenda.TimeGridScrollable day/week grid; owns the drag interactions.
Agenda.WeekHeaderSticky row of day headers.
Agenda.AllDaySectionCollapsible all-day events section (collapsedLabel prop).
Agenda.AllDayLabel"all-day" label text.
Agenda.AllDayEventSpanning bar (event, colStart, colSpan, row).
Agenda.TimeLabelsHour labels column.
Agenda.CurrentTimeIndicatorLive time marker; updates every minute.
Agenda.DayColumnOne day's column (date prop); renders that day's events by default.
Agenda.EventTimed event card (event prop) with resize handle.
Agenda.MonthGridMonth view container; renders weeks by default.
Agenda.MonthWeekdayHeaderWeekday names row.
Agenda.MonthRowOne week row (week prop for default rendering).
Agenda.MonthSpanningEventMulti-day bar (event, colStart, colSpan, row).
Agenda.MonthCellDay cell (date, maxEvents, moreLabel, spanningRowCount).
Agenda.MonthEventPer-cell event chip with drag-to-move support.

Agenda.MonthCell Props

PropTypeDefaultDescription
dateCalendarDate-The date for this cell. Required.
maxEventsnumber2Visible 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.
spanningRowCountnumber0Spanning 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.

On this page