A modern React Hook for detecting browser, device, and rendering engine information using the W3C User-Agent Client Hints API with automatic fallback to User-Agent string parsing.
- π Modern API First: Uses User-Agent Client Hints API when available
- π Automatic Fallback: Gracefully falls back to User-Agent string parsing for older browsers
- π― Accurate Detection: Supports modern browsers including Edge, Brave, Samsung Internet, Vivaldi, and Arc
- π± Device Classification: Distinguishes between mobile phones, tablets, and desktops
- π Privacy-Conscious: Respects browser privacy features with optional high-entropy data requests
- π SSR-Safe: Handles server-side rendering environments gracefully
- π¦ TypeScript Support: Full type definitions included
- β‘ Performance Optimized: Single detection per mount, efficient state updates
NPM:
npm install react-hook-useagentYarn:
yarn add react-hook-useagentBun:
bun add react-hook-useagentimport { useUserAgent } from "react-hook-useagent";
function MyComponent() {
const agent = useUserAgent();
return (
<div>
<p>Browser: {agent.browser?.name} {agent.browser?.version}</p>
<p>Platform: {agent.device?.platform}</p>
<p>Mobile: {agent.device?.isMobile ? 'Yes' : 'No'}</p>
<p>Device Type: {agent.deviceType}</p>
</div>
);
}import { useUserAgent } from "react-hook-useagent";
const MyComponent: React.FC = () => {
const agent = useUserAgent();
// Access individual properties
console.log(agent.browser?.name); // "Chrome"
console.log(agent.browser?.version); // "120.0.0.0"
console.log(agent.device?.isMobile); // false
console.log(agent.device?.platform); // "Windows"
console.log(agent.deviceType); // "desktop"
console.log(agent.renderingEngine?.name); // "Blink"
};const { device, browser, renderingEngine, deviceType } = useUserAgent();
if (device?.isMobile) {
// Mobile-specific logic
}
if (browser?.name === "Safari") {
// Safari-specific logic
}Request detailed device information (requires user permission in some browsers):
const agent = useUserAgent({ highEntropy: true });
console.log(agent.device?.architecture); // "x86"
console.log(agent.device?.model); // "Pixel 5"
console.log(agent.device?.platformVersion); // "13.0.0"
console.log(agent.browser?.fullVersion); // "120.0.6099.109"Request only specific high-entropy values:
const agent = useUserAgent({
highEntropy: true,
hints: ['architecture', 'model']
});Available hints:
architecture- CPU architecture (e.g., "x86", "arm")model- Device model (e.g., "Pixel 5")platform- Detailed platform infoplatformVersion- Platform versionuaFullVersion- Full browser version
The hook is SSR-safe and returns undefined values during server-side rendering:
import { useUserAgent, isSSR } from "react-hook-useagent";
function MyComponent() {
const agent = useUserAgent();
if (isSSR()) {
return <div>Loading...</div>;
}
return <div>Browser: {agent.browser?.name}</div>;
}This hook is marked with "use client" and can only be used in Client Components. User-agent detection requires browser APIs (navigator.userAgent, navigator.userAgentData) that are only available client-side.
Usage in Next.js App Router:
"use client";
import { useUserAgent } from "react-hook-useagent";
export default function ClientComponent() {
const agent = useUserAgent();
return (
<div>
<p>Browser: {agent.browser?.name}</p>
<p>Device: {agent.deviceType}</p>
</div>
);
}Using in a Server Component:
If you need to use this hook within a Server Component tree, create a wrapper Client Component:
// components/UserAgentDisplay.tsx
"use client";
import { useUserAgent } from "react-hook-useagent";
export function UserAgentDisplay() {
const agent = useUserAgent();
return <div>Browser: {agent.browser?.name}</div>;
}// app/page.tsx (Server Component)
import { UserAgentDisplay } from "@/components/UserAgentDisplay";
export default function Page() {
return (
<div>
<h1>My Server Component</h1>
<UserAgentDisplay />
</div>
);
}Why Client-Only?
User-agent detection is inherently client-side because:
- Browser information only exists in the browser environment
navigatorAPIs are not available on the server- React hooks (
useState,useEffect) require Client Components
This is standard behavior for all browser detection libraries in the React ecosystem.
The useUserAgent hook returns an Agent object with the following structure:
{
device?: {
isMobile: boolean;
platform: "Android" | "iOS" | "Windows" | "Linux" | "Mac OS" | "Chrome OS" | "Unknown";
device: "Android" | "iPhone" | "iPad" | "iPod" | "Desktop PC" | "Tablet" | "Unknown";
// High-entropy values (optional)
architecture?: string; // e.g., "x86", "arm"
model?: string; // e.g., "Pixel 5"
platformVersion?: string; // e.g., "13.0.0"
};
browser?: {
name: "Chrome" | "Edge" | "Firefox" | "Safari" | "Brave" | "Samsung Internet" |
"Vivaldi" | "Arc" | "Opera" | "Chromium" | "Seamonkey" | "Opera15+" |
"Opera12-" | "Unknown";
version: string;
fullVersion?: string; // From high-entropy Client Hints
};
renderingEngine?: {
name: "Blink" | "Gecko" | "WebKit" | "Unknown";
version: string;
};
detectionMethod?: "client-hints" | "user-agent-string" | "ssr";
deviceType?: "mobile" | "tablet" | "desktop";
}The User-Agent Client Hints API is a modern W3C standard that provides structured browser and device information in a privacy-preserving manner. It replaces the legacy User-Agent string with a more controlled API.
- Privacy: Reduces passive fingerprinting by limiting default information
- Structured Data: Provides clean, structured data instead of parsing strings
- Future-Proof: Aligns with browser vendors' privacy initiatives
- Opt-in Details: Detailed information requires explicit permission
-
Low-Entropy Values (default): Basic information provided automatically
- Browser brands and versions
- Mobile flag
- Platform
-
High-Entropy Values (opt-in): Detailed information requiring permission
- Device architecture
- Device model
- Platform version
- Full browser version
| Browser | Client Hints Support | High Entropy Support |
|---|---|---|
| Chrome 90+ | β Yes | β Yes |
| Edge 90+ | β Yes | β Yes |
| Opera 76+ | β Yes | β Yes |
| Brave 1.26+ | β Yes | β Yes |
| Samsung Internet 15+ | β Yes | β Yes |
| Firefox | β No | β No |
| Safari | β No | β No |
Note: When Client Hints is not available, the library automatically falls back to User-Agent string parsing, ensuring compatibility with all browsers.
Low-Entropy Data (no permission required):
- Browser name and major version
- Mobile/desktop flag
- Platform name
High-Entropy Data (may require permission):
- Exact device model
- CPU architecture
- Full version numbers
- Detailed platform version
The library implements a progressive enhancement strategy:
- Client Hints Available: Uses
navigator.userAgentDatafor modern, structured data - Client Hints Unavailable: Falls back to parsing
navigator.userAgentstring - SSR Environment: Returns undefined values, detection occurs on client-side
This ensures your application works across all browsers and environments without any code changes.
The library accurately detects:
- Chrome - Google Chrome
- Edge - Microsoft Edge (Chromium-based)
- Firefox - Mozilla Firefox
- Safari - Apple Safari
- Brave - Brave Browser
- Samsung Internet - Samsung's mobile browser
- Vivaldi - Vivaldi Browser
- Arc - Arc Browser
- Opera - Opera (15+ and legacy 12-)
- Chromium - Chromium-based browsers
- Seamonkey - Seamonkey Browser
The library correctly distinguishes Chrome from other Chromium-based browsers (Edge, Brave, Opera, etc.) to avoid false positives.
- Mobile: Smartphones (
deviceType: "mobile",isMobile: true) - Tablet: Tablets including iPads (
deviceType: "tablet",isMobile: false) - Desktop: Desktop computers (
deviceType: "desktop",isMobile: false)
Modern iPads (iPadOS 13+) masquerade as Mac computers in their User-Agent string. The library uses navigator.maxTouchPoints to accurately detect iPads:
const agent = useUserAgent();
if (agent.device?.device === "iPad") {
// iPad-specific logic
}The library distinguishes Android tablets from phones by checking for "Android" without "Mobile" in the User-Agent string.
Detected rendering engines:
- Blink: Chromium-based browsers (Chrome, Edge, Brave, Opera, Vivaldi)
- Gecko: Firefox, Seamonkey
- WebKit: Safari
Note: Modern Edge uses Blink, not EdgeHTML. Legacy EdgeHTML detection is maintained for older Edge versions.
Full TypeScript definitions are included:
import {
useUserAgent,
Agent,
BrowserInfo,
DeviceInfo,
RenderingEngineInfo,
UseUserAgentOptions
} from "react-hook-useagent";
const agent: Agent = useUserAgent();
const browser: BrowserInfo | undefined = agent.browser;import { isSSR, areAgentsEqual } from "react-hook-useagent";
// Check if running in SSR
if (isSSR()) {
// Server-side logic
}
// Compare agent objects
if (areAgentsEqual(agent1, agent2)) {
// Agents are equal
}For advanced use cases, you can use the low-level detection functions directly:
import {
detectFromClientHints,
detectBrowser,
detectDevice,
detectRenderingEngine
} from "react-hook-useagent";
// Direct Client Hints detection
if ('userAgentData' in navigator) {
const agent = await detectFromClientHints(navigator.userAgentData);
}
// Direct UA string detection
const browser = detectBrowser(window.navigator);
const device = detectDevice(window.navigator);
const engine = detectRenderingEngine(browser?.name, window.navigator);See MIGRATION.md for detailed migration instructions.
Quick Summary: v2.0 is backward compatible. No code changes required for basic usage. New features are opt-in.
- Single Detection: Detection runs exactly once per component mount
- Efficient Updates: State updates only occur when agent data changes
- No Re-renders: Optimized comparison prevents unnecessary re-renders
- Async Handling: High-entropy requests don't block initial render
- Prefer Feature Detection: Use User-Agent detection only when feature detection isn't possible
- Minimize High-Entropy Requests: Only request detailed data when necessary
- Handle Undefined Values: Always check for undefined properties
- SSR Awareness: Use
isSSR()utility for conditional logic - Privacy First: Be transparent about data collection with users
function ResponsiveComponent() {
const { deviceType } = useUserAgent();
if (deviceType === 'mobile') {
return <MobileLayout />;
} else if (deviceType === 'tablet') {
return <TabletLayout />;
}
return <DesktopLayout />;
}function BrowserSpecificComponent() {
const { browser } = useUserAgent();
const className = browser?.name === 'Safari'
? 'safari-fix'
: 'default';
return <div className={className}>Content</div>;
}function AnalyticsWrapper() {
const agent = useUserAgent();
useEffect(() => {
if (agent.browser && agent.device) {
analytics.track('page_view', {
browser: agent.browser.name,
platform: agent.device.platform,
deviceType: agent.deviceType,
});
}
}, [agent]);
return <App />;
}This project uses Vitest for testing, including both unit tests and property-based tests using fast-check.
Run all tests once:
npm test
# or
bun testRun tests in watch mode:
npm run test:watch
# or
bun run test:watchRun tests with UI:
npm run test:ui
# or
bun run test:uiRun tests with coverage:
npm run test:coverage
# or
bun run test:coverageThe test suite includes:
-
Unit Tests (
src/__tests__/unit/): Test specific examples and edge casesbrowsers.test.ts- Browser detection testsdevices.test.ts- Device detection testsengines.test.ts- Rendering engine testshook.test.ts- React hook behavior tests
-
Property-Based Tests (
src/__tests__/properties/): Test universal properties across many inputsbrowsers.property.test.ts- Browser detection propertiesdevices.property.test.ts- Device detection propertiesengines.property.test.ts- Engine detection propertiesdetection.property.test.ts- General detection propertiescompatibility.property.test.ts- Cross-browser compatibilityerrors.property.test.ts- Error handling propertieshighEntropy.property.test.ts- High-entropy Client Hintsperformance.property.test.ts- Performance characteristics
Property-based tests use fast-check to generate hundreds of random inputs and verify that correctness properties hold across all of them.
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
- User-Agent Client Hints Specification
- MDN: User-Agent Client Hints API
- Chrome Platform Status: User-Agent Reduction
Hope this helps someone! π
Happy coding π¨βπ»