Skip to content

Form Components

The Admin package provides a comprehensive set of form components with consistent styling, validation, and authorization support.

Overview

All form components:

  • Follow consistent design patterns
  • Support Laravel validation
  • Include accessibility attributes (ARIA)
  • Work with Livewire
  • Support authorization props

Form Group

Wrapper component for labels, inputs, and validation errors:

blade
<x-admin::form-group
    label="Post Title"
    name="title"
    required
    help="Enter a descriptive title for your post"
>
    <x-admin::input
        name="title"
        :value="old('title', $post->title)"
        placeholder="My Amazing Post"
    />
</x-admin::form-group>

Props:

  • label (string) - Field label
  • name (string) - Field name for validation errors
  • required (bool) - Show required indicator
  • help (string) - Help text below field
  • error (string) - Manual error message

Input

Text input with various types:

blade
{{-- Text input --}}
<x-admin::input
    name="title"
    label="Title"
    type="text"
    placeholder="Enter title"
    required
/>

{{-- Email input --}}
<x-admin::input
    name="email"
    label="Email"
    type="email"
    placeholder="user@example.com"
/>

{{-- Password input --}}
<x-admin::input
    name="password"
    label="Password"
    type="password"
/>

{{-- Number input --}}
<x-admin::input
    name="quantity"
    label="Quantity"
    type="number"
    min="1"
    max="100"
/>

{{-- Date input --}}
<x-admin::input
    name="published_at"
    label="Publish Date"
    type="date"
/>

Props:

  • name (string, required) - Input name
  • label (string) - Label text
  • type (string) - Input type (text, email, password, number, date, etc.)
  • value (string) - Input value
  • placeholder (string) - Placeholder text
  • required (bool) - Required field
  • disabled (bool) - Disabled state
  • readonly (bool) - Read-only state
  • min / max (number) - Min/max for number inputs

Textarea

Multi-line text input:

blade
<x-admin::textarea
    name="content"
    label="Post Content"
    rows="10"
    placeholder="Write your content here..."
    required
/>

{{-- With character counter --}}
<x-admin::textarea
    name="description"
    label="Description"
    maxlength="500"
    rows="5"
    show-counter
/>

Props:

  • name (string, required) - Textarea name
  • label (string) - Label text
  • rows (number) - Number of rows (default: 5)
  • cols (number) - Number of columns
  • placeholder (string) - Placeholder text
  • maxlength (number) - Maximum character length
  • show-counter (bool) - Show character counter
  • required (bool) - Required field

Select

Dropdown select:

blade
{{-- Simple select --}}
<x-admin::select
    name="status"
    label="Status"
    :options="[
        'draft' => 'Draft',
        'published' => 'Published',
        'archived' => 'Archived',
    ]"
    :value="$post->status"
/>

{{-- With placeholder --}}
<x-admin::select
    name="category_id"
    label="Category"
    :options="$categories"
    placeholder="Select a category..."
/>

{{-- Multiple select --}}
<x-admin::select
    name="tags[]"
    label="Tags"
    :options="$tags"
    multiple
/>

{{-- Grouped options --}}
<x-admin::select
    name="location"
    label="Location"
    :options="[
        'UK' => [
            'london' => 'London',
            'manchester' => 'Manchester',
        ],
        'US' => [
            'ny' => 'New York',
            'la' => 'Los Angeles',
        ],
    ]"
/>

Props:

  • name (string, required) - Select name
  • label (string) - Label text
  • options (array, required) - Options array
  • value (mixed) - Selected value(s)
  • placeholder (string) - Placeholder option
  • multiple (bool) - Allow multiple selections
  • required (bool) - Required field
  • disabled (bool) - Disabled state

Checkbox

Single checkbox:

blade
<x-admin::checkbox
    name="published"
    label="Publish immediately"
    :checked="$post->published"
/>

{{-- With description --}}
<x-admin::checkbox
    name="featured"
    label="Featured Post"
    description="Display this post prominently on the homepage"
    :checked="$post->featured"
/>

{{-- Group of checkboxes --}}
<fieldset>
    <legend>Permissions</legend>

    <x-admin::checkbox
        name="permissions[]"
        label="Create Posts"
        value="posts.create"
        :checked="in_array('posts.create', $user->permissions)"
    />

    <x-admin::checkbox
        name="permissions[]"
        label="Edit Posts"
        value="posts.edit"
        :checked="in_array('posts.edit', $user->permissions)"
    />
</fieldset>

Props:

  • name (string, required) - Checkbox name
  • label (string) - Label text
  • value (string) - Checkbox value
  • checked (bool) - Checked state
  • description (string) - Help text below checkbox
  • disabled (bool) - Disabled state

Toggle

Switch-style toggle:

blade
<x-admin::toggle
    name="active"
    label="Active"
    :checked="$user->active"
/>

{{-- With colors --}}
<x-admin::toggle
    name="notifications_enabled"
    label="Email Notifications"
    description="Receive email updates about new posts"
    :checked="$user->notifications_enabled"
    color="green"
/>

Props:

  • name (string, required) - Toggle name
  • label (string) - Label text
  • checked (bool) - Checked state
  • description (string) - Help text
  • color (string) - Toggle color (green, blue, red)
  • disabled (bool) - Disabled state

Button

Action buttons with variants:

blade
{{-- Primary button --}}
<x-admin::button type="submit">
    Save Changes
</x-admin::button>

{{-- Secondary button --}}
<x-admin::button variant="secondary" href="{{ route('admin.posts.index') }}">
    Cancel
</x-admin::button>

{{-- Danger button --}}
<x-admin::button
    variant="danger"
    wire:click="delete"
    wire:confirm="Are you sure?"
>
    Delete Post
</x-admin::button>

{{-- Ghost button --}}
<x-admin::button variant="ghost">
    Reset
</x-admin::button>

{{-- Icon button --}}
<x-admin::button variant="icon" title="Edit">
    <x-icon name="pencil" />
</x-admin::button>

{{-- Loading state --}}
<x-admin::button :loading="$isLoading">
    <span wire:loading.remove>Save</span>
    <span wire:loading>Saving...</span>
</x-admin::button>

Props:

  • type (string) - Button type (button, submit, reset)
  • variant (string) - Style variant (primary, secondary, danger, ghost, icon)
  • href (string) - Link URL (renders as <a>)
  • loading (bool) - Show loading state
  • disabled (bool) - Disabled state
  • size (string) - Size (sm, md, lg)

Authorization Props

All form components support authorization attributes:

blade
<x-admin::button
    can="posts.create"
    :can-arguments="[$post]"
>
    Create Post
</x-admin::button>

<x-admin::input
    name="title"
    label="Title"
    readonly-unless="posts.edit"
/>

<x-admin::button
    variant="danger"
    hidden-unless="posts.delete"
    wire:click="delete"
>
    Delete
</x-admin::button>

Authorization Props:

  • can (string) - Gate/policy check
  • can-arguments (array) - Arguments for gate check
  • cannot (string) - Inverse of can
  • hidden-unless (string) - Hide element unless authorized
  • readonly-unless (string) - Make readonly unless authorized
  • disabled-unless (string) - Disable unless authorized

Learn more about Authorization →

Livewire Integration

All components work seamlessly with Livewire:

blade
<form wire:submit="save">
    <x-admin::input
        name="title"
        label="Title"
        wire:model="title"
    />

    <x-admin::textarea
        name="content"
        label="Content"
        wire:model.defer="content"
    />

    <x-admin::select
        name="status"
        label="Status"
        :options="['draft' => 'Draft', 'published' => 'Published']"
        wire:model="status"
    />

    <x-admin::button type="submit" :loading="$isSaving">
        Save Post
    </x-admin::button>
</form>

Real-Time Validation

blade
<x-admin::input
    name="slug"
    label="Slug"
    wire:model.live="slug"
    wire:loading.class="opacity-50"
/>

@error('slug')
    <p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror

Debounced Input

blade
<x-admin::input
    name="search"
    label="Search Posts"
    wire:model.live.debounce.500ms="search"
    placeholder="Type to search..."
/>

Validation

Components automatically show validation errors:

blade
{{-- Controller validation --}}
$request->validate([
    'title' => 'required|max:255',
    'content' => 'required',
    'status' => 'required|in:draft,published',
]);

{{-- Blade template --}}
<x-admin::form-group label="Title" name="title" required>
    <x-admin::input name="title" :value="old('title')" />
</x-admin::form-group>
{{-- Validation errors automatically displayed --}}

Custom Error Messages

blade
<x-admin::form-group
    label="Email"
    name="email"
    :error="$errors->first('email')"
>
    <x-admin::input name="email" type="email" />
</x-admin::form-group>

Complete Form Example

blade
<form method="POST" action="{{ route('admin.posts.store') }}">
    @csrf

    <div class="space-y-6">
        {{-- Title --}}
        <x-admin::form-group label="Title" name="title" required>
            <x-admin::input
                name="title"
                :value="old('title', $post->title)"
                placeholder="Enter post title"
                maxlength="255"
            />
        </x-admin::form-group>

        {{-- Slug --}}
        <x-admin::form-group label="Slug" name="slug" required>
            <x-admin::input
                name="slug"
                :value="old('slug', $post->slug)"
                placeholder="post-slug"
            />
        </x-admin::form-group>

        {{-- Content --}}
        <x-admin::form-group label="Content" name="content" required>
            <x-admin::textarea
                name="content"
                :value="old('content', $post->content)"
                rows="15"
                placeholder="Write your post content..."
            />
        </x-admin::form-group>

        {{-- Status --}}
        <x-admin::form-group label="Status" name="status" required>
            <x-admin::select
                name="status"
                :options="[
                    'draft' => 'Draft',
                    'published' => 'Published',
                    'archived' => 'Archived',
                ]"
                :value="old('status', $post->status)"
            />
        </x-admin::form-group>

        {{-- Category --}}
        <x-admin::form-group label="Category" name="category_id">
            <x-admin::select
                name="category_id"
                :options="$categories"
                :value="old('category_id', $post->category_id)"
                placeholder="Select a category..."
            />
        </x-admin::form-group>

        {{-- Options --}}
        <div class="space-y-3">
            <x-admin::checkbox
                name="featured"
                label="Featured Post"
                :checked="old('featured', $post->featured)"
            />

            <x-admin::toggle
                name="comments_enabled"
                label="Enable Comments"
                :checked="old('comments_enabled', $post->comments_enabled)"
            />
        </div>

        {{-- Actions --}}
        <div class="flex gap-3">
            <x-admin::button type="submit">
                Save Post
            </x-admin::button>

            <x-admin::button
                variant="secondary"
                href="{{ route('admin.posts.index') }}"
            >
                Cancel
            </x-admin::button>

            <x-admin::button
                variant="danger"
                hidden-unless="posts.delete"
                wire:click="delete"
                wire:confirm="Delete this post permanently?"
            >
                Delete
            </x-admin::button>
        </div>
    </div>
</form>

Styling

Components use Tailwind CSS and can be customized:

blade
<x-admin::input
    name="title"
    label="Title"
    class="font-mono"
    input-class="bg-gray-50"
/>

Custom Wrapper Classes

blade
<x-admin::form-group
    label="Title"
    name="title"
    wrapper-class="max-w-xl"
>
    <x-admin::input name="title" />
</x-admin::form-group>

Best Practices

1. Always Use Form Groups

blade
{{-- ✅ Good - wrapped in form-group --}}
<x-admin::form-group label="Title" name="title" required>
    <x-admin::input name="title" />
</x-admin::form-group>

{{-- ❌ Bad - no form-group --}}
<x-admin::input name="title" label="Title" />

2. Use Old Values

blade
{{-- ✅ Good - preserves input on validation errors --}}
<x-admin::input
    name="title"
    :value="old('title', $post->title)"
/>

{{-- ❌ Bad - loses input on validation errors --}}
<x-admin::input
    name="title"
    :value="$post->title"
/>

3. Provide Helpful Placeholders

blade
{{-- ✅ Good - clear placeholder --}}
<x-admin::input
    name="slug"
    placeholder="post-slug-example"
/>

{{-- ❌ Bad - vague placeholder --}}
<x-admin::input
    name="slug"
    placeholder="Enter slug"
/>

4. Use Authorization Props

blade
{{-- ✅ Good - respects permissions --}}
<x-admin::button
    variant="danger"
    hidden-unless="posts.delete"
>
    Delete
</x-admin::button>

Learn More

Released under the EUPL-1.2 License.