Flow

A composition layout that uses margin-block-start to control the vertical spacing between children. It does NOT set display, so elements keep their natural formatting context. Use .flow-space-* utilities to override spacing directly via the --flow-space custom property.

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.

First
Second
Third
<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:

Standard gap above
Extra gap above this one
Standard gap again
<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:

A (outer flow)
This inner flow's margin-block-start reads its own --flow-space (tight), not the outer's (medium).
B (nested, tight gap)
C (nested, tight gap)
D (outer flow)
<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:

A (outer flow)
Wrapper div inherits the outer --flow-space for its own top margin; inner --flow-space stays contained to this flow's children.
B (nested, tight gap)
C (nested, tight gap)
D (outer flow)
<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

TokenDefaultDescription
--flow-spacevar(--gutter-spacious)Vertical spacing between flow children