Skip to content
ShineUI
Install

Calendar

A month of work, moved by hand.

<sui-calendar>DataStable

About

Month and week grid that places records on their dates as chips or milestone diamonds. A chip moves by pointer drag, a keyboard lift or move(), each raising one cancelable suiMove. An agenda view lists a week by day, and a narrow week draws as the agenda, so a phone gets rows it can read.

Usage

<sui-calendar aria-label="Launch calendar" date="2026-09-11" today-date="2026-09-11"></sui-calendar> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-calendar"); el.events = [ { id: "WS-103", date: "2026-09-08", title: "Migrate the billing webhooks", subtitle: "WS-103", priority: "high", owner: "Ana Ruiz" }, { id: "WS-104", date: "2026-09-09", title: "Fix the export timeout", subtitle: "WS-104", priority: "urgent", owner: "Mira Okafor" }, { id: "WS-101", date: "2026-09-11", title: "Write the release notes", subtitle: "WS-101", priority: "medium", owner: "Mira Okafor" }, { id: "WS-102", date: "2026-09-11", title: "Rotate the signing keys", subtitle: "WS-102", severity: "attention", priority: "high", owner: "Teo Lindqvist" }, { id: "WS-108", date: "2026-09-14", title: "Rename the workspace settings", subtitle: "WS-108", priority: "low", owner: "Ana Ruiz" }, { id: "WS-111", date: "2026-09-15", title: "Review the onboarding copy", subtitle: "WS-111", priority: "medium", owner: "Ana Ruiz" }, { id: "WS-112", date: "2026-09-15", title: "Tune the search index", subtitle: "WS-112", priority: "high", owner: "Teo Lindqvist" }, { id: "WS-113", date: "2026-09-15", title: "Add the usage report", subtitle: "WS-113", priority: "low", owner: "Mira Okafor" }, { id: "WS-114", date: "2026-09-15", title: "Close the beta survey", subtitle: "WS-114", priority: "low", owner: "Ana Ruiz" }, { id: "WS-115", date: "2026-09-15", title: "Rotate the API tokens", subtitle: "WS-115", priority: "urgent", owner: "Teo Lindqvist" }, { id: "M-1", date: "2026-09-18", title: "Release 1.2", marker: true }, /* ... 5 more */ ]; /* SuiCalendarEvent[] */ el.addEventListener("suiDateChange", (e) => console.log(e.detail)); </script>
import { SuiCalendar } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { const events = [ { id: "WS-103", date: "2026-09-08", title: "Migrate the billing webhooks", subtitle: "WS-103", priority: "high", owner: "Ana Ruiz" }, { id: "WS-104", date: "2026-09-09", title: "Fix the export timeout", subtitle: "WS-104", priority: "urgent", owner: "Mira Okafor" }, { id: "WS-101", date: "2026-09-11", title: "Write the release notes", subtitle: "WS-101", priority: "medium", owner: "Mira Okafor" }, { id: "WS-102", date: "2026-09-11", title: "Rotate the signing keys", subtitle: "WS-102", severity: "attention", priority: "high", owner: "Teo Lindqvist" }, { id: "WS-108", date: "2026-09-14", title: "Rename the workspace settings", subtitle: "WS-108", priority: "low", owner: "Ana Ruiz" }, { id: "WS-111", date: "2026-09-15", title: "Review the onboarding copy", subtitle: "WS-111", priority: "medium", owner: "Ana Ruiz" }, { id: "WS-112", date: "2026-09-15", title: "Tune the search index", subtitle: "WS-112", priority: "high", owner: "Teo Lindqvist" }, { id: "WS-113", date: "2026-09-15", title: "Add the usage report", subtitle: "WS-113", priority: "low", owner: "Mira Okafor" }, { id: "WS-114", date: "2026-09-15", title: "Close the beta survey", subtitle: "WS-114", priority: "low", owner: "Ana Ruiz" }, { id: "WS-115", date: "2026-09-15", title: "Rotate the API tokens", subtitle: "WS-115", priority: "urgent", owner: "Teo Lindqvist" }, { id: "M-1", date: "2026-09-18", title: "Release 1.2", marker: true }, /* ... 5 more */ ]; /* SuiCalendarEvent[] */ return <SuiCalendar aria-label="Launch calendar" date="2026-09-11" todayDate="2026-09-11" events={events} onSuiDateChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiCalendar } from "@shineui/vue"; import "@shineui/core/shineui.css"; const events = [ { id: "WS-103", date: "2026-09-08", title: "Migrate the billing webhooks", subtitle: "WS-103", priority: "high", owner: "Ana Ruiz" }, { id: "WS-104", date: "2026-09-09", title: "Fix the export timeout", subtitle: "WS-104", priority: "urgent", owner: "Mira Okafor" }, { id: "WS-101", date: "2026-09-11", title: "Write the release notes", subtitle: "WS-101", priority: "medium", owner: "Mira Okafor" }, { id: "WS-102", date: "2026-09-11", title: "Rotate the signing keys", subtitle: "WS-102", severity: "attention", priority: "high", owner: "Teo Lindqvist" }, { id: "WS-108", date: "2026-09-14", title: "Rename the workspace settings", subtitle: "WS-108", priority: "low", owner: "Ana Ruiz" }, { id: "WS-111", date: "2026-09-15", title: "Review the onboarding copy", subtitle: "WS-111", priority: "medium", owner: "Ana Ruiz" }, { id: "WS-112", date: "2026-09-15", title: "Tune the search index", subtitle: "WS-112", priority: "high", owner: "Teo Lindqvist" }, { id: "WS-113", date: "2026-09-15", title: "Add the usage report", subtitle: "WS-113", priority: "low", owner: "Mira Okafor" }, { id: "WS-114", date: "2026-09-15", title: "Close the beta survey", subtitle: "WS-114", priority: "low", owner: "Ana Ruiz" }, { id: "WS-115", date: "2026-09-15", title: "Rotate the API tokens", subtitle: "WS-115", priority: "urgent", owner: "Teo Lindqvist" }, { id: "M-1", date: "2026-09-18", title: "Release 1.2", marker: true }, /* ... 5 more */ ]; // SuiCalendarEvent[] const onChange = (e) => console.log(e.detail); </script> <template> <SuiCalendar aria-label="Launch calendar" date="2026-09-11" today-date="2026-09-11" :events="events" @sui-date-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiCalendar } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiCalendar], template: `<sui-calendar aria-label="Launch calendar" today-date="2026-09-11" [(date)]="date" [events]="events"></sui-calendar>`, }) export class Example { date?: string | undefined; events = [ { id: "WS-103", date: "2026-09-08", title: "Migrate the billing webhooks", subtitle: "WS-103", priority: "high", owner: "Ana Ruiz" }, { id: "WS-104", date: "2026-09-09", title: "Fix the export timeout", subtitle: "WS-104", priority: "urgent", owner: "Mira Okafor" }, { id: "WS-101", date: "2026-09-11", title: "Write the release notes", subtitle: "WS-101", priority: "medium", owner: "Mira Okafor" }, { id: "WS-102", date: "2026-09-11", title: "Rotate the signing keys", subtitle: "WS-102", severity: "attention", priority: "high", owner: "Teo Lindqvist" }, { id: "WS-108", date: "2026-09-14", title: "Rename the workspace settings", subtitle: "WS-108", priority: "low", owner: "Ana Ruiz" }, { id: "WS-111", date: "2026-09-15", title: "Review the onboarding copy", subtitle: "WS-111", priority: "medium", owner: "Ana Ruiz" }, { id: "WS-112", date: "2026-09-15", title: "Tune the search index", subtitle: "WS-112", priority: "high", owner: "Teo Lindqvist" }, { id: "WS-113", date: "2026-09-15", title: "Add the usage report", subtitle: "WS-113", priority: "low", owner: "Mira Okafor" }, { id: "WS-114", date: "2026-09-15", title: "Close the beta survey", subtitle: "WS-114", priority: "low", owner: "Ana Ruiz" }, { id: "WS-115", date: "2026-09-15", title: "Rotate the API tokens", subtitle: "WS-115", priority: "urgent", owner: "Teo Lindqvist" }, { id: "M-1", date: "2026-09-18", title: "Release 1.2", marker: true }, /* ... 5 more */ ]; // SuiCalendarEvent[] }

Basic

A grid of six weeks with the weekday heads as column headers and one tab stop. Today sits on the brand wash, weekends on the sunken surface, the neighbouring months quiet. Drag a chip to another day, or focus one and press Space to lift it: every step is announced and one cancelable suiMove fires on the drop.

<sui-calendar id="cal-basic" aria-label="Launch calendar" date="2026-09-11" today-date="2026-09-11"></sui-calendar>
// setup(root): properties and events setup(root) { root.querySelector('#cal-basic').events = work(); }

Week view and working days

view="week" draws the anchor's week as seven tall columns with room for every chip. first-day-of-week overrides the locale, and hide-weekends drops the resting days; which days those are also comes from the locale.

<div style="display:grid;gap:16px"> <sui-calendar id="cal-week" aria-label="This week" view="week" date="2026-09-11" today-date="2026-09-11" first-day-of-week="1"></sui-calendar> <sui-calendar id="cal-workdays" aria-label="Working days" date="2026-09-11" today-date="2026-09-11" first-day-of-week="1" hide-weekends></sui-calendar> </div>
// setup(root): properties and events setup(root) { root.querySelector('#cal-week').events = work(); root.querySelector('#cal-workdays').events = work(); }

Milestones, overdue work and the ladder

marker draws a milestone as a small brand diamond and its title, so the accent stays with the page's one action. A chip dated before today carries a clock and a danger subtitle; overdue on a record overrides the date, so the closed run on 4 Sep wears none. severity paints from the status ladder.

<sui-calendar id="cal-ladder" aria-label="Release calendar" date="2026-09-11" today-date="2026-09-11"></sui-calendar>
// setup(root): properties and events setup(root) { root.querySelector('#cal-ladder').events = [ { id: 'L-1', date: '2026-09-02', title: 'Renew the domain', subtitle: 'Due 2 Sep' }, { id: 'L-2', date: '2026-09-04', title: 'Invoice run', subtitle: 'Done 4 Sep', severity: 'positive', overdue: false }, { id: 'L-3', date: '2026-09-09', title: 'Security review', subtitle: 'Due 9 Sep', severity: 'attention' }, { id: 'L-4', date: '2026-09-11', title: 'Board update', subtitle: 'WS-120', severity: 'info' }, { id: 'L-5', date: '2026-09-11', title: 'Quarterly close', subtitle: 'WS-121', severity: 'positive' }, { id: 'L-6', date: '2026-09-16', title: 'Design review', subtitle: 'WS-122', severity: 'neutral' }, { id: 'L-7', date: '2026-09-17', title: 'Archive old runbooks', subtitle: 'WS-123', disabled: true }, { id: 'L-8', date: '2026-09-18', title: 'Release 1.2', marker: true }, { id: 'L-10', date: '2026-09-23', title: 'Incident review', subtitle: 'WS-124', severity: 'critical' }, { id: 'L-9', date: '2026-09-30', title: 'Cycle 14 ends', marker: true }, ]; }

Fitting the cell

A page that gives the calendar a height shares it over six rows, and a fixed max-per-day then clips a full cell. max-per-day auto shows what fits, with the more link for the rest: two chips at 98px, three from 114px. chip-subtitle auto hides the key under a 162px cell, where a two-word title stops fitting beside it.

<div style="height:660px;display:flex;flex-direction:column"><sui-calendar id="cal-fit" aria-label="Launch calendar, fitted" date="2026-09-11" today-date="2026-09-11" max-per-day="auto" chip-subtitle="auto" style="flex:1;min-height:0;--sui-calendar-cell-min:56px"></sui-calendar></div>
// setup(root): properties and events setup(root) { root.querySelector('#cal-fit').events = work(); }

Custom chips and the more list

chipRender draws the chip content from the record; the calendar keeps the fill, the glyph, the drag and the lift. Its second argument says whether the key belongs in view, by chip-subtitle and the cell width, and whether the chip is in the day list; a key drawn as a chip-sub code takes has-sub as a built-in one does.

<sui-calendar id="cal-custom" aria-label="Launch calendar with priorities" date="2026-09-11" today-date="2026-09-11" max-per-day="2" chip-subtitle="auto"></sui-calendar>
// setup(root): properties and events setup(root) { const el = root.querySelector('#cal-custom'); el.events = work(); const doc = root.ownerDocument; el.chipRender = (task, chip) => { const box = doc.createElement('span'); box.style.cssText = 'display:flex;align-items:center;gap:5px;min-width:0'; if (task.priority) { const glyph = doc.createElement('i'); glyph.className = PRIORITY_ICON[task.priority]; glyph.setAttribute('aria-hidden', 'true'); glyph.style.cssText = `font-size:9px;flex-shrink:0;color:${task.priority === 'urgent' ? 'var(--danger)' : 'var(--ink-muted)'}`; box.append(glyph); } if (!task.marker && chip.subtitle) { const id = doc.createElement('code'); id.className = 'chip-sub'; id.style.cssText = 'font:460 10px var(--font-mono);flex-shrink:0'; id.textContent = task.id; box.append(id); } else if (!task.marker) { const hidden = doc.createElement('span'); hidden.style.cssText = 'position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap'; hidden.textContent = `${task.id}, `; box.append(hidden); } const title = doc.createElement('span'); title.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'; title.textContent = task.title; box.append(title); return box; }; }

Agenda

view="agenda" lists the week by day, skipping empty days. A row leads with the severity glyph or a milestone flag and says overdue in red. agendaRender returns parts to draw in place of the built-in ones: here the owner joins the key, the priority sits at the end, and a priority mark fills an empty disc.

<sui-calendar id="cal-agenda" aria-label="This week, with owners" view="agenda" date="2026-09-11" today-date="2026-09-11" first-day-of-week="1" style="max-width:640px"></sui-calendar>
// setup(root): properties and events setup(root) { const el = root.querySelector('#cal-agenda'); el.events = work(); el.agendaRender = (task) => { if (task.marker) return undefined; let mark; if (!task.severity) { mark = root.ownerDocument.createElement('i'); mark.className = PRIORITY_ICON[task.priority]; if (task.priority === 'urgent') mark.style.color = 'var(--danger)'; } return { detail: `${task.subtitle} · ${task.owner}`, meta: PRIORITY_LABEL[task.priority], mark }; }; }

A week on a phone

Seven columns on a phone are too narrow for a title. Below breakpoint (640px by default) of its own width, the week draws as the agenda; view stays week and the grid returns when the calendar widens. breakpoint="0" keeps the week grid at every width. The month keeps its grid.

<div style="max-width:390px"><sui-calendar id="cal-phone" aria-label="This week on a phone" view="week" date="2026-09-11" today-date="2026-09-11" first-day-of-week="1"></sui-calendar></div>
// setup(root): properties and events setup(root) { root.querySelector('#cal-phone').events = work(); }

Loading, without drag and without its toolbar

loading draws shimmer rows in the cells. no-drag removes the pointer drag for a touch surface where a long press means something else; the keyboard lift and move() remain, so drag is never the only path. hide-toolbar hands navigation to the page, which calls previous(), today() and next().

Previous Today Next September 2026
<div style="display:grid;gap:16px"> <sui-calendar id="cal-loading" aria-label="Loading calendar" loading date="2026-09-11" today-date="2026-09-11" view="week"></sui-calendar> <div style="display:grid;gap:10px"> <div style="display:flex;align-items:center;gap:10px"> <sui-button id="cal-prev" variant="outline" size="sm">Previous</sui-button> <sui-button id="cal-today" variant="outline" size="sm">Today</sui-button> <sui-button id="cal-next" variant="outline" size="sm">Next</sui-button> <span id="cal-period" style="font:650 13px var(--font-sans);color:var(--ink)">September 2026</span> </div> <sui-calendar id="cal-static" aria-label="Calendar without drag" no-drag hide-toolbar date="2026-09-11" today-date="2026-09-11"></sui-calendar> </div> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#cal-static'); const period = root.querySelector('#cal-period'); el.events = work(); root.querySelector('#cal-prev').addEventListener('click', () => el.previous()); root.querySelector('#cal-today').addEventListener('click', () => el.today()); root.querySelector('#cal-next').addEventListener('click', () => el.next()); el.addEventListener('suiDateChange', (e) => { const d = new Date(e.detail + 'T00:00'); period.textContent = d.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); }); }

Events

suiMove fires once per move from a drop, a keyboard lift or move(), with the record and both days, and is cancelable: here a Shipped task is refused any new date. suiDayClick carries a day, suiEventClick an opened chip, suiDateChange the anchor, suiViewChange the view, and suiOverflow the day behind a more link.

Move WS-101 to 18 Sep Nothing happened yet.
<div style="display:grid;gap:10px"> <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap"> <sui-button id="cal-move" variant="outline" size="sm">Move WS-101 to 18 Sep</sui-button> <code id="cal-log" style="font:460 12px/1.6 var(--font-sans);color:var(--ink-muted)">Nothing happened yet.</code> </div> <sui-calendar id="cal-events" aria-label="Launch calendar with a log" date="2026-09-11" today-date="2026-09-11"></sui-calendar> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#cal-events'); const log = root.querySelector('#cal-log'); el.events = work(); el.addEventListener('suiMove', (e) => { const { id, from, to } = e.detail; const task = el.events.find((t) => t.id === id); if (task.severity === 'positive') { e.preventDefault(); log.textContent = `Refused: ${id} has shipped and keeps ${from}.`; return; } log.textContent = `${id} moved from ${from} to ${to}.`; }); el.addEventListener('suiDayClick', (e) => { log.textContent = `Day ${e.detail.date} clicked.`; }); el.addEventListener('suiEventClick', (e) => { log.textContent = `Opened ${e.detail.id}: ${e.detail.event.title}.`; }); el.addEventListener('suiDateChange', (e) => { log.textContent = `Showing the ${el.view} around ${e.detail}.`; }); el.addEventListener('suiViewChange', (e) => { log.textContent = `Switched to the ${e.detail} view.`; }); el.addEventListener('suiOverflow', (e) => { log.textContent = `${e.detail.events.length} records on ${e.detail.date}; the list opens.`; }); root.querySelector('#cal-move').addEventListener('click', async () => { const moved = await el.move('WS-101', '2026-09-18'); if (!moved) log.textContent = 'WS-101 is already on 18 Sep.'; }); }

Keyboard

KeyAction
TabSteps through the toolbar, then to the one tab stop: the current day cell, chip or more link, or the current agenda row.
ArrowRight ArrowLeftMoves to the next or previous day; past the edge of the grid the month or week turns. With a chip lifted, carries it one day.
ArrowDown ArrowUpMoves down a day: its cell, its chips, its more link, then the same weekday next week. With a chip lifted, carries it one week.
Home EndMoves to the first or last day of the week. With a chip lifted, carries it there.
PageUp PageDownMoves a month, or a week in the week view; with Shift, a year. With a chip lifted, carries it the same distance.
Enter SpaceOn a day cell, raises suiDayClick for that date.
SpaceOn a chip, lifts it; on a lifted chip, drops it and raises one cancelable suiMove.
EnterOn a chip, opens it (suiEventClick); on a lifted chip, drops it.
EscapeCancels a lift and returns the chip to its day, or closes the more list.
ArrowDown ArrowUp Enter Space EscapeIn the more list: move between its chips, open one, lift one into the grid, or close the list.
ArrowDown ArrowUp Home EndIn the agenda: move to the next or previous record, across the day headings, or to the first or last record of the week.
Enter SpaceOn an agenda row, opens the record (suiEventClick). The agenda has no lift.

API

<sui-calendar>

Properties

PropertyAttributeTypeDefaultDescription
agendaRenderproperty only((event: SuiCalendarEvent, row: SuiCalendarAgendaContext) => SuiRenderResult | SuiCalendarAgendaParts) | undefinedDraws an agenda row's parts (event, row) => { mark?, title?, detail?, meta? }, so a row can carry what a chip has no room for, an owner on the second line or a priority at the end. Each part is a string or an element and takes the row's own place and look; a part left out keeps the built-in one. The row still adds "Milestone" and "overdue" after detail, and keeps its focus, its keys and its click. Return a string or an element instead to draw the whole text column yourself: the row keeps its lead disc and tells a reader the date, the severity and whether the record is overdue. Return nothing to keep the built-in row. row gives the day, whether it is today and whether the record is overdue. It is called on every draw, so return a fresh node each time. A callback that throws keeps the built-in row, and the error is logged once. Property, not attribute.
breakpointbreakpointnumber640Width of the calendar in px under which the week view draws as the agenda, measured by the calendar's own observer, so a calendar in a narrow column switches whatever the screen is. Seven columns there are too narrow for a title. view keeps week, so the grid comes back when the calendar widens, and the view switch offers Month and Agenda only. Zero keeps the week grid at every width. The month view keeps its grid at every width.
chipRenderproperty only((event: SuiCalendarEvent, chip: SuiCalendarChipContext) => SuiRenderResult) | undefinedDraws a chip's content (event, chip) => string | HTMLElement; strings render as text. The chip keeps its fill, its overdue clock, its drag and its lift; the clock is not a glyph a priority icon would use, so an urgent task that is overdue shows both. Paint a mark you draw, an urgent priority say, in var(--sui-calendar-mark): the danger ink on a plain chip and the chip's own ink on a toned one, which is what the clock takes, so a red mark never sits on a green, gold or navy chip. In the week view the content is the column after the glyph, where a long title wraps. chip says whether the subtitle belongs in view here, by chipSubtitle and the cell width, and whether the chip is in the day list. Mark the subtitle you draw with the class chip-sub and the chip takes has-sub, as a built-in chip does. It is called on every draw, so return a fresh node each time. A callback that throws costs only its chip, which draws the built-in text, and the error is logged once. Property, not attribute.
chipSubtitlechip-subtitle"always" | "auto" | "never"'always'Whether a chip shows its subtitle. auto drops it from view in a cell narrower than 162px, the width under which a two-word title no longer fits beside a key and the overdue glyph, and keeps it for a reader; never drops it at every width. The overdue glyph stays either way, since it is the chip's one status mark. The day list under a "more" link is wide enough and shows the subtitle unless never. A custom chip is told the decision through chipRender.
datedatestringstring | undefinedThe anchor, ISO YYYY-MM-DD: the month or week on show is the one that holds it. Defaults to today when unset. Mutable; next(), previous(), today() and the keyboard write it and suiDateChange fires. A host write moves the grid silently. A date, not a state, so it does not reflect: read the property.
emptyMessageempty-messagestringstring | undefinedText shown under the toolbar when no record falls in the period on show; the grid is still drawn, and the agenda has no rows to draw. The calendar filters nothing, so it carries no emptyFilterMessage.
eventsproperty onlySuiCalendarEvent[][]The records, { id, date, title, subtitle?, severity?, marker?, overdue?, disabled? }. Mutable: a completed move writes the record back with its new date, so a host that keeps its own copy updates it in suiMove. A record whose date does not parse is not drawn. Property, not attribute.
firstDayOfWeekfirst-day-of-weeknumbernumber | undefinedFirst column of the grid, 0 Sunday to 6 Saturday. Unset, it comes from locale: Sunday in the United States, Monday in Europe, Saturday in the Gulf.
hideToolbarhide-toolbarbooleanfalseHides the calendar's own toolbar (the period, previous, Today, next and the view switch), for a page that draws its own header and drives next(), previous() and today(). The period stays in a polite live region. Reflected.
hideWeekendshide-weekendsbooleanfalseLeaves the weekend columns out of the grid and the weekend's records out of the agenda, for a working-week calendar. Which days rest comes from locale. Reflected.
loadingloadingbooleanfalseDraws shimmer rows in the cells instead of the chips, and in the agenda instead of its rows. Reflected, so a page can style the wait.
localelocalestringstring | undefinedBCP 47 tag for the weekday heads, the month and week headings, the agenda's day headings, the day names the grid announces, the first day and the weekend. Unset, the page's locale (setLocale() or <html lang>), else en-US; never the browser's, so a screen reads the same on every machine.
maxPerDaymax-per-daySuiCalendarChipCap"auto" | number3Chips a month cell shows before it folds the rest behind a "more" link. auto measures the cell and shows what fits its height, with the link for the rest, so a calendar the page has given a height never clips a chip; until the cell has been measured it shows three. The week view has the height and shows every chip.
noDragno-dragbooleanfalseTurns off the pointer drag between days. The keyboard lift and move() remain, so drag is never the only path. The agenda has no drag or lift; move() works in every view. Reflected.
todayDatetoday-datestringstring | undefinedPins "today" (the marked cell, the Today button and the overdue rule) for demos and tests, e.g. "2026-09-11".
viewviewSuiCalendarView"agenda" | "month" | "week"'month'month draws six weeks around the anchor; week draws the anchor's week as seven tall day columns; agenda lists the anchor's week as rows under a heading for each day that holds records, and steps a week at a time as week does. Under breakpoint a week draws as the agenda and keeps its value here. Mutable and reflected; the toolbar switches it and suiViewChange fires.

Events

EventDetailReactVueDescription
suiDateChangestringonSuiDateChange@sui-date-changeFires after the calendar moves its own anchor, from the toolbar, the methods or the keyboard; detail is the new date, ISO. A host write to date does not echo. A page that loads the records on show listens to this and suiViewChange. Does not bubble.
suiDayClickSuiCalendarDayDetailonSuiDayClick@sui-day-clickFires on a click, Enter or Space on a day cell away from its chips; detail is { date }.
suiEventClickSuiCalendarEventClickDetailonSuiEventClick@sui-event-clickFires on a click or Enter on a chip that is not lifted, and on a click, Enter or Space on an agenda row; detail is { id, event }.
suiMoveSuiCalendarMoveDetailonSuiMove@sui-moveFires before a record moves, from a drop, a keyboard lift or move() alike; detail is { id, from, to } with both days ISO. Cancelable: preventDefault() refuses the move and the chip stays where it was. Otherwise the calendar writes events and moves the chip.
suiOverflowSuiCalendarOverflowDetailonSuiOverflow@sui-overflowFires before the "more" link opens the calendar's own list of a day's records; detail is { date, events }. Cancelable: preventDefault() keeps that list closed so the host can open its own surface.
suiViewChangeSuiCalendarView"agenda" | "month" | "week"onSuiViewChange@sui-view-changeFires after the toolbar switches the view; detail is the new view. A week drawn as the agenda under breakpoint keeps week, so a change of width does not fire it, and neither does a host write. Does not bubble.

Methods

MethodSignatureDescription
move()move(id: SuiKey, to: string) => Promise<boolean>Moves a record to the day to (ISO or any date string) through the same cancelable suiMove a drop raises. Resolves true when it moved, false when the id or the day is unknown, the record is disabled or already there, or a listener refused it.
next()next() => Promise<void>Moves the anchor a month or a week forward, by the view, and fires suiDateChange.
previous()previous() => Promise<void>Moves the anchor a month or a week back, by the view, and fires suiDateChange.
setFocus()setFocus() => Promise<void>Moves keyboard focus to the grid's tab stop, the current day cell, chip or "more" link, or to the agenda's current row.
today()today() => Promise<void>Moves the anchor to today and fires suiDateChange; the tab stop lands on today's cell.

Types

SuiCalendarAgendaContext interfaceWhat agendaRender is told about the agenda row it is drawing.
datestringThe day the row sits under, ISO YYYY-MM-DD.
todaybooleanTrue for a row under today's heading.
overduebooleanWhether the row counts the record as overdue: its own overdue, else a date before today. A row drawn from parts says "overdue" itself; a row you draw whole should show it.
SuiCalendarAgendaParts interfaceThe parts of an agenda row that agendaRender can replace. Each one is a string or an element. A part left out keeps the built-in one; null or '' leaves its place empty.
mark?SuiRenderResultDrawn in the lead disc in place of the severity glyph, the milestone flag or the dot. The disc keeps the severity's tint, or the neutral one without a severity.
title?SuiRenderResultThe first line. Built in: title.
detail?SuiRenderResultThe second line. Built in: subtitle. The row adds "Milestone" and "overdue" after it.
meta?SuiRenderResultShort text at the end of the first line: a priority, a time. Built in: none.
SuiCalendarChipCap typeHow many chips a month cell shows: a number, or auto to fit the cell's height.
No members.
SuiCalendarChipContext interfaceWhat chipRender is told about the chip it is drawing.
subtitlebooleanWhether the subtitle belongs in view here, the decision the built-in chip makes from chipSubtitle and the cell width: true under always, under auto in a cell at least 162px wide, and in the day list unless never. Draw the key when it is true; when it is false leave it out, or keep it for a reader as visually hidden text, as the built-in chip does.
inPanelbooleanTrue when the chip is drawn in the day list under a "more" link rather than in its cell. The same record is drawn in both at once, so return a separate node for each.
SuiCalendarDayDetail interfacesuiDayClick detail: the day, ISO.
datestring
SuiCalendarEvent interfaceOne record on the grid. The default chip draws subtitle then title; marker draws a milestone diamond instead of a chip; an agenda row draws title over subtitle. Any other field is the host's to read in chipRender and agendaRender.
idSuiKeyIdentity, unique in events, kept as given: a numeric id stays a number.
datestringThe day it sits on, as ISO YYYY-MM-DD (any string new Date accepts also works).
titlestring
subtitle?stringShort text before the title: a key, a time, a due phrase. A plain overdue chip paints it in the danger ink. In the agenda it is the row's second line.
severity?SuiSeverityPaints the chip from the shared status ladder; without it the chip is a plain surface. In the agenda it picks the row's lead glyph and tint, and a reader hears its name.
marker?booleanDraws a small diamond and the title in place of a chip, for a milestone. An agenda row leads with a flag and says "Milestone".
overdue?booleanWhether the chip wears the overdue glyph and reads "Overdue" to a reader, with its subtitle in the danger ink; an agenda row says "overdue" on its second line, in the same ink. Unset, a record dated before today is overdue; set it to say so yourself, so a closed record dated last week is not.
disabled?booleanInert: no focus, no drag, no lift, no click. move() refuses it too.
SuiCalendarEventClickDetail interfacesuiEventClick detail: the record's id and the record.
idSuiKey
eventSuiCalendarEvent
SuiCalendarMoveDetail interfacesuiMove detail: one record, the day it leaves and the day it lands on, both ISO.
idSuiKey
fromstring
tostring
SuiCalendarOverflowDetail interfacesuiOverflow detail: the day and every record on it, in events order.
datestring
eventsSuiCalendarEvent[]
SuiCalendarView typeThe two grids and the list: a month of six weeks, one week as seven day columns, or the agenda, the same week as a list with a heading for each day that holds records.
No members.
SuiKey typeA record's or an item's key wherever the library identifies one: a row, a tree node, a list item, a selected value. Numbers stay numbers, so a numeric id round-trips through selection, the events and a framework binding without turning into a string.
No members.
SuiRenderResult type
No members.
SuiSeverity type
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

Color36

--accentthe one chart highlight: the flagged series, down candles, the peak cell--accent-softorange 18% on white, warning fills, highlights--accent-soft-ink--bordercard + table outer borders--border-strongcontrol borders, emphasized rules--brand--brand-contrasttext and glyphs sitting on solid brand fills--brand-softnavy 10% on white, quiet chips, selected rows--brand-soft-ink--canvasapp background--control-borderthe resting edge of every field and control: 3:1 on its grounds--danger--danger-fill-edge--danger-soft--disabled-bg--disabled-ink--focus-ring--hairlinerow separators--ink--ink-faintmetadata beside body ink: 4.8:1 on the page grounds (5.4:1 in dark)--ink-mutedsecondary text: 7:1 on the page grounds and 1.5 times faint--ink-placeholdera field's placeholder, not a text tier: 4.5:1 on the page grounds--status-attention-bg--status-attention-inkOpen, Pending, In transit--status-critical-bg--status-critical-inkOverdue, Deleted--status-info-ink--status-neutral-bg--status-neutral-borderDraft, Signed in--status-neutral-ink--status-positive-bg--status-positive-inkPaid, Cleared, Active--surfacecards, tables, inputs--surface-hovera hovered row or quiet control on any ground: the ink, translucent--surface-raisedmenus, popovers, toasts: the card in light, a step above it in dark--surface-sunkentable headers, wells

Typography1

Ag--font-sans

Spacing and size1

800--z-overlay

Radius5

--radius-lgcards, tables, panels, dialogs--radius-mdALL controls: inputs, buttons, selects--radius-pillstatus pills, tabs-as-pills, company chip--radius-smlegend keys, small chips--radius-xscheckboxes, mini swatches

Elevation3

--focus-shadow--shadow-popdropdowns, popovers, toasts--shadow-raisedhover-lift, dragged rows

Motion3

--duration-fasthovers, presses, every exit--duration-overlayevery entrance: palette, popover, dialog, drawer, toast, menu, select list--ease-out

Every name above is listed with its value and its role in Foundations.

See also