BetaForms

MaskField

A value typed into a shape — a date, a time, a card number, or a pattern of your own.

Overview

A value typed into a shape — a date, a time, a card number, or a pattern of your own.

First example

<MaskField mask="credit-card" onValueChange={setCard}>
<MaskField.Label>Card number</MaskField.Label>
<MaskField.Field />
</MaskField>
<MaskField
mask="date"
locale="fr-FR"
convert={text => parseMaskedDate(text, 'fr-FR')}
onValueChange={(text, value) => setBirthday(value as Date | null)}
>
<MaskField.Label>Date de naissance</MaskField.Label>
<MaskField.Field />
<MaskField.Description>Jour, mois, année.</MaskField.Description>
</MaskField>

Anatomy

<MaskField>
  <MaskField.Field />
</MaskField>

Usage

<MaskField mask="credit-card" onValueChange={setCard}>
  <MaskField.Label>Card number</MaskField.Label>
  <MaskField.Field />
</MaskField>

<MaskField
  mask="date"
  locale="fr-FR"
  convert={text => parseMaskedDate(text, 'fr-FR')}
  onValueChange={(text, value) => setBirthday(value as Date | null)}
>
  <MaskField.Label>Date de naissance</MaskField.Label>
  <MaskField.Field />
  <MaskField.Description>Jour, mois, année.</MaskField.Description>
</MaskField>

Root props

MaskFieldProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
maskMaskSpecYesThe shape the box is masked to. Required — there is no default shape.
valuestring | undefinedNoThe masked text. Present means controlled — the field follows it, and `onValueChange` is how it asks for a new one. `''` is an empty field.
defaultValuestring | undefinedNoWhere an uncontrolled field starts. Masked on the way in, so a raw string is fine.
onValueChange((text: string, value: unknown) => void) | undefinedNoEvery edit: the masked text, and — when `convert` is set — what it converts to, or `null` while the text is not a value yet. Without `convert` the second argument is the masked text itself.
convert((text: string) => unknown) | undefinedNoTurns the masked text into a value of your own — a `Date`, `{ hours, minutes }`, a card number. Return `null` while the text is not one yet. The field stays about the string; this is the one plug. `parseMaskedDate` and `parseMaskedTime` are exported for the `date` and `time` shapes.
orderDateOrder | undefinedNoWhich date part is typed first, for the `date` and `datetime` shapes. Unset, it is read out of `locale`. Give it when the order is a decision — an ISO field is `YMD`.
localestring | undefinedNoThe order and the separator come from here when neither is given.
separatorstring | undefinedNoBetween the date parts. Unset, it is the one the locale writes.
segmentLabelsSegmentLabels | undefinedNoWhat the placeholder calls each date part — `{ day: 'JJ', month: 'MM', year: 'AAAA' }`. The letters are a language's, so the default is the `DD` / `MM` / `YYYY` code is written in. A `placeholder` on the field wins over it.
childrenReactNodeNo

Slots

MaskFieldFieldProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

This slot adds nothing to the props of its React Native node.

Variants, sizes and colour

This component adds no visual axis of its own. The values it does take are in the generated types above and in the live demo.

Accessibility

The source defines no extra rule. Keep the React Native labels, roles, states and focus order your case needs, then check the result with VoiceOver and TalkBack.

Migration from legacy

This component declares no rule of its own. The migration guide covers variants, colours and slots.

Implementation notes

It is a `TextField`

The root below is the TextField's root, unchanged: the same recipe, the same four variants, the same size, radius, color, labelPlacement, isInvalid and isDisabled. MaskField.Label, .Description and .Error are the TextField's slots — the same components, re-exported rather than wrapped — and only MaskField.Field differs, by masking what is typed into it. The TextArea's arrangement exactly, and TextField is not touched.

It is a mask, not a set of segments

There is one representation — the accepted characters, in order — and maskInput is the only thing that turns them into text. Everything else is dropped, including the literals, which are put back rather than kept.

That is what makes the field survive a paste, a keyboard that offers its own punctuation, and a backspace over a separator, none of which a segmented field survives without a rule each.

`mask` is a preset or a pattern

presetshaperules
'date'DD/MM/YYYYorder and separator from locale; each part clamped, never raised
'time'HH:MM24-hour; hour ≤ 23, minute ≤ 59
'datetime'DD/MM/YYYY HH:MMthe two above, joined by a space
'credit-card'#### #### #### ####shape only

MASK_FIELD_MASKS is the list of them.

Anything else is a pattern string:

tokenaccepts
#a digit
Aa letter
*either
othera literal, put back in as the parts fill
<MaskField mask="+33 # ## ## ## ##" />
<MaskField mask="AA## ####" />

A pattern is a shape, not a range — it does not clamp. If a custom mask needs clamping, that is a follow-up, an object form { pattern, clamp }.

The date parts come from the locale

Read out of Intl rather than off a table of countries. order overrides it when the order is a decision rather than a locale — an ISO field is YMD wherever it is read. separator and segmentLabels ({ day, month, year }) override the rest.

A date that cannot exist — the 31st of February — is capped by the mask the moment the month is known, and parseMaskedDate returns null for one typed before it, rather than rolling forward into March the way new Date would.

The value is the masked string

value / defaultValue / onValueChange operate on the masked text, controlled or not, as everywhere in the library. A raw string handed to value is masked on the way in.

convert is the one plug that turns the text into a value of your own:

<MaskField mask="time" convert={parseMaskedTime} onValueChange={(text, time) => …} />

parseMaskedDate(text, locale?, order?)Date | null and parseMaskedTime(text){ hours, minutes } | null are exported for the date and time shapes, with formatMaskedDate / formatMaskedTime as their inverses. Without convert, onValueChange's second argument is the masked text itself.

For a date chosen rather than typed

That is DatePicker — a trigger, an overlay and a Calendar in it. MaskField is the box alone; it has no picker.