mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-25 02:31:41 +00:00
258 lines
8.4 KiB
Plaintext
258 lines
8.4 KiB
Plaintext
---
|
|
title: Grid
|
|
description: CSS Grid, said in the same style props as everything else
|
|
name: grid
|
|
---
|
|
|
|
<Highlights
|
|
features={[
|
|
'display is the layout mode. Stacks name the axis; the mode is a prop.',
|
|
'Every grid track and placement property is a style prop, so it themes, responds and compiles.',
|
|
'Grid is native on web. On native there is no grid engine, and this page says where that bites.',
|
|
]}
|
|
/>
|
|
|
|
Gui has always had flexbox. It now has CSS Grid on the same footing: `display`
|
|
is the mode, and every grid property is an ordinary style prop, so it takes
|
|
tokens, media queries and the optimizing compiler like any other.
|
|
|
|
Neither mode is privileged. Reach for whichever expresses the layout.
|
|
|
|
```tsx
|
|
import { View, XStack } from '@hanzo/gui'
|
|
|
|
// flex — one axis, items flow
|
|
<XStack gap="$4">
|
|
<Item /><Item /><Item />
|
|
</XStack>
|
|
|
|
// grid — two axes, explicit tracks
|
|
<View display="grid" gridTemplateColumns="repeat(3, 1fr)" gap="$4">
|
|
<Item /><Item /><Item />
|
|
</View>
|
|
```
|
|
|
|
## The mode is a prop, the axis is the component
|
|
|
|
`XStack` and `YStack` name an **axis** — across, and down. They have never
|
|
meant "flexbox"; they set `flexDirection` and nothing else. So a stack takes a
|
|
mode like anything else:
|
|
|
|
```tsx
|
|
<XStack display="grid" gap="$4"> // across, on grid
|
|
<YStack display="grid" gap="$4"> // down, on grid
|
|
```
|
|
|
|
That works because the axis is resolved against the mode. It is worth knowing
|
|
why, because the raw CSS does not do this for you: `flex-direction` is **inert**
|
|
under grid, and the two engines invert the same word — flex `row` runs across,
|
|
while grid's `grid-auto-flow: row` fills rows and therefore runs _down_. Written
|
|
by hand, `display: grid` on a flex row silently lays out vertically. `XStack`
|
|
says the axis in both vocabularies, so it stays horizontal.
|
|
|
|
A stack you do not pass `display` to emits no extra class and is byte-identical
|
|
to what it always was.
|
|
|
|
## The same layout, both ways
|
|
|
|
Four layouts, each expressed in both modes. Where they are equivalent, prefer
|
|
whichever reads better.
|
|
|
|
**A row of three**
|
|
|
|
```tsx
|
|
<XStack gap="$4">…</XStack>
|
|
|
|
<View display="grid" gridTemplateColumns="repeat(3, 1fr)" gap="$4">…</View>
|
|
```
|
|
|
|
Flex sizes each child to its content and distributes the slack; grid makes three
|
|
tracks of exactly `1fr`. For equal columns, grid says it directly.
|
|
|
|
**A column**
|
|
|
|
```tsx
|
|
<YStack gap="$4">…</YStack>
|
|
|
|
<View display="grid" gap="$4">…</View>
|
|
```
|
|
|
|
Identical. Grid with no template fills one implicit column top to bottom, which
|
|
is a column.
|
|
|
|
**A gallery that reflows**
|
|
|
|
```tsx
|
|
<XStack flexWrap="wrap" gap="$4">
|
|
<View minW={160} flexGrow={1} />…
|
|
</XStack>
|
|
|
|
<View display="grid" gridTemplateColumns="repeat(auto-fit, minmax(160px, 1fr))" gap="$4">
|
|
…
|
|
</View>
|
|
```
|
|
|
|
Both reflow. The grid version needs no per-child sizing and **no media query** —
|
|
`auto-fit` decides the track count from the available width. Measured: two
|
|
tracks at 390px, four at 768px, eight at 1440px.
|
|
|
|
**This is the one case where the two engines disagree, so it is worth knowing
|
|
exactly where.** They are pixel-identical while every row is full. They part on
|
|
a _partial last row_: flex's `flexGrow` stretches a lone trailing item across
|
|
the whole width, while grid holds it to its track. Measured with seven items
|
|
over three tracks, only the orphan differs, and only in width — by 328px on a
|
|
480px container.
|
|
|
|
Neither is wrong; they answer different questions. Grid keeps the rhythm, which
|
|
is usually what a gallery wants. If you want the flex behaviour, say so —
|
|
`gridColumn="1 / -1"` on the last child spans it, and `justifyItems` controls
|
|
how a track's content fills it.
|
|
|
|
**A sidebar and a body**
|
|
|
|
```tsx
|
|
<XStack gap="$4">
|
|
<View w={240} />
|
|
<View flexGrow={1} />
|
|
</XStack>
|
|
|
|
<View display="grid" gridTemplateColumns="240px 1fr" gap="$4">
|
|
<View /><View />
|
|
</View>
|
|
```
|
|
|
|
Equivalent. Grid puts the sizes in one place instead of on each child.
|
|
|
|
## What grid does that flex cannot
|
|
|
|
Flexbox is one-dimensional. These have no flex equivalent, which is the reason
|
|
to reach for grid at all.
|
|
|
|
**Named areas** — the layout is legible as a picture:
|
|
|
|
```tsx
|
|
<View
|
|
display="grid"
|
|
gridTemplateAreas={`"nav head"
|
|
"nav body"`}
|
|
gridTemplateColumns="240px 1fr"
|
|
gridTemplateRows="auto 1fr"
|
|
gap="$4"
|
|
>
|
|
<View gridArea="nav" />
|
|
<View gridArea="head" />
|
|
<View gridArea="body" />
|
|
</View>
|
|
```
|
|
|
|
**Explicit placement** — a child claims its own span, independent of source
|
|
order:
|
|
|
|
```tsx
|
|
<View display="grid" gridTemplateColumns="repeat(4, 1fr)" gap="$4">
|
|
<View gridColumn="1 / 3" />
|
|
<View gridColumn="3 / 5" />
|
|
<View gridColumn="1 / 5" />
|
|
</View>
|
|
```
|
|
|
|
**Overlap** — two children in one cell, no absolute positioning:
|
|
|
|
```tsx
|
|
<View display="grid">
|
|
<Image gridArea="1 / 1" />
|
|
<Overlay gridArea="1 / 1" placeSelf="end" />
|
|
</View>
|
|
```
|
|
|
|
**Dense packing** — `gridAutoFlow="dense"` backfills holes left by items that
|
|
span more than one track.
|
|
|
|
## Native
|
|
|
|
Grid is genuinely native on web: real CSS Grid, no polyfill and no JavaScript
|
|
layout pass.
|
|
|
|
React Native has **no grid engine** — its own `display` admits only `none`,
|
|
`flex` and `contents`, and Yoga implements no grid. So on native, `display:
|
|
grid` becomes `flex` and **no grid property crosses at all** — not the tracks,
|
|
not the areas, not the placement, not even `gridAutoFlow`. Not approximated:
|
|
dropped, because a value an engine cannot read is worse applied than absent.
|
|
|
|
`display` is the only one that crosses. Everything one-dimensional survives on
|
|
the element's own flex properties, which is why the idiom below works.
|
|
|
|
That is the whole rule, and it is blunter than it looks. Measured, a
|
|
three-column grid on native:
|
|
|
|
```
|
|
{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' } → { display: 'flex' }
|
|
```
|
|
|
|
No tracks, and no `flexDirection` either — so React Native's own default takes
|
|
over and you get **one full-width column**. A gallery written as tracks alone
|
|
is a column on a phone.
|
|
|
|
### Writing one layout that works on both
|
|
|
|
Say the axis in both vocabularies. Each is inert on the other engine, so they
|
|
compose on one element with no branch:
|
|
|
|
```tsx
|
|
<View
|
|
display="grid"
|
|
gridTemplateColumns="repeat(auto-fit, minmax(160px, 1fr))"
|
|
flexDirection="row"
|
|
flexWrap="wrap"
|
|
gap="$3"
|
|
>
|
|
```
|
|
|
|
Web reads the tracks and ignores the flex props; native reads the flex props
|
|
and never sees the tracks. One source, both engines, no `$platform` branch.
|
|
|
|
### What cannot cross at all
|
|
|
|
| grid | why flex has no answer |
|
|
| ----------------------------------- | ------------------------------------------- |
|
|
| named areas | flex has no second dimension |
|
|
| `gridColumn` / `gridArea` placement | flex cannot place across rows _and_ columns |
|
|
| overlapping cells | flex cannot put two children in one cell |
|
|
| `gridAutoFlow="dense"` | nothing to backfill in one dimension |
|
|
|
|
These are web-only compositions. Reach for them deliberately and give native its
|
|
own arrangement.
|
|
|
|
### Two more words that invert
|
|
|
|
`flexDirection` is not the only prop that changes meaning across the boundary.
|
|
`justifyContent` and `alignItems` are named relative to `flexDirection` in flex,
|
|
but in grid `justify-*` is always inline and `align-*` always block. They agree
|
|
for a flex **row** and swap for a flex **column** — so on `YStack`, `ZStack` or
|
|
a bare `View`, the same prop moves the other axis. And `flexWrap` is inert under
|
|
grid: where flex would wrap, grid adds implicit tracks and overflows.
|
|
|
|
The top three cover most real layouts — galleries, card decks, uniform columns —
|
|
so grid is usually the better way to write them even in a cross-platform app.
|
|
The bottom four are web-only compositions. Reach for them deliberately, and give
|
|
native its own arrangement with `$platform-native` when you do.
|
|
|
|
## Properties
|
|
|
|
Tracks and placement take the CSS value verbatim, unitless, exactly as the
|
|
stylesheet would:
|
|
|
|
`grid` · `gridTemplate` · `gridTemplateColumns` · `gridTemplateRows` ·
|
|
`gridTemplateAreas` · `gridAutoColumns` · `gridAutoRows` · `gridAutoFlow` ·
|
|
`gridArea` · `gridColumn` · `gridColumnStart` · `gridColumnEnd` · `gridRow` ·
|
|
`gridRowStart` · `gridRowEnd`
|
|
|
|
Alignment splits by who is aligned. `alignItems`, `alignContent` and
|
|
`justifyContent` mean the same as they do in flex. Grid adds the ones flex has
|
|
no use for:
|
|
|
|
`justifyItems` · `justifySelf` · `placeItems` · `placeContent` · `placeSelf`
|
|
|
|
Spacing is `gap`, `columnGap` and `rowGap` — the same props, the same tokens, in
|
|
both modes.
|