Getting Started

Forms

Build Laravel admin forms with Flashboard's schema-tree API and render them through a Nuxt UI-powered admin shell.

Flashboard form payloads are generated from form() definitions on a resource. The preferred authoring path is schema-first: start with $form->schema([...]) for simple create and edit screens, then introduce grouped layout only when operators genuinely need it.

Simple forms first

<?php

use Pepperfm\Flashboard\Contracts\Forms\FieldRenderer;
use Pepperfm\Flashboard\Contracts\Forms\FormContract;
use Pepperfm\Flashboard\Core\Forms\Fields\Select;
use Pepperfm\Flashboard\Core\Forms\Fields\TextInput;

public static function form(FormContract $form): FormContract
{
    return $form
        ->schema([
            Select::make('status')
                ->label('Status')
                ->required(),
            TextInput::make('notes')
                ->label('Notes')
                ->renderer(FieldRenderer::Textarea),
        ])
        ->rules([
            'status' => ['required', 'string'],
        ]);
}

This path renders as a single centered UPageCard shell in the package UI without an artificial wrapper section.

Field layout

Schema-first forms can place multiple fields on one row without introducing nested layout nodes.

<?php

use Pepperfm\Flashboard\Core\Forms\Fields\TextInput;
use Pepperfm\Flashboard\Contracts\Forms\FormContract;

public static function form(FormContract $form): FormContract
{
    return $form
        ->columns(2)
        ->gap(4)
        ->schema([
            TextInput::make('first_name')
                ->label('First name'),
            TextInput::make('last_name')
                ->label('Last name'),
            TextInput::make('email')
                ->label('Email')
                ->email()
                ->columnSpan(2),
        ]);
}

Available layout helpers:

  • columns(int|array) for grid containers
  • gap(int|array) for grid or flex spacing
  • columnSpan(int|array) for grid items
  • fullWidth() as a shorthand for spanning the full grid width

Scalar columns(2) and columns(3) remain mobile-safe by default: Flashboard normalizes them to one column on small screens and activates multiple columns from md upward.

Grouped layouts

Use Section, Tabs, and Tab nodes inside schema() when the form has meaningful visual grouping.

<?php

use Pepperfm\Flashboard\Core\Forms\Fields\Select;
use Pepperfm\Flashboard\Core\Forms\Fields\TextInput;
use Pepperfm\Flashboard\Core\Forms\Fields\Toggle;
use Pepperfm\Flashboard\Contracts\Forms\FormContract;
use Pepperfm\Flashboard\Core\Forms\Layout\Section;
use Pepperfm\Flashboard\Core\Forms\Layout\Tab;
use Pepperfm\Flashboard\Core\Forms\Layout\Tabs;

public static function form(FormContract $form): FormContract
{
    return $form
        ->schema([
            Section::make('content')
                ->label('Content')
                ->schema([
                    TextInput::make('name')
                        ->label('Name')
                        ->required(),
                    TextInput::make('slug')
                        ->label('Slug'),
                ]),
            Tabs::make('settings')
                ->tabs([
                    Tab::make('general')
                        ->label('General')
                        ->schema([
                            Select::make('status')
                                ->label('Status'),
                            Toggle::make('is_active')
                                ->label('Is active'),
                        ]),
                ]),
        ]);
}

Sections and tabs support the same layout API as the root form schema.

Renderer contract

Normalized form payloads expose an explicit renderer hint for every field.

  • typed fields set a stable renderer automatically, such as TextInput -> input
  • override renderer intent explicitly when the visual control differs from the base field type
  • legacy arrays can opt into the same contract with ['key' => 'notes', 'renderer' => 'textarea']
  • the frontend maps these hints through package-owned wrappers such as FBInput, FBTextarea, FBSelect, and FBSwitch

Layout contract

Normalized form payloads also expose package-owned layout metadata:

  • container layout lives on form.layout, section.layout, and tab.layout
  • field sizing lives on field.layout.column_span
  • grid and flex settings are validated during normalization and fail fast on invalid combinations
  • legacy arrays can opt into the same behavior with layout, columns, gap, direction, justify, align, wrap, and column_span

Invalid combinations such as columns() plus direction() on the same container raise an exception instead of being silently ignored.

Runtime flow

  1. Flashboard resolves the create or edit route.
  2. ResourceFormDataSource hydrates field state.
  3. ResourceFormPersister validates and saves data.
  4. afterSave() hooks and runtime hooks run.
  5. The user is redirected to the detail screen.

Validation and hooks

  • create rules: creationRules()
  • update rules: updateRules($record)
  • shared rules: formRules()
  • builder-level mutation: mutateDataUsing()
  • resource-level mutation: mutateFormDataBeforeSave()
  • post-persist hook: afterSave()