Skip to main content

Core Form Components

This document covers the Core Form system built on top of Inertia.js forms. These components provide a consistent, type-safe way to build forms with automatic error handling, validation display, and form state management.
Base Documentation: See Inertia.js Forms Documentation for core functionality.

CoreForm Component

The CoreForm component wraps Inertia’s Form component and provides a context for all form fields. It supports both controlled (using useForm hook) and uncontrolled (using Inertia’s built-in form handling) modes. Use controlled mode when you need programmatic control over form data, validation, or need to access form state:
import {
  CoreForm,
  CoreFormInput,
  CoreFormSubmitButton,
} from '@/core/components/core-form';
import { useForm } from '@inertiajs/react';

export default function UserCreate() {
  const form = useForm({
    name: '',
    email: '',
  });

  return (
    <CoreForm
      form={form}
      onSubmit={() => form.post(route('users.store'))}
      footer={
        <CoreFormSubmitButton variant="brand">
          {__('Create User')}
        </CoreFormSubmitButton>
      }
    >
      <CoreFormInput
        name="name"
        label={__('Name')}
        type="text"
        placeholder={__('Enter name')}
      />

      <CoreFormInput
        name="email"
        label={__('Email')}
        type="email"
        placeholder={__('Enter email')}
      />
    </CoreForm>
  );
}

Uncontrolled Mode

Use uncontrolled mode for simpler forms that rely on Inertia’s built-in form handling:
import {
  CoreForm,
  CoreFormInput,
  CoreFormSubmitButton,
} from '@/core/components/core-form';

export default function UserCreate() {
  return (
    <CoreForm
      action={route('users.store')}
      method="post"
      footer={
        <CoreFormSubmitButton variant="brand">
          {__('Create User')}
        </CoreFormSubmitButton>
      }
    >
      {({ data, setData, errors, processing }) => (
        <>
          <CoreFormInput
            name="name"
            label={__('Name')}
            type="text"
            placeholder={__('Enter name')}
          />

          <CoreFormInput
            name="email"
            label={__('Email')}
            type="email"
            placeholder={__('Enter email')}
          />
        </>
      )}
    </CoreForm>
  );
}

Props

CoreFormPropsWithForm (Controlled)

  • form: Inertia form object from useForm() hook
  • onSubmit: Form submission handler
  • action?: URL string (optional, for form action attribute)
  • footer?: ReactNode to render in the form footer (typically submit buttons)
  • disabled?: Boolean to disable all form fields
  • className?: Additional CSS classes for the form layout
  • children: ReactNode or function that receives form props

CoreFormPropsWithoutForm (Uncontrolled)

  • action: URL string (required for form submission)
  • method?: HTTP method (default: ‘post’)
  • footer?: ReactNode to render in the form footer
  • disabled?: Boolean to disable all form fields
  • className?: Additional CSS classes for the form layout
  • children: ReactNode or function that receives FormComponentSlotProps
For Action Forms: If you need to execute a CoreAction instead of submitting to a regular route, use CoreActionForm instead of CoreForm.
The footer prop renders buttons in a consistent layout at the bottom of the form:
  • Buttons are arranged horizontally on larger screens
  • Stacked vertically on mobile
  • Includes a top border separator
  • Proper spacing between buttons
<CoreForm
  form={form}
  onSubmit={handleSubmit}
  footer={
    <>
      <Button variant="secondary" onClick={onCancel}>
        {__('Cancel')}
      </Button>
      <CoreFormSubmitButton variant="brand">{__('Save')}</CoreFormSubmitButton>
    </>
  }
>
  {/* Form fields */}
</CoreForm>

Disabled State

You can disable the entire form and all its fields:
<CoreForm form={form} onSubmit={handleSubmit} disabled={!canEdit}>
  {/* All fields will be disabled when canEdit is false */}
</CoreForm>

Form Field Components

All form field components automatically:
  • Display validation errors from the server
  • Integrate with form state (disabled/processing)
  • Support both controlled and uncontrolled modes
  • Provide consistent styling and spacing

CoreFormInput

Text input field for various input types.
import { CoreFormInput } from '@/core/components/core-form';

<CoreFormInput
  name="email"
  label={__('Email Address')}
  type="email"
  placeholder={__('Enter email')}
  required
  description={__('We'll never share your email')}
/>

Props

  • name: Field name (required)
  • label: Field label
  • type: Input type ('text' | 'email' | 'password' | 'number' | 'url' | 'tel' | 'date' | 'time' | 'datetime-local')
  • placeholder?: Placeholder text
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • description?: Help text displayed below the label
  • inputProps?: Additional props passed to the underlying Input component
  • className?: Additional CSS classes

CoreFormSelect

Dropdown select field for choosing from a list of options.
import { CoreFormSelect } from '@/core/components/core-form';

<CoreFormSelect
  name="role"
  label={__('Role')}
  options={[
    { value: 'admin', label: __('Administrator') },
    { value: 'user', label: __('User') },
    { value: 'guest', label: __('Guest') },
  ]}
  placeholder={__('Select a role')}
  allowEmpty={true}
/>;

Props

  • name: Field name (required)
  • label: Field label
  • options: Array of { value: string, label: string | ReactNode }
  • placeholder?: Placeholder text
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • description?: Help text
  • allowEmpty?: Allow selecting empty value (only works with controlled forms)
  • selectProps?: Additional props passed to the underlying Select component
  • selectValueProps?: Props for the SelectValue component
  • className?: Additional CSS classes

CoreFormCombobox

Searchable combobox for selecting from a list of options.
import { CoreFormCombobox } from '@/core/components/core-form';

<CoreFormCombobox
  name="country"
  label={__('Country')}
  options={countries}
  placeholder={__('Search countries...')}
  comboboxProps={{
    buttonProps: { size: 'lg' },
  }}
/>;

Props

  • name: Field name (required)
  • label: Field label
  • options: Array of { value: string, label: string }
  • placeholder?: Placeholder text
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • description?: Help text
  • comboboxProps?: Additional props passed to the underlying Combobox component
  • className?: Additional CSS classes

CoreFormMultiSelect

Multi-select field for choosing multiple options from a list. Built on top of the MultiSelect component with full form integration.
import { CoreFormMultiSelect } from '@/core/components/core-form';

<CoreFormMultiSelect
  name="frameworks"
  label={__('Frameworks')}
  options={[
    { value: 'next.js', label: 'Next.js' },
    { value: 'sveltekit', label: 'SvelteKit' },
    { value: 'nuxt.js', label: 'Nuxt.js' },
    { value: 'remix', label: 'Remix' },
    { value: 'astro', label: 'Astro' },
    { value: 'vue', label: 'Vue.js' },
    { value: 'react', label: 'React' },
  ]}
  placeholder={__('Select frameworks...')}
/>;

Single Select Mode

You can enable single select mode to allow only one selection:
<CoreFormMultiSelect
  name="primary_framework"
  label={__('Primary Framework')}
  options={frameworks}
  placeholder={__('Select a framework...')}
  multiSelectProps={{ single: true }}
/>

Custom Badge Labels

You can display different text in badges than in the dropdown:
<CoreFormMultiSelect
  name="languages"
  label={__('Programming Languages')}
  options={[
    { value: 'ts', label: 'TypeScript', badgeLabel: 'TS' },
    { value: 'js', label: 'JavaScript', badgeLabel: 'JS' },
    { value: 'py', label: 'Python', badgeLabel: 'PY' },
  ]}
/>

Search Configuration

Customize or disable search functionality:
// Disable search
<CoreFormMultiSelect
  name="options"
  label={__('Options')}
  options={options}
  multiSelectContentProps={{ search: false }}
/>

// Customize search
<CoreFormMultiSelect
  name="options"
  label={__('Options')}
  options={options}
  multiSelectContentProps={{
    search: {
      placeholder: __('Search options...'),
      emptyMessage: __('No options found'),
    },
  }}
/>

Overflow Behavior

Control how selected values display when they overflow:
<CoreFormMultiSelect
  name="tags"
  label={__('Tags')}
  options={tags}
  multiSelectValueProps={{
    overflowBehavior: 'cutoff', // 'wrap' | 'wrap-when-open' | 'cutoff'
    clickToRemove: true,
  }}
/>

Props

  • name: Field name (required)
  • label: Field label
  • options: Array of { value: string, label: string | ReactNode, badgeLabel?: ReactNode }
  • placeholder?: Placeholder text when no values selected
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • description?: Help text
  • defaultValues?: Default selected values (for uncontrolled forms)
  • multiSelectProps?: Additional props passed to the MultiSelect component
    • single?: Enable single selection mode (default: false)
    • onValuesChange?: Callback when values change
  • multiSelectValueProps?: Props for the MultiSelectValue component
    • overflowBehavior?: How to handle overflow (‘wrap’ | ‘wrap-when-open’ | ‘cutoff’)
    • clickToRemove?: Allow clicking badges to remove them (default: true)
  • multiSelectContentProps?: Props for the MultiSelectContent component
    • search?: Enable search (boolean) or configure it ({ placeholder?, emptyMessage? })
  • className?: Additional CSS classes

CoreFormSearchCombobox

Searchable combobox that fetches options from the server as you type.
import { CoreFormSearchCombobox } from '@/core/components/core-form';

<CoreFormSearchCombobox
  name="user_id"
  label={__('User')}
  searchComboboxProps={{
    url: route('users.search'),
    placeholder: __('Search users...'),
    displayValue: user => user.name,
    getValue: user => user.id.toString(),
  }}
/>;

Props

  • name: Field name (required)
  • label: Field label
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • description?: Help text
  • searchComboboxProps: Props for the underlying SearchCombobox component (see Search Combobox Documentation)
  • className?: Additional CSS classes

CoreFormTextarea

Multi-line text input field.
import { CoreFormTextarea } from '@/core/components/core-form';

<CoreFormTextarea
  name="description"
  label={__('Description')}
  placeholder={__('Enter description')}
  textareaProps={{
    rows: 5,
  }}
/>;

Props

  • name: Field name (required)
  • label: Field label
  • placeholder?: Placeholder text
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • description?: Help text
  • textareaProps?: Additional props passed to the underlying Textarea component
  • className?: Additional CSS classes

CoreFormCheckbox

Single checkbox field.
import { CoreFormCheckbox } from '@/core/components/core-form';

<CoreFormCheckbox
  name="terms_accepted"
  label={__('I accept the terms and conditions')}
  description={__('You must accept the terms to continue')}
/>;

Props

  • name: Field name (required)
  • label: Checkbox label
  • description?: Help text displayed below the label
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • checkboxProps?: Additional props passed to the underlying Checkbox component
  • labelProps?: Props for the label component
  • className?: Additional CSS classes

CoreFormSwitch

Toggle switch field (similar to checkbox but with different UI).
import { CoreFormSwitch } from '@/core/components/core-form';

<CoreFormSwitch
  name="notifications_enabled"
  label={__('Enable notifications')}
  description={__('Receive email notifications about updates')}
  switchProps={{
    size: 'sm',
  }}
/>;

Props

  • name: Field name (required)
  • label: Switch label
  • description?: Help text displayed below the label
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • switchProps?: Additional props passed to the underlying Switch component
  • labelProps?: Props for the label component
  • className?: Additional CSS classes

CoreFormPhoneInput

Phone number input with international formatting.
import { CoreFormPhoneInput } from '@/core/components/core-form';

<CoreFormPhoneInput
  name="phone"
  label={__('Phone Number')}
  placeholder="+46701234567"
/>;

Props

  • name: Field name (required)
  • label: Field label
  • placeholder?: Placeholder text
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • description?: Help text
  • phoneInputProps?: Additional props passed to the underlying phone input component
  • className?: Additional CSS classes

CoreFormEntityInput

Input field for entity identification (SSN or organization number) with validation.
import { CoreFormEntityInput } from '@/core/components/core-form';

<CoreFormEntityInput
  name="ssn"
  label={__('Social Security Number')}
  type="ssn"
  inputProps={{
    placeholder: __('YYYYMMDD-XXXX'),
  }}
/>;

Props

  • name: Field name (required)
  • label: Field label
  • type: Entity type ('ssn' | 'organization')
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • description?: Help text
  • inputProps?: Additional props passed to the underlying input component
  • className?: Additional CSS classes

CoreFormFileSelector

File selector field for choosing files from a file tree or data loader.
import { CoreFormFileSelector } from '@/core/components/core-form';

<CoreFormFileSelector
  name="document_path"
  label={__('Document Path')}
  fileTree={fileTree}
  dataLoader={dataLoader}
  placeholder={__('Select a file...')}
/>;

Props

  • name: Field name (required)
  • label: Field label
  • fileTree?: File tree data structure
  • dataLoader?: Function to load file data asynchronously
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • description?: Help text
  • comboboxProps?: Additional props passed to the underlying combobox component
  • className?: Additional CSS classes

CoreFormMoneyInput

Input field specifically for monetary values with currency formatting.
import { CoreFormMoneyInput } from '@/core/components/core-form';

<CoreFormMoneyInput
  name="price"
  label={__('Price')}
  currency="SEK"
  step={0.01}
/>;

Props

  • name: Field name (required)
  • label: Field label
  • currency: Currency code (required, e.g., 'SEK', 'USD')
  • step?: Step value for number input (default: 0.01)
  • placeholder?: Placeholder text (default: '0.00')
  • disabled?: Disable this specific field
  • required?: Mark field as required
  • description?: Help text
  • inputProps?: Additional props passed to the underlying input component
  • className?: Additional CSS classes

CoreFormSubmitButton

Submit button that automatically integrates with form state (disabled during processing, shows loading state).
import { CoreFormSubmitButton } from '@/core/components/core-form';

<CoreFormSubmitButton variant="brand" size="lg" iconRight={<Kbd>S</Kbd>}>
  {__('Save')}
</CoreFormSubmitButton>;

Props

All props from the Button component are supported. The button automatically:
  • Disables when the form is processing or disabled
  • Shows a loading spinner when processing
  • Sets type="submit"

useCoreForm Hook

Access form context from within CoreForm:
import { useCoreForm } from '@/core/components/core-form';

function CustomField() {
  const form = useCoreForm();

  // Access form state
  const isProcessing = form.processing;
  const isDisabled = form.disabled;
  const errors = form.errors;
  const data = form.data;

  // Update form data (controlled mode only)
  if (form.isControlled && form.setData) {
    form.setData('field_name', 'value');
  }

  return <div>Custom field content</div>;
}

Return Value

  • data: Form data object
  • setData: Function to update form data (controlled mode only)
  • errors: Validation errors object
  • processing: Boolean indicating if form is submitting
  • disabled: Boolean indicating if form is disabled
  • isControlled: Boolean indicating if form is in controlled mode

Complete Example

Here’s a complete example showing a user creation form:
import {
  CoreForm,
  CoreFormInput,
  CoreFormSelect,
  CoreFormCombobox,
  CoreFormMultiSelect,
  CoreFormSwitch,
  CoreFormSubmitButton,
} from '@/core/components/core-form';
import { useForm } from '@inertiajs/react';
import { Button } from '@/core/components/ui/button';

export default function UserCreate() {
  const form = useForm({
    first_name: '',
    last_name: '',
    email: '',
    role: 'user',
    locale: 'sv',
    skills: [],
    notifications_enabled: false,
  });

  const handleSubmit = () => {
    form.post(route('users.store'), {
      onSuccess: () => {
        // Handle success
      },
    });
  };

  return (
    <CoreForm
      form={form}
      onSubmit={handleSubmit}
      footer={
        <>
          <Button variant="secondary" onClick={() => window.history.back()}>
            {__('Cancel')}
          </Button>
          <CoreFormSubmitButton variant="brand">
            {__('Create User')}
          </CoreFormSubmitButton>
        </>
      }
    >
      <div className="grid lg:grid-cols-2 gap-6">
        <CoreFormInput
          name="first_name"
          label={__('First Name')}
          type="text"
          required
        />

        <CoreFormInput
          name="last_name"
          label={__('Last Name')}
          type="text"
          required
        />
      </div>

      <CoreFormInput
        name="email"
        label={__('Email')}
        type="email"
        placeholder={__('[email protected]')}
        required
        description={__('We'll send a verification email to this address')}
      />

      <CoreFormSelect
        name="role"
        label={__('Role')}
        options={[
          { value: 'admin', label: __('Administrator') },
          { value: 'user', label: __('User') },
        ]}
        required
      />

      <CoreFormCombobox
        name="locale"
        label={__('Language')}
        options={[
          { label: __('Swedish'), value: 'sv' },
          { label: __('English'), value: 'en' },
        ]}
      />

      <CoreFormMultiSelect
        name="skills"
        label={__('Skills')}
        options={[
          { value: 'react', label: 'React' },
          { value: 'vue', label: 'Vue' },
          { value: 'angular', label: 'Angular' },
          { value: 'svelte', label: 'Svelte' },
          { value: 'laravel', label: 'Laravel' },
          { value: 'django', label: 'Django' },
        ]}
        placeholder={__('Select skills...')}
        description={__('Select the skills this user has')}
      />

      <CoreFormSwitch
        name="notifications_enabled"
        label={__('Enable Email Notifications')}
        description={__('Receive notifications about account activity')}
      />
    </CoreForm>
  );
}

Best Practices

  1. Use Controlled Mode: Prefer controlled mode (useForm hook) for most forms as it provides better type safety and programmatic control.
  2. Consistent Footer Layout: Always place submit buttons in the footer prop rather than inline with form fields. This ensures consistent spacing and layout.
  3. Field Grouping: Use CSS Grid or Flexbox to group related fields horizontally on larger screens:
<div className="grid lg:grid-cols-2 gap-6">
  <CoreFormInput name="first_name" label={__('First Name')} />
  <CoreFormInput name="last_name" label={__('Last Name')} />
</div>
  1. Error Handling: Form fields automatically display validation errors from the server. Ensure your backend returns errors in the standard Laravel format:
// In your FormRequest or Controller
return back()->withErrors([
    'email' => __('The email address is already taken.'),
]);
  1. Loading States: The form automatically handles loading states. CoreFormSubmitButton shows a loading spinner and disables during submission.
  2. Nested Field Names: Use dot notation for nested form data:
const form = useForm({
  user: {
    name: '',
    email: '',
  },
});

<CoreFormInput name="user.name" label={__('Name')} />
<CoreFormInput name="user.email" label={__('Email')} />
  1. Conditional Fields: Use conditional rendering based on form state:
{
  form.data.role === 'admin' && (
    <CoreFormInput name="admin_notes" label={__('Admin Notes')} type="text" />
  );
}
  1. Form Validation: Always validate on the server side. Client-side validation can be added using the required prop and custom validation logic in the onSubmit handler.
  2. Accessibility: All form fields include proper labels, error messages, and ARIA attributes. Ensure label props are always provided for accessibility.
  3. Type Safety: When using TypeScript, define your form data type:
interface UserFormData {
  name: string;
  email: string;
  role: string;
}

const form = useForm<UserFormData>({
  name: '',
  email: '',
  role: 'user',
});
  1. Action Integration: When using actions with forms, leverage backend defaults and use getParams for dynamic data. Form data is automatically merged with action parameters - you only need to specify what’s different or additional.
  2. Action Validation: Validation errors from actions are automatically displayed on form fields. Don’t manually handle validation errors - they’re mapped to field names automatically.
  3. Action Confirmation: If an action requires confirmation (via confirm()), the form will show a confirmation dialog before submission automatically.
This form system provides a consistent, accessible, and type-safe way to build forms throughout your application while maintaining the flexibility of Inertia.js and seamless integration with Core Actions.