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

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 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:

<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 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:

<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:

<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:

<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:

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 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): Horizontal flex container
  • VStack (VStack.tsx): Vertical flex container
  • Stack (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

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

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.

<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:

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 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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →