@absoluteui/services/env
Runtime and feature detection, user preferences (reduced motion, contrast, forced colours), online and visibility state.
import { ... } from '@absoluteui/services/env';How to use
Section titled “How to use”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 seesHow 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 browsersHow 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'); // => trueprefers24Hour('en-US'); // => falseWhat goes wrong?
Section titled “What goes wrong?”- 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.
API reference
Section titled “API reference”Functions
Section titled “Functions”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 serverdeviceLocale
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 pageisNode
True in Node.js (Bun and Deno report their own runtime).
function isNode(): boolean;Example:
isNode(); // => false // in a browser pageisOnline
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'); // => trueprefers24Hour('en-US'); // => falserawUserAgentForDiagnostics
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'; });Constants
Section titled “Constants”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)Interfaces
Section titled “Interfaces”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';