margoThemeClient
Reads, sets and toggles the theme. The state is the .dark class on the document element — where the CSS already looks for it — and nothing mirrors it.
API
API
| prop | type | default | |
|---|---|---|---|
get | () => MargoTheme | — | the theme currently applied to the document |
set | (next: MargoTheme) => void | — | applies a theme |
toggle | () => void | — | switches to the other one |
import { margoTheme, margoThemeClient } from "margo-ui";
margoThemeClient.get(); // "light" | "dark"
margoThemeClient.set(margoTheme.DARK);
margoThemeClient.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 |
useMargoTheme
The React side. It returns the current theme and a setter, in the shape of a state hook, and keeps the value in sync by observing the class attribute of the document element with a mutation observer.
| prop | type | default | |
|---|---|---|---|
[0] | MargoTheme | — | the current theme, kept in sync with the document |
[1] | (next: MargoTheme | ((current) => MargoTheme)) => void | — | sets it, with the updater form of a state setter |
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.
On the server
Both APIs are safe to call during server rendering: without a document the getter reports dark and the setter does nothing. That makes dark the assumed default of the first paint, so an app that persists a light preference should apply it as early as possible.
const stored = localStorage.getItem("theme");
margoThemeClient.set(stored === margoTheme.LIGHT ? margoTheme.LIGHT : margoTheme.DARK);Persistence is deliberately left out of the kit: where the preference lives — storage, cookie, user account — is a decision only the application can make, and the client is small enough to drive from anywhere.
Why the hook needs no provider
const [theme, setTheme] = useMargoTheme();
const isLight = theme === margoTheme.LIGHT;
<Button onClickBlur={() => margoThemeClient.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.