Skip to content

Theming

Three colours are set by hand. Surfaces, borders and secondary text are mixed from them with color-mix, so changing the background re-skins everything that sits on it.

The three colours

src/app.css
:root {
	--ck-bg: oklch(0.985 0 0);
	--ck-fg: oklch(0.205 0 0);
	--ck-accent: oklch(0.52 0.18 316);
}

That is a complete theme. Put the same three inside .dark for the dark scheme and you are done.

Tokens you set

Defaults below are the light scheme, read from the stylesheet that ships with the package.

TokenDefaultWhat it does
--ck-bgoklch(0.985 0 0)The page colour before the accent is mixed in.
--ck-fgoklch(0.205 0 0)Body text. Everything secondary is mixed from it.
--ck-accentoklch(0.52 0.18 316)Buttons, focus rings, links, selected rows.
--ck-accent-hoveroklch(0.47 0.18 316)The accent on hover. Not derived: contrast is a judgement call.
--ck-accent-fgoklch(0.99 0 0)Text on top of the accent.
--ck-tint1.5%How much accent reaches the neutrals. 0% gives a grey page.
--ck-tint-basevar(--ck-accent)What gets mixed in. Dark mode uses a deepened accent so the page keeps its weight.
--ck-radius-scale1Multiplies Tailwind's radius scale. 0 squares everything off.
--ck-elevate72%How far a raised surface lifts off the canvas.
--ck-successoklch(0.55 0.14 150)Status colours. Plain values, not derived.
--ck-warningoklch(0.58 0.13 68)
--ck-dangeroklch(0.55 0.21 25)
--ck-infooklch(0.55 0.15 250)
--ck-font-sansui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serifBody and UI typeface.
--ck-font-monoui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospaceUsed by Code, Kbd and Terminal.

The contrast ratios are tokens too: muted, border, border-strong, fg-muted, fg-subtle, accent-soft control how far each derived colour sits from the canvas. Raise them for more contrast, lower them for a flatter look.

Tokens you get

These are mixed from the three above and recomputed wherever a theme is declared. Override one and the formula steps aside for that value.

TokenMixed fromUsed for
--ck-canvascolor-mix(in oklab, var(--ck-tint-base) var(--ck-tint), var(--ck-bg))The real page colour: --ck-bg with the tint mixed in.
--ck-surfacecolor-mix(in oklab, white var(--ck-elevate), var(--ck-canvas))Cards, inputs, menus — anything sitting on the canvas.
--ck-mutedcolor-mix(in oklab, var(--ck-fg) var(--ck-mix-muted), var(--ck-canvas))Quiet fills: hover states, table stripes, skeletons.
--ck-bordercolor-mix(in oklab, var(--ck-fg) var(--ck-mix-border), var(--ck-canvas))Hairlines.
--ck-border-strongcolor-mix( in oklab, var(--ck-fg) var(--ck-mix-border-strong), var(--ck-canvas) )Borders that need to be seen, like a checkbox outline.
--ck-fg-mutedcolor-mix(in oklab, var(--ck-fg) var(--ck-mix-fg-muted), var(--ck-canvas))Secondary text.
--ck-fg-subtlecolor-mix(in oklab, var(--ck-fg) var(--ck-mix-fg-subtle), var(--ck-canvas))Placeholders and captions.
--ck-accent-softcolor-mix( in oklab, var(--ck-accent) var(--ck-mix-accent-soft), var(--ck-canvas) )The accent as a background tint.
--ck-ringvar(--ck-accent)Focus ring colour. Follows the accent unless you set it.

Every one of them reaches Tailwind as a utility: bg-surface, text-fg-muted, border-border, bg-accent/12.

A custom accent

Set the hover shade and the text colour alongside it. Neither is derived, because readable contrast on a coloured button is a decision rather than a formula.

src/app.css
:root {
	--ck-accent: #0d9488;
	--ck-accent-hover: #0f766e;
	--ck-accent-fg: #ffffff;
}

Six presets ship with the kit — plum (the default), cobalt, pine, ember, gold and ink. Set data-accent on <html>, or assign theme.accent at runtime.

Typeface

src/app.css
:root {
	--ck-font-sans: 'Hanken Grotesk Variable', ui-sans-serif, system-ui, sans-serif;
	--ck-font-mono: 'JetBrains Mono', ui-monospace, monospace;
}

Themes on part of a page

Put data-ck-theme on any element and that subtree gets its own theme. Set --ck-* variables on it and the derived tokens are recomputed there, which is how the theme generator shows light and dark side by side on one page.

anywhere.svelte
<section data-ck-theme="dark" style="--ck-accent: oklch(0.75 0.15 160)">
	<Card>Dark, green, and only in here.</Card>
</section>

Contrast

The defaults clear WCAG AA in both schemes: body text at 16.8:1 and 17.4:1, secondary text above 6:1, placeholder text above 4.5:1, and control outlines above 3:1. Two of the mix ratios — --ck-mix-border-strong and --ck-mix-fg-subtle — are set exactly where they clear those thresholds, so lowering them for a softer look is a trade you are making knowingly.

A custom accent is the easy thing to get wrong, because --ck-accent-fg has to work on top of it and the accent itself has to work as link text. The theme generator measures both, on the colours the browser actually paints, and can keep rolling until everything passes.

At runtime

The theme object is plain runes state. Changes persist to local storage and the page crossfades rather than snapping.

anywhere.ts
import { theme } from '@joshicodes/cock';

theme.mode = 'dark';              // 'light' | 'dark' | 'system'
theme.toggle();
theme.accent = 'pine';            // a preset, or any name you defined

theme.setToken('--ck-bg', '#f6f5f2');          // the scheme on screen
theme.setToken('--ck-bg', '#101014', 'dark');  // or a specific one
theme.setSharedToken('--ck-radius-scale', '0'); // both schemes at once
theme.clearTokens();                            // back to the stylesheet

theme.resolved is always 'light' or 'dark', even when the mode is 'system'. theme.revision changes on every applied theme, which is the thing to watch if you measure computed styles yourself.

cock. Yes, it stands for core component kit. We checked twice.