Skip to content

Dark Mode

Birdpaper UI implements dark mode with CSS variables. Components adapt automatically—your app does not need extra dark-mode styling. Toggle a class on the root element and the full color system transitions smoothly.


How It Works

Dark mode is toggled by adding the .dark class to the root element. Components read colors through CSS variables, which remap between light and dark values. Component code does not change.

css
/* Light mode */
:root {
  --bp-primary-6: #165dff;
  --bp-gray-0: #ffffff;
}

/* Dark mode: under .dark, semantic variables map to the dark palette */
.dark {
  --bp-primary-6: var(--bp-primary-dark-6); /* #3c7eff */
  --bp-gray-0: var(--bp-gray-dark-0); /* #141414 */
}

Note: Variables under .dark override :root defaults. When a component uses var(--bp-primary-6), the resolved value depends on the active mode.


Scale Direction

In dark mode the scale direction is inverted: 1 is darkest and 10 is lightest. The same variable name therefore resolves to different hex values in each mode.

1Lightest backgroundDarkest background
6PrimaryPrimary (brighter)
10Darkest textLightest text

Brand Color Example

Using Primary as an example, here are the actual values at key steps in both modes:

1#e8f3ffVery light blue#000d4dVery deep blue
6#165dffPrimary blue#3c7effBright blue
10#000d4dVery deep blue#eaf4ffVery light blue

Tip: Functional colors are roughly 20% brighter in dark mode so they stay visually strong on dark backgrounds.


Neutral Mapping

Neutral (Gray) colors fully invert in dark mode: the lightest light-mode step maps to the darkest dark-mode step, and vice versa.

0White background#ffffffDark background#141414
1Secondary background#fafafaSecondary background#1f1f1f
2Border#f0f0f0Border#262626
8Body text#262626Body text#f0f0f0
10Darkest text#141414Lightest text#ffffff

Guideline: In app code, use var(--bp-gray-0) through var(--bp-gray-10) and ignore the active mode—the values switch automatically. Never hard-code hex colors in business styles.


Usage

Toggle Dark Mode

Add or remove the .dark class on the root element:

js
// Enable dark mode
document.documentElement.classList.add("dark");

// Disable dark mode (back to light)
document.documentElement.classList.remove("dark");

Persist Preference

Store the user’s preference in localStorage and restore it on load:

js
// Save on toggle
const toggleDark = () => {
  const isDark = document.documentElement.classList.toggle("dark");
  localStorage.setItem("theme", isDark ? "dark" : "light");
};

// Restore on load
const saved = localStorage.getItem("theme");
if (saved === "dark") {
  document.documentElement.classList.add("dark");
}

Follow System Preference

You can also mirror the OS setting:

js
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");

// Match system on init
if (prefersDark.matches) {
  document.documentElement.classList.add("dark");
}

// Listen for OS changes
prefersDark.addEventListener("change", (e) => {
  document.documentElement.classList.toggle("dark", e.matches);
});

Use in CSS

Components already adapt via CSS variables. For custom styles in your app, reference the same variables:

css
/* Variables switch automatically with the active mode */
.my-element {
  color: var(--bp-gray-10);
  background: var(--bp-gray-0);
  border: 1px solid var(--bp-gray-2);
}

/* Use RGB variants when you need opacity */
.my-overlay {
  background: rgba(var(--bp-gray-0-rgb), 0.8);
}

Tip: Every color variable also has an -rgb suffix for rgba() usage. See the Color docs.


Adaptation Rules

Components follow these rules in dark mode to stay readable and comfortable:

Text colorUse high steps (8–10)Maintain contrast on dark backgrounds
BackgroundUse low steps (0–2)Reduce eye strain and create immersion
BorderUse mid-low steps (2–3)Stay visible without looking harsh
Functional colorSlightly raise primary (6) brightnessKeep brand colors readable on dark surfaces
ShadowLower opacityAvoid harsh edges on dark backgrounds

Guideline: Custom components that follow these rules stay visually aligned with Birdpaper UI in dark mode. The core principle: always use semantic CSS variables—never hard-code color values.