Masked input
canonStructured value typed straight into a visible mask (IČO, DIČ, PSČ, rodné číslo) with inline check-digit validation; input holds the typed text, a muted ghost draws the rest of the template.
Merged to origin/main 28 September 2026 (acrm MR !4082, "fix(web): IČO, DIČ, PSČ a rodné číslo jako maska (MaskedInput v @acrm/ui)"), under the owner's binding rule of 14 Sep 2026 (".claude/rules/design-system.md" §"Vstupy a mobil"): a structured value is a mask, never free text — only valid characters, separators added automatically, the template stays visible, the error is reported while typing. Unlike "masked-date-input", this component IS exported from @acrm/ui (source=package) and ships four masks as pure functions in `field-masks.ts`: IČO (8 digits, mod-11 check digit), DIČ (digits-first = Czech, "CZ" is added automatically; letters-first = the typed country code, CZ is never forced; mod-11 check only for CZ), PSČ ("### ##", Czech/Slovak only — a foreign address falls back to a plain Input in the host), rodné číslo ("######/####" or 9 digits before 1953, date part validated per segment, +50 on the month for women, 10-digit form checked mod 11 with the pre-1985 exception). Two host shapes, no new look: `variant="field"` (default) wraps `.acrm-inputwrap` (`.acrm-mask`/`.acrm-mask-ghost` inside, `.acrm-maskwrap.is-error` colours the typed text `--critical-ink` in addition to the wrapper's usual red border+ring) — e.g. IČO/DIČ in the "Nová firma" drawer (`FirmyPage.tsx`); `variant="formless"` reuses the exact `.fl-mask`/`.fl-mask-in`/`.fl-mask-ghost`/`.fl-mask.bad` classes `masked-date-input` already established in `dialog-formless.css` — e.g. rodné číslo in the "Nová osoba" dialog (`NewContactDialog.tsx`). Save gate: the host reads the value out only when `mask.complete(text)` is true (`ico: mask.complete(f.ico) ? f.ico : null`); an incomplete or invalid field is simply not saved, it never blocks the rest of the form (AD 28 Sep 2026) — same rule as `masked-date-input`'s ISO gate. An old invalid stored value (recorded before the mask existed) is loaded through `applyMask()` and shown as-is, editable, not rejected. `describedBy` links the error text via `aria-describedby` when the caller passes an id — the formless caller (`NewContactDialog`, rodné číslo) wires it; the field-variant callers in `FirmyPage` (IČO, DIČ, PSČ) do not, leaving the same label/error linking gap `text-field/input.a11y.md` documents for `<Field>`. Sibling pattern `MaskedPhoneInput` (phone, dial codes) moved into `packages/ui` 28 September 2026 (acrm MR !4085) alongside the new `PhoneInput` form field and `DialCodePicker`; it now has its own registry card, `phone-input`.
Used in 2 files · 2 areas · 2× — components/org, components/person
@acrm/ui <MaskedInput>.acrm-maskwrap .acrm-maskwrap.is-error .acrm-mask .acrm-mask-ghost +4SourceStoryImport
import { MaskedInput } from '@acrm/ui'
Props
MaskedInput packages/ui/src/components/MaskedInput.tsx
| Name | Type | Default | Description |
|---|---|---|---|
mask * | FieldMask | — | — |
value * | string | — | Text pole v masce („800101/1235"). |
onValue * | (text: string) => void | — | — |
variant | 'field' | 'formless' | 'field' | — |
ariaLabel | string | — | — |
invalid | boolean | — | — |
describedBy | string | — | Id textu chyby pod polem — čtečka ho přečte spolu s aria-invalid. |
className | string | — | Šířková varianta obalu pole (acrm-inputwrap--code …). |
placeholder | string | — | Popisek prázdného pole (výchozí = šablona podtržítky). |
onBlur | () => void | — | — |
autoFocus | boolean | — | — |
CSS classes
Files
packages/ui/src/components/MaskedInput.tsxpackages/ui/src/lib/field-masks.tsapps/web/src/ds/inputs.cssapps/web/src/ds/dialog-formless.css