Flow is a bit odd at first, but once you begin using it, you may find it useful for creating
consistent vertical spacing between elements on a page. It comes from Andy Bell's
Flow pattern
and the stack pattern from
Every Layout. Read those for better
explanations of why you'd use it. It differs from Brio's version of stack, which uses a
flex-box approach for stacking. So, for vertical spacing on a page, use Flow. For vertical
stacking with flex features (alignment, child flex properties), use
Stack instead.
Tokens: --flow-space
Brio comes with several built-in utility classes for the
--flow-space-* space utilities, which affect the
margin-block-start of adjacent siblings inside a .flow container.
See the utilities page page for more information.
Examples
Basic usage
Create a .flow container and add .flow-space-* utilities to it
to create consistent vertical spacing between elements on a page.
<div class="flow">
<div class="docs-box">First</div>
<div class="docs-box">Second</div>
<div class="docs-box">Third</div>
</div>
Per-child override
Set --flow-space on an individual child to tweak the space above that
child. This is the smallest, most useful escape hatch, it doesn't affect any other element:
<div class="flow" style="--flow-space: var(--space-sm)">
<div class="docs-box">Standard gap above</div>
<div class="docs-box" style="--flow-space: var(--space-xl)">Extra gap above this one</div>
<div class="docs-box">Standard gap again</div>
</div>
Nesting quirk (and the fix)
If you set --flow-space on a child element either directly or via one of the
.flow-space-* utilities, you'll quickly find that there is a small quirk you
have to deal with it. When a nested .flow is a direct child of an outer
.flow, the nested flow's own --flow-space overrides the gap
above itself. Essentially, the rule reads the property from the element it's
targeting, not from the parent. This is usually not what you want in a nested context. In my
experience, I tend to set the inner --flow-space to tune spacing between the
nested flow's children, but that leaks into the outer flow's rhythm too.
Without a wrapper, the gap above the nested flow collapses to the nested flow's tight spacing. The dashed box shows where the inner flow starts. Notice how close it sits to item A:
<div class="flow" style="--flow-space: var(--space-md)">
<div class="docs-box">A (outer flow)</div>
<div
class="flow"
style="
--flow-space: var(--space-3xs);
border: 2px dashed var(--color-border);
padding: var(--space-sm);
border-radius: var(--radius-sm);
"
>
<small style="color: var(--color-text-subtle)"
>This inner flow's margin-block-start reads its own --flow-space (tight), not the
outer's (medium).</small
>
<div class="docs-box">B (nested, tight gap)</div>
<div class="docs-box">C (nested, tight gap)</div>
</div>
<div class="docs-box">D (outer flow)</div>
</div>
The fix is to wrap the nested flow in any non-flow element; a bare
<div> is fine. The wrapper inherits the outer flow's
--flow-space so its own top margin is correct, while the nested flow's
--flow-space stays scoped to its own children. Same dashed box, same content,
same inner spacing, only the wrapper changes, and now the gap above the inner flow matches
the outer rhythm:
<div class="flow" style="--flow-space: var(--space-md)">
<div class="docs-box">A (outer flow)</div>
<div>
<div
class="flow"
style="
--flow-space: var(--space-3xs);
border: 2px dashed var(--color-border);
padding: var(--space-sm);
border-radius: var(--radius-sm);
"
>
<small style="color: var(--color-text-subtle)"
>Wrapper div inherits the outer --flow-space for its own top margin; inner
--flow-space stays contained to this flow's children.</small
>
<div class="docs-box">B (nested, tight gap)</div>
<div class="docs-box">C (nested, tight gap)</div>
</div>
</div>
<div class="docs-box">D (outer flow)</div>
</div>
The wrapper <div> is invisible in layout (no padding, no display change);
it exists only to isolate the custom-property scope. So, if you see a bare
<div> around a .flow in a Brio project, this is usually why.
Why Not Fix this In CSS?
I suppose this could be fixed in CSS, but I'm not sure if it's worth it. There are legitimate uses of the current design, where the flow spacing doesn't need to be consistent across all children. For example, you might want to have a smaller gap above a summary under a heading. So, it's a genuinely useful quirk.
In addition, the current design allows for more flexibility in styling nested flows. By wrapping a nested flow in a non-flow element, you can isolate the custom-property scope and apply different styles to the nested flow without affecting the outer flow. And, visually, it's a direct indicator that this is a separate flow. All it costs is a small wrapper element, which, to me, is visually distinct.
The alternative is to drop the class-on-a-child usage from the utilities' contract. Each
rule would then become .flow.flow-space-3xs > * + * (and its siblings), and
the group's docs would drop the per-child clause. Nested flows carrying a utility class
would then nest cleanly, with no wrapper. Per-child overrides would still be available
through inline styles, which is the one form the compound rule cannot cover. But, that's
more CSS and more ceremony for little benefit.
No flex context
Flow doesn't have a flex context. So, keep that in mind. It's meant purely for vertical
rhythm, not layout. And, it's meant to let the browser handle the layout, not dictate it. If
you need more control over vertical rhythm, use
.stack or plain display: flex.
Reference
Source: src/css/composition.css
Applies to
.flow > * + *
Tokens
| Token | Default | Description |
|---|---|---|
--flow-space | var(--gutter-spacious) | Vertical spacing between flow children |