GuideCoreUpdated in v15

Theming

Load the theme, switch light and dark, and rebrand the palette.

Wire it up

In your global stylesheet: the theme first, then one @use per component you render.

@use 'ngwr/theme';   // first: only the first load can take `with (...)`
@use 'ngwr/density'; // only if you use withDensity() or [wrDensity]

@use 'ngwr/button';
@use 'ngwr/input';

Light / dark / auto

The page follows the OS by default. WrTheme switches it and remembers the choice.

mode: auto · resolved: light

// app.config.ts: optional, for another default mode
provideWr(withTheme({ defaultMode: 'dark' }));

// any component
const theme = inject(WrTheme);
theme.set('dark');  // 'light' | 'dark' | 'auto'
theme.toggle();
theme.resolved();   // 'light' | 'dark', the scheme on screen

On a prerendered site, add the pre-paint script so a stored choice applies before first paint.

One subtree in the other scheme

color-scheme: dark on any element puts its whole subtree in dark.

Always dark
<section style="color-scheme: dark">
  <button wr-btn color="primary">Primary</button>
</section>

<!-- An overlay renders outside the subtree: pass the scheme to its panel. -->
<wr-select panelClass="scheme-dark" [(value)]="size">…</wr-select>

/* global styles.scss */
.scheme-dark { color-scheme: dark; }

Your own dark values

Write both sides in one declaration with light-dark().

.login {
  background: light-dark(var(--wr-color-gray-100), var(--wr-color-gray-800));
  border: 1px solid light-dark(#c7d2fe, #3730a3);
}

// An alpha: mix the token toward transparent.
.login__halo {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--wr-color-primary) 20%, transparent);
}

Rebrand

Set $base-colors for the light scheme, and use theme.rebrand() for dark or for one subtree.

// The first ngwr line. List all five intents: this map replaces the default.
@use 'ngwr/theme' with (
  $base-colors: (
    primary: #6366f1,
    success: #22c55e,
    warning: #f59e0b,
    danger: #f43f5e,
    info: #3472d9,
  )
);

// Dark: the second map.
:root {
  @include theme.rebrand((primary: #6366f1), (primary: #5b5bd6));
}

// One subtree. With one map, the seed applies to both schemes.
.marketing {
  @include theme.rebrand((primary: #be123c));
}

Per-component tokens

Each component has its own --wr-<name>-* hooks, listed on its page.

.wr-btn {
  --wr-btn-radius: 999px;
  --wr-btn-padding-x: 1.5rem;
}

.wr-tag.release {
  --wr-tag-bg: #fef3c7;
  --wr-tag-color: #92400e;
}

Styling one instance

Overlays take panelClass, a toast takes class, and a table takes class per column and rowClass.

<wr-select panelClass="w-[28rem] shadow-2xl" [(value)]="size">…</wr-select>

<wr-table [columns]="columns" [items]="rows" [rowClass]="rowClass" />
readonly columns: WrTableColumns = {
  name: { title: 'Name' },
  amount: { title: 'Amount', class: 'text-right tabular-nums' },
};

readonly rowClass: WrTableRowClass = row => (row['overdue'] ? 'bg-danger-soft' : null);

this.dialog.open(EditCard, { panelClass: 'max-w-3xl' });
this.toast.show({ message: 'Saved', class: 'w-96' });

Gotchas

SymptomFix
withDensity() and [wrDensity] change nothingAdd @use 'ngwr/density'.
A rebranded subtree keeps the old hue on hover and soft fillsUse theme.rebrand(), not a hand-set --wr-color-*.
A light-dark() declaration is ignoredIt takes colours only.
A global .wr-card rule has no effectCard, list and a few others override it; set their --wr-* hook instead.
A panelClass rule does not match Put it in a global stylesheet. On select, dropdown and the pickers, target the panel inside: .my-class .wr-select-panel.
A selector breaks after an updateKey on .wr-* class names, not on element nesting.

See also