Service

WrDensity

Density scale — sm / md / lg / touch — applied as CSS-variable multipliers on per-component paddings. md is the default and resolves to 1 everywhere; touch enlarges hit areas for finger input. Persisted via WrStorage and overrideable per-subtree with the [wrDensity] directive.

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.

Active density: md

ngwrdensity

Per-subtree override

Drop [wrDensity] on any element to scope an override. The toolbar below stays sm regardless of the page's global density.

scoped
<!-- 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

NameDescriptionTypeDefault
WrDensityInjectable service.service
currentActive 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 property1

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.

VariableDefaultDeclared on
--wr-density-x1:root +4 variant overrides
--wr-density-y1:root +4 variant overrides