Colors & Surfaces
light-dark() is the sole dark mode mechanism - no second dark stylesheet, no .dark class. ONE value per token covers both light and dark.
Semantic Tokens
| Token | Purpose |
|---|---|
--ncss-color-brand / -brand-2 | Brand / secondary color |
--ncss-color-neutral | Base gray tone |
--ncss-color-success / -warning / -danger | Status colors |
--ncss-color-bg / -bg-subtle | Page background, subtly offset |
--ncss-color-surface | Cards/panels |
--ncss-color-text / -text-muted | Body text, muted text |
--ncss-color-border | Borders |
--ncss-color-focus-ring | Focus ring |
--ncss-color-overlay | Modal/dialog backdrop (transparent) |
Graded Scales
Each of the six color families (brand, brand-2, neutral, success, warning, danger) has derived steps, computed via color-mix() from EXACTLY ONE base value:
| Suffix | Meaning |
|---|---|
-100 / -300 | Lighter/subtler (badge/chip background) |
-500 | The base color itself (alias) |
-700 / -900 | Darker/bolder, stays dark in BOTH modes |
-on-soft | Matching text color for a -100/-300 surface |
Change a brand color in theme.css, the whole scale follows automatically - see Theming.
Colored Surfaces
helpers/surfaces.css: .ncss-surface--brand / --brand-2 / --neutral - background + guaranteed matching text color (900 step + #fff) in a single class, instead of an inline style with hand-maintained contrast.
<div class="ncss-surface--brand">Always readable contrast</div>
Web Awesome on colored surfaces: a <wa-input> or similar renders its label/value in its own shadow DOM - color alone doesn't reach it. webawesome-bridge.css bridges --wa-form-control-label-color and others automatically as soon as .ncss-surface--* or .ncss-text-light/-dark are in play.
Forced Light/Dark Text
.ncss-text-light/-dark (+ -100/-300/-700/-900 as opacity steps) - fixed to white/black, INDEPENDENT of the theme. For surfaces whose color doesn't run through ncss tokens (photo, video poster). UIkit's uk-light counterpart.
Manual Theme Override
[data-theme="light"]/[data-theme="dark"] on <html> forces a color scheme independent of the system setting (e.g. for a UI toggle). Locally on a single element: .ncss-scheme-dark/-light (helpers/surfaces.css).
Toggle with a remembered choice (opt-in)
js/theme-toggle.js + components/theme-toggle.css turn this into a ready-made toggle: the choice is stored in localStorage (“ncss-theme”); without a choice the system setting applies (live, if it changes). js/theme-toggle-early.js - loaded synchronously in the <head> BEFORE the CSS - applies the choice before the page is painted, so nothing flashes; it is a separate file instead of an inline snippet so a strict CSP without 'unsafe-inline' keeps working. Both also set [data-theme-resolved] = the actually active mode (also for “System”), e.g. to show the matching icon.
<script src="dist/js/theme-toggle-early.js"></script>
<link rel="stylesheet" href="dist/ncss.css">
<script src="dist/js/theme-toggle.js" defer></script>
<!-- icon button: light <-> dark -->
<button type="button" class="ncss-theme-toggle" data-ncss-theme-toggle>
<span class="ncss-icon ncss-icon-moon ncss-theme-icon-dark" aria-hidden="true"></span>
<span class="ncss-icon ncss-icon-sun ncss-theme-icon-light" aria-hidden="true"></span>
</button>
<!-- cycle: system -> light -> dark -->
<button type="button" data-ncss-theme-toggle="cycle">Theme: <span data-ncss-theme-label>System</span></button>
The icon button gets aria-pressed and a changing aria-label (“Switch to dark theme”/“Switch to light theme”, German by <html lang>, overridable via data-label-dark/-light). If the new choice equals the system setting again, the stored choice is removed. Colors only cross-fade while switching (.ncss-theme-switching, off under prefers-reduced-motion). Live: demo.