Dark mode

Dark mode is a single prop. Set colorMode on the provider and every component below it reads the dark token set — no per-component styling.

How it works

BootstrapStyledProvider renders a wrapper element with a data-bs-theme attribute set to the active colorMode. The library’s tokens are defined twice: the light values on the root, and a [data-bs-theme='dark'] block that overrides body background, text, borders, and the theme-color emphasis tokens. Components read the tokens through var(--bs-…), so flipping the attribute re-themes the whole subtree instantly.

tsx
<BootstrapStyledProvider colorMode="dark">
<App />
</BootstrapStyledProvider>

Because the switch is attribute-scoped, you can nest a provider to make one region dark inside an otherwise-light app — the inner data-bs-theme wins for its subtree.

Live toggle

The demo below drives colorMode from React state. Press the button — the card, its text, borders, and controls all flip because they read the mode tokens:

Driving the mode

colorMode is a controlled prop — you own the value. A common pattern stores the preference and seeds it from the OS setting:

tsx
import { useState } from 'react';
import { BootstrapStyledProvider } from '@metatoy/bootstrap-styled';

function initialMode(): 'light' | 'dark' {
const saved = localStorage.getItem('theme');
if (saved === 'light' || saved === 'dark') return saved;
return matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}

export default function App() {
const [mode, setMode] = useState(initialMode);

const toggle = () => {
  const next = mode === 'dark' ? 'light' : 'dark';
  setMode(next);
  localStorage.setItem('theme', next);
};

return (
  <BootstrapStyledProvider colorMode={mode}>
    <button onClick={toggle}>Toggle theme</button>
    {/* … */}
  </BootstrapStyledProvider>
);
}

To avoid a flash of the wrong theme on first paint, set data-bs-theme on the document element from an inline script before React hydrates, and seed colorMode from the same source.

Portaled overlays

Modals, dropdowns, tooltips, and popovers render through a portal into document.body, outside the provider wrapper. The library re-establishes the token scope on the portal root automatically via useColorMode, so overlays match the active mode — you do not need to do anything.

Reading the mode

Need the current mode inside your own components? Call the useColorMode hook:

tsx
import { useColorMode } from '@metatoy/bootstrap-styled';

function ModeLabel() {
const mode = useColorMode(); // 'light' | 'dark'
return <span>Current mode: {mode}</span>;
}

See Color modes for the token-level detail on what changes between light and dark.