On this page

Layout

Grid

Arrange direct children in rows, columns, and named areas.

Grid lays out direct children in two dimensions. Use it when a layout needs explicit columns, rows, or named areas. Use Stack or Cluster when children flow on one axis. Read Layout to choose a layout component.

Pass a number to columns for that many equal columns. Each column can shrink below the width of its content, so long content wraps instead of widening one column.

First
Second
Third
Fourth
import { Grid } from '@luke-ui/react/grid';
import { ExampleItem } from '#docs';

export default () => {
	return (
		<Grid columns={2} gap="space.12">
			<ExampleItem>First</ExampleItem>
			<ExampleItem>Second</ExampleItem>
			<ExampleItem>Third</ExampleItem>
			<ExampleItem>Fourth</ExampleItem>
		</Grid>
	);
};

Without columns or areas, Grid has one column that grows with its content. Pass columns={1} when long content should wrap inside the grid instead of widening it.

Columns from the available space

Pass a CSS repeat(auto-fit, …) value when the column count should follow the available width. Wrap the minimum column size in min(…, 100%) so a parent narrower than the minimum does not overflow.

Grid: Auto-fit columns
First
Second
Third
Fourth
Fifth
Sixth
import { Grid } from '@luke-ui/react/grid';
import { ExampleItem } from '#docs';

export default () => {
	return (
		<Grid columns="repeat(auto-fit, minmax(min(12rem, 100%), 1fr))" gap="space.12">
			<ExampleItem>First</ExampleItem>
			<ExampleItem>Second</ExampleItem>
			<ExampleItem>Third</ExampleItem>
			<ExampleItem>Fourth</ExampleItem>
			<ExampleItem>Fifth</ExampleItem>
			<ExampleItem>Sixth</ExampleItem>
		</Grid>
	);
};

Tracks and named areas

columns and rows also accept a CSS value such as "1fr 1fr". areas takes one string per row. Name each cell, use . for an empty cell, and place a child in an area with the area prop of GridItem.

Without columns, columns from areas size to their content. Each named area must form one filled rectangle.

Grid: Tracks and named areas
Area A
Area B
Area C
Area D
import { Grid, GridItem } from '@luke-ui/react/grid';
import { ExampleItem } from '#docs';

export default () => {
	return (
		<Grid
			areas={['a a', 'b c', 'd d']}
			columns="1fr 1fr"
			gap="space.12"
			rows="auto 1fr auto"
			style={{ minBlockSize: '16rem' }}
		>
			<GridItem area="a">
				<ExampleItem>Area A</ExampleItem>
			</GridItem>
			<GridItem area="b" renderRoot={(domProps) => <ExampleItem {...domProps} />}>
				Area B
			</GridItem>
			<GridItem area="c" renderRoot={(domProps) => <ExampleItem {...domProps} />}>
				Area C
			</GridItem>
			<GridItem area="d">
				<ExampleItem>Area D</ExampleItem>
			</GridItem>
		</Grid>
	);
};

Responsive layouts

columns, rows, and areas accept a responsive object with a required initial value. Each columns breakpoint can be a number or a CSS value. Stack named areas in one column at initial, then arrange them from a wider breakpoint.

Grid: Responsive areas
Area A
Area B
Area C
import { Grid, GridItem } from '@luke-ui/react/grid';
import { ExampleItem } from '#docs';

export default () => {
	return (
		<Grid
			areas={{ initial: ['a', 'b', 'c'], bp768: ['a b b', 'a c c'] }}
			columns={{ initial: 1, bp768: '10rem 1fr 1fr' }}
			gap="space.12"
		>
			<GridItem area="a" renderRoot={(domProps) => <ExampleItem {...domProps} />}>
				Area A
			</GridItem>
			<GridItem area="b">
				<ExampleItem>Area B</ExampleItem>
			</GridItem>
			<GridItem area="c">
				<ExampleItem>Area C</ExampleItem>
			</GridItem>
		</Grid>
	);
};

Place a child

GridItem places one child in its parent Grid and does nothing else. It renders a div and accepts area, column, row, and the root props children, className, style, ref, elementType, and renderRoot. The element form also accepts standard DOM attributes such as id, aria-*, and data-*. It accepts className and style, but not Luke UI styling utilities. Use renderRoot to style it with a layout component.

area takes the name of an area from the parent's areas. The value is a plain string, so a name the parent does not define compiles. An unmatched name can create implicit grid tracks. The type does not check it against areas.

column and row take a line placement: auto, a line number such as "2", a span such as "span 2", a start and end such as "2 / 4", or a start and a span such as "1 / span 2". Negative lines count from the end, so "1 / -1" covers every explicit column. The types use ${number}, which accepts any numeric string. It does not check for an integer or a valid track. Pass named lines and other complex placements through className.

All three props accept a responsive object.

Spanning columns

Use column="span 2" when an item should cover more than one column.

When columns is a number, keep each span within the active column count at every breakpoint. A span that fits at bp768 can overflow a smaller initial column count, so change both values together.

Grid: Spanning columns
Spans two columns
Second
Third
import { Grid, GridItem } from '@luke-ui/react/grid';
import { ExampleItem } from '#docs';

export default () => {
	return (
		<Grid columns={2} gap="space.12">
			<GridItem column="span 2">
				<ExampleItem>Spans two columns</ExampleItem>
			</GridItem>
			<ExampleItem>Second</ExampleItem>
			<ExampleItem>Third</ExampleItem>
		</Grid>
	);
};

Explicit lines

Place an item between grid lines with column and row. Each item in the example has a placement, and the sidebar spans two rows.

Grid: Explicit lines
Header
Sidebar
Main
Footer
import { Grid, GridItem } from '@luke-ui/react/grid';
import { ExampleItem } from '#docs';

export default () => {
	return (
		<Grid columns={4} gap="space.12">
			<GridItem column="1 / -1">
				<ExampleItem>Header</ExampleItem>
			</GridItem>
			<GridItem
				column="1"
				renderRoot={(domProps) => <ExampleItem {...domProps} />}
				row="2 / span 2"
			>
				Sidebar
			</GridItem>
			<GridItem column="2 / 5">
				<ExampleItem>Main</ExampleItem>
			</GridItem>
			<GridItem column="2 / 5">
				<ExampleItem>Footer</ExampleItem>
			</GridItem>
		</Grid>
	);
};

Style a child

Pass renderRoot to render a Box or another layout component as the item. The callback receives the resolved children, className, style, and ref. Spread them onto the element you return, and the grid item is that element, with no extra wrapper. Put DOM attributes on the returned element.

Grid: Style an item
Featured
Second
Third
import { Box } from '@luke-ui/react/box';
import { Grid, GridItem } from '@luke-ui/react/grid';
import { ExampleItem } from '#docs';

export default () => {
	return (
		<Grid columns={3} gap="space.12">
			<GridItem
				column="span 2"
				renderRoot={(domProps) => (
					<Box
						{...domProps}
						backgroundColor="info.subtle"
						borderRadius="detail"
						padding="space.16"
					/>
				)}
			>
				Featured
			</GridItem>
			<ExampleItem>Second</ExampleItem>
			<ExampleItem>Third</ExampleItem>
		</Grid>
	);
};

Read Box for the renderRoot contract. elementType and renderRoot are mutually exclusive.

Nest a layout

Return a Stack, a Grid, or any other layout component from renderRoot to lay out the contents of a grid item. The returned component is the item, so it takes part in the parent grid and lays out its own children.

Grid: Nested layouts
First
Second
Third
Fourth
import { Grid, GridItem } from '@luke-ui/react/grid';
import { Stack } from '@luke-ui/react/stack';
import { ExampleItem } from '#docs';

export default () => {
	return (
		<Grid columns={2} gap="space.12">
			<GridItem column="span 2" renderRoot={(domProps) => <Stack {...domProps} gap="space.8" />}>
				<ExampleItem>First</ExampleItem>
				<ExampleItem>Second</ExampleItem>
			</GridItem>
			<GridItem
				column="span 2"
				renderRoot={(domProps) => <Grid {...domProps} columns={2} gap="space.8" />}
			>
				<ExampleItem>Third</ExampleItem>
				<ExampleItem>Fourth</ExampleItem>
			</GridItem>
		</Grid>
	);
};

Alignment

alignItems and justifyItems place each child within its grid area. alignContent and justifyContent distribute the tracks when the grid is larger than its tracks.

Grid: Alignment
Short
Two
lines
import { Grid } from '@luke-ui/react/grid';
import { ExampleItem } from '#docs';

export default () => {
	return (
		<Grid alignItems="end" columns={2} gap="space.12" justifyItems="center">
			<ExampleItem>Short</ExampleItem>
			<ExampleItem>
				Two
				<br />
				lines
			</ExampleItem>
		</Grid>
	);
};

Choose the rendered element

Grid uses Box's elementType and renderRoot contract.

  • First
  • Second
  • Third
  • Fourth
import { Grid } from '@luke-ui/react/grid';
import { ExampleItem } from '#docs';

const plainListStyle = { listStyle: 'none' } as const;

export default () => {
	return (
		<Grid columns={2} elementType="ul" gap="space.12" margin="0" padding="0" style={plainListStyle}>
			<ExampleItem elementType="li">First</ExampleItem>
			<ExampleItem elementType="li">Second</ExampleItem>
			<ExampleItem elementType="li">Third</ExampleItem>
			<ExampleItem elementType="li">Fourth</ExampleItem>
		</Grid>
	);
};

Accessibility

Grid placement changes visual position only. Reading order and focus order follow the DOM order. This applies to area, column, and row. Arrange areas and placements so the visual order matches the DOM order at every breakpoint.

API

GridProps

GridProps also accepts compatible DOM and ARIA attributes and event handlers for its rendered element.

NameTypeDefault
alignContent
<'alignContent'>—

Distribution of row tracks within the grid on the block axis.

alignItems
<'alignItems'>—

Alignment of each child within its grid area on the block axis.

alignSelf
<"auto" |⁠ "baseline" |⁠ "center" |⁠ "flex-end" |⁠ "flex-start" |⁠ "normal" |⁠ "stretch"> |⁠ undefined—
areas
<ReadonlyArray<string>>—

Named grid areas, one string per row, such as ['a a', 'b c'].

Each row needs the same number of cells, and each named area must form a filled rectangle. Use . for an empty cell. Accepts a responsive object with a required initial value.

blockSize
<"0" |⁠ "100%" |⁠ "auto" |⁠ "fit-content" |⁠ "max-content" |⁠ "min-content"> |⁠ undefined—
borderBlockEndWidth
<"none" |⁠ "thick" |⁠ "thin"> |⁠ undefined—
borderBlockStartWidth
<"none" |⁠ "thick" |⁠ "thin"> |⁠ undefined—
borderInlineEndWidth
<"none" |⁠ "thick" |⁠ "thin"> |⁠ undefined—
borderInlineStartWidth
<"none" |⁠ "thick" |⁠ "thin"> |⁠ undefined—
columnGap
<>—

Space between column tracks. Overrides gap on the inline axis.

columns
<number |⁠ string>—

Column tracks.

A positive integer creates that many equal columns that can shrink below their content width. A non-empty string is used as grid-template-columns. Accepts a responsive object with a required initial value, and each breakpoint can use either form.

flexBasis
<"0" |⁠ "100%" |⁠ "auto" |⁠ "content" |⁠ "fit-content" |⁠ "max-content" |⁠ "min-content"> |⁠ undefined—
flexGrow
<"0" |⁠ "1"> |⁠ undefined—
flexShrink
<"0" |⁠ "1"> |⁠ undefined—
gap
<>—

Space between grid tracks.

inlineSize
<"0" |⁠ "100%" |⁠ "auto" |⁠ "fit-content" |⁠ "max-content" |⁠ "min-content"> |⁠ undefined—
inset
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
insetBlock
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
insetBlockEnd
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
insetBlockStart
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
insetInline
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
insetInlineEnd
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
insetInlineStart
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
justifyContent
<'justifyContent'>—

Distribution of column tracks within the grid on the inline axis.

justifyItems
<"baseline" |⁠ "center" |⁠ "end" |⁠ "normal" |⁠ "start" |⁠ "stretch"> |⁠ undefined—

Alignment of each child within its grid area on the inline axis.

justifySelf
<"auto" |⁠ "center" |⁠ "end" |⁠ "normal" |⁠ "start" |⁠ "stretch"> |⁠ undefined—
margin
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
marginBlock
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
marginBlockEnd
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
marginBlockStart
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
marginInline
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
marginInlineEnd
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
marginInlineStart
<"0" |⁠ "auto" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
maxBlockSize
<"0" |⁠ "100%" |⁠ "fit-content" |⁠ "max-content" |⁠ "min-content" |⁠ "none"> |⁠ undefined—
maxInlineSize
<"0" |⁠ "100%" |⁠ "container.448" |⁠ "container.672" |⁠ "container.896" |⁠ "container.1152" |⁠ "container.1280" |⁠ "fit-content" |⁠ "max-content" |⁠ "min-content" |⁠ "none"> |⁠ undefined—
minBlockSize
<"0" |⁠ "100%" |⁠ "auto" |⁠ "fit-content" |⁠ "max-content" |⁠ "min-content"> |⁠ undefined—
minInlineSize
<"0" |⁠ "100%" |⁠ "auto" |⁠ "fit-content" |⁠ "max-content" |⁠ "min-content"> |⁠ undefined—
order
<number> |⁠ undefined—
overflow
<"auto" |⁠ "clip" |⁠ "hidden" |⁠ "scroll" |⁠ "visible"> |⁠ undefined—
overflowX
<"auto" |⁠ "clip" |⁠ "hidden" |⁠ "scroll" |⁠ "visible"> |⁠ undefined—
overflowY
<"auto" |⁠ "clip" |⁠ "hidden" |⁠ "scroll" |⁠ "visible"> |⁠ undefined—
overscrollBehavior
<"auto" |⁠ "contain"> |⁠ undefined—
padding
<"0" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
paddingBlock
<"0" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
paddingBlockEnd
<"0" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
paddingBlockStart
<"0" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
paddingInline
<"0" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
paddingInlineEnd
<"0" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
paddingInlineStart
<"0" |⁠ "space.4" |⁠ "space.8" |⁠ "space.12" |⁠ "space.16" |⁠ "space.24" |⁠ "space.32" |⁠ "space.40" |⁠ "space.48" |⁠ "space.64" |⁠ "space.96"> |⁠ undefined—
placeSelf
<"auto" |⁠ "center" |⁠ "end" |⁠ "normal" |⁠ "start" |⁠ "stretch"> |⁠ undefined—
position
<"absolute" |⁠ "fixed" |⁠ "relative" |⁠ "static" |⁠ "sticky"> |⁠ undefined—
rowGap
<>—

Space between row tracks. Overrides gap on the block axis.

rows
<string>—

Row tracks, as a CSS track list such as "auto 1fr auto".

Accepts a responsive object with a required initial value.

GridItemProps

GridItemProps also accepts compatible DOM and ARIA attributes and event handlers for its rendered element.

NameTypeDefault
area
<string>—

Name of the grid area to occupy. Pass one of the names in the parent Grid's areas.

The value is a plain string, so a name that the parent grid does not define compiles. Accepts a responsive object.

column
<>—

Column lines to occupy, such as "span 2" or "1 / -1". Named lines belong in className. Accepts a responsive object.

row
<>—

Row lines to occupy, such as "span 2" or "1 / 3". Named lines belong in className. Accepts a responsive object.