useId
Hydration‑safe, deterministic IDs for accessibility and labeling — SSR‑friendly and stable across client/server.
useId
useId returns a unique, deterministic ID string you can use for form controls, ARIA relationships, DOM anchors, etc. It is safe for SSR and hydration: it prefers React’s useId when available (React 18+), and falls back to a counter on the client when needed. You can also pass a fixed deterministicId to force a specific ID.
The returned string is prefixed (e.g., loke-…) for clarity. If you pass a deterministicId, that string is returned unmodified.
API
function useId(deterministicId?: string): string;Prop
Type
Return value: a string ID you can safely apply to id attributes and ARIA relationships. When no deterministicId is provided, it is guaranteed to be stable between server and client hydration.
Usage
1) Label → input association (accessible forms)
import * as React from 'react';
import { useId } from '@loke/ui/use-id';
export function EmailField() {
const id = useId();
return (
<div className="flex flex-col gap-1.5">
<label htmlFor={id} className="text-sm">Email</label>
<input
id={id}
type="email"
className="border rounded px-3 py-2"
placeholder="you@example.com"
/>
</div>
);
}2) ARIA relationships (description, error text)
import * as React from 'react';
import { useId } from '@loke/ui/use-id';
export function PasswordField({ error }: { error?: string }) {
const inputId = useId();
const descId = useId(); // description (helper text)
const errId = useId(); // error text
const describedBy = [descId, error ? errId : undefined]
.filter(Boolean)
.join(' ') || undefined;
return (
<div className="flex flex-col gap-1.5">
<label htmlFor={inputId} className="text-sm">Password</label>
<input
id={inputId}
type="password"
aria-describedby={describedBy}
aria-invalid={!!error}
className="border rounded px-3 py-2"
/>
<p id={descId} className="text-xs text-muted-foreground">
Must be at least 8 characters.
</p>
{error && (
<p id={errId} role="alert" className="text-xs text-destructive">
{error}
</p>
)}
</div>
);
}3) Forcing a deterministic ID
Pass a specific string if you need a fixed ID (e.g., for screenshots, docs, tests):
import { useId } from '@loke/ui/use-id';
export function FixedIdExample() {
const id = useId('account-email'); // always "account-email"
return (
<>
<label htmlFor={id}>Email</label>
<input id={id} type="email" />
</>
);
}Behavior & SSR details
- On React 18+, the hook uses
React.useId()when available, returning a hydration‑safe, deterministic ID. - On older React or environments where
useIdis unavailable, the hook falls back to a client‑side counter during layout effect to avoid hydration warnings. - If you supply
deterministicId, that string is returned as‑is; this can be useful in:- server‑only rendering where you must guarantee exact IDs,
- static docs, or
- testing snapshots.
The fallback path only runs on the client — on initial server render, the hook returns an empty string then stabilizes on the client. If you want the exact same ID on both sides, provide a deterministicId.
Patterns & tips
- Generate once per distinct piece of UI you need to reference. For multiple associations (label + description + error), call
useId()multiple times to get separate IDs. - Prefer
aria-describedby(androle="alert"for errors) to communicate additional context to assistive tech. - For deeply nested compound components, create IDs in the outermost component and pass them down to subcomponents.
Import
import { useId } from '@loke/ui/use-id';useLayoutEffect
SSR‑safe drop‑in for React’s useLayoutEffect that no‑ops on the server to avoid hydration warnings, while preserving synchronous layout effects on the client.
Lib Utilities
Index of internal utilities for building accessible, responsive components and application primitives in the LOKE Design System.