Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelog App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip
ARC UI ARC Radiant Components
v3.2 Docs Components Tokens Synthesizer
Getting StartedFrameworksServer Rendering Design TokensThemingTheme SynthesizerTypographyUtilities All ComponentsAccessibilityBrowser SupportChangelogContributingStats App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip

Masked Input

Text field that enforces a character mask as you type — dates, card numbers, phone numbers, license keys. The mask’s literals are typed for the user; value holds only the raw characters, and the raw value is what forms receive, so the mask stays presentation.

Components Masked Input
input interactive
<arc-masked-input>

Overview

MaskedInput is a single-line text field that formats fixed-shape values while the user types them. The `mask` prop describes the shape with four slot characters — `#` for a digit, `A` for an uppercase letter (lowercase input is uppercased), `a` for any letter, and `*` for a letter or digit — and every other character is a literal that the component types for the user. A date mask of `##/##/####` means the user types eight digits and the slashes appear on their own, with the caret always landing on the next position that can accept a character. The decision that makes the component composable: `value` holds only the raw characters, never the mask's literals. A completed date field reports `12042026`, and that raw string is also what the field submits with a form. The formatted string — `12/04/2026` — is presentation, available read-only as `formattedValue` and in the `formatted` key of every event detail. This means switching a card mask from space-grouped to dash-grouped changes nothing downstream, and your backend never has to strip formatting it did not ask for. Editing behaves the way users expect from a good mask. Typing inserts at the caret and skips literals forward; Backspace deletes the previous fillable character, skipping literals backward; pasting strips non-conforming characters and fills the remaining positions, so a card number pasted with dashes lands cleanly in a space-grouped mask. A character that does not fit its slot is silently rejected and the caret stays put. Before typing begins, the native placeholder shows the mask shape; once typing starts, the unfilled remainder renders in the field as a muted hint, such as `12/__/____`. MaskedInput follows the v3 commit contract: `arc-input` fires on each accepted edit, and `arc-change` fires on blur or Enter when the value changed — and immediately when the last mask position fills, the same fixed-length commit that OTP Input established. Constraint validation is built in: a required empty field fails with `valueMissing`, and a partially filled mask fails with an "Incomplete value" pattern error, so a half-typed card number cannot pass a form's validation.

Guidelines

When to use

  • Use a mask when the value has one fixed, well-known shape: dates, card numbers, phone numbers in a single locale, license or serial keys
  • Read `value` (or the form submission) for storage and `formattedValue` only for display — the raw value is the contract
  • Listen for `arc-change` to validate or submit — it fires the moment the mask completes, so users need not leave the field first
  • Set `autocomplete` to the matching token (for example `cc-number` on a card field) so browser autofill keeps working
  • Always provide a `label`; the mask shape in the placeholder is a hint, not a name for the field
  • Prefer `A` over `a` for license and product keys so the stored value is case-normalized without the user caring

When not to use

  • Do not mask free-form values like names, email addresses, or search queries — a mask that fights variable-length input is worse than no mask; use Input instead
  • Do not use MaskedInput for short fixed-length verification codes — OTP Input and Pin Input give each character its own box and auto-advance
  • Do not mask international phone numbers with a single pattern — number lengths vary by country, and a wrong mask locks users out of entering their own number
  • Do not parse `formattedValue` on the server — submit and store the raw value, and format at the display edge
  • Do not use the mask as a substitute for validation of meaning — `##/##/####` accepts 99/99/9999; check that a date is real before accepting it

Features

  • Declarative mask pattern: `#` digit, `A` uppercase letter, `a` any letter, `*` alphanumeric, everything else a literal
  • Raw `value` with no literals — the formatted string is exposed separately as read-only `formattedValue`
  • Forms receive the raw value, so the presentation format never leaks into submitted data
  • Literal skipping in both directions: typing jumps forward past literals, Backspace deletes through them
  • Paste support that strips non-conforming characters and fills the remaining positions
  • Muted in-field hint for unfilled positions once typing starts, configurable via `placeholder-char`
  • Fires `arc-input` per accepted edit and `arc-change` on blur, Enter, or the moment the mask completes
  • Built-in validation: required-empty is `valueMissing`, a partial fill is an "Incomplete value" pattern error
  • Numeric masks automatically request the numeric keyboard on mobile
  • Prefix and suffix slots, label, sizes, and disabled/readonly states matching Input

Preview

Usage

This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.

<!-- Date, card, and license-key masks -->
<div style="display:flex; flex-direction:column; width:100%; max-width:400px; gap:16px;">
  <arc-masked-input label="Expiry date" name="expiry" mask="##/##/####" autocomplete="cc-exp"></arc-masked-input>
  <arc-masked-input label="Card number" name="card" mask="#### #### #### ####" autocomplete="cc-number"></arc-masked-input>
  <arc-masked-input label="License key" name="license" mask="AAA-###-AAA" placeholder-char="•"></arc-masked-input>
</div>

<script>
  const card = document.querySelector('[name="card"]');
  card.addEventListener('arc-change', (e) => {
    // e.detail.value is the raw digits; e.detail.formatted is presentation.
    console.log(e.detail.value, e.detail.formatted);
  });
</script>
import { MaskedInput } from '@arclux/arc-ui-react';

export default function Example() {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', width: '100%', maxWidth: 400, gap: 16 }}>
      <MaskedInput label="Expiry date" name="expiry" mask="##/##/####" autocomplete="cc-exp" />
      <MaskedInput
        label="Card number"
        name="card"
        mask="#### #### #### ####"
        autocomplete="cc-number"
        onArcChange={(e) => console.log(e.detail.value)}
      />
      <MaskedInput label="License key" name="license" mask="AAA-###-AAA" placeholder-char="•" />
    </div>
  );
}
<script setup>
import { MaskedInput } from '@arclux/arc-ui-vue';
</script>

<template>
  <div style="display:flex; flex-direction:column; width:100%; max-width:400px; gap:16px;">
    <MaskedInput label="Expiry date" name="expiry" mask="##/##/####" autocomplete="cc-exp" />
    <MaskedInput label="Card number" name="card" mask="#### #### #### ####" autocomplete="cc-number" />
    <MaskedInput label="License key" name="license" mask="AAA-###-AAA" placeholder-char="•" />
  </div>
</template>
<script>
  import { MaskedInput } from '@arclux/arc-ui-svelte';
</script>

<div style="display:flex; flex-direction:column; width:100%; max-width:400px; gap:16px;">
  <MaskedInput label="Expiry date" name="expiry" mask="##/##/####" autocomplete="cc-exp" />
  <MaskedInput label="Card number" name="card" mask="#### #### #### ####" autocomplete="cc-number" />
  <MaskedInput label="License key" name="license" mask="AAA-###-AAA" placeholder-char="•" />
</div>
import { Component } from '@angular/core';
import { MaskedInput } from '@arclux/arc-ui-angular';

@Component({
  imports: [MaskedInput],
  template: `
    <div style="display:flex; flex-direction:column; width:100%; max-width:400px; gap:16px;">
      <arc-masked-input label="Expiry date" name="expiry" mask="##/##/####" autocomplete="cc-exp"></arc-masked-input>
      <arc-masked-input label="Card number" name="card" mask="#### #### #### ####" autocomplete="cc-number"></arc-masked-input>
      <arc-masked-input label="License key" name="license" mask="AAA-###-AAA" placeholder-char="•"></arc-masked-input>
    </div>
  `,
})
export class PaymentFormComponent {}
import { MaskedInput } from '@arclux/arc-ui-solid';

export default function Example() {
  return (
    <div style={{ display: 'flex', 'flex-direction': 'column', width: '100%', 'max-width': '400px', gap: '16px' }}>
      <MaskedInput label="Expiry date" name="expiry" mask="##/##/####" autocomplete="cc-exp" />
      <MaskedInput label="Card number" name="card" mask="#### #### #### ####" autocomplete="cc-number" />
      <MaskedInput label="License key" name="license" mask="AAA-###-AAA" placeholder-char="•" />
    </div>
  );
}
import { MaskedInput } from '@arclux/arc-ui-preact';

export default function Example() {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', width: '100%', maxWidth: 400, gap: 16 }}>
      <MaskedInput label="Expiry date" name="expiry" mask="##/##/####" autocomplete="cc-exp" />
      <MaskedInput label="Card number" name="card" mask="#### #### #### ####" autocomplete="cc-number" />
      <MaskedInput label="License key" name="license" mask="AAA-###-AAA" placeholder-char="•" />
    </div>
  );
}

API

autoValidates boolean false
Runs its own constraint logic — owns the whole validity flag set.
formattedValue
The formatted presentation string — raw characters interleaved with mask literals, e.g. raw 12042026 under a date mask reads 12/04/2026. Read-only: it is derived from value and mask, never stored, and never submitted.
mask string ''
The mask pattern. `#` accepts a digit, `A` an uppercase letter (lowercase input is uppercased), `a` any letter, `*` a letter or digit; every other character is a literal typed for the user. Examples: `##/##/####`, `#### #### #### ####`, `AAA-###`.
value string ''
The RAW accepted characters only, with no mask literals — `12042026`, never `12/04/2026`. The formatted string is presentation; read it from `formattedValue`. Programmatic values are conformed against the mask, so setting a formatted string keeps only the characters the mask accepts.
placeholder-char string '_'
Character rendered in unfilled positions of the in-field hint once typing starts (for example `12/__/____`). Before any input, the native placeholder shows the full mask shape. Defaults to `_`.
label string ''
Visible label rendered above the field. Automatically associated with the field via a generated id, ensuring screen readers announce it correctly.
name string ''
The `name` attribute sent with form data on submission. The submitted value is the RAW value, without mask literals.
disabled boolean false
Prevents user interaction and applies a muted visual treatment. The field value is excluded from form submission when disabled.
required boolean false
Marks the field as required. An empty field fails validation with valueMissing; a partially filled one fails with an "Incomplete value" pattern error.
autocomplete string ''
Passed through to the inner input, e.g. `cc-number` on a card field so browser autofill can offer saved cards.
error string ''
Error message displayed below the input. When set, the input border turns red and the error text appears.
size 'sm' | 'md' | 'lg' 'md'
Controls the input size. Options: 'sm', 'md', 'lg'.
readonly boolean false
Prevents the user from editing the value while keeping the field focusable, and the value is still submitted with the form.
formAssociated boolean true
properties object { required: { type: Boolean, reflect: true }, readonly: { type: Boolean, reflect: true }, }
Lit merges static properties up the prototype chain, so every consumer gets these without declaring them. `required` participates in constraint validation below; `readonly` reflects for styling and is enforced by each component's interaction handlers (the mixin can't know which gestures mutate state).
form
validity
validationMessage

Events

arc-input detail: { value: string, formatted: string }
Fired on each accepted edit. `value` is the raw characters; `formatted` is the presentation string. A rejected character fires nothing.
arc-change detail: { value: string, formatted: string }
Fired on blur or Enter when the value changed, and immediately when the last mask position fills — a complete mask is a committed value, the fixed-length precedent set by OTP Input.

See Also