Usage
private readonly platform = inject(WrPlatform);
if (this.platform.isBrowser) {
localStorage.setItem('theme', 'dark');
}
protected readonly dark = this.platform.prefersDark; // Signal<boolean>
protected readonly reduce = this.platform.prefersReducedMotion;Why ngwr provides this
isPlatformBrowser(inject(PLATFORM_ID)) boilerplate in every browser-only code path gets old. One injectable with isBrowser, prefersDark, and prefersReducedMotion signals replaces it.
API
| Name | Description | Type | Default |
|---|---|---|---|
isBrowser / isServer | Synchronous platform check. | boolean | — |
userAgent | navigator.userAgent or null on the server. | string | null | — |
prefersDark | Signal mirroring (prefers-color-scheme: dark). | Signal<boolean> | — |
prefersReducedMotion | Signal mirroring (prefers-reduced-motion: reduce). | Signal<boolean> | — |
WrVisualViewport
A second ngwr/platform service tracking window.visualViewport — the slice of the layout viewport still visible once the on-screen keyboard is accounted for. provideWrOverlay() eagerly instantiates it, so responsive overlay sheets and the command-palette lift above the keyboard out of the box; inject it directly for custom handling. Try it on a phone — focus a text field and watch the value change.
private readonly vv = inject(WrVisualViewport);
// Height (px) the on-screen keyboard hides at the bottom — 0 when there
// is none, on the server, or in browsers without the VisualViewport API.
protected readonly keyboard = this.vv.bottomInset; // Signal<number>
// Also mirrored onto the document root as `--wr-keyboard-inset`, so purely
// presentational surfaces can lift above the keyboard with CSS alone:
// padding-bottom: max(env(safe-area-inset-bottom), var(--wr-keyboard-inset, 0px));| Name | Description | Type | Default |
|---|---|---|---|
bottomInset | Height (px) the on-screen keyboard / browser chrome hides at the viewport bottom. 0 when nothing covers it, on the server, or without the API. | Signal<number> | — |
offsetTop | The visual viewport's top offset (px). | Signal<number> | — |
WrHaptics
A third ngwr/platform service — an SSR-safe wrapper over the Vibration API. Semantic presets (impact, selection, success, warning, error) map to sensible patterns. It is a graceful no-op where unsupported: the server, browsers without navigator.vibrate, and iOS Safari (native-only haptics). Tap a button below on an Android / Chrome touch device to feel it.
private readonly haptics = inject(WrHaptics);
onConfirmDelete(): void {
this.haptics.warning(); // buzz before a destructive action
}
onToggle(): void {
this.haptics.selection(); // subtle tick on a switch / segmented change
}
// No-op off-device: false on the server, in browsers without the Vibration
// API, and on iOS Safari (native-only haptics). Branch on `supported`.
protected readonly canBuzz = this.haptics.supported;| Name | Description | Type | Default |
|---|---|---|---|
supported | Whether the Vibration API is available (browser + navigator.vibrate; false on iOS Safari). | boolean | — |
vibrate(pattern) | Raw pattern — ms, or [buzz, pause, …]. Returns whether it fired; 0 / [] cancels. | (number | readonly number[]) => boolean | — |
impact(strength?) | A physical tap — light / medium / heavy. | ('light'|'medium'|'heavy') => boolean | 'medium' |
selection() | A subtle tick for a discrete change. | () => boolean | — |
success() / warning() / error() | Notification pulses for completed / risky / failed actions. | () => boolean | — |
stop() | Cancel any in-progress vibration. | () => void | — |
See also
- GuideServer-side renderingWhat every browser-shaped API in the library answers during a server render, which two overlay calls are NOT no-ops there, and the two markup shapes that break hydration.
- ServiceWrMediaBreakpoint signals. Like the probes here they never throw on the server — and like them they answer a default rather than the truth.