Theming
Calendar color tokens, dark mode, and the event color palette.
The packages don't ship any CSS. The styled parts in the registry read their colors and sizes from
CSS variables defined in calendar-theme.css. Copy that file into your app as described in
UI source, then change the variables to restyle the
calendar.
Tokens
calendar-theme.css defines the standard shadcn/ui tokens (--background, --primary,
--border, and so on) plus these calendar tokens:
| Variable | Used for | Tailwind class |
|---|---|---|
--calendar-surface | Behind sticky headers, between events | bg-calendar-surface |
--calendar-grid | Lines between days and hours | border-calendar-grid |
--calendar-axis | Hour and weekday labels | text-calendar-axis |
--calendar-today | Today's date marker | bg-calendar-today |
--calendar-today-foreground | Text on today's marker | text-calendar-today-foreground |
--calendar-now | The current-time line | bg-calendar-now |
--calendar-selection | Selected days and time ranges | bg-calendar-selection |
--calendar-weekend | Weekend background | bg-calendar-weekend |
--calendar-event | Event background | bg-calendar-event |
--calendar-event-foreground | Event text | text-calendar-event-foreground |
--calendar-event-border | Event accent edge | border-calendar-event-border |
--calendar-hour-height | Height of one hour in time grids (3rem) | h-(--calendar-hour-height) |
The color tokens work with any Tailwind color utility, so text-calendar-today and
ring-calendar-now work too. Most calendar tokens derive from the shadcn tokens: --calendar-grid
is your --border, and the default event colors mix your --primary into --background.
Changing tokens
Every token is declared inside @layer base, so a plain rule in your own CSS wins without
!important. Set the light values on :root and the dark values on .dark:
:root {
--calendar-today: oklch(0.55 0.17 150);
--calendar-hour-height: 4rem;
}
.dark {
--calendar-today: oklch(0.72 0.15 150);
}If your app already defines the shadcn tokens, the calendar follows them automatically.
--calendar-surface is unset by default and falls back to --background. Set it on the
calendar's container when that container has its own background, such as a card, so sticky
headers and gutters match it:
<main className="bg-card [--calendar-surface:var(--card)]">Dark mode
The theme switches when an ancestor of the calendar has the dark class, which is how shadcn/ui
and next-themes work. shadcn apps already define the matching Tailwind dark variant; the CSS step
in UI source shows the line to add if yours doesn't.
Event colors
The theme has a palette of nine event colors: blue, green, red, orange, yellow,
purple, pink, teal, and gray. Put one in a data-color attribute and that element's three
event tokens switch to the color, in both light and dark mode:
<div data-color="green" class="bg-calendar-event text-calendar-event-foreground">Standup</div>EventChip, the event button in registry/calendar, sets data-color from its color prop. The
names are exported from event-colors.ts as calendarEventColors, with the type
CalendarEventColor.
Choosing a color per event
Every built-in view takes an optional getEventColor option: a function from your event record to
a color name. Pass it when you create the view, as the Quick start does, or
per calendar with the viewOptions prop on Root:
const getEventColor = (meeting: Meeting) => (meeting.title === "Standup" ? "green" : "blue");
// On Root:
viewOptions={{ month: { getEventColor }, agenda: { getEventColor } }}Define the function outside your component, so it keeps its identity between renders. See
Performance. Without getEventColor,
events use the theme's default event tokens.
To support it in your own view, add it to the view's options and pass the result to EventChip:
import {
defineCalendarView,
plainDateInterval,
type CalendarViewProjectionInput,
} from "@moltenship/calendar";
import { EventChip } from "@/components/calendar/event-chip";
import type { CalendarEventColor } from "@/components/calendar/event-colors";
import type { Meeting } from "./quick-start";
interface UpcomingOptions {
readonly getEventColor?: (meeting: Meeting) => CalendarEventColor;
}
export const upcomingView = defineCalendarView({
name: "upcoming",
defaultOptions: {},
strategy: {
getRange: (anchor) => plainDateInterval(anchor, anchor.add({ days: 7 })),
navigate: (anchor, delta) => anchor.add({ weeks: delta }),
getTitle: (range, { locale }) => range.start.toLocaleString(locale, { dateStyle: "medium" }),
project: ({ snapshot, instantRange }: CalendarViewProjectionInput<UpcomingOptions, Meeting>) =>
snapshot.queryTimed(instantRange),
},
Renderer: ({ projection, options }) => (
<ul>
{projection.map(({ event }) => (
<li key={event.id}>
<EventChip color={options.getEventColor?.(event)}>{event.title}</EventChip>
</li>
))}
</ul>
),
});Adding a color
Each palette entry only sets a hue and a chroma; the [data-color] rule turns them into the three
event tokens for light and dark mode. To add a color, add its name to calendarEventColors in
event-colors.ts and give it a hue and chroma in your CSS:
[data-color="brand"] {
--calendar-event-hue: 285;
--calendar-event-chroma: 0.2;
}Last updated on