@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';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.
Light / dark / auto
The page follows the OS by default. WrTheme switches it and remembers the choice.
// 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 screenOn 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.
<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
| Symptom | Fix |
|---|---|
withDensity() and [wrDensity] change nothing | Add @use 'ngwr/density'. |
| A rebranded subtree keeps the old hue on hover and soft fills | Use theme.rebrand(), not a hand-set --wr-color-*. |
A light-dark() declaration is ignored | It takes colours only. |
A global .wr-card rule has no effect | Card, 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 update | Key on .wr-* class names, not on element nesting. |
See also
Service WrThemeSwitch between light, dark and auto, and keep the choice.Guide ColorsThe --wr-color-* tokens, and which one to use for fills, text and borders.Guide DensityMake every control tighter or roomier with one setting.Guide MotionEasing and duration tokens for your transitions and animations.Guide Server-side renderingPrerender and hydrate an ngwr app without mismatches.Docs ConfigurationSet app-wide options at bootstrap.Guide OverlayStyle ngwr's floating panels and control where focus goes.