Slots and Children
Introduction
The Form component renders fields automatically based on the PHP configuration. Vue exposes named slots for field replacement, field insertion, and fieldset insertion. React exposes children after the generated fieldsets and a structured slots render map for insertion around fields and fieldsets.
Default Slot
Content placed in the Vue default slot or React children is rendered after all fieldsets.
<Form :form="form">
<template #default="{ form }">
<p v-if="form.isDirty.value" class="text-sm text-iui-form-warning">
You have unsaved changes.
</p>
</template>
</Form><Form form={form}>
{(formInstance) => (
formInstance.isDirty ? (
<p className="text-iui-form-warning text-sm">
You have unsaved changes.
</p>
) : null
)}
</Form>The slot prop or render-prop argument is the return value of useForm, so you have access to form.data, form.errors, form.processing, form.submit(), and everything documented in Customizing Presenters.
Adding Content Around the Form
Since the default slot renders after fieldsets, you may combine it with wrapper markup to add headers, footers, or surrounding content:
<div>
<h2 class="text-xl font-bold mb-4">Create User</h2>
<Form :form="form">
<template #default="{ form }">
<div class="flex justify-between items-center mt-6 pt-6 border-t">
<p class="text-sm text-iui-form-muted-foreground">
All fields marked with * are required.
</p>
</div>
</template>
</Form>
</div><div>
<h2 className="mb-4 text-xl font-bold">Create User</h2>
<Form form={form}>
<div className="mt-6 flex items-center justify-between border-t pt-6">
<p className="text-iui-form-muted-foreground text-sm">
All fields marked with * are required.
</p>
</div>
</Form>
</div>Replacing Field Rendering
In Vue, use a named slot matching the field name plus -field to replace one auto-rendered field while keeping the PHP-defined order:
<Form :form="form">
<template #email-field="{ field, value, error, setValue }">
<div>
<label class="mb-1 block text-sm font-medium">
{{ field.label }}
</label>
<input
class="block w-full rounded-md"
:value="value ?? ''"
@input="setValue($event.target.value)"
/>
<p v-if="error" class="mt-1 text-sm text-iui-form-destructive">
{{ error }}
</p>
</div>
</template>
</Form>The slot is rendered exactly where the field appears in the backend schema, including fields inside fieldsets. Without a matching slot, the default field component renders as usual.
Field replacement slots receive:
| Prop | Description |
|---|---|
field | The serialized field configuration from PHP. |
form | The useForm return value used by the generated form. |
context | The injected form context used by field components. |
name | The field name. |
value | The current form value for the field. |
error | The validation error for the field, if any. |
errors | The full form error object. |
setValue | Updates the form value and clears the field error. |
fieldClass | Layout class the renderer would have passed to the default field wrapper. |
inGrid | Whether the field is currently rendered in a multi-column fieldset grid. |
React does not expose a field replacement renderer. Use the insertion render map below for adjacent content, or use manual form rendering when you need to replace the generated field itself.
Inserting Content Around Fields
In Vue, use #before-{name}-field and #after-{name}-field to add content immediately around one generated field without replacing it. In React, use slots.fields[name].before and slots.fields[name].after. These insertion points render around the default field component or, in Vue, around a matching replacement slot such as #email-field:
<Form :form="form">
<template #before-email-field="{ field }">
<p class="text-sm text-iui-form-muted-foreground">
We will use {{ field.label }} for account notifications.
</p>
</template>
<template #after-email-field="{ value }">
<p v-if="value" class="text-sm text-iui-form-muted-foreground">
You can change this address later.
</p>
</template>
</Form><Form
form={form}
slots={{
fields: {
email: {
before: ({ field }) => (
<p className="text-iui-form-muted-foreground text-sm">
We will use {field.label} for account notifications.
</p>
),
after: ({ value }) =>
value ? (
<p className="text-iui-form-muted-foreground text-sm">
You can change this address later.
</p>
) : null,
},
},
}}
/>Vue field insertion slots and React field renderers receive the field props listed above. The default field still renders between the before and after insertion points unless you also provide the Vue replacement slot for that field.
Vue slot names use the serialized field name plus -field. React field keys use the serialized field name directly. For field names that are not valid static Vue slot names, use dynamic slot names:
<template #[`user.email-field`]="slotProps">
<!-- ... -->
</template>
<template #[`before-user.email-field`]="slotProps">
<!-- ... -->
</template><Form
form={form}
slots={{
fields: {
'user.email': {
before: ({ name }) => (
<p className="text-iui-form-muted-foreground text-sm">
Extra detail for {name}.
</p>
),
},
},
}}
/>Inserting Content Around Fieldsets
In Vue, use #before-{id}-fieldset and #after-{id}-fieldset to add content immediately around a generated fieldset. In React, use slots.fieldsets[id].before and slots.fieldsets[id].after. Fieldset targeting requires an explicit Fieldset::id(...) value. Fieldsets are not targeted by legend text or array position.
use InertiaUI\Forms\Fields\Fieldset;
use InertiaUI\Forms\Fields\TextInput;
Fieldset::make()
->id('account')
->legend('Account')
->fields([
TextInput::make('email')->label('Email'),
]);<Form :form="form">
<template #before-account-fieldset="{ fieldset }">
<p class="text-sm text-iui-form-muted-foreground">
{{ fieldset.legend }} details
</p>
</template>
<template #after-account-fieldset="{ id }">
<p class="text-sm text-iui-form-muted-foreground">
End of {{ id }}.
</p>
</template>
</Form><Form
form={form}
slots={{
fieldsets: {
account: {
before: ({ fieldset }) => (
<p className="text-iui-form-muted-foreground text-sm">
{fieldset.legend} details
</p>
),
after: ({ id }) => (
<p className="text-iui-form-muted-foreground text-sm">
End of {id}.
</p>
),
},
},
}}
/>Fieldset insertion slots and renderers receive:
| Prop | Description |
|---|---|
fieldset | The serialized fieldset configuration from PHP. |
form | The useForm return value used by the generated form. |
context | The injected form context used by field components. |
id | The explicit fieldset id. |
fieldClass | Layout class when provided by the fieldset renderer. |
inGrid | Whether the fieldset uses a multi-column grid. |
For complete control over every field and the form shell itself, use manual form rendering. Manual Usage shows the provider setup and direct component pattern for both Vue and React.
Mixing Auto-Render and Custom Content
You may use the default slot to add custom content alongside auto-rendered fields. The auto-rendered fieldsets appear first, followed by your slot content:
<Form :form="form">
<template #default="{ form }">
<!-- This appears after all auto-rendered fields -->
<div class="bg-iui-form-info-subtle rounded-iui-form-lg p-4 mt-4">
<h3 class="font-medium text-iui-form-info-foreground">Preview</h3>
<p>Name: {{ form.data.name }}</p>
<p>Email: {{ form.data.email }}</p>
</div>
</template>
</Form>Wizard Additional Content
The Vue Wizard component provides a default slot with both form and wizard props. React accepts an equivalent render function as children:
<Wizard :form="form">
<template #default="{ form, wizard }">
<p class="text-sm text-iui-form-muted-foreground mt-4">
Step {{ wizard.currentStep.value + 1 }} of {{ wizard.totalSteps.value }}
</p>
</template>
</Wizard><Wizard form={form}>
{(generatedForm, wizard) => <p>Step {wizard.currentStep + 1} of {wizard.totalSteps}</p>}
</Wizard>Vue field and fieldset insertion slots and the React slots render map work around the active wizard step content the same way they work in Form.
Using Individual Input Components
Direct input components are exported from @inertiaui/form-vue/components and @inertiaui/form-react/components and may be used in manual form layouts. RichText is exported from each package's richtext subpath so Tiptap stays out of the general component barrel. With form context in the component tree, these components bind to form data automatically. Pass the serialized field config into the component:
<script setup lang="ts">
import { SubmitButton, Textarea, TextInput } from '@inertiaui/form-vue/components'
</script>
<template>
<TextInput v-bind="form.getField('name')" />
</template>import { SubmitButton, Textarea, TextInput } from '@inertiaui/form-react/components'
export function CustomLayout({ form }) {
return <TextInput {...form.getField('name')} />
}This is useful when you want the convenience of the pre-built input components but need a custom layout that the auto-render Form component does not support. For components that do not use useForm or serialized form.getField(...) payloads, see Manual Usage.