mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-25 02:31:41 +00:00
`tracks` is a CSS track list along a stack's own axis, so the parent sizes its children and none of them carries a flex prop: <YStack tracks="auto 1fr auto"> header, body that fills, footer <XStack tracks="240px 1fr"> a fixed rail and the page On web the stack is a grid: a YStack's tracks are its rows (row flow, one column as wide as the stack), an XStack's its columns. A prop the author names wins over all of it. Alignment keeps its flex meaning: on a vertical stack grid's axes swap, so propMapper renames every key it emits (items -> justifyItems, justify -> alignContent, content -> justifyContent, and a direct child's self -> justifySelf), which holds in media, pseudo and variant styles alike. A child learns its parent's axis from ComponentContext and clears it for its own children. On native the stack stays flex and each child takes its track as flex longhands: Nfr is flex N, a length is a fixed basis, anything else is content-sized. The compiler leaves a tracks stack and its direct children to the runtime, which is where both are decided. Wrap is a row that wraps, the one place content-sized wrapping lives. All opt in: a stack without tracks renders as it did. Tested in components-test (web and native), static-tests (the deopt) and a Playwright run of the integration app that measures the boxes in dev and in the compiled production build. TracksDemo is in the demos app and on the stacks docs page. Claude-Session: https://claude.ai/code/session_01BLLMyBFSvAfCt7ZGqYuiEz
226 lines
6.7 KiB
Plaintext
226 lines
6.7 KiB
Plaintext
---
|
|
title: Stacks
|
|
description: Views that name an axis — across, down, and stacked
|
|
name: stacks
|
|
component: Stacks
|
|
package: stacks
|
|
demoName: Stacks
|
|
---
|
|
|
|
<HeroContainer>
|
|
<StacksDemo />
|
|
</HeroContainer>
|
|
|
|
```tsx hero template=Stacks
|
|
|
|
```
|
|
|
|
<Highlights
|
|
features={[
|
|
'X, Y, and Z stacks for easy flex layouts.',
|
|
'Gap property to add space between elements.',
|
|
'Handle press, focus, and layout events easily.',
|
|
]}
|
|
/>
|
|
|
|
Gui UI includes optional stack views - XStack, YStack and ZStack. They
|
|
extend directly off the [View](/docs/core/view-and-text) from `@hanzogui/core`,
|
|
and so accept all [style properties](/docs/intro/props).
|
|
|
|
In this example you'd show three `YStack` elements spaced out.
|
|
|
|
A stack names an **axis** — across, down, or stacked — not a layout engine. It
|
|
sets `flexDirection` and nothing else, so `display` still chooses the mode:
|
|
`<XStack display="grid">` is a horizontal grid, and the axis is resolved
|
|
against the mode for you. See [Grid](/ui/grid) for the track and placement
|
|
properties, and for which layouts survive the crossing to native.
|
|
|
|
## Tracks
|
|
|
|
`tracks` lets the stack size its children, so none of them needs a flex prop.
|
|
It takes a CSS track list, one track per child in order, along the stack's own
|
|
axis:
|
|
|
|
```tsx
|
|
import { XStack, YStack } from '@hanzo/gui'
|
|
|
|
// a header, a body that fills, a footer
|
|
<YStack tracks="auto 1fr auto" height="100%">
|
|
<Header />
|
|
<Body />
|
|
<Footer />
|
|
</YStack>
|
|
|
|
// a fixed rail and the page beside it
|
|
<XStack tracks="240px 1fr" gap="$4">
|
|
<Rail />
|
|
<Page />
|
|
</XStack>
|
|
```
|
|
|
|
<HeroContainer>
|
|
<TracksDemo />
|
|
</HeroContainer>
|
|
|
|
On web the stack is a CSS grid:
|
|
|
|
| | `YStack` (down) | `XStack` (across) |
|
|
| -------------- | ----------------------------------- | -------------------------------- |
|
|
| `display` | `grid` | `grid` |
|
|
| flow | `grid-auto-flow: row` | `grid-auto-flow: column` |
|
|
| `tracks` | `grid-template-rows` | `grid-template-columns` |
|
|
| the other axis | `grid-auto-columns: minmax(0, 1fr)` | `grid-auto-rows: minmax(0, 1fr)` |
|
|
|
|
The axis is the stack's `flexDirection`: `row` and `row-reverse` run across,
|
|
anything else runs down. Grid has no reverse flow, so children sit on the tracks
|
|
in source order. A prop you set yourself wins over any of these, so
|
|
`display="none"` still hides the stack.
|
|
|
|
**Alignment keeps its flex meaning.** On an `XStack` grid's axes already agree
|
|
with flex's, so `items`, `justify` and a child's `self` pass through. On a
|
|
`YStack` they swap, and gui renames them for you — in plain props, media
|
|
queries, pseudo styles and variants alike:
|
|
|
|
| written | on a `YStack` with `tracks` |
|
|
| -------------------------------------- | --------------------------- |
|
|
| `items` (`alignItems`) | `justifyItems` |
|
|
| `justify` (`justifyContent`) | `alignContent` |
|
|
| `content` (`alignContent`) | `justifyContent` |
|
|
| `self` (`alignSelf`) on a direct child | `justifySelf` |
|
|
|
|
Only the stack's direct children are renamed; their own children are flex items
|
|
again. The optimizing compiler leaves a tracks stack and its direct children to
|
|
the runtime, which is where both are decided.
|
|
|
|
**On native the stack stays flex** and each child takes the track at its
|
|
position as its flex style, written as longhands so a flex prop the child set
|
|
itself does not combine with it:
|
|
|
|
| track | the child's style |
|
|
| -------------------------------------------- | ------------------------------------------------------ |
|
|
| `Nfr`, `minmax(_, Nfr)` | `flexGrow: N, flexShrink: 1, flexBasis: 0` (`flex: N`) |
|
|
| `Npx`, `N%`, `0` | `flexGrow: 0, flexShrink: 0, flexBasis: N` |
|
|
| `repeat(K, list)` | the list written out K times |
|
|
| anything else, and every child past the list | `flexGrow: 0, flexShrink: 0, flexBasis: 'auto'` |
|
|
|
|
`repeat(auto-fill, …)` and `repeat(auto-fit, …)` need a measured size, so the
|
|
list ends there. Positions count each child that renders, through fragments,
|
|
and skip a child whose `position` is `absolute` or `fixed`, as grid does. A gui
|
|
child takes the style as props; any other element as a `style` entry after its
|
|
own, which a custom component passes to its root.
|
|
|
|
## Wrap
|
|
|
|
`Wrap` is the one place content-sized wrapping lives: a row that wraps its
|
|
children onto as many lines as they need, each at its own size, spaced by `gap`.
|
|
What has tracks goes on a stack; what has none — chips, tags, a run of buttons —
|
|
wraps here, and a child of a `Wrap` is where `grow`, `shrink` and `flexBasis`
|
|
still mean something.
|
|
|
|
```tsx
|
|
import { Wrap } from '@hanzo/gui'
|
|
|
|
;<Wrap gap="$2">
|
|
{tags.map((tag) => (
|
|
<Chip key={tag}>{tag}</Chip>
|
|
))}
|
|
</Wrap>
|
|
```
|
|
|
|
## Installation
|
|
|
|
Stacks is already installed in `hanzogui`, or you can install it independently:
|
|
|
|
```bash
|
|
npm install @hanzogui/stacks
|
|
```
|
|
|
|
## Usage
|
|
|
|
```tsx
|
|
import { XStack, YStack } from '@hanzo/gui'
|
|
|
|
export default () => (
|
|
<XStack gap="$2">
|
|
<YStack />
|
|
<YStack />
|
|
<YStack />
|
|
</XStack>
|
|
)
|
|
```
|
|
|
|
To see all the style properties supported, see the [Props](/docs/intro/props)
|
|
documentation.
|
|
|
|
### Fuller Example
|
|
|
|
An example using a wide variety of style properties:
|
|
|
|
```tsx
|
|
import { Text, XStack, YStack } from '@hanzo/gui'
|
|
|
|
export default () => (
|
|
<XStack
|
|
flex={1}
|
|
flexWrap="wrap"
|
|
backgroundColor="#fff"
|
|
hoverStyle={{
|
|
backgroundColor: 'red',
|
|
}}
|
|
// media query
|
|
$gtSm={{
|
|
flexDirection: 'column',
|
|
flexWrap: 'nowrap',
|
|
}}
|
|
>
|
|
<YStack gap="$3">
|
|
<Text>Hello</Text>
|
|
<Text>World</Text>
|
|
</YStack>
|
|
</XStack>
|
|
)
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### XStack, YStack, ZStack
|
|
|
|
Beyond the [Gui Props](/docs/intro/props), the stacks take `tracks` and two variants:
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'tracks',
|
|
required: false,
|
|
type: 'string',
|
|
description: (
|
|
<span>
|
|
A CSS track list along the stack's axis, one track per child. See{' '}
|
|
<a href="#tracks">Tracks</a>.
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
name: 'fullscreen',
|
|
required: false,
|
|
type: 'boolean',
|
|
deprecated: true,
|
|
description: (
|
|
<span>
|
|
Deprecated: use <Code>inset: 0, position: 'absolute'</Code> instead.
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
name: 'elevation',
|
|
required: false,
|
|
type: 'number | tokens.size',
|
|
description: (
|
|
<span>
|
|
Sets a natural looking shadow that expands out and away as the size gets bigger.
|
|
</span>
|
|
),
|
|
},
|
|
]}
|
|
/>
|