Skip to content
weftGitHub

Row

Puts things side by side and wraps them when they run out of room.

SourcePackage @weft-js/warpPlayground

Preview

tsx
import { Inset, Row, Stack } from '@weft-js/warp'export function Justify() {  return (    <Stack gap="related">      {JUSTIFIES.map(justify => (        <Inset key={justify}>          <Row justify={justify} gap="tight">            <Box>{justify}</Box>            <Box>Two</Box>            <Box>Three</Box>          </Row>        </Inset>      ))}    </Stack>  )}type Status = 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string; name: string; owner: string; region: string; status: Status; deploys: number; uptime: number; latency: number; cost: number; updated: string }[] = [  { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31' },  { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31' },  { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30' },  { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29' },  { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29' },  { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27' },  { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22' },  { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31' },  { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28' },  { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30' },]const min = 60_000const BOX = 'display:grid;align-content:center;gap:2px;padding:var(--warp-space-related);border:var(--warp-border-hairline) solid var(--warp-color-border);border-radius:var(--warp-radius-surface);background:var(--warp-color-surface);color:var(--warp-color-text);font-size:var(--warp-text-caption-size);min-inline-size:0'const projectOf = (c: unknown) => {  const n = Number(typeof c === 'function' ? c() : c)  return Number.isInteger(n) && n > 0 ? PROJECTS[(n - 1) % PROJECTS.length] : undefined}function Box({ tall, children, ...rest }: { tall?: number; children?: unknown; [attr: string]: unknown }) {  return (    <div style={tall ? `${BOX};min-block-size:${tall}px` : BOX} {...rest}>      {projectOf(children) ? <><strong>{projectOf(children)!.name}</strong><span style="color:var(--warp-color-text-subtle)">{projectOf(children)!.region}</span></> : children}    </div>  )}const JUSTIFIES = ['start', 'center', 'end', 'between'] as const

Installation

npm install @weft-js/warp

Usage

tsx
import { Row } from '@weft-js/warp'

Alignment

Items centre by default. start tops them, baseline lines up text.

tsx
import { Grid, Heading, Inset, Row, Text } from '@weft-js/warp'export function Align() {  return (    <Grid min="xs" cols={3} gap="group">      <Inset><Row gap="tight"><Box tall={72}>Tall</Box><Box>center</Box></Row></Inset>      <Inset><Row gap="tight" align="start"><Box tall={72}>Tall</Box><Box>start</Box></Row></Inset>      <Inset><Row gap="tight" align="baseline"><Heading level={3}>Heading</Heading><Text subtle>baseline</Text></Row></Inset>    </Grid>  )}type Status = 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string; name: string; owner: string; region: string; status: Status; deploys: number; uptime: number; latency: number; cost: number; updated: string }[] = [  { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31' },  { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31' },  { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30' },  { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29' },  { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29' },  { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27' },  { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22' },  { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31' },  { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28' },  { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30' },]const min = 60_000const BOX = 'display:grid;align-content:center;gap:2px;padding:var(--warp-space-related);border:var(--warp-border-hairline) solid var(--warp-color-border);border-radius:var(--warp-radius-surface);background:var(--warp-color-surface);color:var(--warp-color-text);font-size:var(--warp-text-caption-size);min-inline-size:0'const projectOf = (c: unknown) => {  const n = Number(typeof c === 'function' ? c() : c)  return Number.isInteger(n) && n > 0 ? PROJECTS[(n - 1) % PROJECTS.length] : undefined}function Box({ tall, children, ...rest }: { tall?: number; children?: unknown; [attr: string]: unknown }) {  return (    <div style={tall ? `${BOX};min-block-size:${tall}px` : BOX} {...rest}>      {projectOf(children) ? <><strong>{projectOf(children)!.name}</strong><span style="color:var(--warp-color-text-subtle)">{projectOf(children)!.region}</span></> : children}    </div>  )}

Wrapping

It wraps by itself. wrap={false} keeps it on one line.

tsx
import { Row } from '@weft-js/warp'export function Wraps() {  return (    <Row gap="tight">      {TAGS.map(t => <Box key={t}>{t}</Box>)}    </Row>  )}type Status = 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string; name: string; owner: string; region: string; status: Status; deploys: number; uptime: number; latency: number; cost: number; updated: string }[] = [  { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31' },  { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31' },  { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30' },  { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29' },  { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29' },  { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27' },  { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22' },  { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31' },  { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28' },  { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30' },]const min = 60_000const BOX = 'display:grid;align-content:center;gap:2px;padding:var(--warp-space-related);border:var(--warp-border-hairline) solid var(--warp-color-border);border-radius:var(--warp-radius-surface);background:var(--warp-color-surface);color:var(--warp-color-text);font-size:var(--warp-text-caption-size);min-inline-size:0'const projectOf = (c: unknown) => {  const n = Number(typeof c === 'function' ? c() : c)  return Number.isInteger(n) && n > 0 ? PROJECTS[(n - 1) % PROJECTS.length] : undefined}function Box({ tall, children, ...rest }: { tall?: number; children?: unknown; [attr: string]: unknown }) {  return (    <div style={tall ? `${BOX};min-block-size:${tall}px` : BOX} {...rest}>      {projectOf(children) ? <><strong>{projectOf(children)!.name}</strong><span style="color:var(--warp-color-text-subtle)">{projectOf(children)!.region}</span></> : children}    </div>  )}const TAGS = ['eu-west', 'us-east', 'ap-south', 'us-west', 'eu-north', 'global', 'staging', 'preview', 'canary', 'archive']

A child that grows

data-grow on a child takes the free space.

tsx
import { Row } from '@weft-js/warp'export function Grow() {  return (    <Row gap="related" wrap={false}>      <Box>Fixed</Box>      <Box data-grow="">data-grow takes the rest</Box>      <Box>Fixed</Box>    </Row>  )}type Status = 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string; name: string; owner: string; region: string; status: Status; deploys: number; uptime: number; latency: number; cost: number; updated: string }[] = [  { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31' },  { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31' },  { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30' },  { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29' },  { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29' },  { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27' },  { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22' },  { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31' },  { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28' },  { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30' },]const min = 60_000const BOX = 'display:grid;align-content:center;gap:2px;padding:var(--warp-space-related);border:var(--warp-border-hairline) solid var(--warp-color-border);border-radius:var(--warp-radius-surface);background:var(--warp-color-surface);color:var(--warp-color-text);font-size:var(--warp-text-caption-size);min-inline-size:0'const projectOf = (c: unknown) => {  const n = Number(typeof c === 'function' ? c() : c)  return Number.isInteger(n) && n > 0 ? PROJECTS[(n - 1) % PROJECTS.length] : undefined}function Box({ tall, children, ...rest }: { tall?: number; children?: unknown; [attr: string]: unknown }) {  return (    <div style={tall ? `${BOX};min-block-size:${tall}px` : BOX} {...rest}>      {projectOf(children) ? <><strong>{projectOf(children)!.name}</strong><span style="color:var(--warp-color-text-subtle)">{projectOf(children)!.region}</span></> : children}    </div>  )}

Reflow

reflow turns it into a stack when its container is narrow. Set the stage's Width to Tablet and Phone to see each step go.

tsx
import { Row, Stack, Text } from '@weft-js/warp'export function Reflow() {  return (    <Stack gap="group">      <Stack gap="tight">        <Text role="label" subtle>reflow="tablet": a stack below 600px</Text>        <Row gap="tight" reflow="tablet"><Box>One</Box><Box>Two</Box><Box>Three</Box></Row>      </Stack>      <Stack gap="tight">        <Text role="label" subtle>reflow="desktop": a stack below 1000px</Text>        <Row gap="tight" reflow="desktop"><Box>One</Box><Box>Two</Box><Box>Three</Box></Row>      </Stack>    </Stack>  )}type Status = 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string; name: string; owner: string; region: string; status: Status; deploys: number; uptime: number; latency: number; cost: number; updated: string }[] = [  { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31' },  { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31' },  { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30' },  { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29' },  { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29' },  { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27' },  { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22' },  { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31' },  { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28' },  { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30' },]const min = 60_000const BOX = 'display:grid;align-content:center;gap:2px;padding:var(--warp-space-related);border:var(--warp-border-hairline) solid var(--warp-color-border);border-radius:var(--warp-radius-surface);background:var(--warp-color-surface);color:var(--warp-color-text);font-size:var(--warp-text-caption-size);min-inline-size:0'const projectOf = (c: unknown) => {  const n = Number(typeof c === 'function' ? c() : c)  return Number.isInteger(n) && n > 0 ? PROJECTS[(n - 1) % PROJECTS.length] : undefined}function Box({ tall, children, ...rest }: { tall?: number; children?: unknown; [attr: string]: unknown }) {  return (    <div style={tall ? `${BOX};min-block-size:${tall}px` : BOX} {...rest}>      {projectOf(children) ? <><strong>{projectOf(children)!.name}</strong><span style="color:var(--warp-color-text-subtle)">{projectOf(children)!.region}</span></> : children}    </div>  )}

A page header

Title and actions apart, stacked when there is no room for both.

tsx
import { Button, Heading, Row, Stack, Text } from '@weft-js/warp'export function PageHeader() {  return (    <Row justify="between" gap="group" reflow="tablet">      <Stack gap="hair">        <Heading level={3} role="title">storefront</Heading>        <Text subtle>Live in eu-west. Last deploy 2 hours ago.</Text>      </Stack>      <Row gap="tight">        <Button emphasis="outline" tone="neutral" icon="terminal">Logs</Button>        <Button icon="rocket">Deploy</Button>      </Row>    </Row>  )}

When to use

Use it for

  • Buttons, tags and small clusters
  • A header with a title and actions

Reach for something else

  • Grid, when the items should line up in columns
  • Split, when one side has a fixed width
  • Stack, when the items read top to bottom

Playground

Justify
Align
import { Row } from '@weft-js/warp'<Row>  <Box>Short</Box>  <Box>Tall</Box>  <Box>A little wider</Box></Row>

Accessibility

  • This has no shortcuts of its own: controls inside it use Tab, Enter and Space as usual, and warp's parts keep their roles and labels.

API Reference

PropTypeDefaultDescription
childrenunknown—What it contains.
as"div" | "section" | "article" | "main" | "header" | "footer" | "nav" | "…'div'The element: div by default, also header, footer, nav and others.
gap"section" | "none" | "hair" | "tight" | "related" | "group" | "page"—The space between items, from the space scale.
justify"start" | "center" | "end" | "between"—Where the items sit along the row.
align"start" | "baseline"—How the items line up across the row. Centred by default.
wrapboolean—false keeps it on one line.
reflowStep—Below this step of its container it becomes a stack: tablet is 600px, desktop 1000px.

RelatedStackSpacerSplit