Skip to content

Reference ​

Architecture ​

os-detect
│
├── src/utils/platform.ts
│     getUADataPlatform()    → navigator.userAgentData.platform (Chrome/Edge)
│     isNodeNavigator()      → distinguishes Node's own synthetic navigator
│                                (Node.js 21+) from a real browser navigator
│     getNodePlatform()      → process.platform (Node.js only)
│
├── src/utils/cache.ts
│     memoizeBoolean()       → wraps a sync detector with a one-time cache
│     memoizeAsyncBoolean()  → same, for the async detectIsWindows11();
│                                also shares one in-flight promise across
│                                concurrent callers before it resolves
│     resetDetectionCache()  → clears every cache registered via either helper
│
├── src/detectors/ios.ts        detectIsIOS()       → UA + maxTouchPoints (iPadOS 13+ aware)
├── src/detectors/macos.ts      detectIsMacOS()      → UAData / UA / process.platform=darwin
├── src/detectors/android.ts    detectIsAndroid()    → UAData / UA / process.platform=android
├── src/detectors/chromeos.ts   detectIsChromeOS()   → UAData / UA (browser only)
├── src/detectors/linux.ts      detectIsLinux()      → UAData / UA / process.platform=linux
│                                                        (excludes Android and ChromeOS)
├── src/detectors/windows.ts    detectIsWindows()    → UAData / UA / process.platform=win32
│                                detectIsWindows11()  → async, memoized via memoizeAsyncBoolean()
│                                  browser: userAgentData.getHighEntropyValues(['platformVersion'])
│                                           platformVersion >= 13.0.0 → Windows 11
│                                  Node.js: import('os').release() build number >= 22000
│
├── src/detectors/touch.ts      detectHasTouch()     → maxTouchPoints / msMaxTouchPoints / ontouchstart
├── src/detectors/tv.ts         detectIsTV()         → UAData platform='tv' / UA (Tizen, webOS, etc.)
├── src/detectors/node.ts       detectIsNode()       → process.versions.node
├── src/detectors/browser.ts    detectIsBrowser()    → typeof window/document !== 'undefined'
├── src/detectors/webworker.ts  detectIsWebWorker()  → self && !window && importScripts
├── src/detectors/electron.ts   detectIsElectron()   → process.versions.electron / UA 'Electron/'
├── src/detectors/pwa.ts        detectIsPWA()        → matchMedia display-mode / navigator.standalone
│                                                        / android-app:// referrer
│
├── src/index.ts  (core entry point — re-exports the detectors above, plus:)
│     getOS()               → calls detectors in priority order
│     isMobileDevice()      → detectIsIOS() || detectIsAndroid()
│     isDesktopDevice()     → detectIsMacOS() || detectIsWindows()
│                               || detectIsLinux() || detectIsChromeOS()
│     getFormFactor()       → detectIsTV() / OS + screen dimensions
│     getRuntime()          → detectIsWebWorker() → detectIsBrowser() → detectIsNode()
│     getPrimaryInput()     → matchMedia('(pointer: …)'), NOT cached
│     getPixelRatio()       → window.devicePixelRatio, NOT cached
│     detectIsiOS()         → deprecated alias for detectIsIOS()
│
├── src/react.ts  (os-detect/react)
│     useOS()              → useState(() => getOS())
│     useIsWindows11()     → useState(null) + useEffect → detectIsWindows11()
│     useFormFactor()      → useState(() => getFormFactor())
│     useRuntime()         → useState(() => getRuntime())
│     usePrimaryInput()    → useState('unknown') + useEffect → matchMedia change listeners
│
└── src/vue.ts  (os-detect/vue)
      useOS()              → readonly(ref(getOS()))
      useIsWindows11()     → readonly(ref(null)) + onMounted → detectIsWindows11()
      useFormFactor()      → readonly(ref(getFormFactor()))
      useRuntime()         → readonly(ref(getRuntime()))
      usePrimaryInput()    → readonly(ref('unknown')) + onMounted/onUnmounted →
                                matchMedia change listeners

SSR compatibility ​

The core entry point is safe to call during server-side rendering — Node.js detection reads process.platform directly (including on Node.js 21+, where the runtime's own synthetic navigator global is correctly recognized and doesn't shadow the Node.js path).

What SSR frameworks need to watch for is hydration mismatches, not incorrect Node.js detection: getOS() returns the server's OS during server rendering, which can differ from the client's. Any UI branching on getOS()/useOS() should defer to the client if it must exactly match — see the SSR sections on React Hooks and Vue Composables for the exact pattern. detectIsWindows11()/useIsWindows11() are unaffected — they're async and only ever resolve on the client.

Functions that require a real browser environment (detectIsIOS(), detectIsChromeOS(), detectHasTouch(), detectIsTV(), detectIsPWA()) always return false in Node.js — there is no userAgent, maxTouchPoints, or matchMedia to read server-side. getFormFactor() and getRuntime() still resolve to a real value in Node.js ('unknown' and 'node' respectively, in a plain script) since they don't depend on browser-only globals for every branch.

getPrimaryInput() and getPixelRatio() are the two exceptions to "everything is cached" above — see How It Works for why, and the Vue SSR notes for the same hydration-mismatch caveat useOS() already has, which useFormFactor()/useRuntime() also carry.

Bundle size & peer dependencies ​

Entry pointPeer depsFormatNotes
os-detect—ESM, CJS, UMDCore — all detection functions and the OS/FormFactor/Runtime/PrimaryInput types
os-detect/reactreact >=17ESM, CJSuseOS, useIsWindows11, useFormFactor, useRuntime, usePrimaryInput hooks
os-detect/vuevue >=3ESM, CJSThe same five, as composables

All three entry points are listed in package.json exports, each with its own real .d.ts. The React and Vue adapters add no runtime logic beyond the hooks/composables themselves — they call the same cached core functions.

The package has zero runtime dependencies. React and Vue are optional peer dependencies — you only need them if you use the corresponding entry point.

Migration ​

detectIsiOS() was renamed to detectIsIOS() (capital OS) for consistency with the rest of the API.

The old name still works in v2 but logs a deprecation warning (once per session as of v2.2.1) and will be removed in v3.0:

ts
detectIsiOS() // ⚠ deprecated — logs console.warn once per session
detectIsIOS() // ✓ use this instead

No other breaking changes between v1 and v2.

License ​

MIT