Skip to content

Fieldsets

Fieldsets let you group related fields together with optional legends, descriptions, and grid layouts. A group with a legend renders as an HTML <fieldset> with a <legend>. A group without a legend renders as a <div> so it does not create an unnamed fieldset in the accessibility tree.

Basic Fieldset

Wrap fields in a Fieldset and give it a legend:

Example
Personal Information
php
use InertiaUI\Forms\Fields\Fieldset;
use InertiaUI\Forms\Fields\TextInput;
use InertiaUI\Forms\Form;

class RegistrationForm extends Form
{
    protected ?string $actionRoute = 'register';

    public function fields(): array
    {
        return [
            Fieldset::make()
                ->legend('Personal Information')
                ->fields([
                    TextInput::make('first_name')->required(),
                    TextInput::make('last_name')->required(),
                    TextInput::make('email')->email()->required(),
                ]),
        ];
    }
}

Legend and Description

Add a description below the legend for extra context:

Example
Shipping Address

Where should we send your order?

php
Fieldset::make()
    ->legend('Shipping Address')
    ->description('Where should we send your order?')
    ->fields([
        TextInput::make('street'),
        TextInput::make('city'),
        TextInput::make('state'),
        TextInput::make('zip'),
    ]),

The frontend renderers output fieldset descriptions as escaped text. Pass plain strings here; markup-looking strings are displayed as text.

Grid Layout with Columns

Use columns() to arrange fields in a CSS grid. The value is the maximum column target, and the grid automatically drops to fewer columns when the container is too narrow:

Example
Address
php
Fieldset::make()
    ->legend('Address')
    ->columns(2)
    ->fields([
        TextInput::make('first_name'),  // Column 1
        TextInput::make('last_name'),   // Column 2
        TextInput::make('street'),      // Column 1
        TextInput::make('city'),        // Column 2
        TextInput::make('state'),       // Column 1
        TextInput::make('zip'),         // Column 2
    ]),

Fields flow left-to-right, top-to-bottom. For a layout that may use up to three columns:

php
Fieldset::make()
    ->columns(3)
    ->fields([
        TextInput::make('city'),
        Combobox::make('state')->options($states),
        TextInput::make('zip'),
    ]),

Multiple Fieldsets

A form may have multiple fieldsets, each with its own layout:

php
public function fields(): array
{
    return [
        Fieldset::make()
            ->legend('Account')
            ->fields([
                TextInput::make('username')->required(),
                TextInput::make('email')->email()->required(),
            ]),

        Fieldset::make()
            ->legend('Profile')
            ->columns(2)
            ->fields([
                TextInput::make('first_name'),
                TextInput::make('last_name'),
                Textarea::make('bio')->rows(3),
            ]),

        Fieldset::make()
            ->legend('Preferences')
            ->fields([
                Toggle::make('notifications'),
                Toggle::make('newsletter'),
            ]),

        Submit::make('Save'),
    ];
}

Conditional Fieldsets

Fieldsets support the same conditional visibility API as fields. A hidden fieldset is removed with all of its children. Server-side validation also excludes each child field while the parent fieldset is hidden.

Example
Phone Details
php
Fieldset::make()
    ->legend('Phone Details')
    ->visibleWhen('contact_method', 'phone')
    ->clearWhenHidden()
    ->fields([
        TextInput::make('phone_number')->required(),
        TextInput::make('phone_extension'),
    ]),

Use clearWhenHidden() when child values should reset after the fieldset hides.

Fields Outside Fieldsets

Fields that are returned outside of any Fieldset are automatically wrapped in one. These two forms are equivalent:

php
// Explicit fieldset
public function fields(): array
{
    return [
        Fieldset::make()->fields([
            TextInput::make('name'),
            TextInput::make('email'),
        ]),
        Submit::make('Save'),
    ];
}

// Auto-wrapped
public function fields(): array
{
    return [
        TextInput::make('name'),
        TextInput::make('email'),
        Submit::make('Save'),
    ];
}

You may mix fieldsets and loose fields freely. Consecutive loose fields are grouped into the same auto-generated fieldset:

php
public function fields(): array
{
    return [
        TextInput::make('title'),       // Auto-wrapped fieldset 1
        TextInput::make('slug'),        // Auto-wrapped fieldset 1

        Fieldset::make()                // Explicit fieldset 2
            ->legend('Details')
            ->columns(2)
            ->fields([
                Combobox::make('category')->options($categories),
                Combobox::make('status')->options($statuses),
            ]),

        Textarea::make('body'),         // Auto-wrapped fieldset 3
        Submit::make('Publish'),        // Auto-wrapped fieldset 3
    ];
}

Custom CSS Classes

Add custom classes to a fieldset:

php
Fieldset::make()
    ->legend('Settings')
    ->class('rounded-lg bg-zinc-50 p-6')
    ->fields([...]),

Fieldset ID

Set an HTML id attribute on the rendered fieldset wrapper, useful for anchor links or JavaScript targeting:

php
Fieldset::make()
    ->id('billing-section')
    ->legend('Billing Information')
    ->fields([...]),

Using the Component Directly

Fieldset is exported from the root Vue and React packages. Pass the serialized fieldset shape, including its fields array. Visibility and child values use the surrounding form context, and each child is rendered through FieldRenderer.

vue
<script setup lang="ts">
import { Fieldset } from '@inertiaui/form-vue'
import type { FormField } from '@inertiaui/form-vue'

const fields: FormField[] = [
    {
        component: 'TextInput',
        name: 'billing_email',
        label: 'Billing email',
        inputType: 'email',
        required: true,
        precognitive: false,
        disabled: false,
        readonly: false,
        autofocus: false,
        clearable: false,
        copyable: false,
        viewable: false,
    },
]

const trackFocus = () => {}
</script>

<template>
    <Fieldset
        :fields="fields"
        legend="Billing"
        description="Invoice contact details"
        :columns="2"
        id="billing-section"
        class="rounded-lg bg-zinc-50 p-6"
        wrapper-class="ring-1 ring-zinc-200"
        :visibility="{ field: 'billing_method', operator: '=', value: 'invoice' }"
        clear-when-hidden
        data-testid="billing-fieldset"
        @focusin="trackFocus"
    />
</template>
tsx
import { Fieldset } from '@inertiaui/form-react'
import type { FormField } from '@inertiaui/form-react'

const fields: FormField[] = [
    {
        component: 'TextInput',
        name: 'billing_email',
        label: 'Billing email',
        inputType: 'email',
        required: true,
        precognitive: false,
        disabled: false,
        readonly: false,
        autofocus: false,
        clearable: false,
        copyable: false,
        viewable: false,
    },
]

export function BillingFieldset() {
    const trackFocus = () => {}

    return (
        <Fieldset
            fields={fields}
            legend="Billing"
            description="Invoice contact details"
            columns={2}
            id="billing-section"
            class="rounded-lg bg-zinc-50 p-6"
            wrapperClass="ring-1 ring-zinc-200"
            visibility={{ field: 'billing_method', operator: '=', value: 'invoice' }}
            clearWhenHidden
            data-testid="billing-fieldset"
            onFocus={trackFocus}
        />
    )
}

The direct component reads fields, legend, description, columns, id, class, wrapperClass, visibility, and clearWhenHidden. Native attributes and listeners target the rendered <fieldset> or fallback <div>. Visibility and child values require the surrounding form context. clearWhenHidden is applied by Form, Wizard, or an outer FieldRenderer when a visible fieldset becomes hidden; the direct Fieldset component only renders or hides the group.