Skip to content
NativeCSS
Docs contents

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

TokenPurpose
--ncss-color-brand / -brand-2Brand / secondary color
--ncss-color-neutralBase gray tone
--ncss-color-success / -warning / -dangerStatus colors
--ncss-color-bg / -bg-subtlePage background, subtly offset
--ncss-color-surfaceCards/panels
--ncss-color-text / -text-mutedBody text, muted text
--ncss-color-borderBorders
--ncss-color-focus-ringFocus ring
--ncss-color-overlayModal/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:

SuffixMeaning
-100 / -300Lighter/subtler (badge/chip background)
-500The base color itself (alias)
-700 / -900Darker/bolder, stays dark in BOTH modes
-on-softMatching 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.