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 containerVStack(VStack.tsx): Vertical flex containerStack(Stack.tsx): Generic component withdirectionprop
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
gaptoken values across both primitives - Predictable alignment: Identical
align/justifybehavior
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
Gridhandles two-dimensional responsive layouts via thecolumnsprop withminWidth,max, andrepeatoptions—implemented inGrid.tsxusingbuildCappedTemplate'fill'preserves empty tracks for consistent widths;'fit'collapses them for stretched itemsGridSpanenables masonry layouts when combined withrowHeightHStack/VStack/Stackprovide one-dimensional flexbox layouts with identical spacing/alignment APIs- Composition pattern: Grid for page structure, Stack for content organization within cells
- StyleX integration via
xstyleallows 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →