MD3
Expressive
MATERIAL DESIGN 3 EXPRESSIVE

Theme

Learn about the Dynamic Color system, design tokens, and how to configure MD3ThemeProvider for your application.

Bug On MD3 Expressive provides native support for the Material Design 3 (Material You) Dynamic Color system. This system generates accessible, harmonious color palettes derived from a single Source Color using the HCT (Hue, Chroma, Tone) color space algorithm.


MD3ThemeProvider

MD3ThemeProvider is the single root entry point for theme state and tokens. It manages:

  • 🎨 Dynamic Palette Generation — Calculates light and dark mode color schemes from a source color.
  • 🌓 Theme Modes — Manages Light, Dark, and System theme state (window.matchMedia).
  • 🔤 Typography & Variables — Configures font family and variable font axes (Google Sans Flex defaults).
  • ⚡ Framer Motion Context — Provides spring motion tokens required by animated components.
  • 🔔 Snackbar Queue (opt-in) — Global notification queue via enableSnackbar.

Basic Configuration

Wrap your application at the root level (e.g., layout.tsx or App.tsx):

import { MD3ThemeProvider } from "@bug-on/m3-expressive/core";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <MD3ThemeProvider
      sourceColor="#6750A4"
      defaultMode="system"
      variant="expressive"
      contrastLevel={0}
      persistToLocalStorage
    >
      {children}
    </MD3ThemeProvider>
  );
}

With Global Snackbar Queue

Pass enableSnackbar to activate the global notification host. This allows useSnackbar() to be called anywhere in your app without needing an extra provider:

<MD3ThemeProvider
  sourceColor="#6750A4"
  defaultMode="light"
  variant="expressive"
  contrastLevel={0}
  persistToLocalStorage
  enableSnackbar
>
  {children}
</MD3ThemeProvider>

With Custom Typography Overrides

Override font family or variable font axes directly on the provider:

<MD3ThemeProvider
  sourceColor="#6750A4"
  fontFamily="'Inter', sans-serif"
  fontVariationAxes={{ ROND: 50 }} // 0 = sharp corners, 100 = full rounded variable font
>
  {children}
</MD3ThemeProvider>

Provider Props Reference

Theme Props

PropTypeDefaultDescription
sourceColorstring#6750A4Primary hex color used to derive the entire MD3 palette.
defaultModeThemeMode"light"Initial theme mode ("light", "dark", or "system").
variantSchemeVariant"expressive"Color scheme variant ("expressive", "tonal_spot", "vibrant", "fidelity", "content", "monochrome", "neutral").
contrastLevelContrastLevel0Contrast level for generated scheme (0 standard, 0.5 medium, 1 maximum contrast / WCAG AAA).
persistToLocalStoragebooleanfalseSaves and restores theme preferences from localStorage.

Scheme Variants & Contrast Levels

Bug On MD3 Expressive integrates with @material/material-color-utilities ^0.4.0 using MD3 Expressive Spec 2025 (specVersion: '2025').

Scheme Variants (variant)

  • "expressive" (Default) — MD3 Expressive 2025 palette with vibrant, high-chroma tones for hero moments and expressive UI elements.
  • "tonal_spot" — Standard Android 12+ Material 3 palette with balanced chroma.
  • "vibrant" — Maximizes colorfulness across primary, secondary, and tertiary tones.
  • "fidelity" — Keeps generated colors tightly matched to the original seed color.
  • "content" — Tailored for content-centric surfaces (e.g., photo viewers, reading apps).
  • "monochrome" — Grayscale scheme using purely neutral tones.
  • "neutral" — Low-chroma scheme with subtle accent tones.

Contrast Levels (contrastLevel)

  • 0 (Default) — Standard contrast level.
  • 0.5 — Medium contrast, providing clearer boundaries between containers and text.
  • 1 — High contrast, meeting WCAG AAA accessibility requirements.

Theme Hooks

useTheme

Returns full theme state, including current source color, mode, active scheme variant, contrast level, and palette mutators.

import { useTheme } from "@bug-on/m3-expressive/core";

function ColorPicker() {
  const { sourceColor, setSourceColor, mode, setMode, effectiveMode, variant, contrastLevel } = useTheme();

  return (
    <div>
      <input
        type="color"
        value={sourceColor}
        onChange={(e) => setSourceColor(e.target.value)}
      />
      <button onClick={() => setMode(effectiveMode === "dark" ? "light" : "dark")}>
        Mode: {effectiveMode} (Variant: {variant})
      </button>
    </div>
  );
}

useThemeMode

Utility hook for managing Light/Dark mode transitions exclusively.

import { useThemeMode } from "@bug-on/m3-expressive/core";

function ThemeToggle() {
  const { mode, setMode, effectiveMode } = useThemeMode();
  const isDark = effectiveMode === "dark";

  return (
    <button onClick={() => setMode(isDark ? "light" : "dark")}>
      {isDark ? "Switch to Light Mode" : "Switch to Dark Mode"}
    </button>
  );
}

How Design Tokens & Tailwind v4 Integration Work

@bug-on/m3-expressive/index.css integrates design tokens at three distinct layers:

  ┌────────────────────────────────────────────────────────┐
  │ 1. Raw Tokens & Variable CSS Defaults                 │
  │    (--md-sys-color-primary, --md-sys-shape-corner-xl)  │
  └───────────────────────────┬────────────────────────────┘
                              │
                              ▼
  ┌────────────────────────────────────────────────────────┐
  │ 2. Tailwind v4 @theme Block Mapping                    │
  │    (--color-m3-primary: var(--md-sys-color-primary))   │
  └───────────────────────────┬────────────────────────────┘
                              │
                              ▼
  ┌────────────────────────────────────────────────────────┐
  │ 3. Tailwind Utility Classes                            │
  │    (bg-m3-primary, text-m3-on-surface, rounded-m3-xl)  │
  └────────────────────────────────────────────────────────┘

Dynamic Runtime Color Injection

When MD3ThemeProvider mounts, it computes the MD3 palette based on sourceColor and updates CSS variables on :root dynamically. Because Tailwind v4 utility classes reference these CSS variables, all components re-render with the new palette instantly without full page reloads.

Overriding Tokens in CSS

Because token mappings use Tailwind v4 @theme, you can override specific color roles or radii in your application's globals.css:

@import "tailwindcss";
@import "@bug-on/m3-expressive/index.css";

@theme {
  /* Override a static color role */
  --color-m3-primary: #1e40af;
  
  /* Override a custom shape radius */
  --radius-m3-xl: 32px;
}