Install
import { provideWrDensity, WrDensity } from 'ngwr/density';
bootstrapApplication(AppComponent, {
providers: [
provideWrDensity({ defaultDensity: 'sm' }),
],
});
// Switch it later:
const density = inject(WrDensity);
density.set('lg');
density.cycle();Live demo — global
Pick a density. Every densified component on this page updates in place.
Per-subtree override
Drop [wrDensity] on any element to scope an override. The toolbar below stays sm regardless of the page's global density.
<!-- Scope an override to a subtree — descendants get sm, the rest of the app keeps the global value. -->
<aside wrDensity="sm">
<wr-list ...></wr-list>
</aside>Override the multipliers
Each level resolves to --wr-density-y, --wr-density-x, --wr-density-text, and --wr-density-gap. Components multiply their static paddings by these. Retune them by re-declaring the level you are on — [data-wr-density='md'], say — rather than on :root.
/* Override the multipliers in your own stylesheet. */
[data-wr-density='sm'] {
--wr-density-y: 0.5; /* even tighter vertical padding */
--wr-density-x: 0.8;
}Why not :root.provideWrDensity() writes data-wr-density on <html> — including the default md — and the library's own [data-wr-density='md'] block re-declares all four multipliers to 1. That block and your :root rule match the same element at the same specificity, so the one that comes later in the cascade wins, and the library's stylesheet is normally later. The symptom is a retune that half applies: whatever is inside a subtree carrying its own [wrDensity] picks the new number up, everything under <html> keeps the shipped one. Declare on the attribute selector and the question does not arise.
Where the provider goes
WrDensity is root-provided — one instance per application, holding one active level — and it reads WR_DENSITY_CONFIG from the root injector. So provideWrDensity() belongs in the application's own providers: put it in a lazy route and the service never sees it, leaving defaultDensity, storageKey and a renamed attribute at their bootstrap values with nothing reported. To vary density by feature, use the [wrDensity] directive on the subtree, which is what it is for — it writes the attribute on its own element and needs no provider at all.
Why ngwr provides this
Compact/comfortable spacing usually ends up as ad-hoc CSS overrides per screen. Density tokens flip the whole catalog (or a subtree) through one provider switch.
API
| Name | Description | Type | Default |
|---|---|---|---|
WrDensity | Injectable service. | service | — |
current | Active density signal. Read-only — use set(d) / cycle() to write. | Signal<WrDensityValue> | 'md' |
set(density) | Switch the active density. Ignores unknown values. | (d: WrDensityValue) => void | — |
cycle() | Cycle sm, md, lg, touch, then back to sm. | () => void | — |
[wrDensity] | Attribute directive — scope a density override to one subtree. | Directive | — |
CSS tokens | --wr-density-y, --wr-density-x, --wr-density-text, --wr-density-gap — multipliers components apply to their paddings via calc(). | CSS custom property | 1 |
See also
CSS variables
Custom properties ngwr/density publishes. Each default below is declared on the component's own selector, so a :root override is shadowed by it — set them on that selector, on a wrapper you scope yourself, or inline on the element. Unlike the BEM class names, these are the supported way to restyle the component.
| Variable | Default | Declared on |
|---|---|---|
--wr-density-x | 1 | :root +4 variant overrides |
--wr-density-y | 1 | :root +4 variant overrides |