Read-Only Hint
Screen-reader hint and described-by id for read-only field controls.
Read-Only Hint is the sr-only description kit fields attach with aria-describedby when a control is readOnly. The visible lock lives on Field Lock Mark; this item only announces “Read-only” so the glyph can stay decorative.
Kit hosts compose this; prefer FormItem / Editable Table / kit inputs that already wire it. Placement and lock chrome: Field Lock Affordance.
When To Use
- You are composing a custom
readOnlycontrol and need a described-by hint that matches kit fields. - Pair
readOnlyDescribedBy(id)on the control with<ReadOnlyHint controlId={id} />as a sibling. - Prefer FormItem, Select, Date / Time pickers, and other kit fields that already mount this — do not add a second hint beside them.
- Do not put the announcement on Field Lock Mark (that would double-speak).
Features
| Area | Behavior |
|---|---|
| Hint | sr-only span whose id is {controlId}-readonly |
| Wiring | readOnlyDescribedBy(controlId) returns that id for aria-describedby |
| Copy | Default Read-only; override once with configureFieldReadOnlyCopy |
| Visual | None — pair with Field Lock Mark / field box chrome for operators |
Installing
pnpm dlx shadcn@latest add https://ui.isaacfei.com/r/read-only-hint.jsonnpx shadcn@latest add https://ui.isaacfei.com/r/read-only-hint.jsonyarn dlx shadcn@latest add https://ui.isaacfei.com/r/read-only-hint.jsonbun x shadcn@latest add https://ui.isaacfei.com/r/read-only-hint.jsonWith a namespace: npx shadcn@latest add @f-ui/read-only-hint.
registryDependencies: none.
Usage
import {
ReadOnlyHint,
readOnlyDescribedBy,
} from "@/components/f-ui/internals/read-only-hint";
<input
id="warehouse"
readOnly
aria-describedby={readOnlyDescribedBy("warehouse")}
/>
<ReadOnlyHint controlId="warehouse" />Examples
Field Wiring
The input stays in the tab order and points aria-describedby at warehouse-readonly. Inspect the hidden span in DevTools — it is not painted on screen.
"use client";
import {
ReadOnlyHint,
readOnlyDescribedBy,
} from "@/components/f-ui/internals/read-only-hint";
const CONTROL_ID = "warehouse";
export function ReadOnlyHintDemo() {
return (
<div className="max-w-sm space-y-1.5">
<label htmlFor={CONTROL_ID} className="text-sm font-medium">
Warehouse
</label>
<input
id={CONTROL_ID}
readOnly
value="East dock"
aria-describedby={readOnlyDescribedBy(CONTROL_ID)}
className="h-8 w-full rounded-lg border border-input bg-transparent px-2.5 text-sm"
/>
<ReadOnlyHint controlId={CONTROL_ID} />
</div>
);
}With a Lock Mark
The visible lock is Field Lock Mark (aria-hidden). This hint stays screen-reader only so the two signals do not double-announce.
"use client";
import { FieldLockMark } from "@/components/f-ui/internals/field-lock-mark";
import {
ReadOnlyHint,
readOnlyDescribedBy,
} from "@/components/f-ui/internals/read-only-hint";
const CONTROL_ID = "warehouse-locked";
export function ReadOnlyHintWithLockDemo() {
return (
<div className="max-w-sm space-y-1.5">
<div className="flex items-center gap-1.5">
<label htmlFor={CONTROL_ID} className="text-sm font-medium">
Warehouse
</label>
<FieldLockMark />
</div>
<input
id={CONTROL_ID}
readOnly
value="East dock"
aria-describedby={readOnlyDescribedBy(CONTROL_ID)}
className="h-8 w-full rounded-lg border border-input bg-transparent px-2.5 text-sm"
/>
<ReadOnlyHint controlId={CONTROL_ID} />
</div>
);
}API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
controlId | string | — | Control id. The hint renders with id set to {controlId}-readonly. |
Helpers
| Export | Description |
|---|---|
readOnlyDescribedBy(controlId) | Returns {controlId}-readonly for aria-describedby. |
configureFieldReadOnlyCopy(fn) | Sets the shared announcement (() => string). Default is Read-only. Call once at app startup — it is not per-field. |
See Also
- Field Lock Affordance — lock mark vs announcement
- Field Lock Mark — visible glyph
- Time Input — wires described-by; host mounts this hint
- Form (Formily)