Skip to content

@absoluteui/services/env

Runtime and feature detection, user preferences (reduced motion, contrast, forced colours), online and visibility state.

import { ... } from '@absoluteui/services/env';

Safe answers to “where am I running and what does the user prefer?”: runtime and feature detection, accessibility preferences (reduced motion, contrast, colour scheme), online state, page visibility, and the device’s locale and time zone. Nothing touches window, document, or navigator at import time, and every function returns a documented default on the server or in a worker, so the same code runs during SSR.

How do I respect reduced motion and other accessibility preferences?

Section titled “How do I respect reduced motion and other accessibility preferences?”

userPreferences() reads the CSS media features once; watchPreference calls you when one changes (for example when the user switches the OS to dark mode). Both return defaults without matchMedia.

import { DEFAULT_USER_PREFERENCES, userPreferences, watchPreference } from '@absoluteui/services/env';
const panel = document.createElement('section');
const { reducedMotion, contrast } = userPreferences();
panel.animate([{ opacity: 0 }, { opacity: 1 }], { duration: reducedMotion ? 0 : 200 });
if (contrast === 'more') panel.classList.add('high-contrast');
const stop = watchPreference('reducedMotion', (reduce) => { panel.toggleAttribute('data-reduced-motion', reduce); });
stop();
DEFAULT_USER_PREFERENCES.reducedMotion; // => false // what the server sees

How do I show an offline banner and queue work until the connection returns?

Section titled “How do I show an offline banner and queue work until the connection returns?”

isOnline() reads navigator.onLine; watchOnline calls you on every change. A true value means the device has a network, not that your server is reachable, so still handle request failures.

import { isOnline, watchOnline } from '@absoluteui/services/env';
const banner = document.createElement('div');
banner.setAttribute('role', 'status');
const queued: { url: string; body: string }[] = [];
const submitTimesheet = async (body: string): Promise<void> => {
if (!isOnline()) {
queued.push({ url: '/api/timesheets', body });
banner.textContent = 'You are offline. Your timesheet will be sent when you reconnect.';
return;
}
await fetch('/api/timesheets', { method: 'POST', body });
};
const stop = watchOnline((online) => {
banner.hidden = online;
if (online) for (const item of queued.splice(0)) void fetch(item.url, { method: 'POST', body: item.body });
});

How do I pause polling while the tab is hidden?

Section titled “How do I pause polling while the tab is hidden?”

pageVisibility() and watchVisibility wrap document.visibilityState. interval in @absoluteui/services/scheduler does this for you with pauseWhenHidden: true.

import { pageVisibility, watchVisibility } from '@absoluteui/services/env';
let paused = pageVisibility() === 'hidden';
const stop = watchVisibility((state) => { paused = state === 'hidden'; });
pageVisibility(); // e.g. 'visible'

How do I check for a browser feature before using it?

Section titled “How do I check for a browser feature before using it?”

supports(name) tests capabilities, not user-agent strings, and never throws. isSecureContext() tells you whether Web Crypto and the async clipboard can work at all.

import { isSecureContext, supports } from '@absoluteui/services/env';
const compression = supports('compressionStream') ? 'deflate' : 'store';
const canShare = supports('webShare');
const canEncrypt = isSecureContext() && supports('webCrypto');
supports('broadcastChannel'); // => true // in current browsers

How do I pick a default locale, time zone, and clock?

Section titled “How do I pick a default locale, time zone, and clock?”

Use the device’s settings as defaults only; let users override them in their profile. For working days and holidays, pair the time zone with a business calendar from @absoluteui/helpers/business-calendar.

import { deviceLocale, deviceTimeZone, prefers24Hour } from '@absoluteui/services/env';
const defaults = { locale: deviceLocale(), timeZone: deviceTimeZone(), hour24: prefers24Hour() };
defaults.timeZone; // e.g. 'Asia/Kolkata'
prefers24Hour('de-DE'); // => true
prefers24Hour('en-US'); // => false
  • Do not branch rendering on these values during SSR: the server sees defaults (light scheme, online, visible), so the client’s first render may differ. Apply preferences after hydration or through CSS media queries.
  • rawUserAgentForDiagnostics() is for support logs only; do not parse it for feature decisions.

detectRuntime

Detects the current runtime by capability, not by user-agent string.

function detectRuntime(): Runtime;

Example:

const cacheDir = detectRuntime() === 'node' ? '.cache' : undefined;
detectRuntime(); // e.g. 'browser' in a page, 'node' on the server

deviceLocale

First preferred locale.

function deviceLocale(): string;

Example:

new Intl.NumberFormat(deviceLocale()).format(1234.5); // e.g. '1,234.5'

deviceLocales

Preferred locales (navigator.languages), falling back to the Intl default locale.

function deviceLocales(): string[];

Example:

deviceLocales() // e.g. ['en-GB', 'en']

deviceTimeZone

IANA time zone of the device ('UTC' when unknown).

function deviceTimeZone(): string;

Example:

deviceTimeZone() // e.g. 'Asia/Kolkata'

hardwareHints

Hardware hints for sizing work pools or choosing lighter experiences.

function hardwareHints(): HardwareHints;

Example:

const workers = Math.max(1, (hardwareHints().hardwareConcurrency ?? 2) - 1);

isBrowser

True in a window (main thread) context.

function isBrowser(): boolean;

Example:

isBrowser(); // => true // in a page

isNode

True in Node.js (Bun and Deno report their own runtime).

function isNode(): boolean;

Example:

isNode(); // => false // in a browser page

isOnline

navigator.onLine; true when unknown. A true value means “has a network”, not “the server is reachable”.

function isOnline(): boolean;

Example:

const pending: string[] = [];
if (!isOnline()) pending.push('/api/timesheets');

isSecureContext

True when the context is secure (HTTPS or localhost), which Web Crypto, clipboard and service workers require. Server runtimes without the flag report true (they are not subject to the browser rule).

function isSecureContext(): boolean;

Example:

if (!isSecureContext()) console.warn('Clipboard and encryption need HTTPS.');

isServer

True in a server runtime (Node, Deno, Bun): the SSR case.

function isServer(): boolean;

Example:

const theme = isServer() ? 'light' : matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';

isStandalonePwa

True when running as an installed PWA (display-mode: standalone, or iOS navigator.standalone).

function isStandalonePwa(): boolean;

Example:

const showInstallPrompt = !isStandalonePwa();

isTouchDevice

True when the primary pointer is coarse (touch). Prefer designing for both inputs over branching on this.

function isTouchDevice(): boolean;

Example:

if (isTouchDevice()) document.documentElement.classList.add('touch');

isWorker

True inside a dedicated, shared or service worker.

function isWorker(): boolean;

Example:

if (isWorker()) postMessage({ type: 'ready' });

pageVisibility

document.visibilityState ('visible' without a document).

function pageVisibility(): PageVisibility;

Example:

const shouldPoll = pageVisibility() !== 'hidden';

prefers24Hour

True when the locale’s default clock is 24-hour (hourCycle h23/h24).

function prefers24Hour(locale?: string): boolean;

Example:

prefers24Hour('de-DE'); // => true
prefers24Hour('en-US'); // => false

rawUserAgentForDiagnostics

The raw navigator.userAgent string ('' when absent). For diagnostics and logs only: never branch on it; user-agent strings are frozen, spoofed and unreliable. Use {@link supports} instead.

function rawUserAgentForDiagnostics(): string;

Example:

import { createLogger } from '@absoluteui/services/logger';
createLogger().info('Client', { ua: rawUserAgentForDiagnostics() });

requestPersistentStorage

Asks the browser not to evict this origin’s storage; resolves whether persistence is granted.

function requestPersistentStorage(): Promise<boolean>;

Example:

if (!(await requestPersistentStorage())) console.info('Offline data may be cleared by the browser.');

storageEstimate

Origin storage usage and quota (navigator.storage.estimate); undefined when unsupported or failing.

function storageEstimate(): Promise<StorageEstimateResult | undefined>;

Example:

const est = await storageEstimate();
if (est && est.usage / est.quota > 0.9) console.warn('Offline storage is almost full.');

supports

Feature detection by capability; unknown names return false. share and webShare are aliases.

function supports(feature: Feature): boolean;

Example:

const method = supports('compressionStream') ? 'deflate' : 'store';

userAgentInfo

navigator.userAgentData (low-entropy values only); undefined when the browser does not support it. Prefer {@link supports} over brand checks.

function userAgentInfo(): UserAgentInfo | undefined;

Example:

userAgentInfo()?.brands.some((b) => b.brand === 'Chromium')

userPreferences

Current preferences via matchMedia; {@link DEFAULT_USER_PREFERENCES} without it.

function userPreferences(): UserPreferences;

Example:

const { reducedMotion } = userPreferences();
document.body.animate([{ opacity: 0 }, { opacity: 1 }], { duration: reducedMotion ? 0 : 200 });

viewportSize

Visual viewport size (falls back to the layout viewport; {0, 0} without a window).

function viewportSize(): ViewportSize;

Example:

const { width } = viewportSize();

watchOnline

Calls callback(online) on online/offline events; returns an unsubscribe function.

function watchOnline(callback: (online: boolean) => void): () => void;

Example:

const banner = document.createElement('div');
const stop = watchOnline((online) => { banner.hidden = online; });

watchPreference

Calls callback with the new value whenever one preference changes; returns an unsubscribe function. A no-op (returning a no-op) without matchMedia.

function watchPreference<K extends PreferenceName>(name: K, callback: (value: UserPreferences[K]) => void): () => void;

Example:

const stop = watchPreference('colorScheme', (scheme) => { document.documentElement.dataset['theme'] = scheme; });
stop();

watchVisibility

Calls callback(state) on visibilitychange; returns an unsubscribe function.

function watchVisibility(callback: (state: PageVisibility) => void): () => void;

Example:

let paused = false;
const stop = watchVisibility((s) => { paused = s === 'hidden'; });

DEFAULT_USER_PREFERENCES

Defaults returned when matchMedia is unavailable (SSR, workers).

const DEFAULT_USER_PREFERENCES: UserPreferences;

FEATURE_NAMES

Every feature name {@link supports} accepts.

const FEATURE_NAMES: readonly Feature[];

Example:

FEATURE_NAMES.filter(supports)

HardwareHints

Coarse hardware hints; fields are absent when the browser does not expose them.

interface HardwareHints {
readonly hardwareConcurrency?: number;
readonly deviceMemory?: number;
}

StorageEstimateResult

Result of {@link storageEstimate}.

interface StorageEstimateResult {
readonly usage: number;
readonly quota: number;
}

UserAgentInfo

Low-entropy User-Agent Client Hints.

interface UserAgentInfo {
readonly brands: readonly {
readonly brand: string;
readonly version: string;
}[];
readonly mobile: boolean;
readonly platform: string;
}

UserPreferences

Accessibility and display preferences from CSS media features.

interface UserPreferences {
readonly reducedMotion: boolean;
readonly reducedTransparency: boolean;
readonly contrast: 'more' | 'less' | 'custom' | 'no-preference';
readonly forcedColors: boolean;
readonly colorScheme: 'light' | 'dark';
readonly reducedData: boolean;
}

ViewportSize

Viewport size in CSS pixels.

interface ViewportSize {
readonly width: number;
readonly height: number;
}

Feature

Features understood by {@link supports}.

type Feature = 'indexedDB' | 'localStorage' | 'sessionStorage' | 'broadcastChannel' | 'serviceWorker' | 'webWorker' | 'webCrypto' | 'compressionStream' | 'decompressionStream' | 'fileSystemAccess' | 'clipboard' | 'clipboardItem' | 'share' | 'webShare' | 'intlSegmenter' | 'intlDurationFormat' | 'intlListFormat' | 'intlRelativeTimeFormat' | 'temporal' | 'resizeObserver' | 'intersectionObserver' | 'mutationObserver' | 'storageManager' | 'webLocks' | 'requestIdleCallback' | 'schedulerYield' | 'sendBeacon' | 'viewTransitions' | 'structuredClone' | 'abortSignalAny' | 'matchMedia';

PageVisibility

Page visibility state.

type PageVisibility = 'visible' | 'hidden';

PreferenceName

Name of one preference in {@link UserPreferences}.

type PreferenceName = keyof UserPreferences;

Runtime

JavaScript runtime families recognised by {@link detectRuntime}.

type Runtime = 'browser' | 'worker' | 'node' | 'deno' | 'bun' | 'unknown';