npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

os-detect

v2.2.1

Published

OS, form-factor, and runtime detection from the user agent — SSR-safe and zero-dependency. Vanilla JS / Vue 3 / React.

Readme

OS Detect

OS Detect

Lightweight OS, form-factor, and runtime detection for browsers, Node.js, and SSR — with React hooks and Vue composables. No dependencies.


Features

  • getOS() — returns a typed string identifier for the current OS; detection priority ensures ChromeOS is never misidentified as Linux
  • Boolean functionsdetectIsIOS(), detectIsMacOS(), detectIsAndroid(), detectIsWindows(), detectIsLinux(), detectIsChromeOS() — all synchronous and cached
  • detectIsWindows11() — async; uses navigator.userAgentData.getHighEntropyValues() in the browser and os.release() in Node.js
  • Device categoryisMobileDevice() and isDesktopDevice() for quick coarse checks
  • getFormFactor()'phone' | 'tablet' | 'desktop' | 'tv', driven by OS and physical screen size (not touch capability — a touchscreen Windows laptop is still 'desktop')
  • detectHasTouch() and getPrimaryInput() — whether the device has a touchscreen at all, and which input type ('mouse' | 'touch') is actually primary right now; the latter updates live on hybrid devices when a keyboard/mouse is attached or detached
  • getPixelRatio() — physical-to-logical pixel ratio, read live
  • getRuntime()'node' | 'browser' | 'webworker', plus standalone detectIsNode(), detectIsBrowser(), detectIsWebWorker(), detectIsElectron(), and detectIsPWA() checks
  • React hooksuseOS(), useIsWindows11(), useFormFactor(), useRuntime(), and the live-updating usePrimaryInput() from os-detect/react
  • Vue composables — the same five, from os-detect/vue, as readonly refs
  • Node.js support — reads process.platform in Node.js (including Node 21+, where the runtime exposes its own synthetic navigator global); detectIsWindows11() uses os.release() build number
  • iPadOS 13+ detection — correctly identifies iPads that send Macintosh in their userAgent via navigator.maxTouchPoints
  • Result cache — every function caches its result after the first call, except getPrimaryInput()/getPixelRatio() (deliberately live — see above)
  • Zero runtime dependencies — no external packages; React and Vue are optional peer deps
  • Tree-shakeable ESM — import only what you use; UMD and CJS bundles also included

How it works: in the browser, navigator.userAgentData.platform is checked first (Chrome 90+ / Edge 90+, not spoofable by userAgent overrides), falling back to navigator.userAgent regex matching for browsers that don't implement it. In Node.js, process.platform is read directly — no userAgent parsing happens server-side.


When you'd reach for this

navigator.userAgent lies more often than you'd think: iPadOS pretends to be a Mac, and naive parsing catches Windows 10 where it's actually Windows 11 — os-detect takes on these edge cases instead of a hand-rolled regexp.

  • Keyboard shortcuts should say Cmd, not Ctrl — A "Ctrl+K" hint looks out of place on a Mac, and "⌘K" looks wrong on Windows. The package figures out which operating system it's running on, without parsing the user agent string by hand.
  • An iPad pretends to be a desktop Mac — Since iPadOS 13, the browser reports itself as "Macintosh," and a naive check would mistake a tablet for a laptop. An extra check for touch support tells them apart correctly.
  • A feature only exists on one version of an OS — A new operating-system feature only works on Windows 11 — telling it apart from Windows 10 and older needs to work the same way in the browser and on the server.
  • OS detection shouldn't break server rendering — Code that directly reads browser globals on the server just crashes during server-side rendering — OS detection stays safe for that case and updates itself once the work moves to the browser.
  • Picking a design language for the platform, not just the OS — Windows should feel like Fluent, Android like Material — but a phone and a desktop browser on the same OS still want different layouts. OS and form factor together answer both questions instead of guessing from screen width alone.
  • A hybrid device's keyboard gets attached or detached mid-session — A Surface Pro (or a foldable) can go from touch-only to mouse-primary without a reload. Checking navigator.maxTouchPoints once at load time misses that entirely — the primary-input check here updates live instead.

Installation

| Environment | Minimum version | | ----------- | ---------------------------------------------------- | | Node.js | 18+ | | React | 17+ (optional — only needed for os-detect/react) | | Vue | 3+ (optional — only needed for os-detect/vue) |

npm install os-detect

React hooks (optional peer dependency):

npm install react@>=17

Vue composables (optional peer dependency):

npm install vue@>=3

A prebuilt UMD bundle is also available via unpkg/jsDelivr — no build step required:

<script src="https://unpkg.com/os-detect/dist/index.umd.js"></script>
<script>
  console.log(OsDetect.getOS());
</script>

Quick start

import {
  getOS,
  detectIsIOS,
  detectIsWindows,
  isMobileDevice,
  getFormFactor,
  getRuntime,
} from 'os-detect';

console.log(getOS()); // 'windows' | 'macos' | 'ios' | 'android' | 'linux' | 'chromeos' | 'unknown'
console.log(detectIsIOS()); // true on iPhone / iPad
console.log(detectIsWindows()); // true on Windows desktop
console.log(isMobileDevice()); // true on iOS or Android
console.log(getFormFactor()); // 'phone' | 'tablet' | 'desktop' | 'tv' | 'unknown'
console.log(getRuntime()); // 'node' | 'browser' | 'webworker' | 'unknown'

All functions are synchronous and cached — safe to call on every render or in any reactive context.

More examples

Vanilla JS

Tells Windows 11 from Windows 10, not just "Windows"

Most OS detectors stop at the userAgent string — this one asks the browser's own Client Hints API (or os.release() in Node) to actually know.

import { detectIsWindows11 } from 'os-detect';

const isWin11 = await detectIsWindows11(); // true only on Windows 11

Choosing a design language from OS + form factor together

getFormFactor() looks at OS and physical screen size, not touch capability — a touchscreen Windows laptop still comes back 'desktop', not 'tablet'.

import { getOS, getFormFactor } from 'os-detect';

if (getOS() === 'windows' && getFormFactor() === 'desktop') {
  loadFluentDesignSystem();
} else if (getOS() === 'android') {
  loadMaterialDesignSystem();
}

Telling a Node.js script apart from an Electron app

getRuntime() answers "am I in a browser, Node, or a Web Worker" — detectIsElectron() narrows a 'browser' (or 'node', for Electron's main process) result further.

import { getRuntime, detectIsElectron } from 'os-detect';

if (getRuntime() === 'browser' && detectIsElectron()) {
  console.log('Running inside an Electron window');
}

Vue

The same thing, as a reactive composable

useOS() from os-detect/vue returns a readonly Ref — detection is synchronous and cached, same as the base function.

<script setup lang="ts">
import { useOS } from 'os-detect/vue';

const os = useOS(); // Readonly<Ref<OS>>
</script>

<template>
  <p>Running on {{ os }}</p>
</template>

Windows 11 as a ready-made reactive Ref

useIsWindows11() starts as null and resolves itself to true/false once the async detection inside onMounted completes.

<script setup lang="ts">
import { useOS, useIsWindows11 } from 'os-detect/vue';

const os = useOS(); // Readonly<Ref<OS>>
const isWin11 = useIsWindows11(); // Readonly<Ref<boolean | null>>
</script>

<template>
  <p v-if="isWin11 === null">Detecting Windows version…</p>
  <p v-else-if="isWin11">Windows 11</p>
  <p v-else-if="os === 'windows'">Windows 10 or older</p>
  <p v-else>OS: {{ os }}</p>
</template>

A primary-input hint that updates itself live

usePrimaryInput() starts at 'unknown', resolves once mounted, and updates again on its own if a hybrid device's keyboard/mouse is attached or detached mid-session — no manual event listeners.

<script setup lang="ts">
import { usePrimaryInput } from 'os-detect/vue';

const input = usePrimaryInput(); // Readonly<Ref<'mouse' | 'touch' | 'unknown'>>
</script>

<template>
  <p v-if="input === 'touch'">Showing larger tap targets</p>
</template>

React

The same hook, as React

useOS() from os-detect/react — the value is computed once and stable across re-renders.

import { useOS } from 'os-detect/react';

function Banner() {
  const os = useOS(); // 'windows' | 'macos' | 'ios' | ...

  return <p>Running on {os}</p>;
}

Windows 11 detection inside useEffect

useIsWindows11() starts the async detection inside useEffect and updates state once it resolves — null while the check is in progress.

import { useIsWindows11 } from 'os-detect/react';

function WindowsBadge() {
  const isWin11 = useIsWindows11(); // null → true | false

  if (isWin11 === null) return <p>Detecting Windows version…</p>;
  return <p>{isWin11 ? 'Windows 11' : 'Windows 10 or older'}</p>;
}

A primary-input hint that updates itself live

usePrimaryInput() starts at 'unknown', resolves once mounted, and updates again on its own if a hybrid device's keyboard/mouse is attached or detached mid-session — no manual event listeners.

import { usePrimaryInput } from 'os-detect/react';

function TapTargets() {
  const input = usePrimaryInput(); // 'mouse' | 'touch' | 'unknown'

  return input === 'touch' ? <BigButtons /> : <CompactButtons />;
}

Documentation & links


License

MIT


💖 Support the project

Open source takes time and effort. If this library saves you time or brings value, consider supporting further development.

Thank you for being part of this journey. ❤️