Валидация в формах

Radix UI — это библиотека низкоуровневых компонентов для React, ориентированных на доступность и расширяемость. Несмотря на то, что она не предоставляет встроенной системы валидации форм, её компоненты идеально сочетаются с библиотеками для управления формами и валидацией, такими как React Hook Form, Formik или Zod. Использование Radix UI позволяет создавать интерфейсные элементы с высокой степенью доступности, а логику валидации делегировать проверенным инструментам.


Интеграция с React Hook Form

Для создания форм с валидацией обычно используют useForm из React Hook Form. Пример структуры:

import * as React from 'react';
import { useForm } from 'react-hook-form';
import * as TextField from '@radix-ui/react-label';

function MyForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSub mit = data => console.log(data);

  return (
    <form onSub mit={handleSubmit(onSubmit)}>
      <div>
        <TextField.Root>
          <TextField.Label htmlFor="email">Email</TextField.Label>
          <input
            id="email"
            {...register("email", { required: "Email обязателен", pattern: { value: /^\S+@\S+$/i, message: "Неверный формат email" } })}
          />
          {errors.email && <span>{errors.email.message}</span>}
        </TextField.Root>
      </div>

      <div>
        <TextField.Root>
          <TextField.Label htmlFor="password">Пароль</TextField.Label>
          <input
            type="password"
            id="password"
            {...register("password", { required: "Пароль обязателен", minLength: { value: 6, message: "Минимум 6 символов" } })}
          />
          {errors.password && <span>{errors.password.message}</span>}
        </TextField.Root>
      </div>

      <button type="submit">Отправить</button>
    </form>
  );
}

Ключевые моменты интеграции:

  • register связывает поля формы с React Hook Form и задаёт правила валидации.
  • formState.errors позволяет выводить пользовательские сообщения об ошибках.
  • Radix UI компоненты (TextField.Root, TextField.Label) обеспечивают доступность и стилизуемую обертку для стандартных <input>.

Использование кастомных компонентов Radix UI с контроллером

Если компонент Radix UI не поддерживает прямое подключение к register, применяют Controller:

import { Controller, useForm } from 'react-hook-form';
import * as Slider from '@radix-ui/react-slider';

function SliderForm() {
  const { control, handleSubmit } = useForm({ defaultValues: { volume: 50 } });

  const onSub mit = data => console.log(data);

  return (
    <form onSub mit={handleSubmit(onSubmit)}>
      <Controller
        name="volume"
        control={control}
        rules={{ min: 0, max: 100 }}
        render={({ field }) => (
          <Slider.Root value={[field.value]} onValueCha nge={([val]) => field.onChange(val)} max={100}>
            <Slider.Track>
              <Slider.Range />
            </Slider.Track>
            <Slider.Thumb />
          </Slider.Root>
        )}
      />
      <button type="submit">Сохранить</button>
    </form>
  );
}

Особенности подхода с Controller:

  • Подходит для нестандартных компонентов, которые не используют ref для формы.
  • Позволяет задавать правила валидации (rules) и получать текущее состояние (field.value).
  • Интеграция с Radix Slider, Checkbox, Toggle и другими контролируемыми элементами.

Сложная валидация с использованием Zod

Для форм с комплексной логикой валидации удобно сочетать Zod и React Hook Form:

import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';

const schema = z.object({
  username: z.string().min(3, "Минимум 3 символа"),
  age: z.number().min(18, "Возраст должен быть 18+"),
});

function AdvancedForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema)
  });

  return (
    <form onSub mit={handleSubmit(console.log)}>
      <div>
        <label htmlFor="username">Имя</label>
        <input id="username" {...register("username")} />
        {errors.username && <span>{errors.username.message}</span>}
      </div>
      <div>
        <label htmlFor="age">Возраст</label>
        <input id="age" type="number" {...register("age", { valueAsNumber: true })} />
        {errors.age && <span>{errors.age.message}</span>}
      </div>
      <button type="submit">Отправить</button>
    </form>
  );
}

Преимущества Zod:

  • Валидация происходит на уровне схемы, а не на каждом поле отдельно.
  • Легко добавлять кастомные проверки и комбинировать с другими типами данных.
  • Интеграция с Radix UI не требует дополнительных адаптеров — компоненты остаются чистыми и доступными.

Управление ошибками и UX

Radix UI позволяет строить наглядные и доступные сообщения об ошибках:

  • Использование aria-invalid и aria-describedby для уведомления скринридеров о проблемах с формой.
  • Стилизация компонентов с помощью CSS или CSS-in-JS для отображения состояния ошибок (красная рамка, иконка, текст).
  • Создание собственных компонентов FormError на основе Radix Tooltip для подсказок при наведении или фокусе.

Пример:

import * as Tooltip from '@radix-ui/react-tooltip';

function ErrorTooltip({ message, children }) {
  return (
    <Tooltip.Provider>
      <Tooltip.Root>
        <Tooltip.Trigger asChild>
          {children}
        </Tooltip.Trigger>
        <Tooltip.Content side="right">
          {message}
          <Tooltip.Arrow />
        </Tooltip.Content>
      </Tooltip.Root>
    </Tooltip.Provider>
  );
}

Итоговые рекомендации по архитектуре

  • Использовать Radix UI для визуальных компонентов, обеспечивающих доступность.
  • Делегировать логику валидации проверенным библиотекам: React Hook Form, Formik, Zod.
  • Для нестандартных компонентов применять Controller из React Hook Form.
  • Обеспечивать обратную связь пользователю через aria-* атрибуты и стилизованные компоненты сообщений об ошибках.

Такой подход позволяет построить формы с высокой степенью контроля, качественной валидацией и адаптируемым UX, сохраняя чистую и модульную архитектуру приложения.