# WrPlatform

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

Source: https://ngwr.dev/reference/services/platform  
Kind: Service

## Usage

```angular-ts
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.

```angular-ts
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.

```angular-ts
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

- [Server-side rendering](https://ngwr.dev/guides/ssr) — What 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.
- [WrMedia](https://ngwr.dev/reference/services/media) — Breakpoint signals. Like the probes here they never throw on the server — and like them they answer a default rather than the truth.
