Icons
Inertia Forms does not ship a built-in icon set. For fields that accept icon names, register an icon resolver that maps those strings to Vue or React components. This keeps the PHP form definition stack-agnostic while letting your app use Lucide, your own components, or another icon set.
Resolver-backed icon names are used by every form icon surface. PHP form definitions pass stable icon names, and the frontend resolver decides which Vue or React component to render.
Lucide Example
On a page, import the icon set and define a resolver function that maps the serialized icon name to the actual component. Pass the resolver to the rendered Form or Wizard component:
<script setup>
import { Form } from '@inertiaui/form-vue'
import * as Icons from '@lucide/vue'
defineProps({
form: Object,
})
function resolveIcon(icon) {
return Icons[icon]
}
</script>
<template>
<Form :form="form" :icon-resolver="resolveIcon" />
</template>import { Form } from '@inertiaui/form-react'
import * as Icons from 'lucide-react'
export default function EditProduct({ form }) {
function resolveIcon(icon) {
return Icons[icon]
}
return <Form form={form} iconResolver={resolveIcon} />
}You may also pass the resolver inline:
<template>
<Form :form="form" :icon-resolver="(icon) => Icons[icon]" />
</template>export default function EditProduct({ form }) {
return <Form form={form} iconResolver={(icon) => Icons[icon]} />
}Resolving Icons Globally
To avoid defining the resolver on every page, register it globally in your app entry by importing setIconResolver and passing the resolver function:
import { setIconResolver } from '@inertiaui/form-vue'
import * as Icons from '@lucide/vue'
setIconResolver((icon, context) => Icons[icon])import { setIconResolver } from '@inertiaui/form-react'
import * as Icons from 'lucide-react'
setIconResolver((icon, context) => Icons[icon])The resolver receives a second context argument. For example, TextInput passes whether the icon is a leading icon, trailing icon, or addon icon. Use that context when the same icon name should resolve differently for different parts of a field.
Wizard step icons configured with WizardConfig::step(title: 'Account', icon: 'UserIcon') use the same resolver. Their context is { component: 'Wizard', part: 'step-indicator', step: index }, where step is the zero-based visible step index.
You may call resolveIcon when a custom component needs to resolve an icon before rendering it:
import { resolveIcon } from '@inertiaui/form-vue'
const Icon = resolveIcon('MailIcon', { part: 'leadingIcon' })import { resolveIcon } from '@inertiaui/form-react'
const Icon = resolveIcon('MailIcon', { part: 'leadingIcon' })resolveIcon uses the global resolver registered through setIconResolver. With no global resolver registered, it returns null.
Direct Components
Standalone components accept the same resolver prop:
<script setup>
import { ref } from 'vue'
import { TextInput } from '@inertiaui/form-vue/components'
import * as Icons from '@lucide/vue'
const email = ref('')
</script>
<template>
<TextInput
v-model="email"
name="email"
label="Email"
leading-icon="MailIcon"
:icon-resolver="(icon) => Icons[icon]"
/>
</template>import { useState } from 'react'
import { TextInput } from '@inertiaui/form-react/components'
import * as Icons from 'lucide-react'
export default function EmailField() {
const [email, setEmail] = useState('')
return (
<TextInput
name="email"
label="Email"
value={email}
onValueChange={setEmail}
leadingIcon="MailIcon"
iconResolver={(icon) => Icons[icon]}
/>
)
}Using DynamicIcon In Custom Presenters
Use DynamicIcon when a custom presenter has an icon name instead of an icon component. Pass the icon name, then pass a local or form-level resolver when the presenter has one. If resolver is omitted, DynamicIcon uses the global resolver registered with setIconResolver.
This example prefers a direct iconResolver prop, then the resolver from the surrounding Form:
<script setup lang="ts">
import { inject } from 'vue'
import { DynamicIcon, FieldWrapper, FormContextKey, useFormField } from '@inertiaui/form-vue'
import type { BaseField, IconResolverFn } from '@inertiaui/form-vue'
interface StatusBadgeField extends BaseField {
component: 'StatusBadge'
icon?: string | null
iconResolver?: IconResolverFn | null
}
const props = defineProps<StatusBadgeField>()
const formContext = inject(FormContextKey, null)
const { value, error, inputId } = useFormField<string | null>(props)
</script>
<template>
<FieldWrapper :field="props" :error="error" :input-id="inputId">
<span class="inline-flex items-center gap-2">
<DynamicIcon
v-if="props.icon"
:icon="props.icon"
:resolver="props.iconResolver ?? formContext?.iconResolver"
:context="{ field: props, part: 'statusIcon' }"
class="h-4 w-4"
aria-hidden="true"
/>
<span>{{ value }}</span>
</span>
</FieldWrapper>
</template>import { useContext } from 'react'
import { DynamicIcon, FieldWrapper, FormContext, useFormField } from '@inertiaui/form-react'
import type { BaseField, IconResolverFn } from '@inertiaui/form-react'
interface StatusBadgeField extends BaseField {
component: 'StatusBadge'
icon?: string | null
iconResolver?: IconResolverFn | null
}
export default function StatusBadge(props: StatusBadgeField) {
const formContext = useContext(FormContext)
const { value, error, inputId } = useFormField<string | null>(props)
return (
<FieldWrapper field={props} error={error} inputId={inputId}>
<span className="inline-flex items-center gap-2">
{props.icon ? (
<DynamicIcon
icon={props.icon}
resolver={props.iconResolver ?? formContext?.iconResolver}
context={{ field: props, part: 'statusIcon' }}
className="h-4 w-4"
aria-hidden="true"
/>
) : null}
<span>{value}</span>
</span>
</FieldWrapper>
)
}Unresolved icons log a warning and render nothing. DynamicIcon always adds the iui-form-dynamic-icon class and forwards SVG attributes to the rendered icon.
Helper Reference
The icon helpers are exported from the root Vue and React packages:
import { DynamicIcon, resolveIcon, setIconResolver } from '@inertiaui/form-vue'
import type { IconContext, IconResolverFn } from '@inertiaui/form-vue'import { DynamicIcon, resolveIcon, setIconResolver } from '@inertiaui/form-react'
import type { IconContext, IconResolverFn } from '@inertiaui/form-react'| Helper | Purpose |
|---|---|
DynamicIcon | Renders an icon name with an optional local resolver or the global resolver. Vue resolvers may return a Vue component or component name. React resolvers return an SVG component. |
resolveIcon(icon, context?) | Resolves an icon name through the global resolver. Use this when you need the component before rendering. |
setIconResolver(resolver) | Registers or clears the global resolver for the current JavaScript runtime. Pass null to clear it. |
IconContext | A Record<string, unknown> passed as the second resolver argument. Built-in presenters use it to describe where the icon is being rendered. |
IconResolverFn | The resolver function type. It receives the icon name and context, then returns a component or null or undefined when the name is not supported. |
Common context keys include:
| Key | Common values |
|---|---|
field | Serialized field props when the icon belongs to a field or display helper. |
part | leadingIcon, trailingIcon, addonIcon, icon, loadingIcon, onIcon, offIcon, optionIcon, or a custom part from your presenter. |
placement | leading or trailing for input icons and addons. |
segment | The input addon segment when part is addonIcon. |
option | The rich option object for Combobox, Radio, and CheckboxGroup option icons. |
selected / disabled | Option state for rich option icons. |
variant | The Callout variant. |
Using A Fixed Icon Map
For a small, fixed set, use an explicit Lucide mapping:
import { Mail, Trash2 } from '@lucide/vue'
setIconResolver((icon) =>
({
MailIcon: Mail,
Trash2Icon: Trash2,
})[icon],
)You can still make the resolver context-aware. Keep public PHP icon names stable and choose the matching Lucide component for the field surface:
import { Mail, MailCheck, MailWarning } from '@lucide/vue'
setIconResolver((icon, context) => {
if (icon === 'MailIcon' && context.part === 'trailingIcon') {
return MailCheck
}
return {
MailIcon: Mail,
MailWarningIcon: MailWarning,
}[icon]
})Icon Surfaces
These fields and option metadata resolve icon names through the same resolver:
TextInput::leadingIcon()andTextInput::trailingIcon()TextInput::leadingAddonIcon()andTextInput::trailingAddonIcon()Submit::icon()andSubmit::loadingIcon()Toggle::onIcon()andToggle::offIcon()Callout::icon()- Rich option metadata such as
iconon customCombobox,Radio, andCheckboxGroupoptions
Do not pass SVG markup to these APIs. Use the dedicated Html display helper only when a form needs trusted author HTML.