Back to Blog
Programming code on a dark laptop screen with blue lighting
Design
February 5, 2026
6 min read

Customising Your JavaScript Calendar: Themes, Dark Mode & CSS Variables

By SimpleCalendarJS Team

SimpleCalendarJS~18 KB gzipped · Zero dependencies · Any framework

If you've ever tried to restyle a third-party calendar, you know the pain: hunting down deeply nested selectors, stacking !important declarations, and discovering that the library injected its own styles at a higher specificity level than anything you can reasonably reach. One library version bump later and your carefully crafted overrides are broken.

SimpleCalendarJS was designed to never put you in that position. Every visual property — colours, spacing, typography, border radius, shadows — is controlled through CSS custom properties. You don't fight the library's styles. You just set a variable and move on.

Why CSS variables beat class overrides

CSS custom properties (also called CSS variables) are resolved at runtime, not at compile time. That means they cascade just like any other CSS property: you define them on a parent element, every child inherits them, and overriding them on a scoped selector is always a clean win — no specificity war required. In contrast, overriding a hardcoded class selector requires matching or exceeding the library's specificity, which is brittle by definition. Sass/Less variables are compile-time only; the output is the same static value. CSS variables give you a living, dynamic theming layer that JavaScript can touch, that @media queries can update, and that design system tooling can generate automatically.

The core variables

Scope your overrides to .uc-calendar to keep them contained. These twelve properties cover the vast majority of real-world customisation needs:

.uc-calendar { /* Brand */ --cal-primary: #facc15; /* Active states, selected day ring */ --cal-primary-dark: #eab308; /* Hover shade of primary */ /* Backgrounds */ --cal-bg: #ffffff; /* Main calendar background */ --cal-header-bg: #f5f5f4; /* Toolbar / month-header strip */ --cal-today-bg: #fef3c7; /* Today's date cell tint */ /* Text */ --cal-text: #1c1917; /* Primary body text */ --cal-text-secondary: #78716c; /* Day names, muted labels */ /* Borders */ --cal-border: #e7e5e4; /* Cell dividers */ --cal-border-radius: 12px; /* Corner rounding on the widget */ /* Typography */ --cal-font-size: 14px; --cal-font-family: system-ui, sans-serif; }

Changes take effect the moment the browser evaluates them — no re-initialisation, no JavaScript call required.

3 ready-to-use themes

Each block below is self-contained. Paste it into your stylesheet and you're done.

SaaS Indigo

A clean, professional palette that works equally well on marketing sites and internal tools.

.uc-calendar { --cal-primary: #6366f1; --cal-primary-dark: #4f46e5; --cal-bg: #ffffff; --cal-header-bg: #f0f0fe; --cal-today-bg: #e0e7ff; --cal-text: #1e1b4b; --cal-text-secondary: #6b7280; --cal-border: #e0e7ff; --cal-border-radius: 10px; --cal-font-size: 14px; --cal-font-family: "Inter", system-ui, sans-serif; }

Forest Green

Warm and calm — ideal for health, wellness, or productivity apps.

.uc-calendar { --cal-primary: #16a34a; --cal-primary-dark: #15803d; --cal-bg: #f0fdf4; --cal-header-bg: #dcfce7; --cal-today-bg: #bbf7d0; --cal-text: #14532d; --cal-text-secondary: #4b7a5e; --cal-border: #bbf7d0; --cal-border-radius: 8px; --cal-font-size: 14px; --cal-font-family: system-ui, sans-serif; }

Slate Dark

A dark-first design that's easy on the eyes for long working sessions.

.uc-calendar { --cal-primary: #818cf8; --cal-primary-dark: #6366f1; --cal-bg: #0f172a; --cal-header-bg: #1e293b; --cal-today-bg: #1e3a5f; --cal-text: #f1f5f9; --cal-text-secondary: #94a3b8; --cal-border: #334155; --cal-border-radius: 12px; --cal-font-size: 14px; --cal-font-family: system-ui, sans-serif; }

Dark mode — two approaches

1. Automatic via prefers-color-scheme

The browser queries the user's OS setting and applies your dark variables instantly, with no JavaScript required. This is the right default for most public-facing sites.

@media (prefers-color-scheme: dark) { .uc-calendar { --cal-bg: #0f172a; --cal-header-bg: #1e293b; --cal-today-bg: #1e3a5f; --cal-text: #f1f5f9; --cal-text-secondary: #94a3b8; --cal-border: #334155; --cal-primary: #818cf8; --cal-primary-dark: #6366f1; } }

The one caveat: if you also want a manual toggle, users who change their OS setting mid-session will see an immediate switch. That's usually fine, but it's worth considering for apps with a strong preference for explicit control.

2. Class-based toggle with localStorage persistence

This approach lets users set their preference independently of their OS. The .uc-dark class triggers a dark variable set, and localStorage ensures the choice survives a page reload. Initialise the theme before first paint to avoid a flash of the wrong colours.

// Run this as early as possible — ideally inline in <head> const saved = localStorage.getItem('cal-theme'); if (saved === 'dark') { document.querySelector('#calendar')?.classList.add('uc-dark'); } // Wire up the toggle button const toggle = document.querySelector('#theme-toggle'); toggle.addEventListener('click', () => { const cal = document.querySelector('#calendar'); const isDark = cal.classList.toggle('uc-dark'); localStorage.setItem('cal-theme', isDark ? 'dark' : 'light'); });

Then define your dark overrides under the .uc-dark selector:

.uc-calendar.uc-dark { --cal-bg: #0f172a; --cal-header-bg: #1e293b; --cal-today-bg: #1e3a5f; --cal-text: #f1f5f9; --cal-text-secondary: #94a3b8; --cal-border: #334155; --cal-primary: #818cf8; --cal-primary-dark: #6366f1; }

You can combine both approaches: default to prefers-color-scheme, but let the class override it when a user explicitly makes a choice.

Accessibility: contrast ratios that actually matter

Dark mode done wrong is worse than no dark mode at all. WCAG 2.1 Level AA sets hard minimums:

Content typeMinimum contrast ratio
Normal text (under 18pt / 14pt bold)4.5 : 1
Large text (18pt+ / 14pt+ bold)3 : 1
UI components (borders, icons, controls)3 : 1

Level AAA raises the bar to 7:1 for normal text and 4.5:1 for large text — worth targeting if your audience includes users with low vision.

To check your variables without leaving your workflow:

  • Browser DevTools: In Chrome or Firefox, click any colour swatch in the Styles panel. The picker shows the computed contrast ratio against the background and flags AA/AAA pass/fail inline.
  • WebAIM Contrast Checker (webaim.org/resources/contrastchecker): paste in hex values and get an instant verdict.
  • Colour Contrast Analyser (free desktop app by TPGi): eyedrop colours from anywhere on screen, including rendered calendar cells.

As a quick sanity check: the default --cal-text: #1c1917 on --cal-bg: #ffffff produces a contrast ratio of approximately 17:1 — well above AA. When you swap to the Slate Dark theme, --cal-text: #f1f5f9 on --cal-bg: #0f172a comes in around 14:1. Both are safe. If you build a custom theme, run your own checks — especially for the muted secondary text colour, which is the most common accessibility failure point.

Responsive overrides

CSS variables update at any breakpoint, so you can tighten the layout on small screens without touching a single JavaScript option:

/* Compact layout on mobile */ @media (max-width: 640px) { .uc-calendar { --cal-font-size: 12px; --cal-border-radius: 6px; } } /* Generous spacing on large screens */ @media (min-width: 1280px) { .uc-calendar { --cal-font-size: 15px; --cal-border-radius: 16px; } }

This keeps your calendar readable in every context — tight on a 375px phone, expansive on a wide desktop dashboard — without duplicating markup or adding JavaScript resize listeners.

Quick-start snippet

Copy this block into your stylesheet and adjust the values. It covers every property you're likely to need:

.uc-calendar { /* --- Swap these to match your brand --- */ --cal-primary: #6366f1; --cal-primary-dark: #4f46e5; /* --- Backgrounds --- */ --cal-bg: #ffffff; --cal-header-bg: #f8fafc; --cal-today-bg: #e0e7ff; /* --- Text --- */ --cal-text: #1e1b4b; --cal-text-secondary: #6b7280; /* --- Structure --- */ --cal-border: #e2e8f0; --cal-border-radius: 10px; /* --- Typography --- */ --cal-font-size: 14px; --cal-font-family: system-ui, -apple-system, sans-serif; } /* Dark mode (automatic) */ @media (prefers-color-scheme: dark) { .uc-calendar { --cal-bg: #0f172a; --cal-header-bg: #1e293b; --cal-today-bg: #1e3a5f; --cal-text: #f1f5f9; --cal-text-secondary: #94a3b8; --cal-border: #334155; --cal-primary: #818cf8; --cal-primary-dark: #6366f1; } }

Summary

  • SimpleCalendarJS exposes 60+ CSS custom properties — override them on .uc-calendar to theme without specificity fights.
  • Three production-ready themes (SaaS Indigo, Forest Green, Slate Dark) are copy-pasteable starting points.
  • Dark mode works two ways: automatic via prefers-color-scheme, or class-based with localStorage for user-controlled persistence.
  • WCAG AA requires 4.5:1 contrast for normal text and 3:1 for large text and UI controls — check with DevTools or WebAIM's contrast checker.
  • Responsive overrides via @media queries let you adjust font size and spacing at any breakpoint, purely in CSS.

See all three themes live — and build your own — in the interactive sandbox.

Frequently Asked Questions

Can I use Tailwind CSS to style SimpleCalendarJS?

You can't apply Tailwind utility classes directly to internal calendar elements, but you can override the CSS custom properties (--cal-primary, --cal-bg, --cal-text, etc.) to match your Tailwind colour palette exactly. Map your Tailwind config colours to the corresponding CSS variables on .uc-calendar and you get full visual consistency with zero specificity fights.

How do I add dark mode to a JavaScript calendar component?

There are two clean approaches. The simplest is a @media (prefers-color-scheme: dark) block that redefines your CSS variables — the browser applies it automatically based on the user's OS setting. For a manual toggle, add a .uc-dark class to the calendar element and store the preference in localStorage so it survives page reloads. You can combine both: default to the system preference but let an explicit user choice override it.

Why should I use CSS variables instead of overriding class selectors to theme a calendar?

CSS custom properties cascade just like any inherited property, so a variable set on a parent element is always reachable without matching the library's specificity. Overriding hardcoded class selectors often requires !important or selector-stacking, and it breaks whenever the library bumps a version and renames a class. Variables give you a stable, living theming layer that CSS media queries and JavaScript can both update at runtime.

Do CSS custom properties work inside shadow DOM web components?

Yes — CSS variables are one of the few CSS mechanisms that pierce the shadow DOM boundary. A variable defined on :root or any ancestor element is inherited by shadow DOM children, which makes CSS custom properties the recommended pattern for theming encapsulated web components.

What contrast ratio do I need for a WCAG-accessible calendar theme?

WCAG 2.1 Level AA requires a 4.5:1 contrast ratio for normal text and 3:1 for large text and interactive UI controls such as borders and icons. Level AAA raises those thresholds to 7:1 and 4.5:1 respectively. The most common failure point in custom calendar themes is the muted secondary text colour (day names, labels) — always check that value specifically.

Can I change the calendar font to match my design system?

Yes. Set --cal-font-family on .uc-calendar to any font stack you choose — a Google Font, a system font stack, or a custom variable from your design tokens. Pair it with --cal-font-size to control the base size, and use responsive @media blocks to adjust both properties at different breakpoints without touching any JavaScript.

Add a calendar to your app today

Free for personal projects. $49/year or $199 lifetime per commercial project.