Open5
shadcn/ui の Formコンポーネント
shadcn/ui の <Form />
コンポーネントはreact-hook-formライブラリのラッパーです。
特徴
- フォームを構築するための
Composable components
- 制御されたフォームフィールドを構築するための
<FormField /> component
-
zod
を使用したフォームバリデーション - アクセシビリティとエラーメッセージを処理
- 一意なIDを生成するために
React.useId()
を使用 - 状態に基づいてフォームフィールドに正しい
aria
属性を適用 - すべての
Radix UIコンポーネント
で動作するように構築 - 自分の好きなスキーマ・ライブラリが使える(shadcn/ui はzod)
- マークアップとスタイリングを完全にコントロールできる
使い方
-
useForm
フックを使ってフォームの制御オブジェクトを作成します。 -
Form
コンポーネントでフォーム全体をラップし、form
プロパティで制御オブジェクトを渡します。 -
FormField
コンポーネントでController
をラップし、name
属性でフィールド名を指定します。 -
FormItem
コンポーネントでフィールド全体をラップし、IDを提供します。 -
FormLabel
でフィールドのラベルを定義します。 -
FormControl
で実際の入力フィールドをラップし、Input
コンポーネントを使用します。 -
FormDescription
でフィールドの説明を表示します。 -
FormMessage
でエラーメッセージを表示します。
const form = useForm()
<Form form={form}>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</Form>
useForm
フックを使ってフォームの制御オブジェクトを作成します。
1. const form = useForm()
Form
コンポーネントでフォーム全体をラップし、form
プロパティで制御オブジェクトを渡します。
2. -
Form
コンポーネントは、react-hook-formのFormProvider
をラップしてエクスポートしています。 - このコンポーネントは、フォーム全体の状態を管理し、子コンポーネントにフォームの状態やメソッドを提供するために使用されます。
const form = useForm()
<Form form={form}>
</Form>
FormField
コンポーネントでController
をラップし、name
属性でフィールド名を指定します。
3. -
FormField
コンポーネントは、react-hook-formのController
コンポーネントをラップし、フィールドのコンテキストを提供します。 - これにより、フォームの各フィールドが一貫して管理され、簡単にエラーメッセージや状態を取得できます。
const form = useForm()
<Form form={form}>
<FormField
control={form.control}
name="username"
/>
</Form>
FormItem
コンポーネントでフィールド全体をラップし、IDを提供します。
4. -
FormItem
コンポーネントは、各FormField
をラップするコンテナとして機能し、フィールドごとに固有のIDを提供します。 - このIDは、フィールドのラベルやエラーメッセージ、説明文と関連付けるために使用されます。
const form = useForm()
<Form form={form}>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
</FormItem>
)}
/>
</Form>
FormLabel
でフィールドのラベルを定義します。
5. フィールドにエラーがある場合、クラス名にtext-destructiveを追加してエラースタイルを適用します。
const form = useForm()
<Form form={form}>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
</FormItem>
)}
/>
</Form>
FormControl
で実際の入力フィールドをラップし、Input
コンポーネントを使用します。
6. FormControl
コンポーネントは、実際の入力フィールド(例:<input>, <select>, <textarea>など)をラップし、アクセシビリティ属性やエラーメッセージの表示に必要な属性を適切に設定します。
属性の設定:
- id: フォームコントロールのIDを設定します。このIDはラベルやエラーメッセージとの関連付けに使用されます。
- aria-describedby: フォームコントロールの説明文とエラーメッセージを参照するための属性です。エラーがある場合はエラーメッセージのIDも含めます。
- aria-invalid: フィールドにエラーがあるかどうかを示すための属性です。エラーがある場合はtrueになります。
const form = useForm()
<Form form={form}>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
</FormItem>
)}
/>
</Form>
FormDescription
でフィールドの説明を表示します。
7. const form = useForm()
<Form form={form}>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
</FormItem>
)}
/>
</Form>
FormMessage
でエラーメッセージを表示します。
8. FormField
のエラーメッセージを表示するコンポーネントで、エラーがない場合は表示しません。
const form = useForm()
<Form form={form}>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</Form>
実装サンプル
この状態だと、onSubmitは実行されますが、バリデーションはありません。
"use client";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { useForm } from "react-hook-form";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
export default function BasicForm() {
const form = useForm();
const onSubmit = (values) => {
console.log(values.username);
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>名前</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>アプリの表示名です。</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button className="mt-4">送信</Button>
</form>
</Form>
);
}
zod
と zodResolver
をインストールして、スキーマによるバリデーションを実装します。
- useForm の resolver オプションにzodResolverを利用してスキーマを指定します。
- onSubmit の引数にスキーマから型を生成して与えます。
const formSchema = z.object({
username: z.string().min(2, {
message: "名前は2文字以上で入力してください。",
}),
});
// ...
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
});
const onSubmit = (values: z.infer<typeof formSchema>) => {
console.log(values);
};
"use client";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { useForm } from "react-hook-form";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const formSchema = z.object({
username: z.string().min(2, {
message: "名前は2文字以上で入力してください。",
}),
});
export default function BasicForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
});
const onSubmit = (values: z.infer<typeof formSchema>) => {
console.log(values);
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>名前</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
この名前はアプリ内で公開されます。
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button className="mt-4">送信</Button>
</form>
</Form>
);
}