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 |
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 fillsA 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:
: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.
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:
: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).
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:
:root {
--color-accent-muted: var(--color-ink-600);
}
.dark {
--color-accent-muted: var(--color-ink-400);
}
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 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.
: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 */
}
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 |
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.