Skip to main content

Cluster

πŸŒ“Dark Mode Compatible

A specialized layout component for grouping collections of elements with intelligent wrapping behavior. Perfect for tags, badges, buttons, and other compact elements that need to flow naturally and wrap responsively.

Overview​

Use Cluster when you need…

  • Tag collections or badge groups that wrap automatically
  • Button groups or action toolbars with flexible layout
  • Navigation links that adapt to available space
  • Inline lists of compact elements
  • Horizontal layouts that wrap naturally on smaller screens
  • Gap-based spacing without margin collapsing issues

Pattern Origin: Based on "The Cluster" pattern by Heydon Pickering from Every Layout.

Chakra UI Equivalent: This component is the semantic equivalent of Chakra UI's Wrap component.

Live demoInteractive
ReactTypeScriptNext.jsTailwindGraphQLNode.js

Anatomy​

Cluster uses CSS flexbox with flex-wrap: wrap to automatically wrap children when space is limited.

Cluster Container (wraps automatically):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Tag β”‚ β”‚ Tag β”‚ β”‚ Tag β”‚ β”‚ Tag β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ ↕ gap ↕ gap ↕ gap β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Tag β”‚ β”‚ Tag β”‚ β”‚ Tag β”‚ (wrapped) β”‚
β”‚ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Component Structure:

  • Container: Flex container with flex-wrap: wrap
  • Gap: CSS gap property for spacing (no margins needed)
  • Alignment: align-items for cross-axis alignment
  • Justification: justify-content for main-axis distribution
  • Children: Direct children (no wrapper elements needed)

Usage​

Import the component:

import { Cluster } from '@grasdouble/lufa_design-system';

Basic usage​

src/App.tsx
import { Cluster } from '@grasdouble/lufa_design-system';

function App() {
return (
<>
<Cluster spacing="compact">
<span>React</span>
<span>TypeScript</span>
<span>Next.js</span>
<span>Tailwind</span>
</Cluster>

<Cluster spacing="default" align="center">
<button>Save</button>
<button>Preview</button>
<button>Cancel</button>
</Cluster>

<Cluster as="nav" spacing="comfortable" align="center" justify="center">
<a href="/">Home</a>
<a href="/features">Features</a>
<a href="/pricing">Pricing</a>
<a href="/contact">Contact</a>
</Cluster>
</>
);
}

Pattern: Tag collection​

src/components/SkillTags.tsx
import { Cluster } from '@grasdouble/lufa_design-system';

const tags = ['React', 'TypeScript', 'Next.js', 'Tailwind', 'GraphQL', 'Node.js'];

export function SkillTags() {
return (
<Cluster spacing="compact">
{tags.map((tag) => (
<span
key={tag}
style={{
padding: '6px 14px',
background: '#6366f1',
color: 'white',
borderRadius: '16px',
fontSize: '13px',
fontWeight: 600,
}}
>
{tag}
</span>
))}
</Cluster>
);
}

Pattern: Button toolbar​

src/components/Toolbar.tsx
import { Cluster } from '@grasdouble/lufa_design-system';

export function Toolbar() {
return (
<Cluster as="nav" spacing="default" align="center" justify="space-between">
<Cluster spacing="default">
<button>New</button>
<button>Open</button>
<button>Save</button>
</Cluster>
<Cluster spacing="default">
<button>Settings</button>
<button>Help</button>
</Cluster>
</Cluster>
);
}

Variants​

Spacing values​

ValueSizeDescriptionUse Case
tight4pxMinimal spacingCompact tags, dense layouts
compact8pxClose spacingSmall badges, inline pills
default16pxStandard spacingGeneral purpose, buttons
comfortable24pxGenerous spacingNavigation, large buttons
spacious32pxMaximum spacingHero sections, marketing

Props​

PropTypeDefaultDescription
spacing'tight' | 'compact' | 'default' | 'comfortable' | 'spacious''default'Gap between children
align'flex-start' | 'center' | 'flex-end' | 'baseline' | 'stretch''center'Cross-axis alignment (align-items)
justify'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around''flex-start'Main-axis justification (justify-content)
as'div' | 'nav' | 'ul' | 'section' | 'article''div'HTML element to render
childrenReactNode-Elements to arrange in cluster
classNamestring-Additional CSS classes

Also supports all standard HTML attributes for the underlying element.

Accessibility​

Cluster is a non-interactive container by default. Use semantic HTML and ARIA attributes for navigation or list patterns.

import { Cluster } from '@grasdouble/lufa_design-system';

export function AccessibleNav() {
return (
<Cluster as="nav" spacing="default" align="center" aria-label="Main navigation">
<a href="/">Home</a>
<a href="/features">Features</a>
<a href="/pricing">Pricing</a>
<a href="/contact">Contact</a>
</Cluster>
);
}
import { Cluster } from '@grasdouble/lufa_design-system';

export function AccessibleTagList() {
return (
<Cluster as="div" role="list" aria-label="Skills">
<div role="listitem">React</div>
<div role="listitem">TypeScript</div>
<div role="listitem">Next.js</div>
</Cluster>
);
}
  • Use as="nav" with aria-label for navigation regions.
  • Add role="list" with role="listitem" for semantic lists.
  • Ensure sufficient touch target size for interactive children (44Γ—44px minimum).
  • Maintain sufficient color contrast for text.
  • Ensure child elements are keyboard-navigable (links, buttons).

Theming & Tokens​

Cluster spacing uses semantic spacing tokens mapped to CSS gap:

  • tight: 4px
  • compact: 8px
  • default: 16px
  • comfortable: 24px
  • spacious: 32px

Spacing tokens stay consistent across themes; alignment and justification map directly to CSS flex properties.

import { Cluster } from '@grasdouble/lufa_design-system';

export function TokenSpacingExample() {
return (
<Cluster spacing="comfortable">
<button>Action 1</button>
<button>Action 2</button>
</Cluster>
);
}

Do / Don't​

Do
  • Use semantic HTML via the as prop for landmarks and lists
  • Use spacing tokens for consistent rhythm across layouts
  • Combine align and justify for precise positioning
  • Prefer Cluster over manual flex layouts for consistency
  • Use role="list" with role="listitem" for tag collections
Don't
  • Don't nest interactive elements (e.g., button inside a link)
  • Don't add margins to Cluster children (use spacing prop instead)
  • Don't use Cluster for layouts with specific column requirements (use Grid instead)
  • Don't make the Cluster itself focusable (children should be focusable)
  • Don't rely solely on color to convey meaning in badges
  • Stack - For vertical or horizontal layouts without wrapping
  • Flex - More control over flexbox properties
  • Grid - For grid-based layouts with columns/rows
  • Box - Basic layout container for padding and margins