@letar/forms

Автоматическая генерация форм

Генерация форм из Zod-схемы — от нулевого JSX до тонкой настройки

4 уровня контроля

Библиотека предлагает четыре уровня генерации форм — каждый следующий даёт больше контроля:

УровеньКомпонентJSXКогда использовать
1Form.FromSchemaНетCRUD, прототипы, админки
2Form.AutoFieldsТолько layoutКастомная раскладка с автополями
3Form.Field.*Поля + layoutПолный контроль над каждым полем
4useAppFormВсёИмперативная логика, кастомные хуки

Уровни совместимы — можно свободно комбинировать их в одной форме.

Уровень 1: Form.FromSchema — Ноль JSX

Генерирует полную форму из Zod-схемы без единого поля в JSX:

import { Form } from '@letar/forms'
import { z } from 'zod/v4'

const UserSchema = z.object({
  firstName: z.string().min(2).meta({ ui: { title: 'Имя' } }),
  lastName: z.string().meta({ ui: { title: 'Фамилия' } }),
  email: z.string().email().meta({ ui: { title: 'Email' } }),
  bio: z.string().max(500).meta({ ui: { title: 'О себе', fieldType: 'textarea' } }),
})

<Form.FromSchema
  schema={UserSchema}
  initialValue={{ firstName: '', lastName: '', email: '', bio: '' }}
  onSubmit={save}
  submitLabel="Создать"
/>

Рендерит все четыре поля с лейблами, валидацией и кнопкой отправки.

Пропсы FromSchema

ПропТипПо умолчаниюОписание
schemaZod schemaобязательныйСхема для валидации и генерации полей
initialValueTDataобязательныйНачальные значения формы
onSubmit(data: TData) => voidобязательныйОбработчик отправки
submitLabelReactNode'Save'Текст кнопки отправки
showResetbooleanfalseПоказать кнопку сброса
resetLabelReactNode'Reset'Текст кнопки сброса
excludestring[]Поля для исключения
validateOnValidateOnРежим валидации
middlewareFormMiddlewareMiddleware для событий формы
disabledbooleanЗаблокировать все поля
readOnlybooleanРежим только чтение
persistenceFormPersistenceConfigКонфигурация localStorage
offlineFormOfflineConfigКонфигурация оффлайн-режима
debugboolean | 'force'JSON-инспектор значений
gapnumber4Отступ между полями
beforeButtonsReactNodeКонтент перед кнопками
afterButtonsReactNodeКонтент после кнопок

Исключение полей

Скрываем системные поля (id, timestamp-ы):

<Form.FromSchema
  schema={UserSchema}
  initialValue={userData}
  onSubmit={updateUser}
  exclude={['id', 'createdAt', 'updatedAt']}
  showReset
/>

Уровень 2: Form.AutoFields — Автогенерация + кастомный layout

Когда нужен контроль над раскладкой, но не хочется писать каждое поле вручную:

<Form schema={Schema} initialValue={data} onSubmit={save}>
  <HStack gap={4}>
    <Form.AutoFields include={['firstName', 'lastName']} />
  </HStack>
  <Form.AutoFields exclude={['firstName', 'lastName']} />
  <Form.Button.Submit />
</Form>

Пропсы AutoFields

ПропТипПо умолчаниюОписание
includestring[]Рендерить только эти поля
excludestring[]Исключить эти поля
recursivebooleantrueРаскрывать вложенные объекты
fieldWrapper(props) => ReactElementКастомная обёртка для каждого поля

Кастомная обёртка полей

Обернуть каждое автогенерируемое поле в кастомный JSX:

<Form.AutoFields
  fieldWrapper={({ name, children }) => (
    <Box key={name} p={2} borderWidth={1} borderRadius="md">
      {children}
    </Box>
  )}
/>

Смешанный подход: авто + ручные поля

Комбинируйте автогенерируемые и ручные поля:

<Form schema={ArticleSchema} initialValue={data} onSubmit={save}>
  <HStack gap={4}>
    <Box flex={2}>
      <Form.Field.Auto name="title" />
    </Box>
    <Box flex={1}>
      <Form.Field.Auto name="slug" />
    </Box>
  </HStack>
  <Form.Field.RichText name="content" label="Содержимое статьи" />
  <Form.Button.Submit>Опубликовать</Form.Button.Submit>
</Form>

Form.Field.Auto рендерит подходящий компонент на основе типа поля в схеме.

Маппинг типов

Библиотека автоматически сопоставляет Zod-типы с компонентами полей:

Маппинг по умолчанию (Zod → Компонент)

Zod типКомпонентУсловие
z.string()Form.Field.StringПо умолчанию
z.string()Form.Field.TextareaЕсли maxLength > 200
z.string().email()Form.Field.StringС type="email"
z.number() / z.int() / z.float()Form.Field.Number
z.boolean()Form.Field.Checkbox
z.date()Form.Field.Date
z.enum() / z.literal()Form.Field.NativeSelect
z.array(z.string())Form.Field.TagsМассив строк
z.array(z.object())Form.Group.ListМассив объектов

Переопределение через fieldType

Используйте .meta({ ui: { fieldType } }) для переопределения маппинга:

const Schema = z.object({
  // Textarea вместо строки
  description: z.string().meta({ ui: { title: 'Описание', fieldType: 'textarea' } }),

  // Слайдер вместо числового поля
  rating: z
    .number()
    .min(0)
    .max(10)
    .meta({ ui: { title: 'Рейтинг', fieldType: 'slider' } }),

  // Rich text редактор
  content: z.string().meta({ ui: { title: 'Контент', fieldType: 'richText' } }),

  // Switch вместо чекбокса
  active: z.boolean().meta({ ui: { title: 'Активен', fieldType: 'switch' } }),

  // Radio cards вместо select
  plan: z.enum(['free', 'pro', 'enterprise']).meta({
    ui: {
      title: 'Тариф',
      fieldType: 'radioCard',
      fieldProps: {
        options: [
          { value: 'free', label: 'Бесплатный', description: 'Для личного использования' },
          { value: 'pro', label: 'Профессиональный', description: 'Для команд' },
          { value: 'enterprise', label: 'Корпоративный', description: 'Индивидуально' },
        ],
      },
    },
  }),
})

Все доступные fieldType

Текст: string, textarea, password, passwordStrength, editable, richText, maskedInput

Числа: number, numberInput, slider, rating, currency, percentage

Дата/Время: date, time, dateRange, dateTimePicker, duration, schedule

Логические: checkbox, switch

Выбор: select, nativeSelect, combobox, autocomplete, listbox, radioGroup, radioCard, segmentedGroup, checkboxCard, tags

Специальные: phone, address, pinInput, otpInput, colorPicker, fileUpload

UI-метаданные

Управление внешним видом полей через .meta({ ui: {...} }):

const Schema = z.object({
  email: z
    .string()
    .email()
    .meta({
      ui: {
        title: 'Email-адрес', // Лейбл
        placeholder: 'user@example.com', // Плейсхолдер
        description: 'Мы никому не передаём', // Подсказка под полем
        fieldType: 'string', // Переопределение компонента
        fieldProps: { type: 'email' }, // Пропсы для компонента
      },
    }),
})
Свойство metaТипОписание
titlestringЛейбл поля
placeholderstringТекст-заполнитель
descriptionstringПодсказка под полем
fieldTypeFieldComponentTypeПереопределение компонента
fieldPropsRecord<string, unknown>Дополнительные пропсы компонента
optionsBaseOption[]Опции для select-полей
autocompletestringHTML атрибут autocomplete

Если title не указан, имя поля конвертируется из camelCase в читаемый формат (firstNameFirst Name).

Вложенные объекты и массивы

Вложенные объекты

Объекты автоматически рендерятся как Form.Group:

const ProfileSchema = z.object({
  name: z.string().meta({ ui: { title: 'Имя' } }),
  settings: z.object({
    theme: z.enum(['light', 'dark']).meta({ ui: { title: 'Тема' } }),
    notifications: z.boolean().meta({ ui: { title: 'Уведомления' } }),
  }),
})

// AutoFields автоматически создаёт группу для settings
<Form.FromSchema schema={ProfileSchema} initialValue={data} onSubmit={save} />

Отключить рекурсивное раскрытие: recursive={false}.

Массивы объектов

Массивы объектов рендерятся как Form.Group.List с кнопками добавления/удаления:

const OrderSchema = z.object({
  customer: z.string(),
  items: z.array(
    z.object({
      product: z.string().meta({ ui: { title: 'Товар' } }),
      qty: z.number().min(1).meta({ ui: { title: 'Количество' } }),
    })
  ),
})

// Каждый элемент массива — карточка с полями + кнопка удаления
<Form.FromSchema schema={OrderSchema} initialValue={data} onSubmit={save} />

Примитивные массивы

Массивы строк рендерятся как Form.Field.Tags:

const Schema = z.object({
  tags: z.array(z.string()).meta({ ui: { title: 'Теги' } }),
})

Автоматические constraints

Ограничения из Zod автоматически передаются как пропсы компонентов:

z.string().min(2).max(100) // → minLength={2} maxLength={100}
z.number().min(1).max(10) // → min={1} max={10}
z.string().email() // → type="email"
z.date().min(new Date()) // → min={now}

Не нужно дублировать ограничения в JSX — схема является единым источником истины.

Когда какой уровень использовать

СценарийРекомендуемый уровень
Админские CRUD-формыForm.FromSchema
Быстрое прототипированиеForm.FromSchema
Простые формы с кастомным layoutForm.AutoFields
Сложные многоколоночные layoutsForm.Field.* (Compound Components)
Мультистеп формыForm.Field.* + Form.Steps
Условный рендерингForm.Field.* + Form.When
Кастомный UX (анимации, inline-editing)useAppForm

Правило: Если форма — линейный вертикальный поток, FromSchema или AutoFields отлично подходят. Для сложных layouts используйте Compound Components.

Живые примеры

Попробуйте интерактивные примеры на forms-example.letar.best:

On this page