The Cluster Layout
The Cluster is one of the layout patterns popularized by Andy Bell and Heydon Pickering's "Every Layout." It solves a deceptively common problem: a group of items of varying, unknown widths (tags, buttons, badges) that need to sit next to each other with consistent spacing, and wrap onto new lines when they run out of room — without any media queries at all.
The core recipe
Flexbox with wrap and gap does the whole job. No breakpoints, no JavaScript measuring — the browser wraps items automatically based on available width.
.cluster {
display: flex;
flex-wrap: wrap;
gap: 0.75rem; /* space between items, both directions */
align-items: center; /* vertical alignment within a wrapped line */
justify-content: flex-start;
}gap had wide flexbox support, clusters needed negative margins on the container plus margins on every child to fake even spacing — and that trick broke at the wrapped edges. `gap` replaces all of that with one line and works correctly on every wrapped row automatically.Pattern 1: tag list
<ul class="cluster tag-list"> <li class="tag">css</li> <li class="tag">layout</li> <li class="tag">flexbox</li> <li class="tag">every-layout</li> <li class="tag">responsive-design</li> </ul>
.tag-list {
list-style: none;
padding: 0;
margin: 0;
}
.tag {
padding: 4px 12px;
border-radius: 999px;
background: #eef2ff;
color: #3730a3;
font-size: 0.875rem;
}Pattern 2: button group / toolbar
<div class="cluster toolbar"> <button>Save</button> <button>Duplicate</button> <button>Archive</button> <button class="danger">Delete</button> </div>
.toolbar {
gap: 0.5rem;
}
.toolbar button {
padding: 8px 16px;
border-radius: 6px;
border: 1px solid #d1d5db;
background: white;
}
.toolbar .danger {
color: #b91c1c;
border-color: #fca5a5;
}Pattern 3: nav items with a pushed-right group
A common variation: some items cluster on the left, others cluster on the right, in the same row. justify-content: space-between splits the two groups apart while each group still clusters internally.
<nav class="cluster nav">
<div class="cluster nav-left">
<a href="/">Home</a>
<a href="/docs">Docs</a>
<a href="/blog">Blog</a>
</div>
<div class="cluster nav-right">
<a href="/login">Log in</a>
<button>Sign up</button>
</div>
</nav>.nav {
justify-content: space-between;
}
.nav-left,
.nav-right {
gap: 1.5rem;
}Alignment variations
Property | Effect |
|---|---|
| Default — items pack to the start |
| Cluster is centered as a group, useful for a centered pill of filters |
| Pushes the first and last groups to opposite edges |
| Aligns wrapped items to the top of their row instead of centering, useful when item heights vary a lot |
Real component: filter chips
<div class="cluster filter-bar"> <span class="filter-label">Filter by:</span> <button class="chip chip--active">All</button> <button class="chip">In stock</button> <button class="chip">On sale</button> <button class="chip">New arrivals</button> </div>
.filter-bar {
gap: 0.5rem;
align-items: center;
}
.filter-label {
font-weight: 600;
color: #6b7280;
}
.chip {
padding: 6px 14px;
border-radius: 999px;
border: 1px solid #d1d5db;
background: white;
font-size: 0.875rem;
}
.chip--active {
background: #111827;
color: white;
border-color: #111827;
}Cluster handles unpredictable item counts — add or remove chips freely, wrapping just works
No fixed widths anywhere — every item sizes to its own content
The same
.clusterclass works for tags, buttons, nav items, and breadcrumbs with only spacing tweaksCombine with
flex-wrap: wrapon nested clusters for grouped-then-clustered UI (like the nav example above)
Pattern 4: breadcrumbs
A breadcrumb trail is a cluster of links with a separator between each — the cluster handles the wrapping and spacing, while a pseudo-element draws the separator without adding extra markup.
<nav class="cluster breadcrumbs" aria-label="Breadcrumb"> <a href="/">Home</a> <a href="/docs">Docs</a> <a href="/docs/css">CSS</a> <span aria-current="page">Cluster Layout</span> </nav>
.breadcrumbs {
gap: 0.4rem;
font-size: 0.875rem;
}
.breadcrumbs > * + *::before {
content: "/";
margin-right: 0.4rem;
color: #9ca3af;
}/ in an ::before pseudo-element instead of typing it directly in the markup keeps it out of the accessible text content — screen readers reading the breadcrumb do not announce a stray slash between every link.Pattern 5: avatar stack (overlapping cluster)
A slight variation: negative margin pulls cluster items to overlap instead of spacing them apart, common for "N people are viewing this" avatar groups.
<div class="cluster avatar-stack"> <img class="avatar" src="/a1.jpg" alt="Alex" /> <img class="avatar" src="/a2.jpg" alt="Sam" /> <img class="avatar" src="/a3.jpg" alt="Jo" /> <span class="avatar avatar--count">+5</span> </div>
.avatar-stack {
gap: 0; /* overlapping instead of spaced */
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
border: 2px solid white;
margin-left: -8px; /* overlap the previous avatar */
}
.avatar:first-child {
margin-left: 0;
}
.avatar--count {
display: flex;
align-items: center;
justify-content: center;
background: #e5e7eb;
font-size: 0.75rem;
font-weight: 600;
}Nested clusters
Clusters compose naturally — a toolbar cluster can contain a smaller cluster of icon buttons on one side and a cluster of text links on the other, each wrapping independently while the outer cluster manages the split between them.
.toolbar-outer {
justify-content: space-between;
}
.toolbar-outer .cluster {
gap: 0.5rem; /* inner clusters can use their own tighter spacing */
}Cluster handles unpredictable item counts — add or remove chips freely, wrapping just works
No fixed widths anywhere — every item sizes to its own content
The same
.clusterclass works for tags, buttons, nav items, breadcrumbs, and avatar stacks with only spacing tweaksCombine with
flex-wrap: wrapon nested clusters for grouped-then-clustered UI (like the nav example above)Negative
margin(instead ofgap) turns the same recipe into an overlapping stack, useful for avatar groups