CardInput
A credit/debit card number field that auto-formats digits into groups and detects the card brand (Visa, Mastercard, Amex, and more) as the user types.
Overview
CardInput wraps Input and specializes it for credit/debit card numbers. It strips non-digit characters as you type, re-inserts spacing groups based on the detected card brand (via the credit-card-type library), and caps the length to that brand's valid digit count — so a Visa caps at 16 digits grouped as 4242 4242 4242 4242, while an Amex caps at 15 digits grouped as 3782 822463 10005.
The detected brand's logo renders automatically at the start of the field (falling back to a generic card icon when nothing matches yet), inputMode="numeric" is set so mobile devices show a numeric keypad, and a pattern restricts accepted characters to digits and spaces. type and startEl are fixed internally and can't be overridden.
Import
import { CardInput } from '@unflow.io/ui/components/CardInput';Props
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | required | Form field name |
label | string | — | Visible label above the field |
value | string | — | Controlled value — raw or formatted digits, re-formatted on render |
onChange | ChangeEventHandler | — | Called with the re-formatted value after digits are stripped/grouped |
placeholder | string | '0000 0000 0000 0000' | Placeholder text |
hint | string | — | Helper text below the label |
maxLength | number | auto (per detected brand) | Overrides the auto-computed max length |
disabled | boolean | false | Disables the input |
required | boolean | false | Marks field as required |
fullWidth | boolean | false | Stretches to fill container |
status | 'valid' | 'invalid' | 'error' | — | Validation state — renders an InputStatus icon |
statusMessage | string | — | Message shown in the InputStatus tooltip |
sensitive | boolean | false | Adds a reveal/hide toggle over the value |
sensitiveDefaultHidden | boolean | true | Whether value starts hidden |
endIcon | ReactNode | — | Icon inside the right edge |
endEl | ReactNode | — | Element attached to the right border (e.g. action button) |
onClear | MouseEventHandler | — | Wires up a clear button — rendered when value is non-empty |
All standard HTMLInputElement props are forwarded. type and startEl are not exposed — CardInput always renders as a text field with the detected brand icon in startEl.
Basic usage
const [value, setValue] = useState('');
<CardInput
name="card-number"
label="Card number"
hint="Enter the 16-digit number on your card."
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
onClear={() => setValue('')}
/>Brand detection
The leading digits determine which brand logo renders on the left. Try Visa (4…), Mastercard (51–55…), Amex (34/37…), and Discover (6011…) — an unrecognized or empty value falls back to a generic card icon.
<CardInput name="visa" label="Visa" value="4242424242424242" onChange={() => {}} />
<CardInput name="mastercard" label="Mastercard" value="5555555555554444" onChange={() => {}} />
<CardInput name="amex" label="American Express" value="378282246310005" onChange={() => {}} />
<CardInput name="discover" label="Discover" value="6011111111111117" onChange={() => {}} />Status
<CardInput
name="valid"
label="Valid"
status="valid"
statusMessage="Looks good!"
value="4242424242424242"
onChange={() => {}}
/>
<CardInput
name="invalid"
label="Invalid"
status="invalid"
statusMessage="Check the card number."
value="4242"
onChange={() => {}}
/>
<CardInput
name="error"
label="Error"
status="error"
statusMessage="This card was declined."
value="4000000000000002"
onChange={() => {}}
/>status renders an InputStatus icon at the top-right of the field. Hovering the icon shows statusMessage in a tooltip.
Disabled
<CardInput
name="card-number-disabled"
label="Card number"
value="4242424242424242"
disabled
onChange={() => {}}
/>Customization (slots & slotProps)
| Slot | Type | Description |
|---|---|---|
shortcut | ReactNode | Custom shortcut hint element |
sensitiveHiddenIcon / sensitiveVisibleIcon | ReactNode | Icon overrides for the reveal/hide toggle (when sensitive is set) |
clearIcon | ReactNode | Icon override for the clear button |
| Slot Prop | Props | Description |
|---|---|---|
startEl | HTMLDivElement props | Wrapper around the auto-detected brand icon — only className/DOM attributes apply, the icon itself can't be replaced |
endIcon / endEl | HTMLSpanElement / HTMLDivElement props | Wrapper around endIcon / endEl |
inputContainer | HTMLDivElement props | The input wrapper div |
inputLabel | Omit<IInputLabel, 'label' | 'hint' | 'required' | 'fullWidth'> | The label component |
clearButton | Omit<IIconButton, 'icon' | 'onClick'> | The clear button |
sensitiveButton | Omit<IIconButton, 'icon' | 'onClick'> | The reveal/hide button |
status | Omit<IInputStatus, 'status' | 'message'> | The status icon |
Accessibility
inputMode="numeric"and a digits/spaces-onlypatternhint mobile browsers to show a numeric keypad without blocking paste of formatted numbers.maxLengthis capped to the detected brand's valid digit count (plus grouping spaces), preventing entry of an obviously-too-long number.- Label,
required, andstatus/statusMessagebehave exactly as in Input — the label is linked viahtmlFor/id, and status is surfaced to screen readers through theInputStatustooltip.