Zero-dependency, type-safe Stack component for streamlining flexbox usage in React & React Native.
You always end up with visual clutter and boilerplate when using flexbox, no matter which styling solution you choose. <Stack />
supports all flexbox properties directly as named props, making your flexbox components visually cleaner and easier to read. For most use cases, you'll only need prop shorthands like gap
, vertical
, center
, and padding
:
import Stack from '@nkzw/stack';
const Component = () => (
<Stack vertical gap center>
<div>Apple</div> {/* Works on the web */}
<View>Banana</View> {/* Works in React Native */}
<Item>Kiwi</Item>
</Stack>
);
Other benefits include:
- Minimal API: Easily control direction, spacing, alignment, and more using shorthand props.
- Sensible Defaults: Ships with sensible cross-platform defaults for React & React Native.
- Consistent spacing: Enforces 4px grid for gap & padding values via TypeScript.
- Flexible padding: Automatically derives padding from gap or accepts custom values.
- Polymorphic by design: Render as any HTML or custom component via the
as
prop while maintaining full type safety.
npm install @nkzw/stack
Using a <Stack />
component without any props renders it like this:
<div
style={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'nowrap',
justifyContent: 'flex-start',
}}
/>
Unlike the default flexbox styles on web, justify-content
is using flex-start
instead of start
. Flexbox on web and on React Natie use different default values for flexDirection
, alignContent
and flexShrink
. <Stack />
uses these defaults for both platforms:
flexDirection: 'row'
alignContent: 'flex-start'
flexShrink
is set to 1
by default on web and 0
on React Native.
Props for ` are meant to be mostly static and passed as shorthand only, without any values.
type StackProps = {
alignCenter?: boolean; // `align-items: center`
alignEnd?: boolean; // `align-items: flex-end`
alignStart?: boolean; // `align-items: center`
around?: boolean; // `justify-content: space-around`
as: ElementType; // The element to render, defaults to `<div />` on the web, and `<View />` on React Native.
baseline?: boolean; // `align-items: baseline`
between?: boolean; // `justify-content: space-between`
center?: boolean; // `justify-content: center`
children?: ReactNode;
columnGap?: Gap; // `column-gap: <Gap>`
content?: AlignContent; // `align-content: start | end | center | stretch | space-between | space-around | space-evenly`
end?: boolean; // `justify-content: flex-end`
evenly?: boolean; // `justify-content: space-evenly`
flex1?: boolean; // `flex: 1`
gap?: Gap; // `gap: <Gap>`
horizontalPadding?: Gap; // `padding-left` and `padding-right` set to the same value as `gap` when boolean or a <Gap> value.
inline?: boolean; // `display: inline-flex` (*web only*)
padding?: Gap; // `padding: <Gap>` or `padding: 8px` when `gap` is `true`.
reverse?: boolean; // `flex-direction: row-reverse` or `flex-direction: column-reverse` when `vertical` is `true`.
rowGap?: Gap; // `row-gap: <Gap>`
safe?: boolean; // When used with `center` or `end`: `justify-content: safe center | safe flex-end`
self?: AlignSelf; // `align-self: center | end | start | stretch | baseline`
shrink0?: boolean; // `flex-shrink: 0`
stretch?: boolean; // `flex-grow: 1`
vertical?: boolean; // `flex-direction: column`
verticalPadding?: Gap; // `padding-top` and `padding-bottom` set to the same value as `gap` when true or a <Gap> value.
wrap?: boolean; // `flex-wrap: wrap`
};
import Stack from '@nkzw/stack';
// Horizontal layout (default).
<Stack gap={16}>
<div>Apple</div>
<div>Banana</div>
<div>Kiwi</div>
</Stack>
// Vertical layout.
<Stack vertical gap={16}>
<div>Apple</div>
<div>Banana</div>
<div>Kiwi</div>
</Stack>
// Using boolean gap (uses default gap of 8px).
<Stack vertical gap>
<div>Apple</div>
<div>Banana</div>
</Stack>
Or with React Native:
<Stack vertical gap={16}>
<Text>Apple</Text>
<Text>Banana</Text>
<Text>Kiwi</Text>
</Stack>
If you are using NativeWind, <Stack />
will automatically support className
:
<Stack vertical gap={16} className="rounded-md border border-gray-300">
<Text>Apple</Text>
<Text>Banana</Text>
<Text>Kiwi</Text>
</Stack>
There is also a shorthand for vertical stacks:
import { VStack } from '@nkzw/stack';
<VStack gap={16}>
<div>Apple</div>
<div>Banana</div>
<div>Kiwi</div>
</VStack>;
// Same as `<Stack vertical gap={16}>`.
// Add padding equal to the gap.
<Stack gap={16} padding>
<div>Content</div>
</Stack>
// Custom padding value.
<Stack gap={16} padding={24}>
<div>Content</div>
</Stack>
// Padding using the default gap value.
<Stack gap padding>
<div>Content</div>
</Stack>
// Different gaps for rows and columns.
<Stack rowGap={8} columnGap={16}>
<div>Apple</div>
<div>Banana</div>
<div>Kiwi</div>
<div>Item 4</div>
</Stack>
// Vertical layout with row gap.
<Stack vertical rowGap={20}>
<div>Apple</div>
<div>Banana</div>
</Stack>
// Vertical padding only.
<Stack gap={16} verticalPadding>
<div>Content</div>
</Stack>
// Horizontal padding only.
<Stack gap={16} horizontalPadding>
<div>Content</div>
</Stack>
// Custom directional padding.
<Stack verticalPadding={24} horizontalPadding={32}>
<div>Content</div>
</Stack>
// Mix with gaps.
<Stack rowGap={12} columnGap={20} verticalPadding horizontalPadding={32}>
<div>Content</div>
</Stack>
// Center items.
<Stack center>
<div>Centered</div>
</Stack>
// Align to end.
<Stack end>
<div>At end</div>
</Stack>
// Space around items.
<Stack around>
<div>Apple</div>
<div>Banana</div>
</Stack>
// Align between.
<Stack between>
<div>Between</div>
</Stack>
// Space evenly.
<Stack evenly>
<div>Apple</div>
<div>Banana</div>
</Stack>
// Space between (default).
<Stack>
<div>Apple</div>
<div>Banana</div>
</Stack>
On web, the safe
prop is supported too:
// Renders with `justify-content: safe center`.
<Stack center safe>
<div>Centered</div>
</Stack>
// Renders with `justify-content: safe flex-end`.
<Stack end safe>
<div>At end</div>
</Stack>
// Center align items.
<Stack alignCenter>
<div>Centered</div>
</Stack>
// Align to start.
<Stack alignStart>
<div>At start</div>
</Stack>
// Align to end.
<Stack alignEnd>
<div>At end</div>
</Stack>
// Baseline alignment.
<Stack baseline>
<div style={{ fontSize: '12px' }}>Small</div>
<div style={{ fontSize: '24px' }}>Large</div>
</Stack>
// Take all available space.
<Stack flex1>
<div>Full width/height</div>
</Stack>
// Grow to fill container.
<Stack stretch>
<div>Stretched</div>
</Stack>
// Prevent shrinking.
<Stack shrink0>
<div>Won't shrink</div>
</Stack>
// Align self to center.
<Stack self="center">
<div>Self-centered</div>
</Stack>
// Other self alignment options.
<Stack self="start">
<div>Self at start</div>
</Stack>
<Stack self="end">
<div>Self at end</div>
</Stack>
<Stack self="stretch">
<div>Self stretched</div>
</Stack>
<Stack content="center">
<div>Content</div>
</Stack>
<Stack content="start">
<div>Content</div>
</Stack>
// Inline flex.
<Stack inline>
<div>Inline flex</div>
</Stack>
// Reverse direction.
<Stack reverse>
<div>Apple (appears last)</div>
<div>Banana (appears first)</div>
</Stack>
// Vertical reverse.
<Stack vertical reverse>
<div>Apple (appears at bottom)</div>
<div>Banana (appears at top)</div>
</Stack>
// Wrap (`flex-wrap: nowrap` is the default).
<Stack wrap>
<div>Apple</div>
<div>Banana</div>
<div>Kiwi</div>
</Stack>
Available gap values: 1
, 2
, 4
, 8
, 12
, 16
, 20
, 24
, 28
, 32
, 36
, 40
, 44
, 48
, or true
for default.
You can set the default gap value globally:
import { setDefaultGap } from '@nkzw/stack';
setDefaultGap(12); // Now gap={true} will use 12px.
<Stack as="section" gap={16}>
<div>Content</div>
</Stack>
Stack carries over component props from custom components in a type-safe manner:
<Stack as="nav" center gap={24}>
<Stack as={Link} to="/home">
Home
</Stack>
<Stack as={Link} to="/about">
About
</Stack>
</Stack>
You can use the asStack
utility to convert any component into a Stack component, allowing you to use all Stack props seamlessly:
import { asStack } from '@nkzw/stack';
const Link = asStack(({ to, children, ...props }) => (
<a href={to} {...props}>
{children}
</a>
));
<Link to="/home" center gap={16}>
Home
</Link>;
Note: Components wrapped with asStack
or when used with as
must support a style
prop.
<Stack
vertical
gap={16}
padding={24}
style={{ border: '1px solid #ccc', borderRadius: '16px' }}
>
<h2>Card Title</h2>
<p>Card content goes here</p>
<Stack gap={8}>
<button>Action 1</button>
<button>Action 2</button>
</Stack>
</Stack>
<Stack alignCenter padding={16} style={{ backgroundColor: '#f5f5f5' }}>
<h1>Logo</h1>
<Stack gap={24} flex1 center>
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</Stack>
<button>Sign In</button>
</Stack>
<Stack as="form" vertical gap={16} padding={24}>
<Stack vertical gap>
<label>Name</label>
<input type="text" />
</Stack>
<Stack vertical gap>
<label>Email</label>
<input type="email" />
</Stack>
<Stack gap end>
<button type="button">Cancel</button>
<button type="submit">Submit</button>
</Stack>
</Stack>