Service

WrPlatform

SSR-safe environment probes. Use to gate browser-only code paths and react to user preferences.

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;
isBrowser: false
prefersDark: false
prefersReducedMotion: false
Reflects your OS / browser preferences.

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

NameDescriptionTypeDefault
isBrowser / isServerSynchronous platform check.boolean
userAgentnavigator.userAgent or null on the server.string | null
prefersDarkSignal mirroring (prefers-color-scheme: dark).Signal<boolean>
prefersReducedMotionSignal 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));
bottomInset (keyboard): 0px
offsetTop: 0px
0 on desktop / without the VisualViewport API.
NameDescriptionTypeDefault
bottomInsetHeight (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>
offsetTopThe 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;
Impact lightImpact mediumImpact heavySelectionSuccessWarningError
supported: false — this reflects only whether the Vibration API exists, not whether the device can buzz: desktops often report supported yet have no vibration hardware, while iOS Safari lacks the API entirely.
NameDescriptionTypeDefault
supportedWhether 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