Custom fields
This walkthrough adds a ColorPicker field — a native <input type="color"> — to a Lattice form.
1. Publish the JS scaffold
Section titled “1. Publish the JS scaffold”If you have not done this yet, publish the registration file the generators expect:
php artisan vendor:publish --tag=lattice-jsThis writes a single resources/js/registry.ts if it does not already exist — the one place custom fields, components, and columns are registered.
2. Generate the field
Section titled “2. Generate the field”php artisan lattice:field ColorPickerThis creates:
app/Forms/Fields/ColorPicker.php— the PHP class.resources/js/fields/color-picker.tsx— the React renderer stub.- An entry under
componentsinresources/js/registry.tswiring them together. - Runs
lattice:typescriptto refresh the generated types file.
The PHP attribute receives the short identifier color-picker; the wire type is field.color-picker.
Pass --type= to override it.
3. The generated PHP class
Section titled “3. The generated PHP class”<?php
namespace App\Forms\Fields;
use Lattice\Lattice\Forms\Attributes\AsField;use Lattice\Lattice\Forms\Components\Field;
#[AsField(type: 'color-picker')]class ColorPicker extends Field{ //}Field provides the standard field API — make(), required(), disabled(), value(), and the rest. Add public properties for any extra data you want available in the renderer:
#[AsField(type: 'color-picker')]class ColorPicker extends Field{ public ?string $swatches = null;
public function swatches(string $swatches): static { $this->swatches = $swatches;
return $this; }}4. The generated React renderer
Section titled “4. The generated React renderer”import type { RendererComponent } from "@lattice-php/lattice";
export const ColorPickerComponent: RendererComponent<"field.color-picker"> = ({ node }) => { // Render the field.color-picker field. Field state is available on node.props. return <div data-lattice-field={String(node.props.name ?? "")} />;};Replace the stub body with real UI:
import type { RendererComponent } from "@lattice-php/lattice";
export const ColorPickerComponent: RendererComponent<"field.color-picker"> = ({ node }) => { return ( <input type="color" name={String(node.props.name ?? "")} defaultValue={String(node.props.value ?? "#000000")} /> );};node.props contains all the serialized field data — name, value, label, required, and any extra properties you added to the PHP class.
5. The registry entry
Section titled “5. The registry entry”The generator appended an entry to resources/js/registry.ts, wrapping the renderer in eagerComponent:
import { createPlugin, eagerComponent, extendRegistry, registry as packageRegistry,} from "@lattice-php/lattice";import { ColorPickerComponent } from "./fields/color-picker";
export const registry = extendRegistry( packageRegistry, createPlugin({ name: "app", components: { "field.color-picker": eagerComponent(ColorPickerComponent), }, columns: {}, }),);createPlugin accepts any number of component type keys. Use lazyComponent instead for a code-split renderer — its loader must resolve to a module with a default export:
"field.color-picker": lazyComponent(async () => ({ default: (await import("./fields/color-picker")).ColorPickerComponent,})),6. Wire the registry in app.tsx
Section titled “6. Wire the registry in app.tsx”registry.ts already merges your plugin onto the built-in registry, so import its exported registry and pass it to Provider:
import "../css/app.css";import { createInertiaApp } from "@inertiajs/react";import { createRoot } from "react-dom/client";import LatticePage from "@lattice-php/lattice/page";import { Provider } from "@lattice-php/lattice";import { registry } from "./registry";
createInertiaApp({ resolve: (name) => { if (name === "lattice/page") return { default: LatticePage }; const pages = import.meta.glob("./Pages/**/*.tsx", { eager: true }); return pages[`./Pages/${name}.tsx`]; }, setup({ el, App, props }) { if (!el) return; createRoot(el).render( <Provider registry={registry}> <App {...props} /> </Provider>, ); },});7. Generate TypeScript types
Section titled “7. Generate TypeScript types”php artisan lattice:typescriptThis writes resources/js/lattice/generated.d.ts, which augments the ComponentProps interface in @lattice-php/lattice:
declare module "@lattice-php/lattice" { interface ComponentProps { "field.color-picker": { swatches: string | null; }; }}After running this command, node.props.swatches is typed in your renderer.
8. Use the field in a form
Section titled “8. Use the field in a form”use App\Forms\Fields\ColorPicker;use Illuminate\Http\Request;use Lattice\Lattice\Attributes\AsForm;use Lattice\Lattice\Forms\Components\Form as FormComponent;use Lattice\Lattice\Forms\FormDefinition;use Symfony\Component\HttpFoundation\Response;
#[AsForm('app.brand-settings')]final class BrandSettingsForm extends FormDefinition{ public function definition(FormComponent $form, Request $request): FormComponent { return $form->schema([ ColorPicker::make('brand_color', 'Brand color') ->swatches('#ff0000,#00ff00,#0000ff') ->value('#6366f1'), ]); }
public function handle(Request $request): Response { $validated = $this->validate($request);
// persist $validated['brand_color'] …
return redirect()->back(); }}The field serializes to a node with type: "field.color-picker" and the renderer picks it up automatically.