Theming

Customize Cotton UI with Tailwind CSS v4's theming system.

Cotton UI's look is driven by overridable CSS variables. Override them in a :root block (and a .dark block for dark mode) to reskin the whole kit. Four concept buckets carry a theme:

Bucket Role Core tokens
Accent Your brand --color-accent, --color-accent-content, --color-accent-foreground
Ink The neutral --color-ink-50…950, --color-muted
Surfaces Depth --color-bg, --surface-level
Radius Shape --radius-control, --radius-box

Changing the Accent Color

The accent drives primary buttons, active states and focus rings. Cotton UI uses a 3-value accent system:

  • --color-accent - primary fills (button backgrounds, active states)
  • --color-accent-content - hover states and readable accent text
  • --color-accent-foreground - text and icons on accent fills

A neutral ink accent ships by default. Brand the kit by overriding the three tokens in a :root block, with matching values under .dark. Same tokens whether you drop them in the inline <style> of the no-build setup or in your app.css on a custom build:

app.css
:root {
    --color-accent: var(--color-blue-500);
    --color-accent-content: var(--color-blue-600);
    --color-accent-foreground: var(--color-white);
}

.dark {
    --color-accent: var(--color-blue-400);
    --color-accent-content: var(--color-blue-300);
    --color-accent-foreground: var(--color-blue-950);
}

Use the Theme Builder to preview and generate the CSS to copy into your app.css.

Ink (the Neutral)

Where the accent is your brand colour, ink is the neutral the rest of the UI is built from: text, borders, dividers, surfaces and the off-accent state. Re-tone the whole UI by pointing the --color-ink-50…950 tokens at another scale:

app.css
:root {
    --color-ink-50: var(--color-stone-50);
    --color-ink-100: var(--color-stone-100);
    /* …through the scale… */
    --color-ink-900: var(--color-stone-900);
    --color-ink-950: var(--color-stone-950);
}

The Theme Builder generates the full block for any tone, including the warm and cool neutrals the kit ships (taupe, mauve, mist, olive).

Accent Toggle

Add :accent="False" as a property to turn off accent highlights for a component. Its selected and active states (a checked radio, an on switch, a selected card) render in ink instead, no theme changes needed, so they re-tone with your neutral and flip light/dark on their own.

The off-accent fill is the --color-accent-muted token, which defaults to ink (--color-ink-900 / --color-ink-100). Override it for a different neutral, e.g. a softer mid-ink:

app.css
:root {
    --color-accent-muted: var(--color-ink-600);
}

.dark {
    --color-accent-muted: var(--color-ink-400);
}

Side by side

With Accent (default)

Without Accent (:accent="False")

Border Radius

Two radius tokens control rounding. Form controls and structural surfaces are kept separate so you can, for example, round cards more than inputs:

  • --radius-control - form controls (inputs, buttons, segments)
  • --radius-box - structural surfaces (cards, dialogs, menus)

Override in the same :root block as the accent tokens.

Surfaces

Surfaces are the page base and the raised surfaces (cards, dialogs, menus) that sit on top of it. Two knobs:

  • --color-bg - page base background
  • --surface-level - how much raised surfaces lift off the page (mixed with white). 100% in light gives flat white cards; 6% in dark lifts subtly off ink-950

--color-surface is derived from those two — you rarely override it directly. Same for --color-input-bg: transparent in light so form fields are border-defined, a faint lift in dark.

app.css
:root {
    --color-bg: var(--color-ink-50);   /* page base */
    --surface-level: 100%;              /* flat white cards */
}

.dark {
    --color-bg: var(--color-ink-950);
    --surface-level: 6%;                /* subtle lift off the dark page */
}

All Tokens

The complete set of themeable tokens and their defaults. Override any of them in a :root block, and set dark-mode values under .dark.

Token Default Dark Purpose
Neutral (Ink)
--color-ink-50…950 Tailwind zinc - The neutral scale behind text, borders, dividers and surfaces. Re-tone it to re-skin the UI; defaults to zinc and never touches your own zinc usage.
--color-muted ink-500 ink-400 Secondary text, reachable as the text-muted utility: descriptions, meta lines, timestamps.
Accent
--color-accent ink-900 ink-100 Primary fills: button backgrounds, active states
--color-accent-content ink-700 ink-300 Hover states and readable accent text
--color-accent-foreground white ink-900 Text and icons on accent fills
--color-accent-muted ink-900 ink-100 Fill and border for :accent="False" controls; defaults to ink.
Surfaces
--color-bg ink-50 ink-950 Page base background
--surface-level 100% 6% How much raised surfaces lift off the page (mixed with white). Turn this knob to change surface contrast, not --color-surface directly.
--color-surface derived from --color-bg + --surface-level Raised surfaces (cards, dialogs, menus). Derived, not literal — override --surface-level to move it.
--color-input-bg transparent +4% white Form field fill: transparent in light (border-defined), a faint lift in dark
--color-box-border ink-200 ink-700 Border on box surfaces: cards, dialogs, menus, popovers. Set transparent for a shadow-only look
--color-card-border = box-border = box-border Card border only. Falls back to --color-box-border; override it alone (e.g. transparent) to affect just cards
Radius
--radius-control 0.375rem Form controls: inputs, buttons, segments
--radius-box 0.5rem Structural surfaces: cards, dialogs, menus
Shadows
--shadow-input 0 1px 2px 0 rgb(0 0 0 / 0.05) Form input elevation (set to none to flatten)
--shadow-box 0 1px 2px 0 rgb(0 0 0 / 0.05) Card and dialog elevation
Focus ring
--focus-ring-width 3px Focus ring thickness
--focus-ring-offset 0px Gap between the control and the ring
--focus-ring-color = accent Focus ring color
--focus-ring-offset-color white ink-900 Color behind the offset gap

Utility classes

Because every token is a Tailwind @theme colour, it also generates an ordinary utility (text-*, bg-*, border-*). Reach for these in your own markup instead of hard-coded colours and it re-tones with the kit, including live edits from the Theme Builder.

text-muted Secondary text

Descriptions, meta, timestamps. Same as ink-500 in light; lifts to ink-400 on dark so it stays legible.

text-ink-500 Neutral text

Any fixed step of the ink scale, 50–950, for text, icons or dividers. Pick the exact shade yourself.

text-accent Accent text

Your brand colour as text.

text-accent-content Readable accent

Accent tuned for text weight: links and hover states.

bg-accent Accent fill

Primary fills: buttons, active states. Put text-accent-foreground on top.

bg-surface Raised surface

Raised surfaces lifted off the page: cards, dialogs, menus.

border-box-border Box border

The matching border for those box surfaces.

focus-ring

Consistent keyboard focus ring. Tab to the button to see it.

rounded-control

Radius for form controls: inputs, buttons, segments.

rounded-box

Radius for structural surfaces: cards, dialogs, menus.

Opacity modifiers work too, e.g. bg-accent/10 for a faint accent tint.

Theme
Accent
Ink