Skip to content

Appearance

Theme
Mode

Style guide: tokens, type, and contrast →

Style guide

Every color on this site comes from a token, and every token pair is measured. Switch themes or modes and the numbers below update — including the ones that would fail. Components are rendered from the same library the site is built from, so nothing here is a screenshot.

Theme

Three themes across light and dark, applied with data-theme and data-mode on <html>. Each is the same token names with different values, which is what makes the high-contrast theme a value swap rather than a rewrite. The preference is a cookie, read by a script before first paint — no browser storage, no flash.

Theme
Mode

Color

Colors are not a flat palette. Every surface has a paired foreground, so a component written as bg-card text-card-foreground is legible in every theme without anyone remembering which color goes where. Values below are read from the running page.

Surface and foreground pairs

  • PageAa
    --ck-background
    --ck-foreground
  • CardAa
    --ck-card
    --ck-card-foreground
  • MutedAa
    --ck-muted
    --ck-muted-foreground
  • PrimaryAa
    --ck-primary
    --ck-primary-foreground
  • AccentAa
    --ck-accent
    --ck-accent-foreground

Structure

  • Border

    --ck-border

    Decorative hairline

  • Input

    --ck-input

    Control boundary · 3:1

  • Ring

    --ck-ring

    Focus indicator · 3:1

Contrast

Contrast is measured between a surface and the text or icon drawn on it, not between individual colors. WCAG AA requires 4.5:1 for body text and 3:1 for large text. AAA requires 7:1. The high-contrast theme is built to clear AAA on every pair.

Measuring the current theme…

The same math runs at build time in npm run check:contrast, reading globals.css directly, so this page and the gate cannot disagree. Note that --ck-border is not held to 3:1: WCAG 1.4.11 governs control boundaries, not decorative hairlines, which is why --ck-input exists separately.

Typography

Archivo carries display type — headings and the name. IBM Plex Mono carries everything else: body copy, labels, tables, UI. The inversion of the usual serif-on-cream portfolio is deliberate.

Display · Archivo 600

Design systems and the standards behind them

Heading · Archivo 600

VimUI, a design system in code

Body · IBM Plex Mono 400

I design products and write the frontend code they're built from.

Label · IBM Plex Mono 400, uppercase, 0.14em

Selected work

Components

cksUI — this site’s component library. Built on shadcn/ui’s patterns as source copied in and owned, not as an installed dependency, with every value rewritten onto the tokens above. Every component declares a data-slot, the same convention VimUI uses.

Try the inspector

Hit Inspect in the header and point at anything on this page — or tab through it, which works the same way. It reports the component, the tokens its rendered values resolve back to, and the rule behind them.

Values are read with getComputedStyle and resolved backwards to token names, so it reports on the token layer rather than dumping CSS. Anything resolving to no token is a violation of the rule in CLAUDE.md, and the panel says so rather than hiding it.

Component

Rendered

Source

<Button>Read case study</Button>

Button

data-slot="button"

Four variants, each pairing a surface with its foreground so every one stays legible in all three themes.

  • The 44px minimum height is enforced in the component, not remembered at call sites.
  • asChild renders the child element instead of a <button>, for wrapping a link.
Variant
Size
State

The source panel is not a code sample kept beside a demo. Each specimen is one tree, projected twice — once through createElement into the components on the left, once through a printer into the JSX on the right. There is no way to write code here that renders something else, which is the same reason the contrast table reads globals.css instead of keeping its own palette.