Core
Grid
A multi-column grid for arranging components, per breakpoint — the same layout in a form and in an infolist.
On this page
The plainest way to put two things side by side. Grid holds no value of its
own: it takes a column count, lays its children out across those columns, and
gets out of the way. Reach for it when a form is a wall of full-width inputs and
first name / last name obviously belong on one line.
use NyonCode\WireCore\Foundation\Schema\Grid;
How It Works
A grid is a layout component: it carries no state and no value, so adding,
removing or reordering one never touches your data. Only fields map to state.
That is why the same Grid renders in a form, an infolist and an action modal —
the class owns the configuration, and each package's Blade view owns the chrome.
The column count resolves in one of two shapes, both through
ResponsiveGrid::cols():
- An int gives a mobile-first reflow:
columns(3)is one column on phones and three from themdbreakpoint up. It is never three columns on a phone, which is the answer most people want and nobody writes. - A breakpoint map says it exactly:
['default' => 1, 'md' => 2, 'xl' => 3]. Keys aredefault(or'', or0) and thensm,md,lg,xl,2xl. Counts are clamped to 1–12, and an unknown breakpoint key is ignored silently —'medium' => 2produces nothing at all.
The default is 2 columns. The gap between children is fixed at Tailwind's
gap-4; a grid that needs a different rhythm is a Flex, which takes
->gap().
Children are rendered in declaration order, and a child whose visible() /
hidden() condition is false is left out of the flow entirely rather than
rendered empty — the grid closes up around it.
The trap: columnSpan() only understands 2, 3, 4 and full. The span map is
a closed match, so ->columnSpan(5) falls through to the default and the field
silently takes one column. If a child must be wider than four, give the grid more
columns rather than the child a bigger span.
The other trap is Tailwind, not this class. The grid class is built at
runtime by string concatenation, so Tailwind's scanner cannot see it in your
code. The package writes every possible grid-cols-* utility out as literal text
for the scanner to find — which only works if the package views are in your
content paths. A grid that renders with the right markup and no columns at all
is nearly always that. See
Getting Started for the paths.
Basic Usage
Grid::make() ->columns(2) ->schema([ TextInput::make('first_name'), TextInput::make('last_name'), ])
Grid::make() takes no name, because a layout that holds no state has nothing to
be addressed by. Pass one only if you want it for your own reference.
Making One Child Wider
A child spans more than its share with columnSpan(), and the whole row with
columnSpanFull():
Grid::make() ->columns(2) ->schema([ TextInput::make('first_name'), TextInput::make('last_name'), Textarea::make('bio')->columnSpanFull(), ])
columnSpanFull() is the one to reach for by default. It means "however many
columns there are, take all of them", so it keeps working when you change the
grid from two columns to three.
Responsive Columns
When the int reflow is not what you want, say every breakpoint yourself:
Grid::make() ->columns([ 'default' => 1, // phones — one column 'md' => 2, // tablets 'xl' => 3, // wide desktops ]) ->schema([ TextInput::make('street'), TextInput::make('city'), TextInput::make('zip'), ])
Only the breakpoints you name are emitted, and each one holds until the next.
Nesting
Grids nest, because a grid is just another component in a schema. This is how a two-column form gets a row that is itself split three ways:
Grid::make() ->columns(2) ->schema([ TextInput::make('name'), TextInput::make('email'), Grid::make()->columns(3)->columnSpanFull()->schema([ TextInput::make('street'), TextInput::make('city'), TextInput::make('zip'), ]), ])
The inner grid takes columnSpanFull() so it occupies the whole row of the outer
one — without it, three fields would be squeezed into one of two columns.
Extended Example
A registration form in a real Livewire host. The grid is the only layout here; everything else is an ordinary field:
use Livewire\Component;use NyonCode\WireCore\Foundation\Schema\Grid;use NyonCode\WireForms\Components\TextInput;use NyonCode\WireForms\Components\Textarea;use NyonCode\WireForms\Forms\Form;use NyonCode\WireForms\Forms\WithForms; class CreateUser extends Component{ use WithForms; public ?array $data = []; public function form(Form $form): Form { return $form ->statePath('data') ->model(User::class) ->schema([ Grid::make() ->columns(['default' => 1, 'md' => 2]) ->schema([ TextInput::make('first_name')->required(), TextInput::make('last_name')->required(), TextInput::make('email')->email()->required()->columnSpanFull(), Textarea::make('bio')->columnSpanFull(), ]), ]) ->successMessage('User created'); } public function save(): void { $this->form->save(); }}
<form wire:submit="save"> {{ $this->form }} <button type="submit">Create</button></form>
One column on a phone, two from md, with e-mail and bio taking the full row on
both.
Grid API
->columns(int|array $columns) // 2 (default), or ['default' => 1, 'md' => 2, 'xl' => 3] // breakpoints: default|sm|md|lg|xl|2xl, counts clamped 1–12->getColumns(): int|array
Everything else a grid understands — label(), visible(), hidden(),
columnSpan(), schema() — is the shared layout surface every schema component
carries. See Common Layout API.