バリデーション・エラー表示
バリデーション・エラー表示は、フォーム入力に対するバリデーション結果をUIに反映する仕組み。必須チェック・文字数制限・メールアドレス形式など、ユーザーの入力内容に問題がある場合にエラーメッセージやスタイルで伝える。
インラインエラーメッセージをフィールド直下に表示するのが最も一般的なパターン。React Hook Form や Zod と組み合わせることで、スキーマ定義とUIを分離したバリデーションが実現できる。
主なバリエーション
- •インラインエラーメッセージ(フィールド直下)
- •エラー時のボーダー色・背景色の変更
- •フォームレベルのバリデーションサマリー
- •リアルタイムバリデーション(入力中)
- •React Hook Form との連携
- •Zod スキーマ連携
ライブラリ横断比較
| 機能 | Mantine | Ant Design | shadcn/ui | MUI |
|---|---|---|---|---|
| インラインエラーメッセージ | ○ error prop | ○ help + status | ○ FormMessage | ○ helperText |
| エラー時のスタイル変更 | ○ 自動適用 | ○ validateStatus | ○ 自動適用 | ○ error prop |
| フォームレベルバリデーション | ○ useForm | ○ Form.useForm | ○ handleSubmit | △ 外部ライブラリ推奨 |
| リアルタイムバリデーション | ○ validateInputOnChange | ○ validateTrigger | ○ mode: onChange | △ カスタム実装 |
| React Hook Form連携 | ○ or @mantine/form | △ Controller利用 | ○ ネイティブ対応 | ○ Controller利用 |
| Zodスキーマ連携 | ○ zodResolver | △ 外部ライブラリ | ○ zodResolver | ○ zodResolver |
○ = 対応 △ = 部分対応・制限あり × = 非対応
ライブラリ別コード例
各ライブラリでバリデーション・エラー表示を実装する際の設定部分を抜粋しています。 動くデモは各比較ページでご確認ください。
Mantine
// @mantine/form でバリデーション
import { useForm } from '@mantine/form';
import { TextInput, PasswordInput, Button, Stack } from '@mantine/core';
function MyForm() {
const form = useForm({
initialValues: { email: '', password: '' },
validate: {
email: (value) =>
/^S+@S+$/.test(value) ? null : '有効なメールアドレスを入力してください',
password: (value) =>
value.length < 8 ? 'パスワードは8文字以上入力してください' : null,
},
validateInputOnChange: true, // リアルタイムバリデーション
});
return (
<form onSubmit={form.onSubmit(console.log)}>
<Stack>
<TextInput
label="メールアドレス"
{...form.getInputProps('email')}
// error prop が自動設定される
/>
<PasswordInput
label="パスワード"
{...form.getInputProps('password')}
/>
<Button type="submit" disabled={!form.isValid()}>送信</Button>
</Stack>
</form>
);
}
// Zod スキーマ連携(mantine-form-zod-resolver)
import { zodResolver } from 'mantine-form-zod-resolver';
import { z } from 'zod';
const schema = z.object({
email: z.string().email('有効なメールアドレスを入力してください'),
age: z.number().min(18, '18歳以上のみ登録可能です'),
});
const form = useForm({ validate: zodResolver(schema), initialValues: { email: '', age: 0 } });Ant Design
// Form + Form.Item でバリデーション
import { Form, Input, Button } from 'antd';
function MyForm() {
const [form] = Form.useForm();
return (
<Form form={form} onFinish={console.log} layout="vertical">
<Form.Item
name="email"
label="メールアドレス"
rules={[
{ required: true, message: 'メールアドレスは必須です' },
{ type: 'email', message: '有効なメールアドレスを入力してください' },
{ max: 100, message: '100文字以内で入力してください' },
]}
validateTrigger="onChange" // リアルタイムバリデーション
>
<Input />
</Form.Item>
<Form.Item
name="password"
label="パスワード"
rules={[{ min: 8, message: 'パスワードは8文字以上です' }]}
>
<Input.Password />
</Form.Item>
<Button htmlType="submit" type="primary">送信</Button>
</Form>
);
}shadcn/ui
// react-hook-form + zod + shadcn/ui(推奨パターン)
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import {
Form, FormControl, FormField, FormItem, FormLabel, FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
const schema = z.object({
email: z.string().email('有効なメールアドレスを入力してください'),
password: z.string().min(8, 'パスワードは8文字以上入力してください'),
});
function MyForm() {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { email: '', password: '' },
mode: 'onChange', // リアルタイムバリデーション
});
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(console.log)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>メールアドレス</FormLabel>
<FormControl><Input {...field} /></FormControl>
<FormMessage /> {/* エラーメッセージを自動表示 */}
</FormItem>
)}
/>
<Button type="submit">送信</Button>
</form>
</Form>
);
}MUI
// react-hook-form + zod + MUI TextField
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { TextField, Button, Stack } from '@mui/material';
const schema = z.object({
email: z.string().email('有効なメールアドレスを入力してください'),
password: z.string().min(8, 'パスワードは8文字以上入力してください'),
});
function MyForm() {
const { control, handleSubmit } = useForm({
resolver: zodResolver(schema),
mode: 'onChange', // リアルタイムバリデーション
});
return (
<form onSubmit={handleSubmit(console.log)}>
<Stack spacing={2}>
<Controller
name="email"
control={control}
render={({ field, fieldState }) => (
<TextField
{...field}
label="メールアドレス"
error={!!fieldState.error}
helperText={fieldState.error?.message} // エラーメッセージ
fullWidth
/>
)}
/>
<Controller
name="password"
control={control}
render={({ field, fieldState }) => (
<TextField
{...field}
label="パスワード"
type="password"
error={!!fieldState.error}
helperText={fieldState.error?.message}
fullWidth
/>
)}
/>
<Button type="submit" variant="contained">送信</Button>
</Stack>
</form>
);
}まとめ・選び方のヒント
- •独自バリデーションロジックをシンプルに書きたい → Mantine(
useFormのvalidateオブジェクトに関数を渡すだけ)・Ant Design(rules配列で宣言的に記述) - •React Hook Form + Zod の組み合わせで使いたい → shadcn/ui(公式ドキュメントで推奨・FormMessage で自動表示)・MUI(Controller で既存コンポーネントと統合)
- •リアルタイムバリデーションを簡単に設定したい → Mantine(
validateInputOnChange: true一行)・Ant Design(validateTrigger="onChange")・shadcn/ui(mode: 'onChange') - •エラースタイルを細かくカスタマイズしたい → Ant Design(
validateStatusで warning/error/success を切り替え)・MUI(error+helperTextprop) - •Zod スキーマと密に連携したい → shadcn/ui・MUI(
@hookform/resolvers/zodで公式サポート)・Mantine(mantine-form-zod-resolverパッケージ)