Skip to content
Core Concepts

Conditional Visibility ​

Show a field (or a whole fieldset) only when another field has a certain value.

php
Combobox::make('contact_method')->options(['email' => 'Email', 'phone' => 'Phone']),

TextInput::make('phone')
    ->required()
    ->visibleWhen('contact_method', 'phone'),

The phone input appears only when "Phone" is selected.

When to use: "Other, please specify" inputs, business-only sections, fields that depend on a toggle, and similar cases.

Examples ​

Each example is a live form built from the PHP below it. Change the answers to watch fields appear and disappear, then submit to see the data your controller would receive.

Basic ​

One condition per field: the radio decides whether you are asked for an email or a phone number.

php
use Erag\InertiaForms\Fields\Radio;
use Erag\InertiaForms\Fields\TextInput;

[
    Radio::make('contact_method')->inline()->default('email')->options([
        'email' => 'Email',
        'phone' => 'Phone',
    ]),
    TextInput::make('email')->email()->required()->visibleWhen('contact_method', 'email'),
    TextInput::make('phone')->tel()->required()->visibleWhen('contact_method', 'phone'),
];

Operators ​

A tour of the operators: < on a number, a list for "one of these", contains on a checkbox group, and not_empty on a text area. Try an age under 18, pick India, tick Workshops and type a note.

php
use Erag\InertiaForms\Fields\CheckboxGroup;
use Erag\InertiaForms\Fields\Combobox;
use Erag\InertiaForms\Fields\Textarea;
use Erag\InertiaForms\Fields\TextInput;
use Erag\InertiaForms\Fields\TimePicker;
use Erag\InertiaForms\Fields\Toggle;

[
    TextInput::make('age')->number()->min(1)->required(),
    TextInput::make('guardian_name')
        ->label('Parent or guardian')
        ->required()
        ->visibleWhen('age', '<', 18),
    Combobox::make('country')->options([
        'IN' => 'India',
        'US' => 'United States',
        'DE' => 'Germany',
        'GB' => 'United Kingdom',
    ]),
    TextInput::make('state')->visibleWhen('country', ['IN', 'US']),
    CheckboxGroup::make('interests')->inline()->options([
        'talks' => 'Talks',
        'workshops' => 'Workshops',
        'networking' => 'Networking',
    ]),
    TimePicker::make('workshop_slot')->minuteStep(30)->visibleWhen('interests', 'contains', 'workshops'),
    Textarea::make('notes')->rows(2),
    Toggle::make('share_notes')->label('Share my notes with the speakers')->visibleWhen('notes', 'not_empty'),
];

Advanced: sections, nested fields and cleanup ​

The Company fieldset only shows for business accounts. Inside it, the VAT number depends on a nested field, and the PO number needs two conditions to pass. With clearWhenHidden(), switching back to Personal empties those values, so the submitted data doesn't carry stale company details.

php
use Erag\InertiaForms\Fields\Combobox;
use Erag\InertiaForms\Fields\Fieldset;
use Erag\InertiaForms\Fields\Radio;
use Erag\InertiaForms\Fields\Submit;
use Erag\InertiaForms\Fields\TextInput;
use Erag\InertiaForms\Fields\Toggle;
use Erag\InertiaForms\Form;

class AccountForm extends Form
{
    public function fields(): array
    {
        return [
            TextInput::make('name')->required(),
            Radio::make('account_type')->buttons()->default('personal')->options([
                'personal' => 'Personal',
                'business' => 'Business',
            ]),
            Fieldset::make('Company')
                ->description('Only for business accounts. Cleared when you switch back.')
                ->visibleWhen('account_type', 'business')
                ->columns(2)
                ->fields([
                    TextInput::make('company.name')->required()->clearWhenHidden(),
                    Combobox::make('company.country')->required()->clearWhenHidden()->options([
                        'DE' => 'Germany',
                        'FR' => 'France',
                        'IT' => 'Italy',
                        'IN' => 'India',
                    ]),
                    TextInput::make('company.vat_number')
                        ->label('VAT number')
                        ->required()
                        ->visibleWhen('company.country', 'in', ['DE', 'FR', 'IT'])
                        ->clearWhenHidden(),
                    Toggle::make('company.pay_by_invoice')->label('Pay by invoice')->columnSpan(2),
                    TextInput::make('company.po_number')
                        ->label('PO number')
                        ->visibleWhen('company.pay_by_invoice', true)
                        ->visibleWhen('company.country', '!=', 'IN')
                        ->clearWhenHidden(),
                ]),
            Submit::make('Create account'),
        ];
    }
}

How it works ​

The rules are serialized into the schema. The frontend evaluates them on every change, and the backend evaluates the same rules when validating. Both sides use the same comparison logic, so they always agree on what is visible.

  • In the browser, hidden fields are not rendered.
  • On the server, hidden fields get no validation rules, so they are never required and never appear in validated().

That second point is important: ->required() on a hidden field is ignored until the field is visible.

visibleWhen() and hiddenWhen() ​

Both methods take the name of another field, then either a value or an operator and a value.

php
// Equal to a value
->visibleWhen('type', 'business')

// A list means "one of these"
->visibleWhen('country', ['IN', 'US'])

// Operator and value
->visibleWhen('age', '>=', 18)
->visibleWhen('tags', 'contains', 'vip')
->visibleWhen('status', '!=', 'draft')

// hiddenWhen() is the opposite of visibleWhen()
->hiddenWhen('same_as_billing', true)
ArgumentsMeaning
('field', $value)Compare with =.
('field', [$a, $b])Compare with in.
('field', $operator, $value)Use any supported operator.

An unknown operator throws an InvalidArgumentException.

Operators without a value

empty, not_empty, truthy, and falsy don't need a value. Pass the operator on its own:

php
->visibleWhen('bio', 'not_empty')
->hiddenWhen('newsletter', 'falsy')

For boolean fields such as a toggle, comparing with true also works: ->visibleWhen('has_company', true).

Multiple conditions ​

Chain calls to add more conditions. All of them must pass.

php
TextInput::make('vat_number')
    ->visibleWhen('type', 'business')
    ->visibleWhen('country', 'in', ['DE', 'FR', 'IT']);

There is no built-in "or" between separate conditions. For "one of these values" on the same field, pass an array.

Nested fields ​

Use dot notation to depend on nested data:

php
TextInput::make('address.state')->visibleWhen('address.country', 'US');

Fieldsets ​

Fieldsets support the same methods. When a fieldset is hidden, every field inside it is hidden and skipped during validation.

php
Fieldset::make('Company details')
    ->visibleWhen('account_type', 'business')
    ->fields([
        TextInput::make('company_name')->required(),
        TextInput::make('vat_number'),
    ]);

Clearing hidden values ​

By default, a hidden field keeps whatever the user typed before it was hidden. Hidden fields are not validated, and their values are not included in validated(), but they are still sent with the request.

Call clearWhenHidden() to reset the value to the field's empty value as soon as it becomes hidden in the browser:

php
TextInput::make('phone')
    ->visibleWhen('contact_method', 'phone')
    ->clearWhenHidden();

This also applies when the field's fieldset becomes hidden.

Value comparison ​

Values are compared as strings after a small normalization step, so 1, '1', and 1.0 are equal, and true equals 'true'. Enum cases are compared by their backing value. Numeric operators (>, >=, <, <=) only pass when both sides are numeric.

See the Visibility Operators reference for exact rules.

Using visibility in your own code ​

Each frontend package exports isVisible(conditions, data), the same function <Form> uses:

vue
<script setup lang="ts">
import { isVisible, type FieldSchema } from '@erag/inertia-forms-vue';

const props = defineProps<{ field: FieldSchema; data: Record<string, unknown> }>();
</script>

<template>
    <p v-if="isVisible(props.field.visibility, props.data)">Visible right now</p>
</template>
tsx
import { isVisible, type FieldSchema } from '@erag/inertia-forms-react';

function Hint({ field, data }: { field: FieldSchema; data: Record<string, unknown> }) {
    return isVisible(field.visibility, data) ? <p>Visible right now</p> : null;
}
svelte
<script lang="ts">
    import { isVisible, type FieldSchema } from '@erag/inertia-forms-svelte';

    let { field, data }: { field: FieldSchema; data: Record<string, unknown> } = $props();
</script>

{#if isVisible(field.visibility, data)}
    <p>Visible right now</p>
{/if}

On the server, $field->isVisibleFor($data) does the same thing.