Styling Tokens: Deep Dive #66
Component Systems Strategist
This post follows the plan’s “Styling Tokens” focus and the site’s global cross-linking requirements:
- The App Router Mindset
- Core Concepts (Best Practices)
- Components and Composition
- Extra (plan): Security and Safety
Some pages on this site use conceptual or illustrative APIs. This post uses standard, real-world React and Next.js patterns (CSS variables + Tailwind or plain CSS) so the code is directly applicable.
Styling tokens are easiest to scale when they’re treated as route-adjacent state:
- layouts define the baseline token set (shared across nested routes),
- component routes (tabs/panels) may apply scoped overrides,
- and every override is visible and reversible.
Even if your “routing” is just UI state, the rule holds: put your token contract in a shell that’s stable across navigation.
What Counts as a Token (Practical Definition)
In production apps, tokens are the small set of values you reference everywhere:
- colors (bg/fg/border)
- spacing scale
- radii and shadows
- typography sizes/line-heights
Tokens are not “a theme” — they’re the contract that makes themes possible.
Worked Example: Route-Shell Tokens With CSS Variables
We’ll build a token system that supports:
- a layout-level baseline (
:rootvariables), - per-section overrides (e.g. docs vs blog),
- and “component route” overrides (e.g. a panel that needs a denser spacing scale).
Step 1: Define Tokens as CSS Variables
Create a small, stable set. Don’t put every color in tokens — only the ones used as system primitives.
/* app/tokens.css */
:root {
--color-bg: #0b0d12;
--color-fg: #e7eaf0;
--color-muted: #9aa3b2;
--color-border: rgba(231, 234, 240, 0.12);
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
--radius-2: 8px;
--radius-3: 12px;
--font-sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
}
Step 2: Apply Tokens in the App Shell (Layout)
If you’re using app/ routing, this lives naturally in your root layout (stable across route
segments).
// app/layout.tsx
import "./tokens.css";
import "./globals.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body data-shell="root">{children}</body>
</html>
);
}
Step 3: Use Tokens in Components (No Magic)
type CardProps = {
title: string;
children: React.ReactNode;
};
export function Card({ title, children }: CardProps) {
return (
<section
style={{
border: "1px solid var(--color-border)",
borderRadius: "var(--radius-3)",
padding: "var(--space-4)",
background: "var(--color-bg)",
color: "var(--color-fg)",
}}
>
<h2 style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-lg)" }}>{title}</h2>
<div style={{ marginTop: "var(--space-2)", color: "var(--color-muted)" }}>{children}</div>
</section>
);
}
Step 4: Route-Level Overrides (Docs vs Blog)
Override tokens by applying a wrapper with different variable values.
/* app/docs/docs-theme.css */
[data-theme="docs"] {
--color-bg: #0a101a;
--color-fg: #eaf2ff;
--color-muted: #9fb1cf;
}
/* app/blog/blog-theme.css */
[data-theme="blog"] {
--color-bg: #0f0f10;
--color-fg: #f2f2f2;
--color-muted: #b9b9b9;
}
// app/docs/layout.tsx
import "./docs-theme.css";
export default function DocsLayout({ children }: { children: React.ReactNode }) {
return <div data-theme="docs">{children}</div>;
}
// app/blog/layout.tsx
import "./blog-theme.css";
export default function BlogLayout({ children }: { children: React.ReactNode }) {
return <div data-theme="blog">{children}</div>;
}
Step 5: Component-Route Overrides (Density Toggle)
For panels/tabs inside a route, you can apply scoped overrides on a container element.
export function DensityPanel({
density,
children,
}: {
density: "comfortable" | "dense";
children: React.ReactNode;
}) {
const style =
density === "dense"
? ({ ["--space-2" as any]: "6px", ["--space-4" as any]: "12px" } as React.CSSProperties)
: undefined;
return (
<div data-density={density} style={style}>
{children}
</div>
);
}
Security Note: Tokens and User-Generated Content
Token overrides should not accept arbitrary user-provided values without validation (e.g. themes stored in user profiles) because CSS variables can be abused to create deceptive UI states or reduce contrast. If you allow user themes:
- whitelist allowed token keys,
- sanitize token values,
- and keep minimum contrast constraints.
See: Security and Safety.
Token Checklist
- Define a small baseline token set in
:root. - Apply route-level overrides in nested layouts.
- Apply scoped overrides for component-route “panels”.
- Avoid user-provided raw CSS variable values unless sanitized.