Skip to content

Grid

WxRow and WxCol are the 24-column grid a screen is laid out on: a form in two columns, a row of statistic cards, a filter bar that becomes a stack on a phone.

For the shell around the screen, see Layout; for a single row or stack of controls, Space is less machinery.

24 columns, split four ways
12
12
8
8
8
6
6
6
6
4
4
4
4
4
4
Offsets
8
8, offset 8
Responsive — drag the bottom-right corner and watch the row, not the window
Card 1
Card 2
Card 3
Card 4
The same markup in a 300px column
Card 1
Card 2
Card 3
Card 4

Usage

vue
<template>
  <wx-row :gutter="16">
    <wx-col :span="12">Left half</wx-col>
    <wx-col :span="12">Right half</wx-col>
  </wx-row>
</template>

span counts columns out of 24, so twelve is a half, eight a third, six a quarter.

Gutters

gutter is the gap between columns, in pixels or as any CSS length. Left alone it is --wx-gap, the step the surrounding layout spaces everything else by — 16 where nothing sets one. Once the columns wrap, the gap between lines is the same — unless gutter-y says otherwise:

vue
<template>
  <wx-row :gutter="24" :gutter-y="12">…</wx-row>
</template>

The gutter is half a gutter of padding on each column, pulled back by a negative margin on the row, rather than a column-gap. That is what keeps span="12" an honest half: a gap subtracted from the track would leave two halves wider than the line they sit on.

Responsive

span is the base and holds at every width. sm, md, lg and xl override it from 640, 768, 1024 and 1280px up — narrowest first, so a column that takes the whole line when there is little room and a quarter of it when there is plenty reads in that order:

vue
<template>
  <wx-col :span="24" :md="12" :lg="6">…</wx-col>
</template>

Each breakpoint also takes an offset of its own:

vue
<template>
  <wx-col :span="24" :lg="{ span: 16, offset: 4 }">…</wx-col>
</template>

The breakpoints measure the row, not the window

They are container queries: md means "from 768px of row", not "from 768px of viewport". The same grid therefore stacks inside a 400px drawer and spreads across a wide screen without knowing which it is in, and a main column that just lost 240px to a sidebar reflows the moment it narrows rather than when the window does. On a page whose content runs the full width the two readings agree, which is why the numbers are the familiar ones.

The container is named, so a WxCol used outside a WxRow does not start answering to the nearest card: with no row above it, a column keeps its base span at every width.

Support is Chrome 105, Safari 16 and Firefox 110 and up — anything older shows the base span, which for the usual mobile-first span="24" is a stack, not a broken layout.

Arranging the row

vue
<template>
  <wx-row justify="between" align="center" :wrap="false">…</wx-row>
</template>

order moves a column without moving it in the markup — the summary panel that belongs first on a phone and last on a desktop:

vue
<template>
  <wx-row>
    <wx-col :span="24" :lg="16" :order="2">The form</wx-col>
    <wx-col :span="24" :lg="8" :order="1">The summary</wx-col>
  </wx-row>
</template>

Row

PropTypeDefaultDescription
gutternumber | stringstepGap between columns
gutterYnumber | stringgutterGap between wrapped lines
justify'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'Distribution along the row
align'start' | 'center' | 'end' | 'stretch' | 'baseline'Alignment across it
wrapbooleantrueLets columns wrap
asstring | Component'div'The element to render

Col

PropTypeDefaultDescription
spannumber24Columns out of 24, at every width
offsetnumberEmpty columns before the cell
smnumber | { span?: number; offset?: number }From 640px of row up
mdnumber | { span?: number; offset?: number }From 768px of row up
lgnumber | { span?: number; offset?: number }From 1024px of row up
xlnumber | { span?: number; offset?: number }From 1280px of row up
ordernumberVisual order within the row
asstring | Component'div'The element to render

Both take a default slot and nothing else.

How the breakpoints work

Every width a column is given is written out as a CSS variable — --wx-col-span, --wx-col-span-md and so on — and the stylesheet holds one @container rule per breakpoint, each falling back to the one below it. Nothing is generated per span, so the grid costs four rules rather than the several hundred a class-per-span grid ships, and a column can be adjusted from the outside:

vue
<template>
  <wx-col style="--wx-col-span: 10">Ten columns, set by hand</wx-col>
</template>

Released under the MIT License.