- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Questionnaire
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
このガイドでは、TanStack Form を使用してフォームを構築する方法を説明します。 <Field /> コンポーネントを使用してフォームを作成し、Zod を使用してスキーマ検証を実装し、エラーを処理し、アクセシビリティを確保する方法を学びます。
デモ#
まずは次のフォームを構築します。単純なテキスト入力とテキストエリアがあります。送信時にフォーム データを検証し、エラーがあれば表示します。
注: このデモのために、スキーマ検証とフォーム エラーが TanStack Form でどのように動作するかを示すため、意図的にブラウザー検証を無効にしています。本番コードでは、基本的なブラウザー検証も追加することをおすすめします。
コンポーネント form-tanstack-demo が見つかりません。
アプローチ#
このフォームは TanStack Form を利用して、強力なヘッドレス フォーム処理を実現します。 <Field /> コンポーネントを使用してフォームを構築します。これにより、マークアップとスタイルを完全に柔軟に行うことができます。
- TanStack Form の
useFormフックをフォーム状態管理に使用します。 - 制御された入力のレンダー プロップ パターンを備えた
form.Fieldコンポーネント。 - アクセシブルなフォームの構築に
<Field />コンポーネントを使います。 - Zod を使用したクライアント側の検証。
- リアルタイム検証フィードバック。
構成#
<Field /> コンポーネントで TanStack Form を使用するフォームの基本的な例を次に示します。
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
<FieldGroup>
<form.Field
name="title"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Bug Title</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={isInvalid}
placeholder="Login button not working on mobile"
autoComplete="off"
/>
<FieldDescription>
Provide a concise title for your bug report.
</FieldDescription>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</Field>
)
}}
/>
</FieldGroup>
<Button type="submit">Submit</Button>
</form>フォーム#
スキーマを作成する#
まず、Zod スキーマを使用してフォームの形状を定義します。
注: この例ではスキーマ検証に zod v3 を使用します。TanStack Form は validators API を通じて、Zod やその他の Standard Schema 検証ライブラリとシームレスに連携します。
import * as z from "zod"
const formSchema = z.object({
title: z
.string()
.min(5, "Bug title must be at least 5 characters.")
.max(32, "Bug title must be at most 32 characters."),
description: z
.string()
.min(20, "Description must be at least 20 characters.")
.max(100, "Description must be at most 100 characters."),
})フォームのセットアップ#
TanStack Form の useForm フックを使って、Zod 検証付きのフォーム インスタンスを作成します。
import { useForm } from "@tanstack/react-form"
import { toast } from "sonner"
import * as z from "zod"
const formSchema = z.object({
// ...
})
export function BugReportForm() {
const form = useForm({
defaultValues: {
title: "",
description: "",
},
validators: {
onSubmit: formSchema,
},
onSubmit: async ({ value }) => {
toast.success("Form submitted successfully")
},
})
return (
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
{/* ... */}
</form>
)
}ここでは onSubmit を使用してフォーム データを検証します。 TanStack Form は他の検証モードをサポートしています。詳細については、ドキュメント を参照してください。
フォームの構築#
TanStack Form の form.Field コンポーネントと <Field /> コンポーネントを使用してフォームを構築できるようになりました。
完了#
以上です。これで、クライアント側の検証を備えた完全にアクセス可能なフォームが完成しました。
フォームを送信すると、検証されたフォーム データを使用して onSubmit 関数が呼び出されます。フォーム データが無効な場合、TanStack Form は各フィールドの横にエラーを表示します。
バリデーション#
クライアント側検証#
TanStack Form は Zod スキーマを使ってフォーム データを検証します。ユーザーの入力中にリアルタイムで検証されます。
import { useForm } from "@tanstack/react-form"
const formSchema = z.object({
// ...
})
export function BugReportForm() {
const form = useForm({
defaultValues: {
title: "",
description: "",
},
validators: {
onSubmit: formSchema,
},
onSubmit: async ({ value }) => {
console.log(value)
},
})
return <form onSubmit={/* ... */}>{/* ... */}</form>
}検証モード#
TanStack Form は、validators オプションを通じてさまざまな検証戦略をサポートしています。
| モード | 説明 |
|---|---|
"onChange" | 変更のたびに検証が実行されます。 |
"onBlur" | フォーカスが外れたときに検証が実行されます。 |
"onSubmit" | 送信時に検証がトリガーされます。 |
const form = useForm({
defaultValues: {
title: "",
description: "",
},
validators: {
onSubmit: formSchema,
onChange: formSchema,
onBlur: formSchema,
},
})エラーの表示#
<FieldError /> を使用してフィールドの横にエラーを表示します。スタイルとアクセシビリティについて:
data-invalidプロパティを<Field />コンポーネントに追加します。<Input />、<SelectTrigger />、<Checkbox />などのフォーム コントロールにaria-invalidプロパティを追加します。
<form.Field
name="email"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Email</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
type="email"
aria-invalid={isInvalid}
/>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</Field>
)
}}
/>さまざまなフィールド タイプの操作#
入力#
- 入力フィールドには、
<Input />コンポーネントのfield.state.valueおよびfield.handleChangeを使用します。 - エラーを表示するには、
aria-invalidプロパティを<Input />コンポーネントに追加し、data-invalidプロパティを<Field />コンポーネントに追加します。
コンポーネント form-tanstack-input が見つかりません。
<form.Field
name="username"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor="form-tanstack-input-username">Username</FieldLabel>
<Input
id="form-tanstack-input-username"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={isInvalid}
placeholder="shadcn"
autoComplete="username"
/>
<FieldDescription>
This is your public display name. Must be between 3 and 10 characters.
Must only contain letters, numbers, and underscores.
</FieldDescription>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</Field>
)
}}
/>テキストエリア#
- テキストエリア フィールドの場合は、
<Textarea />コンポーネントのfield.state.valueおよびfield.handleChangeを使用します。 - エラーを表示するには、
aria-invalidプロパティを<Textarea />コンポーネントに追加し、data-invalidプロパティを<Field />コンポーネントに追加します。
コンポーネント form-tanstack-textarea が見つかりません。
<form.Field
name="about"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor="form-tanstack-textarea-about">
More about you
</FieldLabel>
<Textarea
id="form-tanstack-textarea-about"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={isInvalid}
placeholder="I'm a software engineer..."
className="min-h-[120px]"
/>
<FieldDescription>
Tell us more about yourself. This will be used to help us personalize
your experience.
</FieldDescription>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</Field>
)
}}
/>選択#
- コンポーネントを選択するには、
<Select />コンポーネントでfield.state.valueおよびfield.handleChangeを使用します。 - エラーを表示するには、
aria-invalidプロパティを<SelectTrigger />コンポーネントに追加し、data-invalidプロパティを<Field />コンポーネントに追加します。
コンポーネント form-tanstack-select が見つかりません。
<form.Field
name="language"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field orientation="responsive" data-invalid={isInvalid}>
<FieldContent>
<FieldLabel htmlFor="form-tanstack-select-language">
Spoken Language
</FieldLabel>
<FieldDescription>
For best results, select the language you speak.
</FieldDescription>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</FieldContent>
<Select
name={field.name}
value={field.state.value}
onValueChange={field.handleChange}
>
<SelectTrigger
id="form-tanstack-select-language"
aria-invalid={isInvalid}
className="min-w-[120px]"
>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent position="item-aligned">
<SelectItem value="auto">Auto</SelectItem>
<SelectItem value="en">English</SelectItem>
</SelectContent>
</Select>
</Field>
)
}}
/>チェックボックス#
- チェックボックスには、
<Checkbox />コンポーネントのfield.state.valueおよびfield.handleChangeを使用します。 - エラーを表示するには、
aria-invalidプロパティを<Checkbox />コンポーネントに追加し、data-invalidプロパティを<Field />コンポーネントに追加します。 - チェックボックス配列の場合は、
<form.Field />コンポーネントと TanStack Form の配列ヘルパーでmode="array"を使用します。 - 適切なスタイルと間隔を確保するには、必ず
data-slot="checkbox-group"を<FieldGroup />コンポーネントに追加してください。
コンポーネント form-tanstack-checkbox が見つかりません。
<form.Field
name="tasks"
mode="array"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<FieldSet>
<FieldLegend variant="label">Tasks</FieldLegend>
<FieldDescription>
Get notified when tasks you've created have updates.
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
{tasks.map((task) => (
<Field
key={task.id}
orientation="horizontal"
data-invalid={isInvalid}
>
<Checkbox
id={`form-tanstack-checkbox-${task.id}`}
name={field.name}
aria-invalid={isInvalid}
checked={field.state.value.includes(task.id)}
onCheckedChange={(checked) => {
if (checked) {
field.pushValue(task.id)
} else {
const index = field.state.value.indexOf(task.id)
if (index > -1) {
field.removeValue(index)
}
}
}}
/>
<FieldLabel
htmlFor={`form-tanstack-checkbox-${task.id}`}
className="font-normal"
>
{task.label}
</FieldLabel>
</Field>
))}
</FieldGroup>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</FieldSet>
)
}}
/>ラジオ グループ#
- ラジオ グループの場合は、
<RadioGroup />コンポーネントでfield.state.valueおよびfield.handleChangeを使用します。 - エラーを表示するには、
aria-invalidプロパティを<RadioGroupItem />コンポーネントに追加し、data-invalidプロパティを<Field />コンポーネントに追加します。
コンポーネント form-tanstack-radiogroup が見つかりません。
<form.Field
name="plan"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<FieldSet>
<FieldLegend>Plan</FieldLegend>
<FieldDescription>
You can upgrade or downgrade your plan at any time.
</FieldDescription>
<RadioGroup
name={field.name}
value={field.state.value}
onValueChange={field.handleChange}
>
{plans.map((plan) => (
<FieldLabel
key={plan.id}
htmlFor={`form-tanstack-radiogroup-${plan.id}`}
>
<Field orientation="horizontal" data-invalid={isInvalid}>
<FieldContent>
<FieldTitle>{plan.title}</FieldTitle>
<FieldDescription>{plan.description}</FieldDescription>
</FieldContent>
<RadioGroupItem
value={plan.id}
id={`form-tanstack-radiogroup-${plan.id}`}
aria-invalid={isInvalid}
/>
</Field>
</FieldLabel>
))}
</RadioGroup>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</FieldSet>
)
}}
/>スイッチ#
- スイッチでは、
<Switch />コンポーネントでfield.state.valueとfield.handleChangeを使います。 - エラーを表示するには、
aria-invalidプロパティを<Switch />コンポーネントに追加し、data-invalidプロパティを<Field />コンポーネントに追加します。
コンポーネント form-tanstack-switch が見つかりません。
<form.Field
name="twoFactor"
children={(field) => {
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field orientation="horizontal" data-invalid={isInvalid}>
<FieldContent>
<FieldLabel htmlFor="form-tanstack-switch-twoFactor">
Multi-factor authentication
</FieldLabel>
<FieldDescription>
Enable multi-factor authentication to secure your account.
</FieldDescription>
{isInvalid && <FieldError errors={field.state.meta.errors} />}
</FieldContent>
<Switch
id="form-tanstack-switch-twoFactor"
name={field.name}
checked={field.state.value}
onCheckedChange={field.handleChange}
aria-invalid={isInvalid}
/>
</Field>
)
}}
/>複雑なフォーム#
複数のフィールドと検証を含む、より複雑なフォームの例です。
コンポーネント form-tanstack-complex が見つかりません。
フォームをリセット#
form.reset() を使用してフォームをデフォルト値にリセットします。
<Button type="button" variant="outline" onClick={() => form.reset()}>
Reset
</Button>配列フィールド#
TanStack Form は、mode="array" を使用した強力な配列フィールド管理を提供します。これにより、完全な検証サポートを使用して配列項目を動的に追加、削除、更新できるようになります。
コンポーネント form-tanstack-array が見つかりません。
この例では、配列フィールドを使用して複数の電子メール アドレスを管理する方法を示します。ユーザーは最大 5 つの電子メール アドレスを追加したり、個々のアドレスを削除したりできます。各アドレスは個別に検証されます。
配列フィールド構造#
配列フィールド管理を有効にするには、親フィールドで mode="array" を使用します。
<form.Field
name="emails"
mode="array"
children={(field) => {
return (
<FieldSet>
<FieldLegend variant="label">Email Addresses</FieldLegend>
<FieldDescription>
Add up to 5 email addresses where we can contact you.
</FieldDescription>
<FieldGroup>
{field.state.value.map((_, index) => (
// Nested field for each array item
))}
</FieldGroup>
</FieldSet>
)
}}
/>ネストされたフィールド#
ブラケット記法(fieldName[index].propertyName)で配列の個別項目にアクセスします。この例では InputGroup を使い、入力欄の横に削除ボタンを表示します。
<form.Field
name={`emails[${index}].address`}
children={(subField) => {
const isSubFieldInvalid =
subField.state.meta.isTouched && !subField.state.meta.isValid
return (
<Field orientation="horizontal" data-invalid={isSubFieldInvalid}>
<FieldContent>
<InputGroup>
<InputGroupInput
id={`form-tanstack-array-email-${index}`}
name={subField.name}
value={subField.state.value}
onBlur={subField.handleBlur}
onChange={(e) => subField.handleChange(e.target.value)}
aria-invalid={isSubFieldInvalid}
placeholder="name@example.com"
type="email"
/>
{field.state.value.length > 1 && (
<InputGroupAddon align="inline-end">
<InputGroupButton
type="button"
variant="ghost"
size="icon-xs"
onClick={() => field.removeValue(index)}
aria-label={`Remove email ${index + 1}`}
>
<XIcon />
</InputGroupButton>
</InputGroupAddon>
)}
</InputGroup>
{isSubFieldInvalid && (
<FieldError errors={subField.state.meta.errors} />
)}
</FieldContent>
</Field>
)
}}
/>アイテムの追加#
field.pushValue(item) を使って配列フィールドに項目を追加します。配列が最大長に達したらボタンを無効にできます。
<Button
type="button"
variant="outline"
size="sm"
onClick={() => field.pushValue({ address: "" })}
disabled={field.state.value.length >= 5}
>
Add Email Address
</Button>項目の削除#
field.removeValue(index) を使用して、配列フィールドから項目を削除します。複数の項目がある場合にのみ、条件付きで削除ボタンを表示できます。
{
field.state.value.length > 1 && (
<InputGroupButton
onClick={() => field.removeValue(index)}
aria-label={`Remove email ${index + 1}`}
>
<XIcon />
</InputGroupButton>
)
}配列の検証#
Zod の配列メソッドを使って配列フィールドを検証します。
const formSchema = z.object({
emails: z
.array(
z.object({
address: z.string().email("Enter a valid email address."),
})
)
.min(1, "Add at least one email address.")
.max(5, "You can add up to 5 email addresses."),
})