Masked date input
canonDate typed straight into a dd.mm.rrrr mask with inline validation; no leading zero required, ISO out only once complete and valid.
Merged to origin/main 15 September 2026 (MR !3540), approved by the product owner as part of the New person dialog work (`.claude/rules/design-system.md` §"Vstupy a mobil", 14 Sep 2026: a structured value is a mask, never free text). Sibling pattern to `MaskedPhoneInput` (input + muted ghost-template) but that component has no card of its own yet in this registry — it is only listed under `record-rail/rail`'s files — so this is the mask pattern's first documented entry, filed under Text field for lack of a dedicated family. Code correction against the design brief: `inputMode` is `"decimal"`, not `"numeric"`. The component's only call site (`NewContactDialog`, the New person dialog) is not merged yet, and it wires no calendar next to the field — "the calendar is a complement" is the rule this component exists to satisfy, not yet an observed behaviour. No stories: lives in `apps/web/src/components/multivalue/`, which `packages/ui` must not import. Registry repaired 17 Oct 2026 (review F1, F10): now exported from @acrm/ui (packages/ui/src/components/MaskedDateInput.tsx, lib/date-mask.ts); the apps/web paths and the "lives in apps/web" caveat are outdated.
Used in 2 files · 2 areas · 3× — components/person, components/relations
Default
export const Default = () => (
<Frame label="Empty — only the template „dd.mm.rrrr“ in --fg-muted">
<Row>
<Mask typed="" label="Datum narození" />
</Row>
</Frame>
);States
export const States = () => (
<div style={col}>
<Frame label="Empty">
<Row>
<Mask typed="" label="Datum narození — prázdné" />
</Row>
</Frame>
<Frame label="Typing — „12.1.“ typed, „rrrr“ still muted">
<Row>
<Mask typed="12.1." label="Datum narození — rozepsané" />
</Row>
</Frame>
<Frame label="Complete — no leading zero needed">
<Row>
<Mask typed="12.1.2000" label="Datum narození — celé" />
</Row>
</Frame>
<Frame label="Invalid — --critical-ink value + .fl-err under it (aria-describedby)">
<Row error={{ id: 'mdi-err-day', text: 'Den musí být 1–31' }}>
<Mask typed="32." invalid errId="mdi-err-day" label="Datum narození — chybné" />
</Row>
</Frame>
<Frame label="Invalid — date that does not exist">
<Row error={{ id: 'mdi-err-none', text: 'Takové datum neexistuje' }}>
<Mask typed="31.2.2001" invalid errId="mdi-err-none" label="Datum narození — neexistuje" />
</Row>
</Frame>
<Frame label="Focus — the row tints (--hover-surface), the input has no ring">
<Row>
<Mask typed="12.1." autoFocus label="Datum narození — fokus" />
</Row>
</Frame>
</div>
);Composition
export const Composition = () => (
<div style={col}>
<Frame label="Birth date + gender — empty">
<Row>
<Mask typed="" label="Datum narození" />
<GenderMenu value={null} />
</Row>
</Frame>
<Frame label="Birth date + gender — filled">
<Row>
<Mask typed="12.1.2000" label="Datum narození" />
<GenderMenu value="female" />
</Row>
</Frame>
<Frame label="Birth date invalid — the message is under the value, the menu stays in line">
<Row error={{ id: 'mdi-err-future', text: 'Datum narození nemůže být v budoucnu' }}>
<Mask typed="12.1.2099" invalid errId="mdi-err-future" label="Datum narození" />
<GenderMenu value="male" />
</Row>
</Frame>
</div>
);