# How to Implement Responsive Layouts with Astryx Grid and Stack: A Complete Guide

> Learn to implement responsive layouts with Astryx Grid and Stack. Build adaptive UIs easily without custom CSS using two dimensional column and one dimensional linear arrangements.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-08-05

---

**Use Astryx's `Grid` for two-dimensional responsive column layouts and `Stack`/`HStack`/`VStack` for one-dimensional linear arrangements, combining them to build adaptive UIs without custom CSS.**

Astryx is Facebook's modern UI component library that provides opinionated layout primitives for building responsive interfaces. This guide explains how to implement responsive layouts using the `Grid` and `Stack` components, with direct references to the source implementation in `facebook/astryx`.

## Understanding Astryx Grid for Responsive Columns

The `Grid` component in [`packages/core/src/Grid/Grid.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Grid/Grid.tsx) wraps CSS Grid with a React-friendly API. Its power lies in the `columns` prop, which accepts either a **fixed number** or a **responsive configuration object**.

### Fixed Column Layouts

For simple, non-responsive grids, pass a number:

```tsx
<Grid columns={3} gap={4}>
  <Card>Item 1</Card>
  <Card>Item 2</Card>
  <Card>Item 3</Card>
</Grid>

```

This creates three equal columns regardless of viewport width.

### Responsive Column Configuration

The responsive API uses an object with `minWidth`, optional `max`, and `repeat` properties:

| Prop | Type | Purpose |
|------|------|---------|
| `minWidth` | `number` | Minimum column width in pixels; triggers auto-fill/fit behavior |
| `max` | `number` | Maximum number of columns to prevent excessive expansion |
| `repeat` | `'fill'` \| `'fit'` | `'fill'` (default) keeps empty tracks; `'fit'` collapses them |

The helper `buildCappedTemplate` at lines 31-55 of [`Grid.tsx`](https://github.com/facebook/astryx/blob/main/Grid.tsx) computes the `grid-template-columns` value. When `max` is provided, it calculates a dynamic minimum track size that caps column count while ensuring tracks can still stretch to `1fr`.

## Grid Repeat Modes: Fill vs. Fit

### `'fill'` Mode (Default)

Maintains consistent column widths by preserving empty tracks:

```tsx
<Grid columns={{minWidth: 280}} gap={3}>
  {[...Array(5)].map((_, i) => (
    <Card key={i}>Item {i + 1}</Card>
  ))}
</Grid>

```

On a 1400px container, this creates five 280px columns. Empty tracks remain, keeping widths uniform.

### `'fit'` Mode

Collapses empty tracks so items stretch to fill available space:

```tsx
<Grid columns={{minWidth: 200, repeat: 'fit'}} gap={2}>
  <FeaturedCard />
  <FeaturedCard />
</Grid>

```

With only two items in a wide container, each expands to fill half the width rather than leaving gaps.

## Capping Maximum Columns

Prevent layouts from becoming too wide on large screens:

```tsx
<Grid columns={{minWidth: 250, max: 4}} gap={4}>
  {items.map(item => <ProductCard key={item.id} {...item} />)}
</Grid>

```

Even on a 4K display, this never exceeds four columns. The single-column case still expands to full container width—critical for mobile responsiveness.

## Building Masonry Layouts with GridSpan

Combine `rowHeight` with `GridSpan` for Pinterest-style layouts:

```tsx
import { Grid, GridSpan } from '@astryx/core';

<Grid columns={{minWidth: 300}} rowHeight={80} gap={2}>
  <GridSpan rows={4}>
    <TallImageCard />
  </GridSpan>
  <GridSpan rows={2}>
    <ShortQuoteCard />
  </GridSpan>
  <GridSpan rows={1}>
    <CompactStatCard />
  </GridSpan>
</Grid>

```

The `GridSpan` component in [`packages/core/src/Grid/GridSpan.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Grid/GridSpan.tsx) sets `grid-row: span ${rows}` to control vertical occupation. `rowHeight` maps to `grid-auto-rows` in the generated CSS.

## Using Stack Components for Linear Layouts

Astryx provides three one-dimensional layout primitives in `packages/core/src/Stack/`:

- **`HStack`** ([`HStack.tsx`](https://github.com/facebook/astryx/blob/main/HStack.tsx)): Horizontal flex container
- **`VStack`** ([`VStack.tsx`](https://github.com/facebook/astryx/blob/main/VStack.tsx)): Vertical flex container
- **`Stack`** ([`Stack.tsx`](https://github.com/facebook/astryx/blob/main/Stack.tsx)): Generic component with `direction` prop

All share `Grid`'s spacing and alignment API:

| Prop | Values |
|------|--------|
| `gap`, `rowGap`, `columnGap` | Spacing tokens: `0 \| 0.5 \| 1 \| ... \| 10` |
| `align` | `'start' \| 'center' \| 'end' \| 'stretch'` |
| `justify` | `'start' \| 'center' \| 'end' \| 'stretch'` |

### Horizontal Toolbars

```tsx
import { HStack } from '@astryx/core';

<HStack gap={2} justify="space-between" align="center">
  <Logo />
  <HStack gap={1}>
    <SearchButton />
    <NotificationsButton />
    <ProfileMenu />
  </HStack>
</HStack>

```

### Vertical Form Layouts

```tsx
import { VStack } from '@astryx/core';

<VStack gap={3} align="stretch">
  <TextField label="Email" />
  <TextField label="Password" type="password" />
  <HStack gap={2} justify="end">
    <Button variant="secondary">Cancel</Button>
    <Button variant="primary">Sign In</Button>
  </HStack>
</VStack>

```

## Combining Grid and Stack for Page Layouts

The recommended pattern: `Grid` for macro layout, `Stack` for micro arrangement within cells.

```tsx
<Grid columns={{minWidth: 280, max: 3}} gap={4}>
  {/* Dashboard card 1 */}
  <VStack gap={2} align="start">
    <HStack gap={1} align="center">
      <Icon name="chart" />
      <h3>Revenue</h3>
    </HStack>
    <MetricValue>$124K</MetricValue>
    <HStack gap={1} justify="end">
      <Button size="sm">Details</Button>
    </HStack>
  </VStack>

  {/* Dashboard card 2 - spans 2 rows in masonry */}
  <GridSpan rows={2}>
    <VStack gap={2}>
      <LargeChart />
      <ChartLegend />
    </VStack>
  </GridSpan>

  {/* Additional cards... */}
</Grid>

```

This composition ensures:
- **Responsive columns**: Grid adapts from 1 to 3 columns based on viewport
- **Consistent spacing**: Same `gap` token values across both primitives
- **Predictable alignment**: Identical `align`/`justify` behavior

## Customizing with StyleX

Both `Grid` and `Stack` accept `xstyle` for additional CSS via StyleX. The grid template value is emitted as a CSS variable (`dynamicStyles.templateColumns`), so overrides work correctly in media queries:

```tsx
import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  highlighted: {
    backgroundColor: 'var(--color-bg-emphasis)',
    border: '1px solid var(--color-border-accent)',
  },
  compactOnMobile: {
    '@media (max-width: 600px)': {
      gap: 'var(--space-1)',
    },
  },
});

<Grid 
  columns={{minWidth: 280, max: 4}} 
  gap={4}
  xstyle={stylex.props(styles.highlighted, styles.compactOnMobile)}
>
  {/* ... */}
</Grid>

```

Spacing tokens are defined in `packages/core/src/theme/tokens.stylex` and map to CSS custom properties.

## Summary

- **`Grid`** handles two-dimensional responsive layouts via the `columns` prop with `minWidth`, `max`, and `repeat` options—implemented in [`Grid.tsx`](https://github.com/facebook/astryx/blob/main/Grid.tsx) using `buildCappedTemplate`
- **`'fill'`** preserves empty tracks for consistent widths; **`'fit'`** collapses them for stretched items
- **`GridSpan`** enables masonry layouts when combined with `rowHeight`
- **`HStack`/`VStack`/`Stack`** provide one-dimensional flexbox layouts with identical spacing/alignment APIs
- **Composition pattern**: Grid for page structure, Stack for content organization within cells
- **StyleX integration** via `xstyle` allows custom overrides without breaking responsive behavior

## Frequently Asked Questions

### How does Astryx Grid handle mobile responsiveness?

Astryx Grid uses CSS Grid's native responsive behavior through the `minmax()` function. When `columns` receives `{minWidth: 280}`, the generated `grid-template-columns` uses `repeat(auto-fill, minmax(280px, 1fr))` (or `auto-fit`). This automatically reduces column count as viewport narrows, eventually collapsing to a single full-width column. The `max` property adds an upper bound for large screens.

### What's the difference between HStack and VStack versus a generic Stack?

`HStack` and `VStack` are convenience components that hardcode `flex-direction: row` and `flex-direction: column` respectively. The generic `Stack` component accepts a `direction` prop. According to [`packages/core/src/Stack/Stack.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Stack/Stack.tsx), all three share identical implementation logic—choosing between them is a matter of code clarity and preference.

### Can I nest Grid inside Stack or vice versa?

Yes, nesting works in both directions. A common pattern nests `VStack` or `HStack` inside `Grid` cells for content organization, or places a `Grid` inside a `Stack` when a portion of a linear layout needs two-dimensional arrangement. Both components render as `div` elements by default with no positioning constraints that would prevent nesting.

### Where are spacing token values defined?

Spacing tokens (`gap={4}`, etc.) reference values in `packages/core/src/theme/tokens.stylex`. The mapping converts numeric props to CSS custom properties like `--space-4`. This ensures consistent spacing across all Astryx components and allows theme-wide adjustments without component changes.