Мультистеп формы
Пошаговые мастера с валидацией
Живой пример
Steps
API: Form.Steps
Разбейте длинную форму на управляемые шаги:
<Form schema={Schema} initialValue={data} onSubmit={save}>
<Form.Steps animated validateOnNext>
<Form.Steps.Step title="Личные данные">
<Form.Field.String name="firstName" />
<Form.Field.String name="lastName" />
<Form.Field.String name="email" />
</Form.Steps.Step>
<Form.Steps.Step title="Адрес">
<Form.Field.String name="street" />
<Form.Field.String name="city" />
<Form.Field.String name="zip" />
</Form.Steps.Step>
<Form.Steps.Step title="Настройки">
<Form.Field.Checkbox name="newsletter" />
<Form.Field.Select name="language" />
</Form.Steps.Step>
<Form.Steps.Navigation />
</Form.Steps>
</Form>Полный пример
Полный пример из песочницы form-develop-app / form-develop-app-shadcn, прочитан напрямую из
исходников на сборке. У shadcn тот же compound-компонент (FormSteps, beta, уровня формы, не
Field) с более компактной двухшаговой схемой — механика та же, масштаб меньше.
'use client'
import { Box, Card, Container, Heading, Stack, Text, VStack } from '@chakra-ui/react'
import { Form } from '@letar/forms'
import { z } from 'zod/v4'
// Schema for multi-step form
const RegistrationSchema = z.object({
// Step 1: Personal info
firstName: z.string().min(2, 'Minimum 2 characters'),
lastName: z.string().min(2, 'Minimum 2 characters'),
birthDate: z.string().optional(),
// Step 2: Contact info
email: z.string().email('Invalid email'),
phone: z.string().optional(),
// Step 3: Account settings
username: z.string().min(3, 'Minimum 3 characters'),
notifications: z.boolean().default(true),
theme: z.enum(['light', 'dark', 'system']).default('system'),
})
type RegistrationData = z.infer<typeof RegistrationSchema>
const initialValues: RegistrationData = {
firstName: '',
lastName: '',
birthDate: '',
email: '',
phone: '',
username: '',
notifications: true,
theme: 'system',
}
export default function StepsDemoPage() {
const handleSubmit = (data: RegistrationData) => {
// eslint-disable-next-line no-console -- демо-приложение
console.log('Form submitted:', data)
alert('Registration complete!\n\n' + JSON.stringify(data, null, 2))
}
return (
<Container maxW="container.md" py={8}>
<VStack gap={8} align="stretch">
<Heading size="xl">Form.Steps Demo</Heading>
<Text color="fg.muted">Multi-step form with validation on each step</Text>
<Card.Root>
<Card.Body>
<Form initialValue={initialValues} schema={RegistrationSchema} onSubmit={handleSubmit}>
<Form.Steps validateOnNext linear colorPalette="brand" animated>
<Form.Steps.Indicator showDescriptions />
<Box my={6}>
<Form.Steps.Step title="Personal" description="Your details">
<Stack gap={4}>
<Heading size="md">Personal Information</Heading>
<Form.Field.String name="firstName" label="First Name" required />
<Form.Field.String name="lastName" label="Last Name" required />
<Form.Field.Date name="birthDate" label="Birth Date" />
</Stack>
</Form.Steps.Step>
<Form.Steps.Step title="Contact" description="How to reach you">
<Stack gap={4}>
<Heading size="md">Contact Information</Heading>
<Form.Field.String name="email" label="Email" required />
<Form.Field.String name="phone" label="Phone" />
</Stack>
</Form.Steps.Step>
<Form.Steps.Step title="Account" description="Settings">
<Stack gap={4}>
<Heading size="md">Account Settings</Heading>
<Form.Field.String name="username" label="Username" required />
<Form.Field.Switch name="notifications" label="Enable notifications" />
<Form.Field.RadioGroup
name="theme"
label="Theme"
options={[
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'system', label: 'System' },
]}
/>
</Stack>
</Form.Steps.Step>
<Form.Steps.CompletedContent>
<Stack gap={4} py={4}>
<Heading size="md" color="green.500">
All steps complete!
</Heading>
<Text>Review your information and click Submit to finish registration.</Text>
</Stack>
</Form.Steps.CompletedContent>
</Box>
{/* data-* на кнопках навигации: якоря для подсказок наставника и тестов (v2.14.22+) */}
<Form.Steps.Navigation
prevLabel="Back"
nextLabel="Continue"
submitLabel="Create Account"
prevProps={{ 'data-assist-id': 'steps-demo.prev' }}
nextProps={{ 'data-assist-id': 'steps-demo.next' }}
submitProps={{ 'data-assist-id': 'steps-demo.submit' }}
/>
</Form.Steps>
</Form>
</Card.Body>
</Card.Root>
{/* Non-linear example with animation */}
<Heading size="lg" mt={8}>
Non-linear Steps (with animation)
</Heading>
<Text color="fg.muted">Click on any step to navigate directly. Animation duration: 0.5s</Text>
<Card.Root>
<Card.Body>
<Form
initialValue={{ step1Field: '', step2Field: '', step3Field: '' }}
// eslint-disable-next-line no-console -- демо-приложение
onSubmit={(data) => console.log('Non-linear form:', data)}
>
<Form.Steps colorPalette="teal" animated animationDuration={0.5}>
<Form.Steps.Indicator />
<Box my={6}>
<Form.Steps.Step title="Step 1">
<Form.Field.String name="step1Field" label="Step 1 Field" />
</Form.Steps.Step>
<Form.Steps.Step title="Step 2">
<Form.Field.String name="step2Field" label="Step 2 Field" />
</Form.Steps.Step>
<Form.Steps.Step title="Step 3">
<Form.Field.String name="step3Field" label="Step 3 Field" />
</Form.Steps.Step>
</Box>
<Form.Steps.Navigation />
</Form.Steps>
</Form>
</Card.Body>
</Card.Root>
</VStack>
</Container>
)
}'use client'
import { FieldString, FormSteps } from '@letar/forms-shadcn'
import { DemoForm, DemoPageLayout } from '../_components'
export default function StepsDemoPage() {
return (
<DemoPageLayout
title="FormSteps (beta)"
description="Compound-компонент форм-уровня, не Field — изолированная песочница."
>
<DemoForm<{ firstName: string; email: string }>
defaultValues={{ firstName: '', email: '' }}
onSubmit={(value) => {
// eslint-disable-next-line no-console
console.log('steps submit', value)
}}
>
<FormSteps>
<FormSteps.Indicator showDescriptions />
<FormSteps.Step title="Личное" description="Как к вам обращаться">
<FieldString name="firstName" label="Имя" required />
</FormSteps.Step>
<FormSteps.Step title="Контакты" description="Как с вами связаться">
<FieldString name="email" label="Email" type="email" />
</FormSteps.Step>
<FormSteps.CompletedContent>
<p className="text-sm">Все шаги пройдены — можно отправлять.</p>
</FormSteps.CompletedContent>
{/* data-* на кнопках навигации: якоря для подсказок наставника и тестов */}
<FormSteps.Navigation
prevProps={{ 'data-assist-id': 'steps-demo.prev' }}
nextProps={{ 'data-assist-id': 'steps-demo.next' }}
submitProps={{ 'data-assist-id': 'steps-demo.submit' }}
/>
</FormSteps>
</DemoForm>
</DemoPageLayout>
)
}Vue-пример для этого поля появится позже.
Angular-пример для этого поля появится позже.
Возможности
animated— плавные переходы между шагамиvalidateOnNext— валидация текущего шага перед переходомForm.Steps.Navigation— кнопки Назад / Далее / Отправить- Индикатор — отображение прогресса по шагам
- Навигация клавиатурой — поддержка стрелок
Пропсы кнопок навигации
prevProps, nextProps, submitProps и skipProps передают дополнительные атрибуты каждой
кнопке — data-* (якоря подсказок наставника, data-testid, аналитика), aria-* и другие
атрибуты нативной кнопки. Литералы data-* пишутся без as:
<Form.Steps.Navigation
prevProps={{ 'data-assist-id': 'wizard.prev' }}
nextProps={{ 'data-assist-id': 'wizard.next' }}
submitProps={{ 'data-assist-id': 'wizard.submit' }}
/>- «Далее» и «Отправить» — две разные кнопки. На последнем шаге вместо
nextPropsдействуетsubmitProps; атрибуты с одной кнопки на другую не переезжают. onClick,disabled,loadingиtypeзадаёт сам компонент: в тип пропсов они не входят, а в рантайме перекрываются, поэтому навигацию по шагам через эти пропсы не сломать.- Доступно с
@letar/forms2.14.22; те же пропсы есть в@letar/forms-shadcn,@letar/forms-vueи@letar/forms-vue-shadcn.
Экран после завершения
Form.Steps.CompletedContent рендерит экран, показываемый после подтверждения последнего шага —
сводку или панель «проверьте перед отправкой»:
<Form.Steps validateOnNext>
<Form.Steps.Step title="Личные данные">...</Form.Steps.Step>
<Form.Steps.Step title="Адрес">...</Form.Steps.Step>
<Form.Steps.CompletedContent>
<p>Всё готово! Проверьте данные и нажмите «Отправить».</p>
</Form.Steps.CompletedContent>
<Form.Steps.Navigation />
</Form.Steps>Его присутствие в дереве меняет поведение кнопки на последнем шаге (@letar/forms 2.16.0+):
Continue сначала переводит в состояние «завершено» (там рендерится CompletedContent), и только
оттуда Form.Steps.Navigation переключается на Submit. Без CompletedContent последний шаг
по-прежнему отправляет форму сразу — экран завершения подключается по желанию, существующие формы
не затрагиваются.
Сохранение шагов
Шаги можно сохранять в localStorage для восстановления при перезагрузке страницы:
<Form.Steps animated validateOnNext persistKey="checkout-wizard">
...
</Form.Steps>