Customizing
Tokens, the accent, dark mode, forced colors and how to override the themes' rules.
Tokens
Override any --calendar-* token on an element around the calendar, or on :root:
.schedule {
--calendar-accent: light-dark(#1f5f8b, #7cb7e0);
--calendar-accent-contrast: light-dark(#ffffff, #0b1a24);
--calendar-slot-height: 2.5rem;
}Defaults from default.css, light then dark:
| Token | Default |
|---|---|
--calendar-surface, --calendar-surface-muted | #ffffff #f4f4f5, #161618 #1f1f22 |
--calendar-text, --calendar-text-muted | #18181b #5b5b63, #f1f1f3 #a3a3ab |
--calendar-border | #e2e2e5, #323236 |
--calendar-border-strong | unset: the border mixed 15% toward the text |
--calendar-accent, --calendar-accent-contrast | #18181b #ffffff, #f1f1f3 #18181b |
--calendar-event-color, --calendar-event-color-contrast | #2f63d6 #ffffff, #7ea6ff #0b1530 |
--calendar-event, --calendar-event-border | unset: see roles |
--calendar-event-text, --calendar-focus-ring | unset: fall back to the text and the accent |
--calendar-radius, --calendar-bar-radius | 0.375rem, 0.25rem |
--calendar-font-size, --calendar-font-size-sm, --calendar-font-size-xs | 0.875rem, 0.75rem, 0.6875rem |
--calendar-slot-height, --calendar-axis-width | 3rem, 4rem |
--calendar-bar-height, --calendar-bar-gap, --calendar-bar-inset | 1.375rem, 2px, 2px |
--calendar-day-header-height, --calendar-overflow-height, --calendar-row-min-height | 2.25rem, 1.5rem, 6rem |
--calendar-event-gap | 4px: space off each timed event's width and height |
--calendar-resource-width | 10rem: the timeline's resource column |
--calendar-resource-indent | 1rem per level in nested timeline resource cells |
--calendar-background | unset: the text mixed 6% into transparent |
--calendar-now | #d4321f, #ff6d5a |
--calendar-background-event-color | #1a8a6e, #4cc9a4 |
--calendar-background-event | unset: the background event color mixed 14% into transparent |
--calendar-weekend | unset: the text mixed 3% into transparent |
--calendar-section-min-width | 20rem: the narrowest day-grid section column |
--calendar-section-gap | 1rem between day-grid sections; 2rem columns and 1.5rem rows in multi-month overviews |
--calendar-list-date-width | 10rem: the widest list date column |
The event fill, border and text, the background fills, --calendar-weekend, --calendar-border-strong and
--calendar-focus-ring are unset on purpose. A custom property declared as var(--other) resolves where it is
declared, so a default on :root would ignore an override of the token it points to further down.
The size tokens also drive layout in base.css, which falls back to the same defaults when no theme is loaded,
except --calendar-day-header-height, which falls back to 2rem.
Nested timeline cells indent by their resource depth. TimelineViewResourceToggle uses the theme's native-button
text and focus styles; supply its visible icon or text as children.
Roles
Each kind of thing on the calendar has its own color:
| Role | Token | Drawn as |
|---|---|---|
| Today and focus | --calendar-accent | today's marker and header; the focus ring |
| Events | --calendar-event-color | a tint with a 3px stripe; solid for all-day and multi-day bars |
| Now | --calendar-now | the now line and its dot |
| Background events | --calendar-background-event-color | a translucent wash with a 2px top edge |
| Unavailable time | none | hatching |
| Background layers | --calendar-background | a flat shade |
| Weekends | --calendar-weekend | a faint shade on Saturday and Sunday cells, columns and headers |
| Days outside month | --calendar-text-muted | muted text, with no fill, so they read apart from weekends |
Set --calendar-accent-contrast with the accent, for the numbers on today's marker, and use a light-dark() pair so
each mode gets a fitting shade.
Timed events are --calendar-event-color mixed into the surface: 14% for the fill and 30% for the border in light
mode, 26% and 45% in dark mode, where a lighter tint would sink into the surface. A stripe in the event color marks
where an event starts, so a bar continued from an earlier week has none. All-day and multi-day bars (data-all-day,
data-multi-day) are filled with the event color, with --calendar-event-color-contrast text. --calendar-event,
--calendar-event-border and --calendar-event-text override the fill, border and text of every event.
The colors are resolved on each event, so a color set on any element around the calendar, or on one event, reaches it. To color events by kind, pass a class to the event part that sets the event color:
.event-focus {
--calendar-event-color: light-dark(#7a3fd1, #c4a3ff);
--calendar-event-color-contrast: light-dark(#ffffff, #1d0b3a);
}Weekends are Saturday and Sunday. Parts carry data-weekday (ISO, 1 is Monday), so restyle it for other weeks:
[data-scope='calendar'][data-weekday] {
--calendar-weekend: transparent;
}
[data-scope='calendar']:is([data-weekday='5'], [data-weekday='6']) {
--calendar-weekend: light-dark(#f4f3f0, #201f1d);
}Lines
Time-grid columns and timeline lanes draw a half-strength line at each slot and a full line on each hour, from the
--cal-slots-per-hour and --cal-hour-offset the parts emit, so a visible window starting off the hour keeps its hour
lines on the hour. Timeline lanes add a --calendar-border-strong line where each day starts. Header bands sit on the
muted surface over a --calendar-border-strong line.
Dark mode
Colors use light-dark(), so they follow the inherited color-scheme. The themes never set it:
:root {
color-scheme: light dark; /* follow the system */
}
.always-dark {
color-scheme: dark; /* force a mode on any ancestor */
}The shadcn/ui, Material UI and FullCalendar themes also follow their library's own dark-mode selector; see themes.
Forced colors
Under forced-colors: active (Windows High Contrast), the browser drops fills. Events keep their borders, and the
default theme redraws what fills carried with system colors:
- Today's marker and the now line use
Highlight. - Slot lines, unavailable hatching and outside-month dates use
GrayText. - Background layers are hatched in
GrayTextat the opposite angle from unavailable time, and background events are hatched the same way inHighlight, so the three stay apart.
Every theme inherits these rules. For more contrast without forced colors, use the high contrast theme.
Overriding rules
The themes use cascade layers and zero-specificity selectors. Unlayered CSS always wins over them, without
!important. Layered CSS wins only if its layer comes after the calendar's: CSS that declares its own layers after
importing a theme beats it, and layers declared earlier lose.
Select parts by their attributes, or pass a class to the part:
[data-scope='calendar'][data-part='time-grid-event'] {
border-radius: 0;
}Tailwind is layered too, and needs the theme imported into its components layer.
Scrolling
TimeGridView scrolls, with a sticky header. Give it a height to scroll inside the page instead of showing every
hour, or set the view's timeWindow to limit the clock range before rendering:
[data-scope='calendar'][data-part='time-grid-root'] {
max-block-size: 40rem;
}Known limits
- Day-grid and all-day bars default to 22px tall, below a 44px touch target. Raise
--calendar-bar-heightfor touch interfaces. - The all-day row scrolls with the time grid instead of staying under the sticky header.