ink technical css

Design Tokens: One File to Brand an Entire Site

How Ink uses CSS custom properties to make rebranding a static site a five-minute task.


The rebranding problem

I've rebuilt client sites more times than I'd like to admit. Not because the content changed — because the brand changed. New primary color, different font, adjusted spacing. In a well-structured project this takes an hour. In a WordPress theme with styles scattered across twenty files, it takes a weekend.

When I started building Ink, I wanted site-wide rebranding to be a five-minute task. Not "swap the theme" five minutes — actually change specific values and have the whole site respond.

What design tokens are

Design tokens are named values that define the visual identity of a site. Instead of writing color: #2563eb in twelve different CSS rules, you write color: var(--color-primary) and define --color-primary: #2563eb once.

This isn't new. Design systems at companies like Salesforce, IBM, and Google have used tokens for years. But most CMS platforms either ignore them entirely or bury them behind a theme settings page that generates CSS you can't easily read or override.

Ink puts the tokens in a single CSS file you can open, read, and understand in thirty seconds.

The token file

Here's the core of Ink's design system — the :root block in src/css/main.css:

:root {
    /* Colors */
    --color-primary: #2563eb;
    --color-secondary: #7c3aed;
    --color-background: #ffffff;
    --color-surface: #f8fafc;
    --color-text: #1e293b;
    --color-text-muted: #64748b;
    --color-border: #e2e8f0;

    /* Typography */
    --font-body: 'Inter', system-ui, sans-serif;
    --font-heading: 'Inter', system-ui, sans-serif;
    --font-mono: 'JetBrains Mono', monospace;
    --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
    --text-lg: clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
    --text-xl: clamp(1.5rem, 1.25rem + 1vw, 2rem);

    /* Spacing */
    --spacing-xs: 0.25rem;
    --spacing-sm: 0.5rem;
    --spacing-md: 1rem;
    --spacing-lg: 2rem;
    --spacing-xl: 4rem;

    /* Layout */
    --container-max: 72rem;
    --radius-default: 0.5rem;
    --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
    --shadow-md: 0 4px 6px rgba(0,0,0,0.07);
    --transition-speed: 150ms;
}

Every component, layout, and partial references these variables. The hero section uses --color-primary for its CTA button. The cards use --radius-default for their corners. The prose content uses --font-body and --text-base for paragraph text.

Why this works

One source of truth. There's no "I changed the color in the header but forgot the footer" situation. Change --color-primary and every element that references it updates simultaneously.

Fluid typography built in. The clamp() values for font sizes mean text scales smoothly between mobile and desktop without media queries. The token defines the range, the browser does the math.

Easy handoff. When you hand a site to a client or a new developer, they don't need to understand the CSS architecture. "Open this file, change these values" is a complete rebranding guide.

Dark mode is trivial. Add a second set of token values scoped to a class or media query:

@media (prefers-color-scheme: dark) {
    :root {
        --color-background: #0f172a;
        --color-surface: #1e293b;
        --color-text: #f1f5f9;
        --color-text-muted: #94a3b8;
        --color-border: #334155;
    }
}

Same token names, different values. Every component adapts without touching its own CSS.

Tokens in practice

Here's what a component looks like when it uses tokens — the card component from Ink's starter:

.card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-default);
    padding: var(--spacing-md);
    box-shadow: var(--shadow-sm);
    transition: box-shadow var(--transition-speed) ease;
}

.card:hover {
    box-shadow: var(--shadow-md);
}

No hard-coded values. If the site's border radius changes from rounded to sharp, every card updates. If the surface color shifts for a different brand, cards follow automatically.

This pattern repeats across all twelve of Ink's built-in components. The pricing table, testimonial carousel, feature grid, timeline — they all reference the same token set.

The desktop app's theme customizer

The Ink desktop app includes a visual theme customizer that makes this even more accessible. It reads the current token values from main.css, presents them as color pickers, font selectors, and sliders, and writes the changes back to the file. The live preview updates instantly.

This bridges the gap for users who are comfortable working with an editor but don't want to hand-edit CSS. The underlying mechanism is the same — CSS custom properties in a single file — but the interface is visual.

What I learned building this

The biggest lesson: constraints enable speed. By committing to CSS custom properties as the only theming mechanism, I eliminated an entire category of decisions. There's no "should this be a Sass variable or a CSS variable?" debate. There's no theme.json configuration layer. There's one file, one format, one pattern.

The tradeoff is that you can't do things like conditional styling based on content (e.g., "use a different primary color for blog posts vs. services"). But in practice, I've never had a client request that. Consistent branding is the goal, and tokens deliver it with zero overhead.