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.
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> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/checkbox';import '@golemui/gui-components/textinput';
@Component({ selector: 'app-layout-row', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-2"> <gui-textinput label="Street" hint="Number and street name" ></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Postcode"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-checkbox label="Default address"></gui-checkbox> </div> </div> `,})export class LayoutRowComponent {}<script setup lang="ts">import '@golemui/gui-components/checkbox';import '@golemui/gui-components/textinput';import '@golemui/gui-components/index.css';</script>
<template> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-2"> <gui-textinput label="Street" hint="Number and street name" ></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Postcode"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-checkbox label="Default address"></gui-checkbox> </div> </div></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/checkbox';import '@golemui/gui-components/textinput';import '@golemui/gui-components/index.css';
@customElement('layout-row')export class LayoutRow extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-2"> <gui-textinput label="Street" hint="Number and street name" ></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Postcode"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-checkbox label="Default address"></gui-checkbox> </div> </div> `; }}<div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-2"> <gui-textinput label="Street" hint="Number and street name"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Postcode"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-checkbox label="Default address"></gui-checkbox> </div></div>The grid classes
Section titled “The grid classes”.gui-grid lays out its .gui-grid__cell children:
| Class | Layout |
|---|---|
gui-grid | A stack, one cell below the other. |
gui-grid--row | One line, the cells sharing its width by span. |
gui-grid--columns-1 … gui-grid--columns-12 | That many equal columns, which the cells fill in order and span. |
gui-grid--auto | As many columns as fit, each at least --gui-layout-column-min wide (12rem). |
gui-grid__cell--span-2 … gui-grid__cell--span-12 | A cell twice, or up to twelve times, as wide, in a row or numbered columns. |
gui-grid--gap-none, -xs, -sm, -lg, -xl | The space between cells. It is md by default. |
gui-grid--justify-start, -center, -end, -space-between | In 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.
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> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/textinput';
@Component({ selector: 'app-layout-columns', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <div class="gui-grid gui-grid--columns-3 gui-grid--gap-sm"> <div class="gui-grid__cell"> <gui-textinput label="First name"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Last name"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Company"></gui-textinput> </div> <div class="gui-grid__cell gui-grid__cell--span-2"> <gui-textinput label="Email"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Phone"></gui-textinput> </div> </div> `,})export class LayoutColumnsComponent {}<script setup lang="ts">import '@golemui/gui-components/textinput';import '@golemui/gui-components/index.css';</script>
<template> <div class="gui-grid gui-grid--columns-3 gui-grid--gap-sm"> <div class="gui-grid__cell"> <gui-textinput label="First name"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Last name"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Company"></gui-textinput> </div> <div class="gui-grid__cell gui-grid__cell--span-2"> <gui-textinput label="Email"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Phone"></gui-textinput> </div> </div></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/textinput';import '@golemui/gui-components/index.css';
@customElement('layout-columns')export class LayoutColumns extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <div class="gui-grid gui-grid--columns-3 gui-grid--gap-sm"> <div class="gui-grid__cell"> <gui-textinput label="First name"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Last name"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Company"></gui-textinput> </div> <div class="gui-grid__cell gui-grid__cell--span-2"> <gui-textinput label="Email"></gui-textinput> </div> <div class="gui-grid__cell"> <gui-textinput label="Phone"></gui-textinput> </div> </div> `; }}<div class="gui-grid gui-grid--columns-3 gui-grid--gap-sm"> <div class="gui-grid__cell"><gui-textinput label="First name"></gui-textinput></div> <div class="gui-grid__cell"><gui-textinput label="Last name"></gui-textinput></div> <div class="gui-grid__cell"><gui-textinput label="Company"></gui-textinput></div> <div class="gui-grid__cell gui-grid__cell--span-2"><gui-textinput label="Email"></gui-textinput></div> <div class="gui-grid__cell"><gui-textinput label="Phone"></gui-textinput></div></div>How fields line up
Section titled “How fields line up”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:
- its
.gui-label, with the hint; - its
.gui-widget, the control, centered in the track. A checkbox, a toggle and agui-buttonsit here too; - 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.
Fields side by side
Section titled “Fields side by side”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.
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> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { UploadService } from '@golemui/gui-components';import '@golemui/gui-components/date-picker';import '@golemui/gui-components/file-upload';import '@golemui/gui-components/number';import '@golemui/gui-components/select';import '@golemui/gui-components/tags';import '@golemui/gui-components/time-picker';
// 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, ), ),};
@Component({ selector: 'app-layout-inline', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <div class="gui-grid gui-grid--columns-3"> <div class="gui-grid__cell"> <gui-date-picker label="Departure" [errors]="departureErrors" ></gui-date-picker> </div> <div class="gui-grid__cell"> <gui-time-picker label="Time" minute-step="30"></gui-time-picker> </div> <div class="gui-grid__cell"> <gui-tags label="Travelers" hint="Press Enter after each name" ></gui-tags> </div> <div class="gui-grid__cell"> <gui-select label="Class" [options]="classes"></gui-select> </div> <div class="gui-grid__cell"> <gui-file-upload id="passport" label="Passport scan" [dependencies]="dependencies" ></gui-file-upload> </div> <div class="gui-grid__cell"> <gui-number label="Bags" minimum="0" maximum="3" [errors]="bagsErrors" ></gui-number> </div> </div> `,})export class LayoutInlineComponent { departureErrors = ['Pick a date after today.']; classes = ['Economy', 'Business', 'First']; dependencies = { uploadService }; bagsErrors = ['Up to 3 bags per booking.'];}<script setup lang="ts">import type { UploadService } from '@golemui/gui-components';import '@golemui/gui-components/date-picker';import '@golemui/gui-components/file-upload';import '@golemui/gui-components/number';import '@golemui/gui-components/select';import '@golemui/gui-components/tags';import '@golemui/gui-components/time-picker';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 };</script>
<template> <div class="gui-grid gui-grid--columns-3"> <div class="gui-grid__cell"> <gui-date-picker label="Departure" :errors.prop="['Pick a date after today.']" ></gui-date-picker> </div> <div class="gui-grid__cell"> <gui-time-picker label="Time" :minuteStep="30"></gui-time-picker> </div> <div class="gui-grid__cell"> <gui-tags label="Travelers" hint="Press Enter after each name"></gui-tags> </div> <div class="gui-grid__cell"> <gui-select label="Class" :options.prop="['Economy', 'Business', 'First']" ></gui-select> </div> <div class="gui-grid__cell"> <gui-file-upload id="passport" label="Passport scan" :dependencies.prop="dependencies" ></gui-file-upload> </div> <div class="gui-grid__cell"> <gui-number label="Bags" :minimum="0" :maximum="3" :errors.prop="['Up to 3 bags per booking.']" ></gui-number> </div> </div></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { UploadService } from '@golemui/gui-components';import '@golemui/gui-components/date-picker';import '@golemui/gui-components/file-upload';import '@golemui/gui-components/number';import '@golemui/gui-components/select';import '@golemui/gui-components/tags';import '@golemui/gui-components/time-picker';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 };const departureErrors = ['Pick a date after today.'];const classes = ['Economy', 'Business', 'First'];const bagsErrors = ['Up to 3 bags per booking.'];
@customElement('layout-inline')export class LayoutInline extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <div class="gui-grid gui-grid--columns-3"> <div class="gui-grid__cell"> <gui-date-picker label="Departure" .errors=${departureErrors} ></gui-date-picker> </div> <div class="gui-grid__cell"> <gui-time-picker label="Time" minute-step="30"></gui-time-picker> </div> <div class="gui-grid__cell"> <gui-tags label="Travelers" hint="Press Enter after each name" ></gui-tags> </div> <div class="gui-grid__cell"> <gui-select label="Class" .options=${classes}></gui-select> </div> <div class="gui-grid__cell"> <gui-file-upload id="passport" label="Passport scan" .dependencies=${dependencies} ></gui-file-upload> </div> <div class="gui-grid__cell"> <gui-number label="Bags" minimum="0" maximum="3" .errors=${bagsErrors} ></gui-number> </div> </div> `; }}<div class="gui-grid gui-grid--columns-3"> <div class="gui-grid__cell"> <gui-date-picker label="Departure" errors='["Pick a date after today."]' ></gui-date-picker> </div> <div class="gui-grid__cell"> <gui-time-picker label="Time" minute-step="30"></gui-time-picker> </div> <div class="gui-grid__cell"> <gui-tags label="Travelers" hint="Press Enter after each name"></gui-tags> </div> <div class="gui-grid__cell"> <gui-select label="Class" options='["Economy", "Business", "First"]'></gui-select> </div> <div class="gui-grid__cell"> <gui-file-upload id="passport" label="Passport scan"></gui-file-upload> </div> <div class="gui-grid__cell"> <gui-number label="Bags" minimum="0" maximum="3" errors='["Up to 3 bags per booking."]' ></gui-number> </div></div>
<script type="module"> // A stand-in for your upload endpoint: it resolves after a second. document.querySelector('#passport').dependencies = { uploadService: { upload: (file) => new Promise((resolve) => setTimeout(() => resolve({ url: `/uploads/${encodeURIComponent(file.name)}` }), 1000), ), }, };</script>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.
Narrow containers
Section titled “Narrow containers”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.