Moltenship Time

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:

VariableUsed forTailwind class
--calendar-surfaceBehind sticky headers, between eventsbg-calendar-surface
--calendar-gridLines between days and hoursborder-calendar-grid
--calendar-axisHour and weekday labelstext-calendar-axis
--calendar-todayToday's date markerbg-calendar-today
--calendar-today-foregroundText on today's markertext-calendar-today-foreground
--calendar-nowThe current-time linebg-calendar-now
--calendar-selectionSelected days and time rangesbg-calendar-selection
--calendar-weekendWeekend backgroundbg-calendar-weekend
--calendar-eventEvent backgroundbg-calendar-event
--calendar-event-foregroundEvent texttext-calendar-event-foreground
--calendar-event-borderEvent accent edgeborder-calendar-event-border
--calendar-hour-heightHeight 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:

globals.css
: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:

upcoming-view.tsx
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:

globals.css
[data-color="brand"] {
  --calendar-event-hue: 285;
  --calendar-event-chroma: 0.2;
}
Edit on GitHub

Last updated on

On this page