Theming

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

Cotton UI's look is driven by overridable CSS variables. Two colours carry a theme: an accent (your brand) and ink (the neutral behind text, borders and surfaces), alongside radius, surfaces, shadows and the focus ring. Override any with a :root block (and a .dark block for dark mode). Start with the main ones below, or see All Tokens for the full list.

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. To brand the kit, override the accent tokens with a :root block and a .dark block for dark mode. This goes in the inline <style> from the no-build setup, or your app.css on a custom build, the same three tokens either way:

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. It's the --color-ink-50…950 scale, defaulting to Tailwind's zinc.

Re-tone the whole UI by pointing those tokens at another scale in a :root block. It's a plain token override, so it works on the no-build <link> path too, and it never touches your own zinc usage:

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. Together, accent and ink are the two colours every theme turns on.

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-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-muted: var(--color-ink-600);
}

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

Side by side

With Accent (default)

Without Accent (:accent="False")

Wi-Fi
Wi-Fi

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)
app.css
:root {
    --radius-control: 0.5rem;      /* form controls */
    --radius-box: 0.75rem;  /* structural surfaces */
}

Surfaces

Surface tokens set the page base and the raised surfaces drawn on top of it. The faint tinted base lets white cards and menus read as elevated:

  • --color-bg - page base background
  • --color-surface - raised surfaces (cards, dialogs, menus)
  • --color-input-bg - form field fill (transparent in light so fields are border-defined; a faint lift in dark)
app.css
:root {
    --color-bg: var(--color-ink-50);     /* page base */
    --color-surface: #ffffff;             /* cards, dialogs, menus */
    --color-input-bg: transparent;        /* form fields (border-defined) */
}

.dark {
    --color-bg: var(--color-ink-900);
    --color-surface: var(--color-ink-800);
}

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-900 ink-100 Fill and border for :accent="False" controls; defaults to ink.
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
Surfaces
--color-bg ink-50 ink-900 Page base background
--color-surface white ink-800 Raised surfaces: cards, dialogs, menus
--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 2px 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

Advanced: Changing the Base Gray

Cotton UI uses Tailwind's built-in zinc for grays, which works with zero configuration. If you prefer a different gray family, remap zinc to another palette:

app.css
/* Remap zinc to slate */
@theme {
    --color-ink-50: var(--color-slate-50);
    --color-ink-100: var(--color-slate-100);
    --color-ink-200: var(--color-slate-200);
    --color-ink-300: var(--color-slate-300);
    --color-ink-400: var(--color-slate-400);
    --color-ink-500: var(--color-slate-500);
    --color-ink-600: var(--color-slate-600);
    --color-ink-700: var(--color-slate-700);
    --color-ink-800: var(--color-slate-800);
    --color-ink-900: var(--color-slate-900);
    --color-ink-950: var(--color-slate-950);
}

Theming approach inspired by Flux UI.