Skip to content
Components, switch product

Grid

The gui-grid classes lay out fields with CSS only, no element and no JavaScript. In a row, they line up the labels, the controls and the errors of fields side by side, whatever the length of each label.

Code of the example above
LayoutRow.tsx
import { GuiCheckbox, GuiTextinput } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function LayoutRow() {
return (
<div className="gui-grid gui-grid--row">
<div className="gui-grid__cell gui-grid__cell--span-2">
<GuiTextinput label="Street" hint="Number and street name" />
</div>
<div className="gui-grid__cell">
<GuiTextinput label="Postcode" />
</div>
<div className="gui-grid__cell">
<GuiCheckbox label="Default address" />
</div>
</div>
);
}

.gui-grid lays out its .gui-grid__cell children:

ClassLayout
gui-gridA stack, one cell below the other.
gui-grid--rowOne line, the cells sharing its width by span.
gui-grid--columns-1 … gui-grid--columns-12That many equal columns, which the cells fill in order and span.
gui-grid--autoAs many columns as fit, each at least --gui-layout-column-min wide (12rem).
gui-grid__cell--span-2 … gui-grid__cell--span-12A cell twice, or up to twelve times, as wide, in a row or numbered columns.
gui-grid--gap-none, -xs, -sm, -lg, -xlThe space between cells. It is md by default.
gui-grid--justify-start, -center, -end, -space-betweenIn a row: the cells keep their own width and the row places them.

The gaps are the --gui-layout-gap-* tokens, so a theme can change each step. A grid can sit in a cell of another grid.

Code of the example above
LayoutColumns.tsx
import { GuiTextinput } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function LayoutColumns() {
return (
<div className="gui-grid gui-grid--columns-3 gui-grid--gap-sm">
<div className="gui-grid__cell">
<GuiTextinput label="First name" />
</div>
<div className="gui-grid__cell">
<GuiTextinput label="Last name" />
</div>
<div className="gui-grid__cell">
<GuiTextinput label="Company" />
</div>
<div className="gui-grid__cell gui-grid__cell--span-2">
<GuiTextinput label="Email" />
</div>
<div className="gui-grid__cell">
<GuiTextinput label="Phone" />
</div>
</div>
);
}

In a row or in columns, each cell spans three row tracks, shared with the other cells of its row, and a field puts its parts in them:

  1. its .gui-label, with the hint;
  2. its .gui-widget, the control, centered in the track. A checkbox, a toggle and a gui-button sit here too;
  3. its .gui-validator, the errors. A checkbox and a toggle put their hint here as well.

So the controls of sibling fields stay level whatever the length of their labels, and an error never moves the controls next to it.

Anything else in a cell, such as a nested grid, an alert or text, spans the three tracks. A field of your own lines up the same way when its root has the gui-field class and its parts use these three classes.

Most fields are one control tall, so any of them can sit next to any other in a row or in columns: text, number, currency and password inputs, select, tags, file upload, the date, time and range fields and their pickers, a checkbox, a toggle and a button. The pickers open their calendar or time list in a popup, so they keep that height too.

The grid lines up their parts through subgrid: every cell of a row shares the label, control and error tracks. A long label or an error in one cell grows its track for the whole row, so the controls stay level and the errors start at the same line.

Code of the example above
LayoutInline.tsx
import {
GuiDatePicker,
GuiFileUpload,
GuiNumber,
GuiSelect,
GuiTags,
GuiTimePicker,
} from '@golemui/gui-components/react';
import type { UploadService } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
// A stand-in for your upload endpoint: it resolves after a second.
const uploadService: UploadService = {
upload: (file) =>
new Promise((resolve) =>
setTimeout(
() => resolve({ url: `/uploads/${encodeURIComponent(file.name)}` }),
1000,
),
),
};
const dependencies = { uploadService };
export function LayoutInline() {
return (
<div className="gui-grid gui-grid--columns-3">
<div className="gui-grid__cell">
<GuiDatePicker
label="Departure"
errors={['Pick a date after today.']}
/>
</div>
<div className="gui-grid__cell">
<GuiTimePicker label="Time" minuteStep={30} />
</div>
<div className="gui-grid__cell">
<GuiTags label="Travelers" hint="Press Enter after each name" />
</div>
<div className="gui-grid__cell">
<GuiSelect label="Class" options={['Economy', 'Business', 'First']} />
</div>
<div className="gui-grid__cell">
<GuiFileUpload
id="passport"
label="Passport scan"
dependencies={dependencies}
/>
</div>
<div className="gui-grid__cell">
<GuiNumber
label="Bags"
minimum={0}
maximum={3}
errors={['Up to 3 bags per booking.']}
/>
</div>
</div>
);
}

Some fields need height of their own: the textarea, the Markdown editor, the lists, the calendars and a radio group with its options in a column. They line up the same way, but the row’s control track takes the height of the tallest control, which leaves space under the one-line controls next to it. Give them a row of their own, or a cell that spans the row.

Some elements change layout when there is little room. Below 480px wide:

  • every grid stacks its cells, one below the other;
  • a radio group with its options in a row (direction="row") stacks them;
  • the calendars shrink to the width they have;
  • the tabs take less room.

They measure the nearest ancestor with the gui-container class, not the window, so a form in a narrow sidebar adapts on a wide screen too. Put the class on the element that sets their width:

<aside class="gui-container">
<div class="gui-grid gui-grid--row">…</div>
</aside>

A GolemUI Forms form is already a container.