w6w docs

Theming

@w6w/ui ships a complete light and dark theme as CSS custom properties. This page shows how to make the components follow your app’s own mode, and how to recolor them without forking the stylesheet.

Before you start

  • @w6w/ui/styles.css (or @w6w/ui/code.css) is imported once in your app. See the overview.

1. Tell the components which mode your app is in

Pass your app’s resolved mode to W6WUIProvider:

import { W6WUIProvider } from "@w6w/ui";

<W6WUIProvider api={api} theme={isDark ? "dark" : "light"}>
  <YourApp />
</W6WUIProvider>

Re-render with the new value when the user toggles, and every component switches without a remount.

You’ll know it worked when a visitor whose OS is set to dark mode sees the components in your page’s light theme (and vice versa). Open DevTools: the provider renders a <div data-theme="light" style="display: contents"> around your tree. display: contents keeps that wrapper out of your layout, so flex and grid gaps behave as if it weren’t there.

Good to know: without theme, the components follow the visitor’s OS setting, not your page. A dark-mode visitor on a light page gets dark modals and dark icon swatches inside it. If your app has any theme of its own, pass it.

How the mode is chosen

Most specific wins:

  1. A theme prop on the component itself (AppIcon, CodeEditor, JsonEditor, YamlEditor, and the components that pass it to their icons: AppPicker, AddConnectionModal, StepBuilderModal).
  2. <W6WUIProvider theme>.
  3. data-theme="light" or data-theme="dark" on <html>. For the CSS, any ancestor element works.
  4. The visitor’s prefers-color-scheme.

The CSS and the components that branch in JavaScript (app icons picking a dark variant, the code editors picking a dark highlight theme, the workflow canvas) follow the same order, so they never disagree.

If your app already sets data-theme="light|dark" on <html>, that works too, but the provider prop is the supported contract: it doesn’t depend on your page happening to use the same attribute.

2. Recolor with your own tokens

Every default is declared with :where(...), which has zero specificity. Any rule you write, even a plain :root { }, wins without !important:

/* One token, both modes */
:root {
  --w6w-accent: #0f766e;
  --w6w-radius: 6px;
}

/* A mode-specific override */
[data-theme="dark"] {
  --w6w-panel: #101418;
}

Load your overrides after @w6w/ui/styles.css, or anywhere: specificity already favors yours.

color-scheme belongs to your page. @w6w/ui/styles.css never sets color-scheme on <html>, whatever data-theme is placed there, so importing it can’t change your page’s canvas, scrollbars or native controls. Instead the resolved mode is exposed as an inherited custom property, --w6w-scheme (light or dark, set alongside the colour tokens), and every element with a w6w- class, plus the native controls inside them, sets color-scheme: var(--w6w-scheme). The nearest data-theme ancestor wins, so a light panel inside a dark page (or the reverse) gets the right native controls. If you want your page’s scheme to track the mode, set color-scheme on <html> yourself.

Only elements whose class list has a token starting w6w- get color-scheme; your own elements are never matched, including a data-theme wrapper of yours, a class that merely contains w6w- (my-w6w-card), and a control of yours nested in a ui component (it inherits your color-scheme). A ui component whose root has no w6w- class inherits the host’s color-scheme instead: today that is Combobox (its root is an unclassed div; its input carries only the caller’s className).

You’ll know it worked when primary buttons (.w6w-btn) and the border of a focused input switch to your accent.

Color tokens

TokenLightDarkUsed for
--w6w-bg#f7f8fa#0f1115Page background behind panels
--w6w-panel#ffffff#181b22Cards, modals, inputs
--w6w-panel-2#f0f2f6#1f232cSecondary surfaces, code blocks
--w6w-border#d9dee5#2a2f3aBorders and dividers
--w6w-text#1a1d24#e6e8eeBody text
--w6w-muted#5f6875#9aa3b2Secondary text, hints
--w6w-accent#3355e6#5b8cffPrimary actions, links, focus
--w6w-danger#c1362f#ff6b6bDestructive actions, errors
--w6w-success#2e9e5b#3fbf77Success states
--w6w-health-okvar(--w6w-success)var(--w6w-success)Health “Operational”
--w6w-health-degraded#c77700#f0a020Health “Degraded”
--w6w-health-downvar(--w6w-danger)var(--w6w-danger)Health “Down”
--w6w-health-unknown#5f6875#9aa3b2Health “Unknown”
--w6w-icon-swatch#f0f2f6#1f232cTile behind app icons
--w6w-radius10px10pxCorner radius of panels and controls

Code highlighting tokens

CodeBlock colors its tokens from these, so a recolor reaches code too:

TokenLightDark
--w6w-code-comment#6b7280#7d8797
--w6w-code-keyword#8250df#d2a8ff
--w6w-code-string#0a7b34#7ee787
--w6w-code-number#b3541e#ffab70
--w6w-code-function#1f6feb#79c0ff
--w6w-code-punctuation#5f6875#9aa3b2
--w6w-code-variable#953800#ffa657

The defaults come from the W6W brand palette. Spacing, type sizes and fonts are tokens too; see Design system.

3. Restyle a single component

Every component renders stable, global .w6w-* class names (the stylesheet deliberately doesn’t use CSS Modules, so they never get hashed). Target them from your own CSS when a token isn’t enough:

.w6w-modal {
  box-shadow: 0 24px 48px rgb(0 0 0 / 0.25);
}

Prefer tokens where one exists: they follow the mode for you, while a hard-coded color in a class override is the same in light and dark.

Troubleshooting

SymptomCauseWhat to do
Components are dark on your light page (or the reverse)No theme on the provider, and no data-theme on an ancestor, so the OS preference wins.Pass theme to W6WUIProvider.
App icons or the code editor stay in the old mode after a toggle, while the CSS switchedYou set data-theme on an element other than <html>. The CSS follows any ancestor, but the JavaScript side only watches <html> and the provider.Pass theme to W6WUIProvider instead.
Your --w6w-* override does nothingIt’s set on an element that isn’t an ancestor of the component, such as a sibling.Set it on :root or on a wrapper around the components.

Where to next