use Margo Theme
Reads, sets and toggles the theme from React. The state is the class on the document element — where the CSS already looks for it — and nothing mirrors it.
API
useMargoTheme
The whole theme API is this one hook. It returns the current theme and the two ways to change it, and keeps the value in sync by observing the class attribute of the document element.
| prop | type | default | |
|---|---|---|---|
theme | MargoTheme | undefined | — | the theme currently applied to the document; undefined during server render and hydration |
set | (next: MargoTheme) => void | — | applies a theme |
toggle | () => void | — | switches to the other one |
import { margoTheme, useMargoTheme } from "margo-ui";
const { theme, set, toggle } = useMargoTheme();
set(margoTheme.DARK);
toggle();margoTheme
The two theme names as constants, so a comparison is checked by the compiler instead of spelled out as a string at every call site.
| prop | type | default | |
|---|---|---|---|
margoTheme.LIGHT | "light" | — | the light theme |
margoTheme.DARK | "dark" | — | the dark theme |
Details
MetaColorScheme
A one line component that declares the document supports both schemes. It is what makes the browser paint its own surfaces — scrollbars, form controls, the address bar on mobile — to match the theme instead of assuming light.
<head>
<MetaColorScheme />
</head>Why the DOM holds the state
The CSS already needs the theme as a class on the document: that is how the dark variables win. Keeping a second copy in React would mean two sources of truth that can disagree, and a first paint that flashes the wrong one. Reading the class back is the cheaper and more honest option.
The hook takes no default: the default theme is the class you write on the html element in the server render, and a missing dark class always reads as light, which is what the CSS renders. Changing the theme writes exactly one of the two classes.
<html className={margoTheme.DARK}>On the server
theme is undefined during server render and hydration, then the value found on the document element. Handle that case, or drive theme-dependent UI with the dark: variant, which needs no JavaScript and cannot flash.
set and toggle write to the document, so call them from event handlers or effects, never while rendering. That is also where a persisted preference belongs: persistence is deliberately left out of the kit, because where it lives — storage, cookie, user account — is a decision only the application can make.
const { set } = useMargoTheme();
useEffect(() => {
const stored = localStorage.getItem("theme");
set(stored === margoTheme.LIGHT ? margoTheme.LIGHT : margoTheme.DARK);
}, [set]);Why the hook needs no provider
const { theme, toggle } = useMargoTheme();
const isLight = theme === margoTheme.LIGHT;
<Button onClickBlur={toggle} aria-pressed={isLight}>
<Button.Label label={isLight ? "Light" : "Dark"} />
</Button>Because the subscription is to the DOM and not to a store, two toggles rendered in different parts of the tree stay in sync without a provider between them — and so does a theme changed from outside React entirely.