PlFlex
A row or a column, and the gap between the things in it. The axis is responsive and is resolved by the stylesheet, so a form that stacks on a phone and lines up on a laptop is one prop and no re-render.
import { PlFlex } from 'plass-ui';
<PlFlex direction={{ xs: 'vertical', md: 'horizontal' }} spacing={3} alignItems="center">
<PlAvatar name="Ada Lovelace" />
<PlTextField className="flex-1" label="Display name" fullWidth />
<PlButton>Save</PlButton>
</PlFlex>;This one is React-only, and it is not an omission. Row, Column and Wrap are already in package:flutter/widgets.dart, they already take a spacing, and a widget wrapping them would be a fourth name for three things every Flutter developer has known since their first screen.
Row(
spacing: 12,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[…],
);The half worth having in Dart is the responsive axis, and that is a LayoutBuilder rather than a widget:
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final children = <Widget>[…];
return constraints.maxWidth >= 768
? Row(spacing: 12, children: children)
: Column(spacing: 12, children: children);
},
);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| direction | PlassResponsive<'horizontal' | 'vertical'> | 'horizontal' | Which way the children run. Resolved in CSS, so a server render is already right at every width |
| reverse | boolean | false | Runs the children the other way along the axis. A painting order only — the DOM order does not move |
| spacing | PlassResponsive<number> | 2 | The gap between children, on Tailwind's spacing scale — 4 is 1rem, and fractions are allowed |
| rowSpacing · columnSpacing | PlassResponsive<number> | — | The gap on one axis only. Falls back to spacing |
| justify | 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly' | 'stretch' | — | How the box distributes the space its children did not use, along the axis |
| alignItems | 'start' | 'center' | 'end' | 'stretch' | 'baseline' | 'stretch' | How the children sit against each other across the axis |
| alignContent | 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly' | 'stretch' | — | Where the lines sit when the box is longer across than its content |
| wrap | boolean | false | Whether children that run out of room continue on the next line |
| inline | boolean | false | Lays the box out inline, only as wide as its children |
| render | RenderProp | — | Renders something other than a <div> (<ul />, <nav />) |
| children | ReactNode | — | What is laid out |
PlFlex is not in the Flutter package yet.
Every native <div> attribute passes straight through. What the shared axes mean across the library is in prop conventions.
PlFlex, PlStack or PlGrid
Three components lay children out in a line, and they are not variations on each other.
| Reach for it when | |
|---|---|
PlFlex | The children are whatever size they are. "These, side by side, with a gap." |
PlGrid | The children take a share of the row, a span out of twelve, with offsets and a wrap that lines up. |
PlStack | The children overlap, a run of avatars, a pile of cards. |
PlFlex is the one with no arithmetic in it. It is a flex box with the library's own vocabulary on it, which buys three things a className="flex gap-3" does not: the axis can change at a breakpoint without Tailwind, spacing is the same scale a PlGrid's gutter is, and it works in a project that imports plass-ui/styles.css and has no Tailwind of its own.
direction resolves in CSS
direction={{ xs: 'vertical', md: 'horizontal' }} is a column below 48rem and a row from it up, and the stylesheet decides which, one --p-dir-* slot per rung the caller named, cascaded by the same @variant blocks a PlGrid's columns use.
So the first paint a server sends is already right at every width, dragging a window costs no re-render, and no listener is installed. That is the same line breakpoints draws for every responsive prop in the library: a value that decides only style is resolved in CSS, and a value that decides structure (an orientation that changes which DOM a component builds and which way its arrow keys walk) is resolved in JavaScript and pays for it.
A rung that was not named inherits the one below it, so { xs: 'horizontal', lg: 'vertical' } is a row at every width until 64rem. Naming one rung never drops the others: the xs entry falls back to the documented default rather than to nothing.
reverse is a painting order
It runs the children the other way along the axis and changes nothing else. The DOM order is what a screen reader reads out and what the Tab key walks, and neither of them moves.
That makes it right for an arrangement (a chat bubble that hangs off the other end, a footer whose actions sit to the trailing side), and wrong for content whose order is the information. If the second thing should be read second, put it second.
It is not responsive, deliberately. It folds into the same slot direction writes, so one custom property carries the whole answer and a breakpoint changes the axis without having to restate which end it starts from.
Examples
A toolbar that wraps
wrap is false by default, which is what a flex box already does. A row that wraps is a decision rather than the absence of one. The opposite default would silently reflow a toolbar somebody had sized to fit.
One gutter per axis
spacing sets both. rowSpacing and columnSpacing each take one and fall back to it, which is what a wrapping row of chips usually wants, tight between the lines, wider along them.
<PlFlex wrap spacing={3} rowSpacing={1.5}>Inline
inline lays the box out in a line of text, only as wide as its children. For a run of tokens inside a sentence rather than a block of its own.
<p>
Assigned to{' '}
<PlFlex inline spacing={1} alignItems="center">
<PlAvatar size="xs" name="Ada" /> Ada
</PlFlex>{' '}
since March.
</p>Notes
- It draws nothing. No surface, no padding, and no
variant,color,size,densityorelevation. A flex box is the arrangement of the surfaces inside it, andspacing(the space between children) is the only measurement it owns. spacingis Tailwind's spacing scale and not an eight-pixel one:spacing={4}is1rem, exactly whatgap-4means and what aPlGrid's gutter of4is. Fractions are on the same ladder, so1.5is0.375rem.renderswaps the<div>for the element the markup actually wants (<ul>,<nav>,<fieldset>) without changing anything about the layout.flex-directionis declared on the component's own class, so aclassNamecarryingflex-colloses to it. Usedirection, which is the prop that means the same thing and is responsive besides.
Accessibility
- Reordering is visual only.
reverse, and anyordera caller sets on a child, move the pixels and not the document. A reading order that disagrees with the visual one is a documented failure of meaningful sequence, so put the content in the order it should be read and let the layout follow. - It has no role and adds none.
render={<ul />}with<li>children is what turns an arrangement into a list for a screen reader; a<div>of<div>s is what it looks like.