Calendar
An event calendar with month, week, day, year, and agenda views. It handles recurring events, date-range selection, and per-day constraints.
Playground
<script lang="ts">
import { Calendar } from '@urbicon-ui/blocks';
const events = [
{
id: '1',
title: 'Sprint Planning',
start: new Date('2026-03-02T00:00:00.000Z'),
categoryId: 'meeting',
description: 'Sprint 14 planning'
},
{
id: '2',
title: 'Design Review',
start: new Date('2026-03-05T00:00:00.000Z'),
categoryId: 'meeting'
},
{
id: '3',
title: 'Release v3.0',
start: new Date('2026-03-07T00:00:00.000Z'),
categoryId: 'deadline'
},
{
id: '4',
title: 'Deep Work',
start: new Date('2026-03-09T10:00:00.000Z'),
end: new Date('2026-03-09T12:00:00.000Z'),
allDay: false,
categoryId: 'focus'
},
{
id: '5',
title: 'Team Lunch',
start: new Date('2026-03-10T00:00:00.000Z'),
categoryId: 'social'
},
{
id: '6',
title: 'Standup',
start: new Date('2026-03-10T09:00:00.000Z'),
end: new Date('2026-03-10T09:30:00.000Z'),
allDay: false,
categoryId: 'meeting'
},
{
id: '7',
title: '1:1 Sarah',
start: new Date('2026-03-11T14:00:00.000Z'),
end: new Date('2026-03-11T15:00:00.000Z'),
allDay: false,
categoryId: 'meeting'
},
{ id: '8', title: 'Retro', start: new Date('2026-03-12T00:00:00.000Z'), categoryId: 'meeting' },
{
id: '9',
title: 'Code Freeze',
start: new Date('2026-03-14T00:00:00.000Z'),
categoryId: 'deadline'
},
{
id: '10',
title: 'Conference',
start: new Date('2026-03-16T00:00:00.000Z'),
end: new Date('2026-03-18T00:00:00.000Z'),
categoryId: 'social',
description: 'SvelteConf, Berlin'
},
{
id: '11',
title: 'Sprint Review',
start: new Date('2026-03-19T00:00:00.000Z'),
categoryId: 'meeting'
},
{
id: '12',
title: 'Hackathon',
start: new Date('2026-03-23T00:00:00.000Z'),
end: new Date('2026-03-24T00:00:00.000Z'),
categoryId: 'social'
},
{
id: '13',
title: 'Quarterly report',
start: new Date('2026-03-25T00:00:00.000Z'),
categoryId: 'deadline'
},
{
id: '14',
title: 'Sprint Planning',
start: new Date('2026-03-26T00:00:00.000Z'),
categoryId: 'meeting'
},
{
id: '15',
title: 'Go Live',
start: new Date('2026-03-31T00:00:00.000Z'),
categoryId: 'deadline'
}
];
const categories = [
{ id: 'meeting', label: 'Meeting', color: '#8b5cf6' },
{ id: 'deadline', label: 'Deadline', color: '#ef4444' },
{ id: 'focus', label: 'Focus time', color: '#3b82f6' },
{ id: 'social', label: 'Social', color: '#06b6d4' }
];
</script>
<Calendar
{events}
{categories}
/>01 Examples
Events in a month view
import type { CalendarEvent, CalendarEventCategory } from '@urbicon-ui/blocks';
const events: CalendarEvent[] = [
// One day: start only.
{ id: '1', title: 'Code freeze', start: new Date(2026, 2, 18), categoryId: 'deadline' },
// A span: add end, and the event draws across every day between.
{ id: '2', title: 'Sprint 14', start: new Date(2026, 2, 9), end: new Date(2026, 2, 20) },
// A series: one object plus a rule, expanded by the calendar.
// byDay is 0-6 (Sunday-Saturday): on `weekly` it generates one occurrence
// per listed day, on `daily` it filters the days the interval produces.
// interval repeats every n periods; until ends the series (inclusive).
{
id: '3',
title: 'Standup',
start: new Date(2026, 2, 2),
recurrence: {
frequency: 'weekly',
byDay: [1, 2, 3, 4, 5],
until: new Date(2026, 2, 31)
}
},
{
id: '4',
title: 'Sprint review',
start: new Date(2026, 2, 6),
recurrence: { frequency: 'weekly', interval: 2, byDay: [5] }
}
];
// A category colours its events' dots and labels the legend. `color` takes
// any CSS colour or a Tailwind class; `categoryId` on an event points here.
const categories: CalendarEventCategory[] = [
{ id: 'deadline', label: 'Deadline', color: '#ef4444' }
];
<Calendar {events} {categories} showLegend showWeekNumbers />Week, year and agenda views
agendaDays days from the reference date and its arrows step that whole window, so a one-day list is agendaDays={1} beside a defaultDate. The snippet shows only what differs between the three.<!-- One component, one prop. The default view is "month". -->
<Calendar
view="week"
{events}
{categories}
timeGridStartHour={8}
timeGridEndHour={18}
timeGridInterval={30}
/>
<Calendar view="year" views={['month', 'year']} {events} {categories} defaultYear={2026} />
<!-- The agenda counts its days from the reference date, so this lists three
weeks starting on 10 March — and agendaDays={1} beside a defaultDate is
one day's list. -->
<Calendar
view="agenda"
{events}
{categories}
agendaDays={21}
defaultDate={new Date(2026, 2, 10)}
/>Constrained selection
<!-- Two clicks pick a range; minDate/maxDate cap what you can reach and pick. -->
<Calendar
selectionMode="range"
bind:value
variant="bordered"
minDate={new Date(2026, 2, 1)}
maxDate={new Date(2026, 3, 30)}
/>
<!-- disabledDates locks named days, isDateDisabled locks a rule. -->
<Calendar
bind:value={selectedDate}
variant="bordered"
{minDate}
{maxDate}
disabledDates={holidays}
isDateDisabled={(d) => d.getDay() === 0 || d.getDay() === 6}
/>Custom day cells – heatmap
<!-- urbicon-ignore raw-tailwind-color — the emerald ramp IS the demo: a
contribution-heatmap day cell, where four fixed opacity steps of one hue encode
the count. A semantic token has one value and cannot express a scale. -->
<script lang="ts">
import { Calendar } from '@urbicon-ui/blocks';
import type { CalendarEvent, DayCellContext } from '@urbicon-ui/blocks';
// Realistic activity data spread across the month
const events: CalendarEvent[] = [
{ id: '1', title: 'Standup', start: new Date(2026, 2, 2) },
{ id: '2', title: 'Code Review', start: new Date(2026, 2, 3) },
{ id: '3', title: 'Standup', start: new Date(2026, 2, 3) },
{ id: '4', title: 'Standup', start: new Date(2026, 2, 4) },
{ id: '5', title: 'Planning', start: new Date(2026, 2, 5) },
{ id: '6', title: 'Standup', start: new Date(2026, 2, 5) },
{ id: '7', title: 'Review', start: new Date(2026, 2, 5) },
{ id: '8', title: 'Standup', start: new Date(2026, 2, 9) },
{ id: '9', title: 'Deep Work', start: new Date(2026, 2, 9) },
{ id: '10', title: 'Standup', start: new Date(2026, 2, 10) },
{ id: '11', title: 'Release', start: new Date(2026, 2, 10) },
{ id: '12', title: 'Hotfix', start: new Date(2026, 2, 10) },
{ id: '13', title: 'Monitoring', start: new Date(2026, 2, 10) },
{ id: '14', title: 'Standup', start: new Date(2026, 2, 11) },
{ id: '15', title: 'Standup', start: new Date(2026, 2, 12) },
{ id: '16', title: 'Retro', start: new Date(2026, 2, 12) },
{ id: '17', title: 'Standup', start: new Date(2026, 2, 16) },
{ id: '18', title: 'Sprint Review', start: new Date(2026, 2, 16) },
{ id: '19', title: 'Demo', start: new Date(2026, 2, 16) },
{ id: '20', title: 'Standup', start: new Date(2026, 2, 17) },
{ id: '21', title: 'Hackathon', start: new Date(2026, 2, 19) },
{ id: '22', title: 'Hackathon', start: new Date(2026, 2, 19) },
{ id: '23', title: 'Hackathon', start: new Date(2026, 2, 19) },
{ id: '24', title: 'Demo', start: new Date(2026, 2, 19) },
{ id: '25', title: 'Hackathon', start: new Date(2026, 2, 19) },
{ id: '26', title: 'Standup', start: new Date(2026, 2, 20) },
{ id: '27', title: 'Standup', start: new Date(2026, 2, 23) },
{ id: '28', title: 'Planning', start: new Date(2026, 2, 23) },
{ id: '29', title: 'Standup', start: new Date(2026, 2, 24) },
{ id: '30', title: 'Review', start: new Date(2026, 2, 25) },
{ id: '31', title: 'Standup', start: new Date(2026, 2, 25) },
{ id: '32', title: 'Deploy', start: new Date(2026, 2, 25) },
{ id: '33', title: 'Standup', start: new Date(2026, 2, 26) },
{ id: '34', title: 'Standup', start: new Date(2026, 2, 30) },
{ id: '35', title: 'Quarterly report', start: new Date(2026, 2, 31) },
{ id: '36', title: 'Deploy', start: new Date(2026, 2, 31) },
{ id: '37', title: 'Review', start: new Date(2026, 2, 31) }
];
/** Map event count to a heatmap intensity level (0–4) */
function heatLevel(count: number): number {
if (count === 0) return 0;
if (count === 1) return 1;
if (count === 2) return 2;
if (count <= 4) return 3;
return 4;
}
const heatBg = [
'', // 0 – no bg
'bg-emerald-500/15',
'bg-emerald-500/30',
'bg-emerald-500/50',
'bg-emerald-500/75'
];
// Heatmap ink, mode-aware via the CSS light-dark() function (darker emerald in
// light mode, lighter in dark). This follows `color-scheme` natively — incl.
// system mode, where there is no `.dark` class — so it needs no `dark:` override
// (which the design linter flags and which would silently break in system mode).
const heatText = [
'text-text-primary', // 0 – no heat
'text-[color:light-dark(var(--color-emerald-700),var(--color-emerald-300))]',
'text-[color:light-dark(var(--color-emerald-800),var(--color-emerald-200))]',
'text-[color:light-dark(var(--color-emerald-900),var(--color-emerald-100))]',
'text-[color:light-dark(white,var(--color-emerald-950))]'
];
</script>
<div class="max-w-sm">
<Calendar {events} showEventList showViewSwitcher={false} defaultMonth={2} defaultYear={2026}>
{#snippet dayCell(ctx: DayCellContext)}
{@const level = heatLevel(ctx.events.length)}
<!-- A heatmap cell is read-only, so it renders as a div, not a button. -->
<div
class="flex h-10 w-full items-center justify-center rounded-md text-sm tabular-nums
{ctx.isOutsideMonth ? 'opacity-20' : ''}
{heatBg[level]}
{ctx.isToday ? 'font-black underline decoration-2 underline-offset-2' : ''}
{level > 0
? heatText[level]
: ctx.isOutsideMonth
? 'text-text-quaternary'
: 'text-text-primary'}"
>
{ctx.date.getDate()}
</div>
{/snippet}
</Calendar>
<!-- Heatmap legend -->
<div class="mt-3 flex items-center justify-end gap-1.5 px-3">
<span class="text-text-tertiary text-xs">Less</span>
{#each [0, 1, 2, 3, 4] as lvl (lvl)}
<span class="border-border-subtle size-3 rounded-sm border {heatBg[lvl] || 'bg-surface-base'}"
></span>
{/each}
<span class="text-text-tertiary text-xs">More</span>
</div>
</div>
02 Accessibility
ARIA Roles
The month grid uses role="grid" with role="row" for weeks and role="gridcell" for days. Each cell carries aria-selected, aria-disabled, and aria-current="date" for today.
Keyboard Navigation
← → move focus between days, ↑ ↓ between weeks. Home/End jump to the start/end of the week. PageUp/PageDown navigate between months. Enter/Space select the focused day. Focus rings use focus-visible: for keyboard-only visibility.
Screen Reader Labels
Every day cell has an aria-label with the full date
(e.g. "Thursday, March 12, 2026"). Navigation buttons have descriptive labels. Event dots
are aria-hidden; event details remain accessible through
the event list.
Touch & Gestures
Horizontal swiping navigates between months and days. In the week view, when the time grid
overflows sideways on a narrow screen, a horizontal drag pans the grid instead of
navigating; once it fits, swiping navigates weeks again. Swipe gestures can be disabled with swipeable={false}. Animations respect prefers-reduced-motion.
Narrow Viewports
Below phone widths the week keeps its seven columns behind a horizontal scroll with a sticky
time column, so no information is dropped. Where a single day reads better, drive view from a MediaQuery (with a false server fallback, so a prerendered page does not
flip on hydration) and pass views without week, so the switcher offers day instead.
Internationalization
All visible text and ARIA labels are localized through i18n keys. Date formatting relies on
the native Intl.DateTimeFormat with the configured locale. Weekday names, month names, and date formats
adapt automatically.
03 API Reference
62 propsProp | Type | Default | Description | |
|---|---|---|---|---|
agendaDays | number | 30 | How many days the agenda lists, counted **from the reference date** — the
day value/defaultDate anchors on, or today. So agendaDays={1} next to
a defaultDate is one day's list, and the default is roughly a month ahead
of that day rather than the calendar month it sits in.
The arrows, ArrowLeft/ArrowRight and the swipe step the whole window, so
the next list starts the day after the current one ends, and minDate/
maxDate bound the WINDOW rather than its anchor: a step clamps
span-preserving and the arrows disable once an edge is reached. Navigation
reports through onNavigate — uniformly, including at agendaDays={1},
rather than switching callbacks on a prop value; the header's month picker
still reports onMonthChange, because a month is what it picks.
The window is always exactly this many days; the RENDERING skips days with
no events, so a month-long window of one busy day is one heading, and a
window of nothing renders the empty state.
Values outside 1–366 (and non-numbers) fall back to the default and warn in
DEV — a list is not a place to accidentally walk a decade. | |
animated | boolean | true | Enable animated transitions for navigation. | |
categories | DateCategory[] | [] | Event categories for color coding and legend. | |
children | Snippet | — | Default children snippet for custom layout composition. | |
class | string | — | Extra CSS classes on the root element. | |
dayCell | Snippet<[DayCellContext]> | — | Custom snippet for rendering a day cell. | |
defaultDate | Date | — | Initial reference day the grid is anchored on, without selecting it. Use
this to open a **week** or **day** view on a specific week — defaultMonth/
defaultYear resolve to the 1st, whose week can fall mostly in the previous
month. Ignored when value is set (the selection anchors instead); takes
priority over defaultMonth/defaultYear. Read at mount only. | |
defaultMonth | number | — | Initial displayed month (0–11). Used only when value and defaultDate
are unset; when value is provided, the calendar opens on the value's
month. Best for month/year views — for week/day views prefer defaultDate.
Defaults to current month. | |
defaultYear | number | — | Initial displayed year. Used only when value is unset; when
value is provided, the calendar opens on the value's year.
Defaults to current year. | |
disabled | boolean | false | Disable the entire calendar. | |
disabledDates | Date[] | — | Specific dates that are disabled (not selectable). | |
draggable | boolean | false | Enable drag & drop to move events between dates. | |
eventItem | Snippet<[EventItemContext]> | — | Custom snippet for rendering an event item in the list-based views (agenda
and the month event list). Time-grid views (week/day) render events through
their hour grid and ignore this snippet.
Replaces the default row entirely, including the clock time an event with
allDay: false shows there — format it from event.start/event.end
yourself (formatTimeRange from @urbicon-ui/blocks/date is the same
helper the default uses). For a multi-day event the context's isStart /
isEnd say which end of the span this row is: the default prints start
on the first day, "until <end>" on the last, nothing in between, because
event.start is the same instant on every row. The chronological order of
a day is not part of the snippet's job: the calendar sorts each day before
rendering. | |
eventPopover | boolean | false | Show a rich popover on hover/focus for days with events (month view). | |
events | CalendarEvent[] | [] | Array of events to display on the calendar. | |
fixedWeeks | boolean | false | Always show 6 weeks in the grid. | |
header | Snippet<[HeaderContext]> | — | Custom snippet for the header area. | |
highlightToday | boolean | true | Visually mark today across every view — the month cell, the week column
header, the year mini-day, the agenda day header and the mini calendar.
aria-current="date" is **not** affected: it is a semantic pointer, so a
screen-reader user keeps the orientation a purely visual preference should
not take away. Neither is the time grid's current-time line, which marks the
current *time* rather than the day (see timeGridHourHeight's neighbours).
Matches Planner's prop of the same name. | |
isDateDisabled | (date: Date) => boolean | — | Function to test whether a date is disabled. | |
locale | string | 'auto' | BCP 47 locale tag for date formatting — month names, weekday names, the
header title and the clock time of timed events in the list-based views
(hour cycle, separator and padding all follow the locale, so a 12-hour
locale renders "9:05 AM"). Defaults to 'auto', which follows the active
<I18nProvider> locale, so an app that already declares its language does
not have to repeat it here. SSR-safe: the locale comes from context, so the
server and the client resolve the same tag (Intl with undefined would
follow the runtime and disagree across hydration). Falls back to the base
locale (en) when no provider is mounted. Pass an explicit tag
(e.g. 'de-DE', 'ja-JP') to override. | |
maxDate | Date | — | Latest selectable/navigable date. | |
minDate | Date | — | Earliest selectable/navigable date. | |
miniCalendarPosition | leftright | 'left' | Position of the mini calendar sidebar. | |
navPlacement variant | next | — | Controls the navPlacement behavior and appearance of the Calendar component. Available options: next. | |
onDateClick | (date: Date) => void | — | Fires when a date cell is clicked (regardless of selection change). | |
onDateCreate | (date: Date, view: CalendarViewMode) => void | — | Fires on double-click on a day cell for event creation. The consumer shows their own form. | |
onDayChange | (date: Date) => void | — | Fires when the displayed day changes (day view). | |
onEventClick | (event: CalendarEvent) => void | — | Fires when an event is clicked. | |
onEventMove | (event: CalendarEvent, newStart: Date, newEnd: Date) => void | — | Fires when an event is moved via drag & drop. | |
onEventResize | (event: CalendarEvent, newEnd: Date) => void | — | Fires when an event is resized via drag handle. | |
onMonthChange | (month: number, year: number) => void | — | Fires when the displayed month/year changes via navigation — the month and
year views, and the header's month picker in any view. The agenda's arrows
step a window of days instead of a month and report through onNavigate. | |
onNavigate | (date: Date, range: DateRange) => void | — | Fires after **any** navigation, in every view, with the new reference date
and the visible range — load data here. A **view switch** counts as one: it
changes the window (a padded month grid, seven days, agendaDays from the
anchor …) without moving the reference date, and a loader that missed it
rendered the new view against the old view's rows. The per-view callbacks
(onMonthChange / onWeekChange / onDayChange) still fire and are the
better fit when you only care about one view; this one spares you
reconstructing the window yourself — and in the agenda it is the *only*
navigation callback, whose window no per-view callback can name. The range
is view-accurate: month spans the padded cell grid (spill days included),
week/day the visible days, year 1 Jan–31 Dec, agenda agendaDays from the
reference date. Matches Planner's onNavigate. | |
onTimeSlotCreate | (start: Date, end: Date) => void | — | Fires on click on an empty time slot for event creation. Returns default 1h duration. | |
onValueChange | (value: CalendarSelection) => void | — | Fires when the selected date(s) change. | |
onViewChange | (view: CalendarViewMode) => void | — | Fires when the view mode changes — before onNavigate, which the switch
also fires because the visible window changed with it. A switch to the view
already on screen is a no-op and fires neither. | |
onWeekChange | (weekStart: Date) => void | — | Fires when the displayed week changes (week view). | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ Calendar: {...} }}>.
Prefer this over class overrides when the requested look falls outside the
semantic intent palette — presets keep hover/active/dark-mode logic coherent
and make the custom look reusable across the project. | |
resizable | boolean | false | Enable resize handles on timed events in the time grid. | |
selectionMode | singlerangemultiple | 'single' | Selection behavior. | |
showEventList | boolean | — | Whether to show the detail list when a date is selected. Auto-enabled when events are provided. | |
showLegend | boolean | — | Whether to show the built-in legend. Defaults to true when categories are provided. | |
showMiniCalendar | boolean | false | Show a mini month calendar sidebar (week/day/agenda views). | |
showOutsideDays | boolean | true | Show days from previous/next months to fill the grid. | |
showToday variant | falsetrue | — | Controls the showToday behavior and appearance of the Calendar component. Available options: false, true. | |
showViewSwitcher | boolean | true | Show the view switcher in the header. Below sm its labels condense to
their short form; the full label stays the accessible name. | |
showWeekNumbers | boolean | false | Show ISO week numbers in the left margin. | |
size | smmdlg | 'md' | Component size. | |
slotClasses | Partial<Record<CalendarSlots, string>> | — | Per-slot class overrides. | |
stacksOnNarrow variant | falsetrue | — | Controls the stacksOnNarrow behavior and appearance of the Calendar component. Available options: false, true. | |
swipeable | boolean | true | Enable swipe gestures for touch navigation. | |
timeGridEndHour | number | 20 | Last visible hour in time grid (exclusive). | |
timeGridHourHeight | number | — | Height of one hour row in the time grid, in pixels. Left unset it follows
size (sm 40 · md 48 · lg 64), which is the only reason a nine-hour day
costs 432 px of card height whether or not the consumer has it. Set a
smaller number for a compact day, a larger one for finer slots. Drives the
label column, the slot rows, the grid's min-height and the auto-scroll
to the current time, so it is a number rather than a CSS variable — the
scroll math has to read it. | |
timeGridInterval | 3060 | 60 | Time slot interval in minutes. | |
timeGridStartHour | number | 7 | First visible hour in time grid. | |
unstyled | boolean | — | Strip all default tv() classes. | |
value | CalendarSelection | — | Currently selected date(s). Supports bind:value. | |
variant | defaultborderedghost | 'default' | Visual style. | |
view | CalendarViewMode | 'month' | Active view mode. Supports bind:view.
week and day are hour grids. The week's seven columns keep at least
--blocks-calendar-day-min-width each (5 rem at size="sm", 6 rem at md,
7 rem at lg); once they no longer fit, the grid scrolls sideways instead of
shrinking the days to a stripe. The hour gutter stays pinned to the left
while it does and the day heads — with the all-day band under them — to the
top, and arrow-key navigation brings the focused day into view. Set the
property on the calendar — style="--blocks-calendar-day-min-width: 8rem" —
to trade more scrolling for wider days.
While the week is scrolling, a horizontal touch drag belongs to the
scroller: it moves the days rather than paging to the next week. A week that
fits keeps the swipe. The header arrows, ArrowLeft/ArrowRight and
bind:view are unaffected either way. On a phone the honest week is often
no week at all: bind this prop to a MediaQuery and narrow views with it
(the MediaQuery example on the component) rather than handing over seven
columns behind a scrollbar. | |
views | CalendarViewMode[] | ['month', 'week', 'day', 'year', 'agenda'] | Which views the header's switcher offers. It filters the switcher only: a
view left out of the list still renders, so narrowing the list is how a
layout hides a view it cannot serve while still choosing it itself — the
viewport-driven pairing under view. | |
weekStartsOn | 0123 +3 more | 1 | First day of the week. 0 = Sunday, 1 = Monday. | |
...CalendarVariants variant | VariantProps | — | Styling variants from CalendarVariants | |
...HTMLAttributes<HTMLDivElement> inherited | HTMLAttributes | — | HTML attributes (excluding: 'children') |
04 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
CalendarSlotName | type | helper | 0 | The canonical slot-key type: the shared CalendarContext (slotClasses map
+ createSlotHelper) and the per-view sub-components (e.g. CalendarHeader's
slot() helper) index slots by it, and the public slotClasses props below
are typed from it.
An alias, not a second list. It used to be 66 hand-written members that a
comment promised were "kept in lockstep" with CalendarSlots — nothing
checked that, and adding a slot to the config left it unreachable from every
sub-component until someone noticed the type error. | |
CalendarProps | interface | props | 0 | — | |
CalendarHeaderProps | interface | props | 0 | — | |
CalendarVariants | type | variant | 0 | — | |
CalendarSlots | type | variant | 0 | — | |
CalendarEvent | interface | helper | 1 | A single calendar event/appointment. | |
CalendarSelection | type | helper | 1 | Selection value depending on selection mode. | |
CalendarViewMode | type | helper | 2 | Available view modes for the calendar. | |
DateCategory | interface | helper | 1 | A colour bucket for a calendar event, a timeline span, and the legend row
below the grid. One type across Calendar, Planner and ResourceTimeline, so
moving categories between them needs no mapping.
color takes any CSS colour value: hex, rgb(), oklch() or a
var(--token) reference. Where a bar or event chip carries a LABEL on the
colour, the component picks black or white by measuring that value, which
only works for a value it can read. A var() or a color-mix() is resolved
by the browser, not by the component, so those fall back to white: pass a
colour dark enough to carry it, or set the label colour yourself through that
component's slotClasses. | |
DateRange | interface | helper | 0 | An inclusive start/end date pair: a selected range, a visible window, the
range onNavigate reports. One type for Calendar, Planner and
ResourceTimeline alike.
ResourceTimeline.getRange is not this type: it also accepts local date
strings ('2026-06-16'), which a selection value must not. | |
DayCellContext | interface | helper | 0 | Context passed to custom dayCell snippets. | |
EventItemContext | interface | helper | 0 | Context passed to custom eventItem snippets. | |
HeaderContext | interface | helper | 0 | Context passed to custom header snippets. | |
RecurrenceRule | interface | helper | 0 | Recurrence rule for repeating events. |
05 Installation
Import
import { Calendar, CalendarHeader, CalendarGrid } from '@urbicon-ui/blocks';
import type { CalendarEvent, DateCategory } from '@urbicon-ui/blocks';