Form Context
The Tagixo builder supports a form context that switches the entire builder shell into form-authoring mode. This document covers every aspect of using the form context inside a Filament (or Primix) admin panel.
1. Setting getContext() to 'form'
Override getContext() on your builder page class to activate form mode:
protected function getContext(): string
{
return 'form';
}
This single change affects every dimension of the builder UI. The table below shows what differs between the two contexts:
| Dimension | page context |
form context |
|---|---|---|
| Module palette | All registered modules | Form-specific buckets only |
| Canvas drop target | Full layout canvas | Form canvas (no layout nesting) |
| Design drawer panels | All panels | Subset (see §4) |
| Preview mode | Full page render | Form embed or app-target render |
| Toolbar actions | Page-level save / publish | Form schema save |
| Right-click context menu | Section / Row / Column | Field / Group / Fieldset |
| Keyboard shortcuts | Standard | Standard (same) |
| Slug / SEO tab | Visible | Hidden |
| Global variables tab | Visible | Hidden |
2. Form Module Palette
The palette is split into two buckets.
Universal modules
These always render via the shared form-embed-node partial and work in every environment (plain HTML, Livewire, Inertia, etc.).
| Module | field_type |
Notes |
|---|---|---|
| Text input | text |
Single-line <input type="text"> |
| Textarea | textarea |
Multi-line <textarea> |
| Select | select |
<select> with options |
| Checkbox | checkbox |
Single boolean checkbox |
| Radio group | radio |
<input type="radio"> group |
| Date | date |
<input type="date"> |
| File upload | file |
<input type="file"> |
| Submit button | submit |
<button type="submit"> |
| Grid | grid |
Layout column wrapper |
| Fieldset | fieldset |
Semantic <fieldset> + <legend> |
App-target modules
These require a registered AppFormPreviewer to render in the canvas. Without one, they degrade gracefully to a placeholder in universal preview.
| Module | field_type |
Notes |
|---|---|---|
| Tabs | tabs |
Filament Tabs component |
| Wizard | wizard |
Filament Wizard component |
| Group | group |
Filament Group component |
3. App-target modules and Tagixo::enableAppForms()
When the Tagixo plugin boots, it calls Tagixo::enableAppForms() automatically as part of plugin registration. This call:
- registers the
AppFormPreviewercontract binding - enables the Tabs, Wizard, and Group modules in the palette
- wires up the
/tagixo/form-preview/{key}internal route used by the canvas iframe
If you are building a form builder for a non-Filament frontend (e.g. a plain Blade form, an API-consumed schema, or a headless SPA), you can disable app-target modules:
// In your AppServiceProvider or plugin registration
Tagixo::disableAppForms();
This removes the Tabs / Wizard / Group palette entries and skips the previewer route registration. Universal modules still work normally.
4. excludedCanvasPropTypes()
Override this method to suppress design panels that are irrelevant inside a form canvas. The method returns an array of PropType class names to hide from the drawer.
protected function excludedCanvasPropTypes(): array
{
return [
\Ccast\Tagixo\PropTypes\AnimationPropType::class,
\Ccast\Tagixo\PropTypes\BoxShadowPropType::class,
\Ccast\Tagixo\PropTypes\FilterPropType::class,
\Ccast\Tagixo\PropTypes\TransformPropType::class,
];
}
Recommended exclusions for form context:
| PropType | Reason to exclude |
|---|---|
AnimationPropType |
Scroll/entrance animations are meaningless on form fields |
BoxShadowPropType |
Box shadow on inputs conflicts with browser focus rings |
FilterPropType |
CSS filters (blur, grayscale) degrade form usability |
TransformPropType |
CSS transforms break stacking context for dropdowns / date pickers |
You may keep SpacingPropType, BorderPropType, TypographyPropType, and BackgroundPropType — they all apply meaningfully to form elements.
5. Two Preview Modes
Universal HTML preview
Always available, no extra configuration required. The canvas renders every field using the form-embed-node partial, which emits plain semantic HTML. App-target modules (Tabs, Wizard, Group) degrade to a labelled placeholder block showing the module name and field count.
Use this mode when:
- you are building a form that will be rendered server-side as HTML
- you have not registered a Filament app previewer
- you want a fast, dependency-free preview
App-target preview
Requires a registered previewer (see §6). When active, the canvas iframe loads your previewer route, which renders the form schema as real Filament components inside a Livewire page. The preview updates on every save.
Use this mode when:
- the form will be embedded into a Filament panel as a live component
- you need accurate rendering of Tabs / Wizard / Group layout
- your QA process involves stakeholders who need to interact with the real form
6. Registering the Filament App Form Previewer
Route
Add a named route inside your admin panel route group (or wherever your panel routes live):
Route::get('/tagixo/form-preview/{key}', [FormPreviewController::class, 'show'])
->name('tagixo.form-preview')
->middleware(['web', 'auth']);
FormPreviewController
<?php
namespace App\Http\Controllers\Admin;
use Ccast\Tagixo\Models\FormSchema;
use Illuminate\Http\Request;
class FormPreviewController extends Controller
{
public function show(Request $request, string $key)
{
abort_unless($request->user()?->can('viewAny', FormSchema::class), 403);
$schema = FormSchema::where('key', $key)->firstOrFail();
return view('admin.form-preview', ['schema' => $schema]);
}
}
Security note: always gate on a real authorization check. Never render a schema by key without verifying the authenticated user has read access to form schemas. The key parameter comes from the builder iframe URL and must be treated as untrusted input.
Blade view (resources/views/admin/form-preview.blade.php)
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@livewireStyles
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-white dark:bg-gray-950">
<livewire:form-preview-wrapper :schema="$schema" />
@livewireScripts
</body>
</html>
FormPreviewWrapper Livewire component
<?php
namespace App\Livewire;
use Ccast\Tagixo\Models\FormSchema;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Form;
use Livewire\Component;
class FormPreviewWrapper extends Component implements HasForms
{
use InteractsWithForms;
public FormSchema $schema;
public ?array $data = [];
public function mount(FormSchema $schema): void
{
$this->schema = $schema;
$this->form->fill();
}
public function form(Form $form): Form
{
return $form
->schema($this->schema->toFilamentSchema())
->statePath('data');
}
public function render()
{
return view('livewire.form-preview-wrapper');
}
}
{{-- resources/views/livewire/form-preview-wrapper.blade.php --}}
<div class="p-6">
{{ $this->form }}
</div>
Auth callout: the FormPreviewWrapper component itself does not re-check authorization — it trusts the controller gate. Never register this component on a public Livewire route.
7. Complete Example
A production-ready EditFormSchemaBuilder page with all methods implemented.
Builder page class
<?php
namespace App\Primix\Resources\FormSchemas\Pages;
use Ccast\TagixoPrimix\Resources\FormSchemas\Pages\EditFormSchemaBuilder as BaseEditFormSchemaBuilder;
use Ccast\Tagixo\PropTypes\AnimationPropType;
use Ccast\Tagixo\PropTypes\BoxShadowPropType;
use Ccast\Tagixo\PropTypes\FilterPropType;
use Ccast\Tagixo\PropTypes\TransformPropType;
class EditFormSchemaBuilder extends BaseEditFormSchemaBuilder
{
protected function getContext(): string
{
return 'form';
}
protected function excludedCanvasPropTypes(): array
{
return [
AnimationPropType::class,
BoxShadowPropType::class,
FilterPropType::class,
TransformPropType::class,
];
}
protected function getPreviewUrl(): ?string
{
return route('tagixo.form-preview', ['key' => $this->record->key]);
}
}
Resource page registration
Register your override in the FormSchemaResource (or wherever the base resource registers its pages):
public static function getPages(): array
{
return [
'index' => Pages\ListFormSchemas::route('/'),
'create' => Pages\CreateFormSchema::route('/create'),
'edit' => Pages\EditFormSchema::route('/{record}/edit'),
'builder' => EditFormSchemaBuilder::route('/{record}/builder'),
];
}
Table action linking to the builder
Add this action to your FormSchemaResource table:
use Filament\Tables\Actions\Action;
Action::make('open_builder')
->label('Open Builder')
->icon('heroicon-o-pencil-square')
->url(fn (FormSchema $record): string =>
static::getUrl('builder', ['record' => $record])
),