/// getting-started
Theming
Theming in PDS is declarative. Four data-attributes on any element — usually <html> — retune the token layer for everything inside it. No theme provider is required to change how the system looks.
The four axes
| Attribute | Values | Controls |
|---|---|---|
data-theme | dark · light | Surface, ink, and hairline palette. Dark is the default. |
data-accent | green · blue · red · amber · violet | The single reserved signal colour. |
data-density | default · compact | Control heights, body size, and section rhythm. |
data-env | dev · staging · prod | Environment chrome colour (env badges and strips). |
Attributes cascade and can be nested — set data-theme on <html> for the app, then override data-accent on a single panel to preview another accent locally.
Accents
Five curated accents ship with the system. Each is tuned for AA contrast in both themes — the light theme automatically darkens where a value would fail on white. The swatches below render the live --pds-accent value for each choice in the theme you are viewing.
- green#3fcf8e
- blue#2a6fdb
- red#e8533f
- amber#c9a227
- violet#a855f7
Environment chrome
data-env tints the environment chrome so a staging or production surface is unmistakable at a glance. It drives components like the env badge and env strip through the single --pds-color-env token.
- dev
- staging
- prod
Density
data-density="compact" tightens control heights, shrinks the body size, and reduces vertical rhythm for dense console screens — without changing any component’s markup. Set it high in the tree for a whole app, or on one AppShell region.
<section data-density="compact">
{/* controls here render at the tighter scale */}
</section>Persisting the choice without a flash
Read the saved theme and accent from localStorage and stamp them on <html> in a tiny blocking script placed before your app renders. Because it runs before first paint, the user never sees a flash of the wrong theme.
<script
dangerouslySetInnerHTML={{
__html: `(function () {
try {
var t = localStorage.getItem("pds-theme");
if (t === "light" || t === "dark") document.documentElement.dataset.theme = t;
var a = localStorage.getItem("pds-accent");
if (a) document.documentElement.dataset.accent = a;
} catch (e) {}
})();`,
}}
/>Then persist on change — from a toggle, or by subscribing to useTheme:
function persist(theme) {
document.documentElement.dataset.theme = theme;
localStorage.setItem("pds-theme", theme);
}<html>, add suppressHydrationWarning to your <html> element so React does not flag the server/client attribute mismatch.Overriding tokens
When you need to bend the system — a tenant brand colour, a one-off surface — do it at the semantic layer (--pds-*), never by editing component CSS. Redefine a semantic variable on a scope and everything downstream follows:
/* a single panel with a bespoke accent, still AA-managed by you */
.brand-panel {
--pds-accent: #7c5cff;
--pds-accent-fg: #ffffff;
}--pds-p-* primitives — they are not API and can change between releases. Style through the semantic layer only. More in Styling.