I Will Never Use Shadcn Form Components Again Thumbnail

Starting Code

Before diving into the new ShadCN form components, it’s essential to set up a solid foundation with some boilerplate code. This starting point establishes the environment in which the new form components will be implemented and tested.

Initial Setup

We begin with a blank client-side page in a Next.js application. This page serves as the canvas where the form will be built, tested, and refined. Alongside this, a simple schema is defined using Zod, a TypeScript-first schema validation library.

Project Schema Using Zod

The schema defines the structure and validation rules for the form data. Initially, the schema contains a single property :

Property Type Details
name string Required field representing the project name.

This schema will be expanded later as more fields and functionalities are introduced.

Server Action for Form Submission

Since the project uses Next.js, a server action is created to handle form submissions. This action is designed to :

For now, the server action simply returns true or false to indicate whether the operation was successful. The detailed backend logic can be added later.

Focusing on the Form Section

The main focus of development is within the form section of the page. This is where the user interface elements will be built and integrated with validation and submission logic. The form section is wrapped inside a simple <div> to provide spacing, ensuring the form elements are not cramped against the edges of the page.

For example, adding a simple text like “Hi” inside this container confirms that the page is rendering correctly and the layout is functioning as expected.

Summary of Starting Code Essentials

This starting code provides a clean slate to explore the new ShadCN form components and their integration with form libraries like React Hook Form.

Basic React hook form implementation

Implementing the new ShadCN form components with React Hook Form requires understanding how to hook them together effectively. Since the new Field component no longer ships with built-in library support, manual integration with React Hook Form is necessary. This section covers the basic steps to achieve this.

Setting up React Hook Form

The first step is to import and use React Hook Form’s useForm hook. This hook manages form state, validation, and submission handling.

import { useForm, Controller } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { projectSchema } from '@/schemas/project';

Here, the zodResolver bridges the Zod schema validation with React Hook Form.

Initializing the Form

When creating the form instance, specify :

const form = useForm<z.infer<typeof projectSchema>>({ resolver : zodResolver(projectSchema), defaultValues : { name : '', }, }); 

Creating the Form Element

Instead of relying on ShadCN’s deprecated Form component, use a standard HTML <form> element with React Hook Form’s handleSubmit method :

<form onSubmit={form.handleSubmit((data) => onSubmit(data))}> {/ Form fields here /} </form>

The onSubmit function calls the server action and handles success or error responses accordingly.

Implementing the Field Component

To create a form field with the new ShadCN Field component, you need to wrap it inside React Hook Form’s Controller. This wrapper synchronizes the form field with React Hook Form’s state management.

The basic anatomy of a controlled field includes :

Example of a Controlled Input Field

<Controller name="name" control={form.control} render={({ field, fieldState }) => ( <Field data-invalid={fieldState.invalid}> <FieldLabel htmlFor={field.name}>Name</FieldLabel> <Input id={field.name} {...field} aria-invalid={fieldState.invalid} /> {fieldState.invalid && ( <FieldError errors={[fieldState.error]} /> )} </Field> )} />

Key integration points :

Form Submission and Validation Feedback

A submit button triggers validation and submission :

<Button type="submit">Create</Button>

If validation fails (e.g., the name is empty), the form automatically shows error messages and highlights invalid fields. When valid input is provided and the form is submitted, the server action is called, and on success, the form resets to default values.

Summary of Basic React Hook Form Implementation

Adding descriptions to form fields

Enhancing form fields with descriptions improves user experience by providing additional context or instructions. The new ShadCN form components facilitate this with dedicated description elements and layout helpers.

Expanding the Schema

To accommodate descriptions, the Zod schema is extended by adding an optional description property :

description : z.string() .optional() .transform(value => value === '' ? undefined : value)

This transformation ensures that empty strings are converted to undefined, preventing empty strings from being stored in the database.

Updating Default Values

The form’s default values are updated accordingly to include an empty string for the new description field :

defaultValues : { name : '', description : '', },

This setup prevents type errors and ensures the form initializes correctly.

Creating a TextArea Field with Description

The description field uses a textarea input, created similarly to the input field but with additional description content.

Using FieldDescription

The FieldDescription component allows adding descriptive text related to a form field. It can be placed anywhere within the Field component but typically goes near the label for clarity.

For example :

<Field> <FieldLabel>Description</FieldLabel> <FieldContent> <FieldDescription>Be as specific as possible</FieldDescription> </FieldContent> <Textarea /> <FieldError /> </Field>

Using FieldContent to Group Text

When multiple pieces of text, such as a label and description, need to be displayed close together, wrapping them in FieldContent tightens the spacing and keeps the layout neat and coherent.

This wrapper is optional but recommended for better visual grouping :

<FieldContent> <FieldLabel>Description</FieldLabel> <FieldDescription>Be as specific as possible</FieldDescription> </FieldContent>

Developers can customize the spacing by adjusting the gap styling within FieldContent.

Integrating Description with React Hook Form

When using React Hook Form’s Controller, the description is passed as a prop to the form field component and rendered conditionally :

<Controller name="description" control={form.control} render={({ field, fieldState }) => ( <Field data-invalid={fieldState.invalid}> <FieldContent> <FieldLabel htmlFor={field.name}>Description</FieldLabel> <FieldDescription>Be as specific as possible</FieldDescription> </FieldContent> <Textarea id={field.name} {...field} aria-invalid={fieldState.invalid} /> {fieldState.invalid && <FieldError errors={[fieldState.error]} />} </Field> )} />

This structure ensures that the description is semantically linked with the input and that validation errors are displayed correctly.

Flexibility in Placement

The FieldDescription component is flexible and can be placed :

However, placing it next to the label inside FieldContent is generally preferred for clarity.

Summary of Adding Descriptions to Form Fields

By adding these descriptions, forms become more user-friendly and accessible, guiding users to provide the correct information confidently.

Select form fields

When transitioning to the new Shadcn field components, handling <select> elements requires a bit more attention compared to basic text inputs or text areas. The new components are more stripped down and don’t come with default library bindings, so you need to manually hook up the select field within your form logic, especially when integrating with form libraries like React Hook Form.

Defining Select Options and Schema

Start by defining the possible select options as an enumeration or a constant array. For example, if you have project statuses like “draft”, “active”, and “archived”, you can define these as a TypeScript enum or a Zod enum schema.

const projectStatuses = ["draft", "active", "archived"] as const; const projectStatusEnum = z.enum(projectStatuses); 

In your Zod validation schema, include a field for the status that uses this enum. Also, set a default value for the select field in your form’s default values, such as “draft”. This ensures the select field has a valid initial value that matches the schema.

Implementing the Select Field with React Hook Form

Inside your form component, wrap the select field in a Controller from React Hook Form to connect the field with form state and validation. The basic structure looks like this :

<Controller name="status" control={form.control} render={({ field }) => ( <Select {...field} onValueChange={field.onChange} value={field.value} onBlur={field.onBlur} id={field.name} aria-invalid={fieldState.invalid} > <SelectTrigger> <SelectValue /> </SelectTrigger> <SelectContent> {projectStatuses.map((status) => ( <SelectItem key={status} value={status}>{status}</SelectItem> ))} </SelectContent> </Select> )} /> 

Note the following :

Handling Field Properties for Select

Since the select component’s event handlers differ from standard inputs, you need to destructure the field object to separate onChange and other properties. This allows you to assign onValueChange correctly to the select and ensure the rest of the field properties like value, name, and ref are passed down appropriately.

Accessibility and Styling

Assigning the correct id to the select and matching it with the corresponding label’s htmlFor attribute is essential for accessibility. The aria-invalid attribute is dynamically set based on the field’s validation state, providing visual cues (such as red outlines or error highlighting) when the input is invalid.

Summary

Setting up select form fields with Shadcn’s new field components requires :

Once configured, the select field integrates seamlessly with your form validation and state management.


Advanced field components and checkboxes

Beyond simple inputs and selects, Shadcn’s new field component suite offers advanced components like FieldSet, FieldLegend, and grouped checkboxes, designed to provide better structure, semantics, and accessibility to complex form sections.

Using FieldSet and FieldLegend

A FieldSet component groups related form fields into logical sections. This is especially useful for sets of checkboxes or radio buttons where the group relates to a common topic. Accompany the FieldSet with a FieldLegend to provide a heading or title for the group, improving screen reader support and form clarity.

<FieldSet> <FieldLegend>Notifications</FieldLegend> <FieldDescription>Select how you would like to receive notifications.</FieldDescription> <FieldGroup data-slot="checkbox"> </FieldGroup> </FieldSet> 

The FieldDescription component adds descriptive text to guide the user, and the FieldGroup handles spacing and layout within the set.

Checkbox Groups with FieldGroup and Data Slots

When rendering multiple checkboxes closely related, wrapping them in a FieldGroup with the data-slot="checkbox" attribute adjusts the spacing to be tighter, suitable for checkbox collections.

For example, if you have notification preferences like Email, SMS, and Push :

<FieldGroup data-slot="checkbox"> <FormCheckbox name="notifications.email" label="Email" control={form.control} /> <FormCheckbox name="notifications.sms" label="SMS" control={form.control} /> <FormCheckbox name="notifications.push" label="Push" control={form.control} /> </FieldGroup> 

Integrating Checkboxes with React Hook Form

Checkboxes require special handling because their value is a boolean representing checked or unchecked state. In React Hook Form, use the Controller to manage each checkbox, passing the checked property instead of value, and hooking the onCheckedChange event to update form state.

<Controller name="notifications.email" control={form.control} render={({ field }) => ( <Checkbox checked={field.value} onCheckedChange={field.onChange} id={field.name} aria-invalid={fieldState.invalid} /> <FieldLabel htmlFor={field.name}>Email</FieldLabel> <FieldError errors={[fieldState.error]} /> )} /> 

Note the use of checked and onCheckedChange instead of the usual value and onChange. This is because checkboxes represent boolean states.

Horizontal Layouts and Label Positioning

For better UX, you can set the checkbox Field component’s orientation prop to horizontal to arrange the checkbox and label side-by-side.

However, by default, the label may appear on the left and the checkbox on the right, which is often not desired. To fix this, move the label below or after the checkbox in the render tree or wrap them appropriately in a FieldContent component to control spacing.

Error Message Placement

When using horizontal layouts, error messages may appear awkwardly to the side of the label. To improve readability, wrap the label and error message inside a FieldContent container so the error displays below the label, maintaining a clean and accessible layout.

Summary

The advanced field components provide :

These tools, combined with React Hook Form’s controller mechanism, allow you to build complex, accessible, and maintainable checkbox groups and advanced field sections in your forms.


Handling field arrays

Handling dynamic arrays of fields—such as lists of users or email addresses—is a common requirement in forms. The new Shadcn field components, together with React Hook Form’s useFieldArray hook or Tanstack Form’s field array support, allow you to manage these arrays effectively.

Defining the Array in the Schema and Default Values

Start by defining an array schema in your validation logic. For example, to handle a dynamic list of user emails :

users : z .array(z.object({ email : z.string().email() })) .min(1, "At least one user required") .max(5, "At most five users allowed"); 

Set the default value in your form’s defaultValues object to have at least one empty user object to render the initial field.

Using useFieldArray Hook

React Hook Form’s useFieldArray manages dynamic fields by providing methods to append and remove items :

const { fields : users, append : addUser, remove : removeUser, } = useFieldArray({ control : form.control, name : "users", }); 

This hook returns :

Rendering Dynamic User Fields

Render each user input by iterating over the users array. Each user field is wrapped inside a Field component, with its name dynamically set using bracket notation (e.g., users[0].email) for React Hook Form to track individual fields properly.

{users.map((user, index) => ( <Field key={user.id} id={users[${index}].email} isInvalid={/ validation state /}> <InputGroup> <Input type="email" aria-label={User ${index + 1} email} value={/ field value /} onChange={/ field change handler /} onBlur={/ field blur handler /} /> <InputGroupAddon align="end"> <Button variant="ghost" size="icon-xs" onClick={() => removeUser(index)} aria-label={Remove user ${index + 1}} > <XIcon /> </Button> </InputGroupAddon> </InputGroup> <FieldError errors={/ validation errors /} /> </Field> ))} 

The InputGroup and InputGroupAddon components help visually group the input and the remove button.

Adding and Removing Users

Provide an “Add User” button that calls addUser({ email : "" }) to append a new empty user field. Each remove button calls removeUser(index) to delete the corresponding user.

The form will validate the minimum and maximum array length constraints specified in the schema.

Handling Validation Errors for Field Arrays

Validation errors related to array length (too few or too many users) are handled at the array level. These errors can be accessed via the form state and rendered using the FieldError component placed near the array group.

Individual field errors, like invalid email formats, are displayed under each input field accordingly.

Accessibility and User Experience

Each input field is given a unique id and an aria-label that includes the user index (e.g., “User 1 email”) for screen readers. The remove button also receives an accessible label.

The layout is spaced consistently using FieldGroup components to maintain clean spacing between dynamic fields.

Summary

Handling dynamic arrays with Shadcn’s new field components involves :

By combining these techniques, you can build powerful, user-friendly dynamic form sections that scale well and maintain accessibility.

Advanced React hook form implementation

In this section, we dive deep into implementing advanced form handling using React Hook Form alongside the new ShadCN form components. With ShadCN deprecating its old form components and introducing a new, more flexible field component, this approach shows how to efficiently integrate React Hook Form’s capabilities while maintaining clean, reusable code.

Setting Up the Basic Form

Start by initializing the form with useForm from React Hook Form. Import the necessary hooks and the zodResolver to integrate Zod schema validation seamlessly :

import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { projectSchema } from "@/schemas/project"; 

Define your form type using Zod’s inference :

type FormData = z.infer; 

Initialize the form with default values and connect the Zod resolver :

const form = useForm<FormData>({ resolver : zodResolver(projectSchema), defaultValues : { name : "", description : "", status : "draft", notifications : { email : false, sms : false, push : false }, users : [{ email : "" }] } }); 

Creating Field Components with Controller

Since the new ShadCN Field component doesn’t inherently support any form libraries, React Hook Form’s Controller is essential to bridge this gap. Wrap each field inside a Controller to manage its state, validation, and event handling.

For instance, to create a controlled text input for the project name :

<Controller name="name" control={form.control} render={({ field, fieldState }) => ( <Field data-invalid={fieldState.invalid} id={field.name}> <FieldLabel htmlFor={field.name}>Name</FieldLabel> <Input {...field} aria-invalid={fieldState.invalid} /> {fieldState.invalid && ( <FieldError errors={[fieldState.error]} /> )} </Field> )} /> 

Key points to note :

Handling Different Input Types

The same pattern applies to other input types, with minor adjustments :

Text Area with Description

Add a description to the field using FieldDescription, usually wrapped inside FieldContent along with the label to tighten spacing :

<Controller name="description" control={form.control} render={({ field, fieldState }) => ( <Field data-invalid={fieldState.invalid} id={field.name}> <FieldContent> <FieldLabel htmlFor={field.name}>Description</FieldLabel> <FieldDescription>Be as specific as possible.</FieldDescription> </FieldContent> <Textarea {...field} aria-invalid={fieldState.invalid} /> {fieldState.invalid && ( <FieldError errors={[fieldState.error]} /> )} </Field> )} /> 

Select Component with Custom Event Handling

The select component requires special handling because it uses onValueChange instead of onChange. Extract onChange from the field props and wire it to onValueChange. The onBlur handler is attached to the SelectTrigger to capture blur events correctly :

<Controller name="status" control={form.control} render={({ field, fieldState }) => { const { onChange, onBlur, ...rest } = field; return ( <Field data-invalid={fieldState.invalid} id={field.name}> <FieldLabel htmlFor={field.name}>Status</FieldLabel> <Select value={rest.value} onValueChange={onChange} {...rest} aria-invalid={fieldState.invalid}> <SelectTrigger onBlur={onBlur} id={field.name}> <SelectValue /> </SelectTrigger> <SelectContent> {projectStatuses.map(status => ( <SelectItem key={status} value={status}>{status}</SelectItem> ))} </SelectContent> </Select> {fieldState.invalid && ( <FieldError errors={[fieldState.error]} /> )} </Field> ); }} /> 

Checkbox Groups Using FieldSet and FieldGroup

For checkbox groups, use FieldSet to group related checkboxes and a FieldLegend as the group title. Wrap checkboxes in a FieldGroup with the data-slot="checkbox-group" attribute to adjust spacing :

<FieldSet> <FieldLegend>Notifications</FieldLegend> <FieldDescription>Select how you want to receive notifications.</FieldDescription> <FieldGroup data-slot="checkbox-group"> <Controller name="notifications.email" control={form.control} render={({ field, fieldState }) => ( <Field orientation="horizontal" data-invalid={fieldState.invalid} id={field.name}> <Checkbox checked={field.value} onCheckedChange={field.onChange} onBlur={field.onBlur} id={field.name} /> <FieldLabel>Email</FieldLabel> {fieldState.invalid && ( <FieldError errors={[fieldState.error]} /> )} </Field> )} /> {/ Repeat for SMS and Push notifications /} </FieldGroup> </FieldSet> 

Handling Dynamic Arrays with useFieldArray

For dynamic fields such as a list of users, React Hook Form’s useFieldArray hook manages adding and removing items. Initialize it with the form control and the array name :

const { fields : users, append : addUser, remove : removeUser, } = useFieldArray({ control : form.control, name : "users", }); 

Render the list inside a FieldSet with a FieldGroup to maintain spacing :

<FieldSet> <FieldLegend variant="label">User Emails</FieldLegend> <FieldDescription>Add up to five users.</FieldDescription> {form.formState.errors.users?.root && ( <FieldError errors={[form.formState.errors.users.root]} /> )} <FieldGroup> {users.map((user, index) => ( <Controller key={user.id} name={users.${index}.email} control={form.control} render={({ field, fieldState }) => ( <Field data-invalid={fieldState.invalid} id={field.name}> <InputGroup> <Input {...field} type="email" aria-label={User ${index + 1} email} /> <InputGroupAddon align="inline-end"> <Button variant="ghost" size="icon-xs" onClick={() => removeUser(index)} aria-label={Remove user ${index + 1}} >✕</Button> </InputGroupAddon> </InputGroup> {fieldState.invalid && ( <FieldError errors={[fieldState.error]} /> )} </Field> )} /> ))} </FieldGroup> <Button type="button" variant="outline" size="sm" onClick={() => addUser({ email : "" })} >Add User</Button> </FieldSet> 

Refactoring with Generic Form Input Components

Because the controlled fields share similar boilerplate, the video recommends abstracting the form input into a reusable FormInput component. This component accepts props like name, label, control, and optionally description, and internally manages the Controller logic, field rendering, error handling, and accessibility features.

This approach drastically reduces form code duplication and improves maintainability. It also enforces strong TypeScript typing for field names and values by leveraging React Hook Form’s generics and Zod schemas.

Summary

Advanced React Hook Form implementation with ShadCN’s new field components involves wrapping input elements inside Controller components, managing controlled input props, and handling validation state for styling and error display. The use of FieldSet, FieldGroup, and related components enhances semantic grouping and spacing, particularly for complex inputs like checkbox groups and dynamic arrays.

Abstracting repetitive logic into generic reusable components streamlines form development and ensures consistent behavior across diverse input types.

Basic Tanstack form implementation

This section explores implementing ShadCN’s new field components using the Tanstack Form library, emphasizing a basic setup. Tanstack Form offers a modern approach to form management with simpler TypeScript integration compared to React Hook Form, making it appealing for developers seeking a streamlined experience.

Initial Setup with Tanstack Form

Replace React Hook Form imports and usage with Tanstack Form’s useForm hook :

import { useForm } from "@tanstack/react-form"; import { projectSchema } from "@/schemas/project"; 

Define a type representing the form data via Zod’s infer :

type FormData = z.infer; 

Initialize the form with default values and validators :

const form = useForm({ defaultValues : { name : "", description : "", status : "draft", notifications : { email : false, sms : false, push : false }, users : [{ email : "" }] }, validate : { onSubmit : projectSchema.parse, }, }); 

Note that Tanstack Form uses an onSubmit validator function rather than a resolver pattern. This means validation occurs when the form is submitted, aligning well with server-side validation in many apps.

Creating a Basic Field

Tanstack Form provides a convenient form.createField method to generate field instances. Use this to create a field for a given form field name :

const nameField = form.createField("name"); 

This field object contains all the properties and handlers needed to bind a controlled input.

Rendering the Field with ShadCN Components

Render the field using the new ShadCN field components, passing the appropriate props from the Tanstack Form field object :

<Field data-invalid={nameField.state.isInvalid} id={nameField.name}> <FieldLabel htmlFor={nameField.name}>Name</FieldLabel> <Input id={nameField.name} value={nameField.state.value} onChange={e => nameField.setValue(e.target.value)} onBlur={nameField.handleBlur} aria-invalid={nameField.state.isInvalid} /> {nameField.state.isInvalid && ( <FieldError errors={[nameField.state.error]} /> )} </Field> 

This straightforward approach eliminates the need for Controller components as in React Hook Form. The field object directly exposes state and handlers.

Handling Accessibility and Validation States

Similar to React Hook Form, pass data-invalid and aria-invalid attributes dynamically based on field validity. This triggers ShadCN’s styling for invalid inputs and ensures screen readers announce errors appropriately.

Display error messages conditionally using FieldError, which expects an array of error objects. Tanstack Form’s field.state.error provides this in the correct format.

Summary

The basic Tanstack Form implementation simplifies form state management by exposing field data and handlers directly through createField. This removes the need for additional wrapper components and complicated controller logic, making form creation more declarative and easier to read.

The integration with ShadCN’s new field components remains consistent, leveraging Field, FieldLabel, Input, and FieldError for a cohesive UI and UX.

Advanced Tanstack form impementation

Building upon the basic usage, this section describes an advanced approach to integrating Tanstack Form with ShadCN components, focusing on scalability, code reuse, and handling complex form scenarios like checkbox groups and dynamic arrays.

Creating a Custom Hook for Form Context

Tanstack Form offers createFormContext to encapsulate form logic and provide context-aware hooks. Create a hook to manage form state and expose custom components :

import { createFormContext } from "@tanstack/react-form"; const formContext = createFormContext(); export const useAppForm = formContext.useFormContext; export const FormProvider = formContext.FormProvider; export const useField = formContext.useFieldContext; 

This abstraction allows you to build components that automatically consume form context without needing to pass control props explicitly.

Building Reusable Form Field Components

Create generic field components like FormInput, FormTextArea, FormSelect, and FormCheckbox that internally use useField to access field state and handlers.

Example of a generic FormInput :

export function FormInput({ name, label, description } : FormInputProps) { const field = useField(name); return ( <Field data-invalid={field.state.isInvalid} id={field.name}> <FieldContent> <FieldLabel htmlFor={field.name}>{label}</FieldLabel> {description && <FieldDescription>{description}</FieldDescription>} </FieldContent> <Input id={field.name} value={field.state.value} onChange={e => field.setValue(e.target.value)} onBlur={field.handleBlur} aria-invalid={field.state.isInvalid} /> {field.state.isInvalid && <FieldError errors={[field.state.error]} />} </Field> ); } 

This pattern centralizes form logic, enforces consistency, and reduces boilerplate across the app.

Implementing Select and Checkbox Components

Select : Since select components require children (options), extend the generic input to accept a children prop that renders inside the select content :

export function FormSelect({ name, label, children } : FormSelectProps) { const field = useField(name); return ( <Field data-invalid={field.state.isInvalid} id={field.name}> <FieldLabel htmlFor={field.name}>{label}</FieldLabel> <Select value={field.state.value} onValueChange={field.setValue} onBlur={field.handleBlur} id={field.name} aria-invalid={field.state.isInvalid} > <SelectTrigger><SelectValue /></SelectTrigger> <SelectContent>{children}</SelectContent> </Select> {field.state.isInvalid && <FieldError errors={[field.state.error]} />} </Field> ); } 

Checkbox : For checkboxes, handle the checked state and onCheckedChange event, and use layout props to control horizontal orientation and control-label order :

export function FormCheckbox({ name, label, horizontal, controlFirst } : FormCheckboxProps) { const field = useField(name); return ( <Field orientation={horizontal ? "horizontal" : "vertical"} data-invalid={field.state.isInvalid} id={field.name}> {controlFirst ? ( <> <Checkbox checked={field.state.value} onCheckedChange={field.setValue} onBlur={field.handleBlur} id={field.name} /> <FieldLabel>{label}</FieldLabel>  ) : ( <> <FieldLabel>{label}</FieldLabel> <Checkbox checked={field.state.value} onCheckedChange={field.setValue} onBlur={field.handleBlur} id={field.name} />  )} {field.state.isInvalid && <FieldError errors={[field.state.error]} />} </Field> ); } 

Handling Dynamic Arrays with Form Field

Tanstack Form simplifies array handling by allowing you to create a field with the mode : 'array' option. This field exposes methods to add and remove items directly :

const usersField = form.createField({ name : "users", mode : "array", }); 

Render the dynamic user list inside a FieldSet with a button to append new users. Use the usersField.pushValue and usersField.removeValue methods to manage the array :

<FieldSet> <FieldLegend variant="label">User Emails</FieldLegend> <FieldDescription>Add up to five users.</FieldDescription> {form.state.errors.users?.root && ( <FieldError errors={[form.state.errors.users.root]} /> )} <FieldGroup> {usersField.state.values.map((user, index) => { const userField = usersField.createField(users[${index}].email); return ( <Field data-invalid={userField.state.isInvalid} id={userField.name} key={index}> <InputGroup> <Input id={userField.name} value={userField.state.value} onChange={e => userField.setValue(e.target.value)} onBlur={userField.handleBlur} aria-invalid={userField.state.isInvalid} /> <InputGroupAddon align="inline-end"> <Button variant="ghost" size="icon-xs" onClick={() => usersField.removeValue(index)} aria-label={Remove user ${index + 1}} >✕</Button> </InputGroupAddon> </InputGroup> {userField.state.isInvalid && ( <FieldError errors={[userField.state.error]} /> )} </Field> ); })} </FieldGroup> <Button type="button" variant="outline" size="sm" onClick={() => usersField.pushValue({ email : "" })} >Add User</Button> </FieldSet> 

Benefits of Advanced Tanstack Form Integration

Summary

Advanced Tanstack Form integration with ShadCN components promotes scalability and ease of maintenance by utilizing context hooks, declarative field creation, and reusable components. This approach effectively handles complex UI scenarios such as checkbox groups and dynamic arrays while offering a simpler TypeScript experience than React Hook Form.