@letar/forms

Russian Documents

INN, OGRN, BIK, SNILS, passport fields with checksum validation

Overview

Every B2B form in Russia requires INN, OGRN, BIK, bank account numbers. These fields have checksums — a simple mask is useless without validation. @letar/forms provides both headless validators and ready-to-use UI components.

For how the underlying masking works (token model, formatting modes, when a mask should be skipped) see the Input Mask Engine guide.

Full example

The complete sandbox example, read directly from the form-develop-app source at build time.

apps/form-develop-app/src/app/documents-demo/page.tsx
'use client'

import { Code, Heading, Text, VStack } from '@chakra-ui/react'
import { Form } from '@letar/forms'
import { useState } from 'react'
import { DemoPageLayout } from '../_components'

export default function DocumentsDemoPage() {
  const [result, setResult] = useState<Record<string, unknown> | null>(null)

  return (
    <DemoPageLayout
      title="Russian Documents"
      description="Form.Document.* — ИНН, ОГРН, БИК, СНИЛС, Паспорт с масками и контрольными суммами"
    >
      {/* Реквизиты компании */}
      <VStack gap={4} align="stretch">
        <Heading size="lg">Реквизиты компании</Heading>
        <Text color="fg.muted">Маски ввода + realtime валидация контрольных сумм.</Text>

        <Form
          initialValue={{
            inn: '',
            kpp: '',
            ogrn: '',
            bik: '',
            account: '',
            corrAccount: '',
          }}
          onSubmit={(data) => setResult(data)}
        >
          <Form.Document.INN name="inn" label="ИНН" required />
          <Form.Document.KPP name="kpp" label="КПП" />
          <Form.Document.OGRN name="ogrn" label="ОГРН" />
          <Form.Document.BIK name="bik" label="БИК" />
          <Form.Document.BankAccount name="account" label="Расчётный счёт" />
          <Form.Document.CorrAccount name="corrAccount" label="Корр. счёт" />
          <Form.Button.Submit>Сохранить</Form.Button.Submit>
        </Form>
      </VStack>

      {/* Документы физлица */}
      <VStack gap={4} align="stretch" mt={8}>
        <Heading size="lg">Документы физлица</Heading>

        <Form
          initialValue={{
            snils: '',
            passport: '',
            foreignPassport: '',
            departmentCode: '',
            birthCertificate: '',
          }}
          onSubmit={(data) => setResult(data)}
        >
          <Form.Document.SNILS name="snils" label="СНИЛС" />
          <Form.Document.Passport name="passport" label="Паспорт" />
          <Form.Document.ForeignPassport name="foreignPassport" label="Загранпаспорт" />
          <Form.Document.DepartmentCode name="departmentCode" label="Код подразделения" />
          <Form.Document.BirthCertificate name="birthCertificate" label="Свидетельство о рождении" />
          <Form.Button.Submit>Сохранить</Form.Button.Submit>
        </Form>
      </VStack>

      {result && (
        <Code whiteSpace="pre" mt={4}>
          {JSON.stringify(result, null, 2)}
        </Code>
      )}
    </DemoPageLayout>
  )
}

Architecture: Two Layers

Layer 1: zRu Validators (headless)

Pure Zod schemas with checksum validation. Work on server and client, no UI dependency.

import { zRu } from '@letar/forms/validators/ru'

const CompanySchema = z.object({
  inn: zRu.inn(), // 10 or 12 digits + checksum
  kpp: zRu.kpp(), // 9 characters
  ogrn: zRu.ogrn(), // 13 digits + checksum
  bik: zRu.bik(), // 9 digits, starts with "04"
  account: zRu.bankAccount(), // 20 digits
  snils: zRu.snils(), // 11 digits + checksum
})

// INN variants
zRu.inn.legal() // legal entity only (10 digits)
zRu.inn.individual() // individual only (12 digits)

Layer 2: Form.Document.* (UI)

Ready-to-use fields with input masks, icons, and real-time checksum validation.

<Form.Document.INN name="inn" label="INN" />
<Form.Document.KPP name="kpp" label="KPP" />
<Form.Document.OGRN name="ogrn" label="OGRN" />
<Form.Document.BIK name="bik" label="BIK" />
<Form.Document.BankAccount name="account" label="Bank Account" />
<Form.Document.CorrAccount name="corrAccount" label="Corr. Account" />
<Form.Document.SNILS name="snils" label="SNILS" />
<Form.Document.Passport name="passport" label="Passport" />

Available Fields

ComponentFormatChecksum
Form.Document.INN10 or 12 digitsWeighted sum mod 11
Form.Document.KPP9 charactersFormat check (NNNNPPXXX)
Form.Document.OGRN13 digitsFirst 12 mod 11
Form.Document.BIK9 digitsStarts with "04"
Form.Document.BankAccount20 digitsControl key (with BIK)
Form.Document.CorrAccount20 digitsStarts with "301"
Form.Document.SNILSXXX-XXX-XXX YYModule 101
Form.Document.PassportXX XX XXXXXXFormat check

Custom Document Fields

Use createDocumentField to create your own document fields:

import { createDocumentField } from '@letar/forms'
import { LuFileText } from 'react-icons/lu'

const FieldCustomDoc = createDocumentField({
  displayName: 'FieldCustomDoc',
  mask: '999-999-999',
  placeholder: '123-456-789',
  icon: <LuFileText />,
  validate: (value) => {
    if (value.length < 9) return 'Too short'
    return undefined
  },
})

Live Example

Try the interactive example on forms-example.letar.best.

On this page