Calendars
Events on a grid of time: one stateful Calendar with month, week, and day views, plus ten base components. Nine event colors and a mobile single-day collapse.
Calendars
Events on a grid of time: one stateful Calendar with month, week, and day views, plus ten base components. Nine event colors and a mobile single-day collapse.
Demo
Every export with real props — the ten base components and the whole Calendar, whose view control remounts it.
The two chips are not symmetrical
CalendarMonthViewEvent defaults withDot to true and lays the dot, label, and time out on one line; CalendarDwViewEvent has no default for it, stacks the time under the label, and puts the dot at the end. Both take the same nine colors from the same exported style object, and both are real <button type="button"> elements since 2026-08-23. Read from the source.Contextual demo
Six working schedules at the real viewport: search filters, both add buttons create, the channel chips narrow.
The family
Eleven exports across eleven modules: the stateful Calendar plus ten base components, no barrel. The views are internal.
| Component | Description | Import from |
|---|---|---|
| Calendar | The whole surface, and the only stateful export: it holds the anchor date, the selected day, the view, and a minute-ticking clock, and renders the header plus one of the three views. Its internals — MonthView, WeekView, WeekViewDay, DayView, MobileSingleDayGrid, PositionedEvent — are not exported. | @/components/application/calendar/calendar |
| CalendarHeader | The bar above every view: the date icon, the title with a week badge, the period line, a search button, the Prev/Today/Next group, the view menu, and Add event. Fully controlled since 2026-08-26 — every control takes a handler, and search and Add event render only when you supply one. The title is the full date in day view and the month and year otherwise. | @/components/application/calendar/base-components/calendar-header |
| CalendarViewDropdown | The view menu: a secondary Button trigger over a 216px single-selection Dropdown with radio indicators and keyboard-shortcut addons. Takes its options as data, so Day, Week, and Month are not baked in. | @/components/application/calendar/base-components/calendar-view-dropdown |
| CalendarDateIcon | The 64px date tile at the start of the header: a gray month strip over a bold brand day, in a ringed 8px-radius box. Hidden below md by the header, not by itself. | @/components/application/calendar/base-components/calendar-date-icon |
| CalendarColumnHeader | One weekday column head. Renders a real button when you pass onClick and a plain div otherwise, with default, selected, and today states on the 24px date circle. | @/components/application/calendar/base-components/calendar-column-header |
| CalendarMonthViewCell | One day cell in the month grid: the date circle, your event children, logical hairlines, a hover tint and a hover add-button from md up, and a disabled fill for days outside the month. | @/components/application/calendar/base-components/calendar-month-view-cell |
| CalendarMonthViewEvent | The month grid’s event chip: a real button with an optional leading dot, a truncating label, an optional time, and one of the nine color ramps. collapseOnMobile swaps the whole chip for a bare dot below md. | @/components/application/calendar/base-components/calendar-month-view-event |
| CalendarDwViewCell | One 48px half-hour slot in a day or week column: a hover tint, logical hairlines, and a 28px add-event button that reveals on hover or focus. | @/components/application/calendar/base-components/calendar-dw-view-cell |
| CalendarDwViewEvent | The day and week chip: a real button that fills the block its parent positions, with the label over the start time and an optional trailing dot. The time is drawn only above 30 minutes. | @/components/application/calendar/base-components/calendar-dw-view-event |
| CalendarRowLabel | One hour row in the time gutter: a 96px block on the secondary fill with its label pulled 8px above the hour line, except the first, which sits below it. | @/components/application/calendar/base-components/calendar-row-label |
| CalendarTimeMarker | The current-time rule across a day or week grid: a dot centered on the gutter border, 1px lines, and a <time> element with an optional machine-readable dateTime and an sr-only “Current time” prefix. | @/components/application/calendar/base-components/calendar-time-marker |
Built from
Eleven layers make up the calendar; seven have their own docs page.
Component | Where it’s used here |
|---|---|
| Button | Five call sites: the header’s primary sm Add event and its tertiary sm search button (named “Search events” since 2026-08-23), the mini calendar’s two 32px tertiary month-nav buttons at radius 6, and the two secondary add-event buttons inside the cells, resized to 32 and 28 at the same radius. |
| ButtonGroup | The header’s Prev / Today / Next control at size sm, with Today stretched to fill the row below lg. The two arrow items are icon-only and carry aria-labels — “Previous period” and “Next period” — since 2026-08-23; a ButtonGroupItem’s id is a collection key, not a name. |
| Dropdown | The view menu: Dropdown.Root, a 216px popover placed bottom end, and a single-selection menu with disallowEmptySelection, radio indicators, and the keyboard-shortcut addons. |
| Badge | The “Week N” chip beside the header title, at size sm, color gray, type modern. N is computed as the day of the month over seven, rounded up. |
| ButtonUtility | The day sidebar’s three event actions — Copy link, Delete, and Edit — at size xs, color tertiary, each named by its tooltip. |
| Avatar | The day sidebar’s guest stack: five photo avatars and one initials avatar at size sm, each with a 1.5px bg-primary ring, overlapping by 8px. |
| @untitledui/icons | Plus on both add-event buttons and Add event, SearchLg, ArrowLeft and ArrowRight in the button group, ChevronDown on the view trigger, ChevronLeft and ChevronRight on the mini calendar, and Calendar, Clock, BellRinging01, Copy01, Trash01, and Edit01 in the sidebar. |
The three views
Three views at two breakpoints — the sheet’s six Calendar Types. The view prop seeds each instance, once.
Search, “Add event”, and the per-cell add button are wired — activate one.
These three are the only clock-dependent things on the page
Calendar seeds its anchor date from today() and exposes no prop for it, so a fixture pinned to a fixed date would open on an empty month. The schedule below is therefore anchored on the current week — a standup, a design review, a 1:1, a customer call, and a three-day design sprint — and the current-time marker draws itself from a real clock that ticks once a minute. Every chip, cell, header, and gutter specimen further down uses fixed strings instead.Search, “Add event”, and the per-cell add button are wired — activate one.
Search, “Add event”, and the per-cell add button are wired — activate one.
Sized for a page, not a panel
md:min-h-[912px] in month view and a fixed h-[912px] in week and day, and the sheet draws it 1,280px wide. Inside this docs column it is doing its best at roughly half that. The contextual demo above is the honest view of its proportions. Note too that the component returns null until its mount effect runs, so nothing at all arrives from the server.Event chips
Two chips, nine ramps, two states each, five durations — every event symbol the sheet draws, plus the mobile dot.
Why the hover row is drawn, not hovered
100 fill applied statically — the same value the kit writes as hover:bg-utility-<color>-100. Hover a chip in the row above it to confirm the two agree.The ramps flip in dark mode
utility-* scale, which inverts in dark mode; bare interface-* does not. Switch the theme at the top of this page and the nine cells above follow. The chips carry no role beyond “button” and no color information in the accessibility tree, which is why the guidelines insist the label says what the event is.Height is arithmetic, not a prop
SLOT_HEIGHT is 48 and one slot is 30 minutes, so PositionedEvent computes a block’s top as the minutes since midnight over 30, times 48, and its height the same way — 60, 90, and 120-minute events are exactly two, three, and four stacked cells. Anything under 30 minutes draws at one full slot — the sheet’s smallest Type — since 2026-09-04; the old half-slot block left 12px for a chip that needs 28. The wrapper insets the chip by 6px on every side; the chip itself is h-full w-full flex-1, so it fills whatever it is given.Day and column states
Cells and headers in every state the sheet draws, at both breakpoints. Only the header is a real button.
The “more” row, rebuilt
MonthView rather than exported: text-utility-interface-500 at 12px semibold, inset 4px below md and 8px from md up, reading +N on mobile and N more... on desktop, and dimmed to 50% inside a disabled cell. All three of those behaviors landed on 2026-08-23. The cap itself, MAX_EVENTS_PER_CELL, is 3 and is not a prop.Tab into the two middle cells
onClick is what makes the header a real <button type="button">, with a focus ring and the right ARIA state; without it the component renders a div, which is exactly what month view wants. Before 2026-08-23 the clickable case was a div too, with no role, no keyboard path, and selected and today distinguished by color alone.Header parts
The whole bar at both breakpoints and in two title modes, plus the date tile and the view menu on their own.
Search and Add event render only when wired
CalendarHeader given neither draws neither — which is why every header specimen on this page passes all five. Drop onClickSearch in the Demo above and watch the magnifier leave the bar.The shortcuts are labels only
addon strings render beside each item and nothing in the family binds a key, so ⌘D, ⌘W, and ⌘M are a promise the calendar does not keep. Either wire them yourself on the surface that owns the calendar, or pass options without addons.The time gutter
The hour label, the half-hour slot, and the marker at both breakpoints. Two hours rebuilt, the marker pinned to 10:30.
Three 1px decisions live here
pe-[7px] from the gutter edge (it drew 8px) with its baseline pulled -translate-y-2, 8px above the hour line (it drew 9), and the marker’s dot sits at -start-1 so its 8px circle centers on the 1px border rather than a pixel inboard. The first label is the exception on purpose — it pushes down by 4px so it does not clip the top of the grid. None of the three has been measured in a browser yet. The slots above pass onAddEvent, which is what makes their 28px add button render at all — a Calendar never passes it in week or day view, which the Code tab records as an open item.