Document inputs
Ready-made fields for Uzbek document numbers — PINFL, passport, TIN, car plate and bank account.
Qiymat: ""
Qiymat: ""
Qiymat: ""
Qiymat: ""
Qiymat: ""
'use client';
import {
BankAccountInput,
CarPlateInput,
Field,
InnInput,
PassportInput,
PinflInput,
} from '@akhatmirzo/ui';
import { useState } from 'react';
export default function DocumentInputsOverview() {
const [pinfl, setPinfl] = useState('');
const [passport, setPassport] = useState('');
const [inn, setInn] = useState('');
const [plate, setPlate] = useState('');
const [account, setAccount] = useState('');
return (
<div className="grid w-full max-w-2xl gap-4 sm:grid-cols-2">
<Field label="JShShIR" description={`Qiymat: "${pinfl}"`}>
<PinflInput value={pinfl} onValueChange={setPinfl} />
</Field>
<Field label="Pasport yoki ID karta" description={`Qiymat: "${passport}"`}>
<PassportInput value={passport} onValueChange={setPassport} />
</Field>
<Field label="STIR" description={`Qiymat: "${inn}"`}>
<InnInput value={inn} onValueChange={setInn} />
</Field>
<Field label="Avtomobil raqami" description={`Qiymat: "${plate}"`}>
<CarPlateInput value={plate} onValueChange={setPlate} />
</Field>
<Field label="Hisob raqami" description={`Qiymat: "${account}"`} className="sm:col-span-2">
<BankAccountInput value={account} onValueChange={setAccount} />
</Field>
</div>
);
}
Import
import {
BankAccountInput,
CarPlateInput,
InnInput,
PassportInput,
PinflInput,
} from '@akhatmirzo/ui';They all follow the MaskedInput rule: the display is formatted, the
value is clean (no spaces). Validation uses the isValidX functions from @akhatmirzo/core —
see Identifiers.
| Component | Display | Value (value) |
|---|---|---|
PinflInput | 31503 90123 4567 | "31503901234567" |
PassportInput | AA 1234567 | "AA1234567" |
InnInput | 123 456 789 | "123456789" |
CarPlateInput | 01 | A 123 BC | "01A123BC" |
BankAccountInput | 2020 8000 9000 1234 5001 | "20208000900012345001" |
When to use
- Government, banking, insurance and leasing forms — users don't type the spaces, and the value reaches your server ready to use.
- Always add a label — inside a Field. Next to "JShShIR", say where to find
it in
description("the 14-digit personal number in your passport"). - Explain errors in text — the component only sets the
invalidstate; put the reason in theFielderrorprop. - Everything comes from
Input:size,clearable,disabled,readOnly,name.
Validation
With validate (default true) a complete value that fails the check marks the field
invalid. It doesn't turn red while you type. Two exceptions show the error right away: an
InnInput value starting with 0, and a CarPlateInput region code that isn't on the list.
0 bilan boshlanmaydi — darhol xato
validate={false} — tekshiruvsiz
'use client';
import { Field, InnInput, PassportInput } from '@akhatmirzo/ui';
export default function DocumentValidation() {
return (
<div className="grid w-full max-w-2xl gap-4 sm:grid-cols-2">
<Field label="STIR" description="0 bilan boshlanmaydi — darhol xato">
<InnInput defaultValue="012345678" />
</Field>
<Field label="Pasport" description="validate={false} — tekshiruvsiz">
<PassportInput defaultValue="AB1234567" validate={false} />
</Field>
</div>
);
}
PinflInput
The PINFL (JShShIR) is 14 digits in 5-5-4 groups. When the 14th digit is typed, onDetails
fires with { valid, gender, birthDate, complete } — gender and birth date are read from the
number itself. The check digit is not verified by default; turn it on with checksum.
14 raqam — pasportdagi shaxsiy raqam
'use client';
import { Field, PinflInput, type PinflInputDetails } from '@akhatmirzo/ui';
import { useState } from 'react';
export default function PinflDemo() {
const [details, setDetails] = useState<PinflInputDetails | null>(null);
const info =
details?.valid && details.birthDate
? `${details.gender === 'male' ? 'Erkak' : 'Ayol'}, tugʻilgan: ${details.birthDate.toLocaleDateString('uz-UZ')}`
: undefined;
return (
<div className="w-full max-w-sm">
<Field
label="JShShIR"
description={info ?? '14 raqam — pasportdagi shaxsiy raqam'}
error={details && !details.valid ? 'JShShIR notoʻgʻri' : undefined}
>
<PinflInput
onValueChange={(_, d) => {
if (!d.complete) setDetails(null);
}}
onDetails={setDetails}
/>
</Field>
</div>
);
}
PassportInput
Passport or ID card: 2 Latin letters + 7 digits. Lowercase becomes uppercase; Cyrillic letters
and extra characters are rejected. Phones open the keyboard in caps
(autoCapitalize="characters").
InnInput and BankAccountInput
The TIN (STIR) is 9 digits (000 000 000); a bank account is 20 digits in groups of four. Both
open a numeric keypad on phones.
CarPlateInput
The personal (01 A 123 BC) or legal-entity (01 123 ABC) format is detected from the third
character: a letter means personal, a digit means legal. The region code sits on the left behind
a divider, with a flag mark and "UZ" on the right — like a real plate.
01 A 123 BC
01 123 ABC
'use client';
import { CarPlateInput, Field, type CarPlateInputDetails } from '@akhatmirzo/ui';
import { useState } from 'react';
export default function CarPlateDemo() {
const [details, setDetails] = useState<CarPlateInputDetails | null>(null);
const type =
details?.type === 'legal' ? 'yuridik shaxs' : details?.type ? 'jismoniy shaxs' : null;
const description = [details?.region?.name, type].filter(Boolean).join(' · ');
return (
<div className="grid w-full max-w-2xl gap-4 sm:grid-cols-2">
<Field label="Jismoniy shaxs" description={description || '01 A 123 BC'}>
<CarPlateInput defaultValue="01A123BC" onDetails={setDetails} />
</Field>
<Field label="Yuridik shaxs" description="01 123 ABC">
<CarPlateInput defaultValue="30123ABC" />
</Field>
</div>
);
}
onDetails fires on every change — the region is known after the first two digits, the type
after the third character:
<CarPlateInput
onDetails={({ region, type, valid }) => {
// region — an object from UZ_REGIONS or null; type — 'personal' | 'legal' | null
}}
/>You can get the same details outside the component: getCarPlateDetails('01A123BC'),
getPinflDetails('31503901234567').
Props
Every component accepts the Input props and ref. With name, a hidden
<input> with the clean value is added to the form.
Common
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Clean value, no spaces |
defaultValue | string | — | |
onValueChange | (value: string, details) => void | — | details: { complete, valid } (+ component-specific fields) |
validate | boolean | true | Mark a complete value invalid when it fails the check |
PinflInput
| Prop | Type | Default | Description |
|---|---|---|---|
onDetails | (details: PinflInputDetails) => void | — | When 14 digits are in: { valid, gender, birthDate, complete } |
checksum | boolean | false | Also verify the check digit |
placeholder | string | '00000 00000 0000' |
CarPlateInput
prefix and leftIcon are not accepted — the left side is reserved for the region code.
| Prop | Type | Default | Description |
|---|---|---|---|
onDetails | (details: CarPlateInputDetails) => void | — | On every change: { region, type, complete, valid } |
rightIcon | ReactNode | — | Defaults to the flag mark and "UZ" |
placeholder | string | '01 A 123 BC' |
Other placeholders
| Component | placeholder |
|---|---|
PassportInput | 'AA 1234567' |
InnInput | '000 000 000' |
BankAccountInput | '0000 0000 0000 0000 0000' |
Data attributes
| Attribute | Component | Value |
|---|---|---|
data-plate-type | CarPlateInput | personal · legal |
data-region | CarPlateInput | region id (tashkent-city) |
Accessibility
- Native
<input type="text">— screen readers read the formatted text. - Mobile keyboards:
PinflInput,InnInput,BankAccountInputuseinputMode="numeric";PassportInputandCarPlateInputuseautoCapitalize="characters". - The error state is exposed as
aria-invalid. Explain the reason in theFielderrorprop — color shouldn't be the only signal. - Decorative parts — the plate divider and the flag mark are
aria-hidden; screen readers only read the number.