Field
canonWrapper that stacks label, control, hint and error message.
acrm MR !4085 (28 Sep 2026, "fix(ui): chyba pod DS Field se čtečce provází sama") added a new export, `useFieldDescribedBy`: `<Field>` now gives the rendered hint/error span an automatic `id` (`useId()`, error takes precedence over hint) and provides that id through a context. Any control rendered as a child of `<Field>`/`<DrawerField>` can call `useFieldDescribedBy()` and fall back to it for `aria-describedby` unless the caller passed an explicit `describedBy` — `masked-input` and `phone-input` both do this now. This does not add `htmlFor`/label association (the pre-existing known limit in `field.a11y.md` — no automatic label–control link), and it only helps a control that explicitly opts into the hook; a plain `<input>` still needs it wired by hand.
Used in 29 files · 15 areas · 214× — components/accounting, components/callwrapup, components/clen-tymu, components/datamart, components/mail, components/person …
@acrm/ui <Field>.acrm-field .acrm-field-label .acrm-field-hint .acrm-field-error +1SourceStoryLegacy card Inputs.htmlDefault
export const Default = () => {
const [v, setV] = useState('Jane Doe');
return (
<div style={{ width: 360 }}>
<Field label="Full name">
<Input value={v} onChange={(e) => setV(e.target.value)} />
</Field>
</div>
);
};Variants
export const Variants = () => (
<div style={row}>
<Labeled label="label">
<Field label="Full name">
<Input placeholder="Jane Doe" />
</Field>
</Labeled>
<Labeled label="label + hint">
<Field label="E-mail" hint="Work address — used for invitations.">
<Input type="email" placeholder="[email protected]" />
</Field>
</Labeled>
<Labeled label="label + error">
<Field label="Phone" error="Enter a valid number in the +420… format.">
<Input className="is-error" defaultValue="123" aria-invalid />
</Field>
</Labeled>
<Labeled label="labelExtra (icon after the label)">
<Field label="Company ID" labelExtra={<Icon name="info-circle" size={14} />}>
<Input className="acrm-input--code" placeholder="12345678" />
</Field>
</Labeled>
<Labeled label="no label (control + hint)">
<Field hint="Optional.">
<Input placeholder="Internal note" aria-label="Internal note" />
</Field>
</Labeled>
</div>
);States
export const States = () => (
<div style={grid}>
<div style={row}>
<Labeled label="hint + error → error wins">
<Field
label="Phone"
hint="Include the country code."
error="Enter a valid number in the +420… format."
>
<Input className="is-error" defaultValue="123" aria-invalid />
</Field>
</Labeled>
<Labeled label="disabled control">
<Field label="Owner" hint="Set by the reservation.">
<Input defaultValue="Jane Doe" disabled />
</Field>
</Labeled>
</div>
</div>
);Composition
export const Composition = () => {
const [name, setName] = useState('Acme Robotics');
const [agree, setAgree] = useState(true);
return (
<div style={{ display: 'grid', gap: 'var(--sp-4)', width: 360 }}>
<Field label="Company">
<Input value={name} onChange={(e) => setName(e.target.value)} />
</Field>
<Field label="E-mail" hint="Work address — used for invitations.">
<Input type="email" placeholder="[email protected]" />
</Field>
<Field label="Note">
<Textarea rows={3} placeholder="What was agreed on the call" />
</Field>
<Checkbox
label="Send a confirmation e-mail"
checked={agree}
onChange={(e) => setAgree(e.target.checked)}
/>
<div style={{ display: 'flex', gap: 'var(--sp-3)', justifyContent: 'flex-end' }}>
<Button kbd="Esc">Cancel</Button>
<Button variant="primary" kbd="⌘↵">
Save
</Button>
</div>
</div>
);
};