Skip to content

riyajath-ahamed/react-unique-id-generator

react-unique-id-generator

Unique IDs for React. Readable. Predictable. SSR-safe.

npm version bundle size license TypeScript

Quick StartProviderHooksSSRAutomationStrategiesCollision DetectionPoolsDelimitersSelectorsMigration


A lightweight, zero-dependency library for generating unique, sequential IDs in React applications. Built for accessibility, form labeling, SSR hydration, and automated testing - with full control over prefixes, suffixes, and counter state. Works with React 16.8+ and supports both client-side and server-side rendering out of the box.

The Problem

React 18's useId() gives you opaque IDs like :r0:, :r1:. You can't customize them, can't scope them, and can't use them outside components. On the server, global counters leak between requests.

This library fixes all of that.

Before / After

React useId

function Form() {
  const id = useId();          // ":r0:" - what?
  return <input id={id} />;   // no prefix, no control
}

react-unique-id-generator

function Form() {
  const id = useUniqueId('email-');  // "email-1" - readable
  return <input id={id} />;         // full control
}

Global state (v1.x)

// Leaks across SSR requests
setGlobalPrefix('app-');
nextId(); // "app-1"
// Another request sees "app-2" ...

Provider-scoped (v2.0)

// Each provider = isolated counter
<IdProvider prefix="app-">
  <Component />  {/* "app-1" */}
</IdProvider>
// Clean. No leaks. SSR-safe.
+---------------------------------------------+
|  ZERO DEPENDENCIES          ============ 0  |
|  SSR-SAFE ISOLATION         ======== Yes    |
|  REACT VERSION SUPPORT      ======== 16.8+  |
|  AUTOMATION STRATEGIES      ======== 4      |
|  ID GENERATION STRATEGIES   ======== 4      |
|  COLLISION DETECTION        ======== Yes    |
|  ID POOL MANAGEMENT         ======== Yes    |
|  CUSTOM DELIMITERS          ======== Yes    |
|  STABLE CSS SELECTORS       ======== Yes    |
|  TEST COVERAGE              ======== 100%   |
|  BUNDLE SIZE                ======== Tiny   |
+---------------------------------------------+

Why react-unique-id-generator?

Feature react-unique-id-generator React useId
Custom prefix/suffix Yes No
Provider-scoped IDs Yes (v2.0) No
SSR-safe with isolation Yes (v2.0) Partial
Automation test IDs Yes (v2.0) No
Server ID manager Yes (v2.0) No
Custom generation strategies Yes (v2.2) No
Collision detection Yes (v2.2) No
ID pool management Yes (v2.2) No
Custom delimiters Yes (v2.2) No
Stable CSS selectors Yes (v2.3) No
SSR request-scoped provider Yes (v2.2) No
Counter reset Yes No
Works in React 16+ Yes React 18+ only
Non-component usage Yes Hooks only
Predictable format Yes (prefix-1) Opaque (:r0:)
Zero runtime deps Yes Built-in

Features

  • Provider-scoped - Isolate ID counters per subtree with <IdProvider> for SSR safety
  • SSR-safe - Dedicated server ID manager and <SSRProvider> prevent cross-request state leaks
  • Custom strategies - Plug in your own ID format with the IdStrategy interface, or use built-in numeric, zero-padded, timestamp, and hash strategies
  • Collision detection - Track generated IDs and detect duplicates with configurable warn/throw/skip actions
  • ID pools - Pre-allocate IDs for high-throughput scenarios with acquire/release semantics
  • Custom delimiters - Join prefix, counter, and suffix with any delimiter (., ::, __, etc.)
  • Stable CSS selectors - Generate unique, human-readable CSS selectors for DOM elements using IDs, data attributes, ARIA roles, and semantic attributes
  • Automation-ready - Generate deterministic data-testid values with multiple naming strategies
  • Lightweight - Minified bundles with zero runtime dependencies
  • Flexible - Global and local prefix/suffix support for fine-grained ID control
  • React-friendly - Works in components, utilities, and outside of hooks
  • TypeScript - Fully typed with declaration files and source maps included
  • Well-tested - Comprehensive test suite with 100% coverage
  • Tree-shakeable - ESM build with sideEffects: false; only ship what you use
  • Dual package - Ships CommonJS (require) and ES module (import) builds

Installation

npm install react-unique-id-generator
yarn add react-unique-id-generator
pnpm add react-unique-id-generator

Quick Start

Provider-based approach (recommended)

Wrap your app (or a subtree) in <IdProvider> for scoped, SSR-safe ID generation:

import { IdProvider, useUniqueId } from 'react-unique-id-generator';

function App() {
  return (
    <IdProvider prefix="app-">
      <Form />
    </IdProvider>
  );
}

function Form() {
  const emailId = useUniqueId('email-');
  const passwordId = useUniqueId('password-');

  return (
    <form>
      <label htmlFor={emailId}>Email</label>
      <input id={emailId} type="email" />
      <label htmlFor={passwordId}>Password</label>
      <input id={passwordId} type="password" />
    </form>
  );
}

Simple approach (legacy)

For quick, non-SSR usage you can still call nextId directly:

import nextId from 'react-unique-id-generator';

function Field({ label }: { label: string }) {
  const id = nextId('field-');
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} type="text" />
    </div>
  );
}

API Reference

Provider API

<IdProvider>

React context provider that scopes ID state (counter, prefix, suffix) to its subtree. Each provider maintains an independent counter, preventing ID collisions between different parts of your app or across SSR requests.

import { IdProvider } from 'react-unique-id-generator';

Props:

Prop Type Default Description
prefix string "" Prefix applied to all IDs generated within the provider
suffix string "" Suffix appended to all IDs generated within the provider
startId number 0 Initial counter value (the first ID will be startId + 1)
children ReactNode - Child components that can consume the provider

Basic usage:

<IdProvider prefix="app-" suffix="-id">
  <YourComponent />
</IdProvider>

Nested providers create independent scopes - each maintains its own counter:

<IdProvider prefix="page-">
  <Header />          {/* useUniqueId() => "page-1" */}
  <IdProvider prefix="modal-">
    <ModalContent />  {/* useUniqueId() => "modal-1" */}
  </IdProvider>
  <Footer />          {/* useUniqueId() => "page-2" */}
</IdProvider>

Parallel providers are fully isolated from each other:

<div>
  <IdProvider prefix="sidebar-">
    <NavItem />   {/* "sidebar-1" */}
    <NavItem />   {/* "sidebar-2" */}
  </IdProvider>
  <IdProvider prefix="main-">
    <Card />      {/* "main-1" */}
    <Card />      {/* "main-2" */}
  </IdProvider>
</div>

useIdContext(): IdContextValue | null

Access the current provider's context value. Returns null when called outside a provider.

import { useIdContext } from 'react-unique-id-generator';

function DebugIds() {
  const ctx = useIdContext();
  if (!ctx) return <p>No provider found</p>;
  return <p>Current prefix: {ctx.prefix}</p>;
}

Hooks

useUniqueId(localPrefix?: string): string

Generates a stable unique ID for a component instance. The ID is created once on mount and remains the same across re-renders. When used inside an <IdProvider>, the ID is scoped to that provider. When used outside a provider, it falls back to global state.

import { useUniqueId } from 'react-unique-id-generator';

function MyComponent() {
  const id = useUniqueId('my-input-');
  return (
    <div>
      <label htmlFor={id}>Name</label>
      <input id={id} />
    </div>
  );
}

The localPrefix argument overrides the provider's prefix for that hook call:

<IdProvider prefix="form-">
  <Input />  {/* useUniqueId()         => "form-1" */}
  <Input />  {/* useUniqueId('email-') => "email-2" */}
</IdProvider>

useUniqueIds(count: number, localPrefix?: string): string[]

Generates multiple stable unique IDs at once. Useful for forms with several labeled elements.

import { useUniqueIds } from 'react-unique-id-generator';

function LoginForm() {
  const [emailId, passwordId] = useUniqueIds(2, 'login-');
  return (
    <form>
      <label htmlFor={emailId}>Email</label>
      <input id={emailId} type="email" />
      <label htmlFor={passwordId}>Password</label>
      <input id={passwordId} type="password" />
    </form>
  );
}

Server-Side Rendering

createServerIdManager(options?): ServerIdManager

Creates an isolated ID manager for server-side rendering. Each request should create its own manager to prevent ID leaks between concurrent requests.

import { createServerIdManager } from 'react-unique-id-generator';

Options:

Option Type Default Description
prefix string "" Default prefix for generated IDs
suffix string "" Default suffix for generated IDs
startId number 0 Initial counter value

ServerIdManager methods:

Method Returns Description
nextId(localPrefix?) string Generate the next ID, optionally overriding the prefix
resetId() void Reset the counter to 0
getCurrentId() number Get the current counter value
setId(id) void Set the counter to a specific value

Example - Express middleware:

import { createServerIdManager } from 'react-unique-id-generator';

app.get('*', (req, res) => {
  const idManager = createServerIdManager({ prefix: 'ssr-' });

  const id1 = idManager.nextId();         // "ssr-1"
  const id2 = idManager.nextId();         // "ssr-2"
  const id3 = idManager.nextId('nav-');   // "nav-3"

  // Render your app using the manager...
});

Example - Next.js API route / server action:

import { createServerIdManager } from 'react-unique-id-generator';

export async function generatePageIds() {
  const ids = createServerIdManager({ prefix: 'page-', suffix: '-el' });

  return {
    heroId: ids.nextId(),      // "page-1-el"
    contentId: ids.nextId(),   // "page-2-el"
    footerId: ids.nextId(),    // "page-3-el"
  };
}

Each call to createServerIdManager produces a fully independent instance - no shared state, no cross-request collisions.


Automation Testing

Tools for generating deterministic, human-readable IDs for data-testid attributes and other testing scenarios.

Pick your naming strategy:

sequential

"Button-1"
"Button-2"

kebab-case

"user-profile-1"
"user-profile-2"

camelCase

"myButton-1"
"myButton-2"

custom

"e2e__Btn__1"
"e2e__Btn__2"

generateAutomationId(componentName, options?): string

Generate a single automation ID with a naming strategy.

import { generateAutomationId } from 'react-unique-id-generator';

generateAutomationId('Button')                                    // "Button-1"
generateAutomationId('UserProfile', { strategy: 'kebab-case' })   // "user-profile-2"
generateAutomationId('myButton', { strategy: 'camelCase' })       // "myButton-3"
generateAutomationId('Btn', { prefix: 'qa-', separator: '_' })   // "qa-Btn_4"

Options:

Option Type Default Description
strategy 'sequential' | 'kebab-case' | 'camelCase' | 'custom' 'sequential' Naming strategy for the component name
prefix string "" Prefix prepended to the ID
separator string "-" Separator between the name and counter
customFn (name: string, index: number) => string - Custom ID generator (required for 'custom' strategy)

Custom strategy example:

generateAutomationId('LoginForm', {
  strategy: 'custom',
  customFn: (name, index) => `e2e__${name}__${index}`,
});
// => "e2e__LoginForm__1"

useAutomationId(componentName, options?): string

React hook version of generateAutomationId. Returns a stable automation ID that persists across re-renders.

import { useAutomationId } from 'react-unique-id-generator';

function SubmitButton() {
  const testId = useAutomationId('SubmitButton', { strategy: 'kebab-case' });
  return <button data-testid={testId}>Submit</button>;
  // => <button data-testid="submit-button-1">Submit</button>
}

AutomationIdPool

Pre-allocates a pool of automation IDs for better performance in large applications with many test IDs.

import { AutomationIdPool } from 'react-unique-id-generator';

const pool = new AutomationIdPool(100, 'component', {
  strategy: 'kebab-case',
  prefix: 'test-',
});

const id1 = pool.acquire();   // "test-component-1"
const id2 = pool.acquire();   // "test-component-2"

pool.release(id1);             // Return id1 to the pool for reuse
const id3 = pool.acquire();   // "test-component-1" (reused)

Constructor:

new AutomationIdPool(poolSize: number, componentName?: string, options?: AutomationIdOptions)

Methods:

Method Returns Description
acquire() string Get the next available ID (auto-expands if exhausted)
release(id) void Return an ID to the pool for reuse
drain() void Clear all available and released IDs
size number Number of available + released IDs in the pool

resetAutomationCounter(): void

Resets the global automation counter to 0. Useful in test setup.

import { resetAutomationCounter } from 'react-unique-id-generator';

beforeEach(() => {
  resetAutomationCounter();
});

Custom ID Strategies

Define how IDs are formatted using the IdStrategy interface. Swap strategies without changing your component code.

IdStrategy interface

interface IdStrategy {
  generate(prefix: string, suffix: string, counter: number): string;
}

Built-in strategies

numericStrategy

"btn-1"
"btn-2"

zeroPaddedStrategy(4)

"btn-0001"
"btn-0002"

timestampStrategy

"ts-1714300000000-1"
"ts-1714300000001-2"

hashStrategy

"h-a3f2b1c0"
"h-7e4d9f12"

generateIdWithStrategy(strategy, prefix?, suffix?): string

Generate an ID using a strategy outside of React components.

import {
  generateIdWithStrategy,
  numericStrategy,
  zeroPaddedStrategy,
  hashStrategy,
} from 'react-unique-id-generator';

generateIdWithStrategy(numericStrategy, 'item-')        // "item-1"
generateIdWithStrategy(zeroPaddedStrategy(4), 'id-')    // "id-0002"
generateIdWithStrategy(hashStrategy, 'h-')              // "h-a3f2b1c0"

useIdWithStrategy(strategy, prefix?, suffix?): string

React hook that generates a stable ID using the given strategy. The ID persists across re-renders.

import { useIdWithStrategy, zeroPaddedStrategy } from 'react-unique-id-generator';

function OrderItem() {
  const id = useIdWithStrategy(zeroPaddedStrategy(6), 'order-');
  return <div id={id}>...</div>;
  // => <div id="order-000001">...</div>
}

Writing a custom strategy

import { generateIdWithStrategy } from 'react-unique-id-generator';
import type { IdStrategy } from 'react-unique-id-generator';

const base36Strategy: IdStrategy = {
  generate(prefix, suffix, counter) {
    return `${prefix}${counter.toString(36)}${suffix}`;
  },
};

generateIdWithStrategy(base36Strategy, 'x-')  // "x-1", "x-2", ... "x-a", "x-b"

resetStrategyCounter(): void

Resets the strategy counter to 0. Useful in test setup.


Collision Detection

Track generated IDs and detect duplicates. Useful for debugging, testing, or enforcing ID uniqueness across your app.

CollisionDetector

import { CollisionDetector } from 'react-unique-id-generator';

const detector = new CollisionDetector({ action: 'throw' });

detector.register('input-1');  // true - registered
detector.register('input-2');  // true - registered
detector.register('input-1');  // throws Error: ID collision detected

Constructor options:

Option Type Default Description
action 'warn' | 'throw' | 'skip' 'warn' What to do when a collision is detected
maxSize number 100000 Maximum registry size before warning

Methods:

Method Returns Description
register(id) boolean Register an ID. Returns false on collision
has(id) boolean Check if an ID is registered
unregister(id) boolean Remove an ID from the registry
clear() void Clear all registrations and reset collision count
getRegisteredIds() string[] Get all registered IDs
size number Number of registered IDs
collisions number Total number of collisions detected

checkCollision(id, action?): boolean

Convenience function using a global singleton detector. Returns true if the ID was registered successfully, false on collision.

import { checkCollision, resetGlobalCollisionDetector } from 'react-unique-id-generator';

checkCollision('field-1');          // true
checkCollision('field-1', 'skip');  // false (duplicate)
checkCollision('field-1', 'throw'); // throws Error

// Reset between tests
resetGlobalCollisionDetector();

ID Pool Management

Pre-allocate IDs for high-throughput scenarios. The IdPool class is a generic pool (distinct from AutomationIdPool which is automation-specific).

IdPool

import { IdPool } from 'react-unique-id-generator';

const pool = new IdPool(100, { prefix: 'widget-', suffix: '-el' });

const id1 = pool.acquire();  // "widget-1-el"
const id2 = pool.acquire();  // "widget-2-el"

pool.release(id1);           // Return to pool for reuse
pool.acquire();              // "widget-1-el" (reused)

Constructor:

new IdPool(size: number, options?: IdPoolOptions)

Options:

Option Type Default Description
prefix string "" Prefix for generated IDs
suffix string "" Suffix for generated IDs
autoRefill boolean true Auto-generate new IDs when pool is exhausted
generator (index: number) => string - Custom ID generator function

Methods:

Method Returns Description
acquire() string Get the next available ID (reuses released IDs first)
release(id) void Return an ID to the pool for reuse
drain() void Clear all available and released IDs
refill(count) void Add more pre-generated IDs to the pool
size number Number of available + released IDs
totalGenerated number Total IDs ever generated by this pool

Custom generator example:

const pool = new IdPool(50, {
  generator: (i) => `session-${Date.now()}-${i}`,
});

useIdPool(size, prefix?): () => string

React hook that pre-generates a pool of IDs and returns an acquire function. The pool is stable across re-renders.

import { useIdPool } from 'react-unique-id-generator';

function DynamicList() {
  const acquireId = useIdPool(20, 'item-');

  return items.map((item) => (
    <li key={acquireId()} id={acquireId()}>
      {item.name}
    </li>
  ));
}

SSR Utilities

Enhanced server-side rendering support beyond createServerIdManager. These utilities provide React context-based isolation and request-scoped ID namespacing.

<SSRProvider>

A React context provider that scopes IDs per server request. Use the requestId prop to namespace IDs for deterministic, collision-free SSR output.

import { SSRProvider, useSSRSafeId } from 'react-unique-id-generator';

// Server render (e.g., Next.js, Remix)
function App({ requestId }: { requestId: string }) {
  return (
    <SSRProvider requestId={requestId} prefix="app-" suffix="-el">
      <Form />
    </SSRProvider>
  );
}

function Form() {
  const emailId = useSSRSafeId('email-');
  const passId = useSSRSafeId('pass-');
  return (
    <form>
      <label htmlFor={emailId}>Email</label>
      <input id={emailId} />
      <label htmlFor={passId}>Password</label>
      <input id={passId} />
    </form>
  );
  // emailId => "email-req123-1-el"
  // passId  => "pass-req123-2-el"
}

Props:

Prop Type Default Description
prefix string "" Default prefix for IDs
suffix string "" Suffix appended to all IDs
requestId string - Request identifier for namespacing (e.g., a short UUID)
children ReactNode - Child components

useSSRSafeId(prefix?): string

React hook for generating IDs inside an <SSRProvider>. Falls back to a timestamp-based ID outside a provider (with a dev warning).

useSSRContext(): SSRContextValue | null

Access the SSR provider context directly. Returns null outside an <SSRProvider>.

createSSRIdFactory(requestId, prefix?, suffix?)

Non-React factory for server-side ID generation with request scoping. Each factory is fully independent.

import { createSSRIdFactory } from 'react-unique-id-generator';

app.get('*', (req, res) => {
  const ids = createSSRIdFactory(req.id, 'ssr-');

  const headerId = ids.nextId();     // "ssr-abc123-1"
  const contentId = ids.nextId();    // "ssr-abc123-2"

  ids.resetId();                     // Reset for reuse
  ids.getCurrentId();                // 0
});

Custom Delimiters

Control how prefix, counter, and suffix are joined. By default, the library concatenates parts directly (prefix + counter + suffix). The delimiter API lets you specify any separator.

generateDelimitedId(options?): string

Generate an ID with parts joined by a custom delimiter.

import { generateDelimitedId } from 'react-unique-id-generator';

generateDelimitedId({ prefix: 'app', delimiter: '.' })
// "app.1"

generateDelimitedId({ prefix: 'com', suffix: 'widget', delimiter: '.' })
// "com.2.widget"

generateDelimitedId({ prefix: 'ns', suffix: 'item', delimiter: '::' })
// "ns::3::item"

generateDelimitedId({ prefix: 'path', suffix: 'el', delimiter: '/' })
// "path/4/el"

Options:

Option Type Default Description
prefix string "" Prefix part (omitted from output if empty)
suffix string "" Suffix part (omitted from output if empty)
delimiter string "-" Separator between parts

useDelimitedId(prefix?, delimiter?, suffix?): string

React hook version. Returns a stable delimited ID across re-renders.

import { useDelimitedId } from 'react-unique-id-generator';

function Widget() {
  const id = useDelimitedId('widget', '.', 'container');
  return <div id={id}>...</div>;
  // => <div id="widget.1.container">...</div>
}

resetDelimiterCounter(): void

Resets the delimiter counter to 0. Useful in test setup.


Stable CSS Selectors

Generate unique, human-readable CSS selectors for DOM elements. Useful for analytics tracking, session replay tagging, automated testing, and any scenario where you need a stable way to reference an element.

The selector generator tries strategies in priority order until it finds a unique match:

  1. Stable ID#my-element (skips dynamic IDs matching /^[:.]|^\d/ by default)
  2. Data attributes[data-testid="submit-btn"] (checks data-testid, data-test-id, data-cy)
  3. ARIA role[role="navigation"] or [role="button"][aria-label="Close"]
  4. Semantic attributesinput[name="email"], a[href="/home"], label[for="name"]
  5. CSS classesbutton.primary, div.card.active (single or paired)
  6. Landmark tagsheader, nav, main, footer
  7. Ancestor traversal — Combines ancestor selectors with the target (up to maxDepth, default 5)

generateSelector(element, options?): string

Generate a unique CSS selector for a DOM element.

import { generateSelector } from 'react-unique-id-generator';

const button = document.querySelector('button.submit');
const selector = generateSelector(button);
// e.g. "button.submit" or "#form-container button.submit"

Options (SelectorOptions):

Option Type Default Description
root Element document Scope uniqueness checks to a subtree
dataAttributes string[] ['data-testid', 'data-test-id', 'data-cy'] Data attributes to check (in priority order)
maxDepth number 5 Maximum ancestor levels to traverse for combined selectors
ignoreIdPattern RegExp `/^[:.] ^\d/`
ignoreClassPattern RegExp - Classes matching this pattern are excluded from selectors

Custom options example:

generateSelector(element, {
  dataAttributes: ['data-qa', 'data-automation-id'],
  maxDepth: 3,
  ignoreClassPattern: /^css-|^sc-/,  // Ignore CSS-in-JS generated classes
});

useStableSelector(ref, options?): string | null

React hook that generates a stable CSS selector for a ref'd element. The selector is computed once on mount and remains stable across re-renders.

import { useRef } from 'react';
import { useStableSelector } from 'react-unique-id-generator';

function TrackedButton() {
  const ref = useRef<HTMLButtonElement>(null);
  const selector = useStableSelector(ref);

  return (
    <button ref={ref} onClick={() => analytics.track('click', { selector })}>
      Click me
    </button>
  );
}

Returns null until the ref is attached (i.e., before mount).

resetSelectorCache(): void

Reset function included for API consistency. The selector module is stateless (no module-level cache), so this is a no-op.


Legacy Functions

These functions use global state and are still fully functional. In v2.0, they emit a one-time deprecation warning in development mode, recommending migration to <IdProvider> for SSR safety.

nextId(localPrefix?: string | null): string

Generates the next unique ID. Accepts an optional prefix that overrides the global prefix for that call only.

nextId()           // "1"
nextId()           // "2"
nextId('input-')   // "input-3"
nextId('input-')   // "input-4"

generateId(prefix?: string, suffix?: string): string

Generates the next ID with an explicit prefix and suffix, ignoring the global prefix/suffix.

generateId('btn-', '-primary') // "btn-1-primary"
generateId('icon-')            // "icon-2"
generateId()                   // "3"

setGlobalPrefix(prefix: string): void

Sets a prefix applied to every subsequent nextId() call (unless overridden locally).

setGlobalPrefix('app-');
nextId(); // "app-1"
nextId(); // "app-2"

Deprecated in v2.0 - Use <IdProvider prefix="app-"> instead.

setGlobalSuffix(suffix: string): void

Sets a suffix appended to every subsequent nextId() call.

setGlobalSuffix('-id');
nextId(); // "1-id"
nextId(); // "2-id"

Deprecated in v2.0 - Use <IdProvider suffix="-id"> instead.

resetId(): void

Resets the counter back to 0.

nextId();   // "1"
nextId();   // "2"
resetId();
nextId();   // "1"

getCurrentId(): number

Returns the current counter value without incrementing it.

nextId();         // "1"
getCurrentId();   // 1

setId(id: number): void

Sets the counter to a specific value. Negative values are clamped to 0; decimals are floored.

setId(10);
nextId(); // "11"

Usage Examples

Accessible Form Fields

Linking a <label> to an <input> via htmlFor / id is critical for screen readers. Use the provider for SSR-safe ID generation:

import { IdProvider, useUniqueId } from 'react-unique-id-generator';

function TextField({ label, type = 'text' }: { label: string; type?: string }) {
  const id = useUniqueId('field-');
  return (
    <div className="field">
      <label htmlFor={id}>{label}</label>
      <input id={id} type={type} />
    </div>
  );
}

function App() {
  return (
    <IdProvider>
      <TextField label="Email" type="email" />       {/* id="field-1" */}
      <TextField label="Password" type="password" /> {/* id="field-2" */}
    </IdProvider>
  );
}

Checkbox and Radio Groups

import { IdProvider, useUniqueId } from 'react-unique-id-generator';

function Checkbox({ label }: { label: string }) {
  const id = useUniqueId('checkbox-');
  return (
    <div>
      <input type="checkbox" id={id} />
      <label htmlFor={id}>{label}</label>
    </div>
  );
}

function RadioGroup({ name, options }: { name: string; options: string[] }) {
  return (
    <IdProvider prefix="radio-">
      <fieldset>
        {options.map((option) => (
          <RadioOption key={option} name={name} option={option} />
        ))}
      </fieldset>
    </IdProvider>
  );
}

function RadioOption({ name, option }: { name: string; option: string }) {
  const id = useUniqueId();
  return (
    <div>
      <input type="radio" id={id} name={name} value={option} />
      <label htmlFor={id}>{option}</label>
    </div>
  );
}

ARIA Attributes

import { useUniqueIds } from 'react-unique-id-generator';

function Modal({ title, children }: { title: string; children: React.ReactNode }) {
  const [titleId, descriptionId] = useUniqueIds(2, 'modal-');

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby={titleId}
      aria-describedby={descriptionId}
    >
      <h2 id={titleId}>{title}</h2>
      <p id={descriptionId}>{children}</p>
    </div>
  );
}

SSR with Per-Request Isolation

import { createServerIdManager } from 'react-unique-id-generator';

// Express middleware
app.get('*', (req, res) => {
  const ids = createServerIdManager({ prefix: 'ssr-' });

  // Each request gets its own counter - no cross-request leaks
  const headerId = ids.nextId();    // "ssr-1"
  const contentId = ids.nextId();   // "ssr-2"

  const html = renderToString(
    <Layout headerId={headerId} contentId={contentId} />
  );

  res.send(html);
});

Automation Testing with data-testid

import { useAutomationId } from 'react-unique-id-generator';

function LoginPage() {
  const formId = useAutomationId('LoginForm', { strategy: 'kebab-case' });
  const submitId = useAutomationId('SubmitButton', { strategy: 'kebab-case' });

  return (
    <form data-testid={formId}>
      {/* ... */}
      <button data-testid={submitId} type="submit">Log in</button>
    </form>
  );
  // => data-testid="login-form-1"
  // => data-testid="submit-button-2"
}

Pre-Allocated ID Pool for Large Apps

import { AutomationIdPool } from 'react-unique-id-generator';

// Pre-allocate 500 IDs at startup
const testIdPool = new AutomationIdPool(500, 'widget', {
  strategy: 'kebab-case',
  prefix: 'e2e-',
});

// Acquire IDs as components mount
function Widget() {
  const testId = testIdPool.acquire();
  return <div data-testid={testId}>...</div>;
}

Custom Strategy for Structured IDs

import { useIdWithStrategy } from 'react-unique-id-generator';
import type { IdStrategy } from 'react-unique-id-generator';

const bemStrategy: IdStrategy = {
  generate(prefix, suffix, counter) {
    return `${prefix}__element-${counter}${suffix ? `--${suffix}` : ''}`;
  },
};

function Card() {
  const id = useIdWithStrategy(bemStrategy, 'card', 'active');
  return <div id={id}>...</div>;
  // => <div id="card__element-1--active">...</div>
}

Collision-Safe ID Generation

import { CollisionDetector, generateIdWithStrategy, numericStrategy } from 'react-unique-id-generator';

const detector = new CollisionDetector({ action: 'throw' });

function safeGenerateId(prefix: string): string {
  const id = generateIdWithStrategy(numericStrategy, prefix);
  detector.register(id);  // Throws if duplicate
  return id;
}

Dynamic List with ID Pool

import { useIdPool } from 'react-unique-id-generator';

function TodoList({ items }: { items: string[] }) {
  const acquireId = useIdPool(items.length, 'todo-');

  return (
    <ul>
      {items.map((item) => {
        const id = acquireId();
        return (
          <li key={id}>
            <label htmlFor={id}>{item}</label>
            <input id={id} type="checkbox" />
          </li>
        );
      })}
    </ul>
  );
}

SSR with Request-Scoped IDs

import { SSRProvider, useSSRSafeId } from 'react-unique-id-generator';

// Server handler (e.g., Next.js, Express)
function handleRequest(requestId: string) {
  return renderToString(
    <SSRProvider requestId={requestId} prefix="app-">
      <Page />
    </SSRProvider>
  );
  // IDs: "app-abc123-1", "app-abc123-2", ...
  // Each request gets unique, deterministic IDs
}

function Page() {
  const headerId = useSSRSafeId('header-');
  const contentId = useSSRSafeId('content-');
  return (
    <div>
      <header id={headerId}>...</header>
      <main id={contentId}>...</main>
    </div>
  );
}

Stable CSS Selectors for Analytics

import { useRef } from 'react';
import { useStableSelector } from 'react-unique-id-generator';

function TrackedSection({ children }: { children: React.ReactNode }) {
  const ref = useRef<HTMLElement>(null);
  const selector = useStableSelector(ref);

  const handleClick = () => {
    analytics.track('section_click', { selector });
  };

  return (
    <section ref={ref} onClick={handleClick}>
      {children}
    </section>
  );
}

Generating Selectors Outside React

import { generateSelector } from 'react-unique-id-generator';

document.addEventListener('click', (e) => {
  const target = e.target as Element;
  const selector = generateSelector(target, {
    ignoreClassPattern: /^css-/,  // Skip CSS-in-JS classes
    maxDepth: 3,
  });
  console.log('Clicked:', selector);
});

Dot-Delimited Namespace IDs

import { useDelimitedId } from 'react-unique-id-generator';

function Widget({ section }: { section: string }) {
  const id = useDelimitedId(section, '.', 'widget');
  return <div id={id}>...</div>;
  // useDelimitedId('sidebar', '.', 'widget') => "sidebar.1.widget"
  // useDelimitedId('header', '.', 'widget')  => "header.2.widget"
}

Migration Guide (v1.x to v2.0)

Before (v1.x)

import nextId, {
  setGlobalPrefix,
  setGlobalSuffix
} from 'react-unique-id-generator';

setGlobalPrefix('app-');
setGlobalSuffix('-id');

function MyComponent() {
  const id = nextId();
  return <input id={id} />;
}

After (v2.0)

import {
  IdProvider,
  useUniqueId
} from 'react-unique-id-generator';

function App() {
  return (
    <IdProvider prefix="app-" suffix="-id">
      <MyComponent />
    </IdProvider>
  );
}

function MyComponent() {
  const id = useUniqueId();
  return <input id={id} />;
}

Step 1: Wrap your app in <IdProvider>

+ import { IdProvider } from 'react-unique-id-generator';

  function App() {
    return (
+     <IdProvider>
        <YourApp />
+     </IdProvider>
    );
  }

Step 2: Replace global prefix/suffix with provider props

- import { setGlobalPrefix, setGlobalSuffix } from 'react-unique-id-generator';
- setGlobalPrefix('app-');
- setGlobalSuffix('-id');

+ <IdProvider prefix="app-" suffix="-id">
+   <YourApp />
+ </IdProvider>

Step 3: Use hooks inside components (already compatible)

If you are already using useUniqueId and useUniqueIds, no changes are needed - they automatically detect the provider and use its scoped state.

What still works

All legacy functions (nextId, setGlobalPrefix, setGlobalSuffix, resetId, getCurrentId, setId, generateId) continue to work in v2.0. They will emit a one-time deprecation warning in development mode. The backward compatibility layer will be maintained for 6 months.


Framework Support

Next.js (App Router / Pages Router)

Use <IdProvider> in your root layout for SSR-safe IDs:

// app/layout.tsx
import { IdProvider } from 'react-unique-id-generator';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <IdProvider prefix="app-">{children}</IdProvider>
      </body>
    </html>
  );
}

For server-side ID generation (API routes, server actions), use createServerIdManager:

import { createServerIdManager } from 'react-unique-id-generator';

export async function GET() {
  const ids = createServerIdManager({ prefix: 'api-' });
  // ...
}

Vite + React

No configuration needed - the ESM build is picked up automatically:

import { IdProvider, useUniqueId } from 'react-unique-id-generator';

Create React App

import { IdProvider, useUniqueId } from 'react-unique-id-generator';

TypeScript Support

The library is written in TypeScript and ships declaration files for all exports:

import {
  IdProvider,
  useUniqueId,
  useUniqueIds,
  useIdContext,
  createServerIdManager,
  generateAutomationId,
  useAutomationId,
  AutomationIdPool,

  // v2.2.0
  generateIdWithStrategy,
  useIdWithStrategy,
  numericStrategy,
  zeroPaddedStrategy,
  timestampStrategy,
  hashStrategy,
  CollisionDetector,
  checkCollision,
  IdPool,
  useIdPool,
  SSRProvider,
  useSSRSafeId,
  useSSRContext,
  createSSRIdFactory,
  generateDelimitedId,
  useDelimitedId,

  // v2.3.0
  generateSelector,
  useStableSelector,
  resetSelectorCache,
} from 'react-unique-id-generator';

import type {
  IdProviderProps,
  IdContextValue,
  ServerIdManager,
  ServerIdManagerOptions,
  AutomationIdStrategy,
  AutomationIdOptions,

  // v2.2.0
  IdStrategy,
  CollisionAction,
  CollisionDetectorOptions,
  IdPoolOptions,
  SSRContextValue,
  SSRProviderProps,
  DelimitedIdOptions,

  // v2.3.0
  SelectorOptions,
} from 'react-unique-id-generator';

No additional @types package is required.

Testing

# Run tests
npm test

# Watch mode
npm run test:watch

# Coverage report
npm run test:coverage

Building

npm run build

Produces:

dist/
  index.cjs.js      # CommonJS bundle (minified)
  index.cjs.js.map  # CJS source map
  index.esm.js      # ES module bundle (minified, tree-shakeable)
  index.esm.js.map  # ESM source map
  index.d.ts        # Type declarations
  index.d.ts.map    # Declaration source map

Watch mode for development:

npm run build:watch

Contributing

Contributions are welcome. Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Make your changes and add tests for new functionality
  4. Ensure all tests pass (npm test)
  5. Submit a pull request

Bug reports and feature requests can be filed at the issue tracker.

License

MIT License - see LICENSE for details.


Changelog

v2.3.0

New Features:

  • New: generateSelector() - Generate unique, human-readable CSS selectors for DOM elements using a priority chain (ID > data attributes > ARIA roles > semantic attributes > classes > landmarks) with ancestor traversal fallback
  • New: useStableSelector() - React hook that computes a stable CSS selector for a ref'd element once on mount
  • New: resetSelectorCache() - Reset function for API consistency
  • New: SelectorOptions type - Configure root, dataAttributes, maxDepth, ignoreIdPattern, and ignoreClassPattern
  • Backward Compatible: All existing APIs remain unchanged

v2.2.0

New Features:

  • New: Custom ID generation strategies - IdStrategy interface with built-in numericStrategy, zeroPaddedStrategy, timestampStrategy, and hashStrategy
  • New: generateIdWithStrategy() and useIdWithStrategy() - Generate IDs with pluggable formatting strategies
  • New: CollisionDetector class - Track generated IDs and detect duplicates with configurable actions (warn, throw, skip)
  • New: checkCollision() - Convenience function using a global singleton collision detector
  • New: IdPool class - Generic ID pool with acquire/release/drain/refill, custom generators, and auto-refill
  • New: useIdPool() hook - Pre-generate a pool of IDs and return an acquire function for React components
  • New: <SSRProvider> - React context provider with requestId prop for per-request ID namespacing
  • New: useSSRSafeId() - Hook for SSR-safe IDs with fallback outside provider
  • New: createSSRIdFactory() - Non-React factory for server-side request-scoped ID generation
  • New: generateDelimitedId() and useDelimitedId() - Join prefix/counter/suffix with custom delimiters
  • Backward Compatible: All existing APIs remain unchanged

v2.1.0

New Features:

  • New: generateSecureId() - Cryptographically random ID generation using crypto.randomUUID/getRandomValues with Math.random fallback
  • New: Scoped ID management - nextIdForScope, resetIdForScope, resetAllScopes, getScopeCounter, getActiveScopes
  • New: Performance monitoring - useTrackedUniqueId, useTrackedUniqueIds, useIdMetrics, getIdMetrics, getActiveIdCount
  • New: Overflow protection - Dev-mode warnings when counters exceed 1M IDs

v2.0.0

Breaking Changes & New Features:

  • New: <IdProvider> - Provider-based architecture for SSR-safe, scoped ID generation
  • New: useIdContext() - Access the current provider's context value
  • New: createServerIdManager() - Isolated, per-request ID manager for server-side rendering
  • New: generateAutomationId() - Generate deterministic automation test IDs with naming strategies (sequential, kebab-case, camelCase, custom)
  • New: useAutomationId() - React hook for stable automation test IDs
  • New: AutomationIdPool - Pre-allocated ID pool for large-scale automation testing
  • New: resetAutomationCounter() - Reset the automation ID counter
  • Updated: useUniqueId() and useUniqueIds() now automatically detect and use <IdProvider> context when available
  • Deprecated: setGlobalPrefix() and setGlobalSuffix() - use <IdProvider> props instead (still functional with deprecation warnings)
  • Breaking: Global state is now scoped to providers when using the new API

v1.4.0

  • Added useUniqueId() React hook for stable, component-scoped IDs
  • Added useUniqueIds() React hook for generating multiple stable IDs at once
  • Added input validation warnings in development mode (non-breaking)
  • Enhanced JSDoc comments for better IDE autocomplete and IntelliSense
  • Improved example app to demonstrate both imperative and hook-based usage

v1.3.0

  • Migrated build toolchain from Webpack to tsup (esbuild-based)
  • Added proper CommonJS build (dist/index.cjs.js)
  • Added proper ES module build (dist/index.esm.js)
  • Added minified production output for both formats
  • Added sideEffects: false for tree-shaking support in consumer bundlers
  • Added build:watch script for development
  • Fixed exports map to correctly resolve CJS vs ESM per environment
  • Updated CI to Node.js 18/20 (Node 14/16 are EOL)

v1.2.1

  • ESM support improvements
  • TypeScript and Webpack configuration enhancements

v1.1.1

  • Added comprehensive test suite
  • Improved TypeScript definitions
  • Added new utility functions (getCurrentId, setId, generateId)
  • Fixed build configuration
  • Added proper npm package configuration
  • Improved documentation and examples

About

A lightweight, flexible library for generating unique IDs in React applications. Perfect for form elements, accessibility attributes, and any scenario where you need guaranteed unique identifiers.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors