Cluster
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.
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
gapproperty for spacing (no margins needed) - Alignment:
align-itemsfor cross-axis alignment - Justification:
justify-contentfor main-axis distribution - Children: Direct children (no wrapper elements needed)
Usageβ
Import the component:
import { Cluster } from '@grasdouble/lufa_design-system';
Basic usageβ
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β
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β
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β
| Value | Size | Description | Use Case |
|---|---|---|---|
tight | 4px | Minimal spacing | Compact tags, dense layouts |
compact | 8px | Close spacing | Small badges, inline pills |
default | 16px | Standard spacing | General purpose, buttons |
comfortable | 24px | Generous spacing | Navigation, large buttons |
spacious | 32px | Maximum spacing | Hero sections, marketing |
Propsβ
| Prop | Type | Default | Description |
|---|---|---|---|
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 |
children | ReactNode | - | Elements to arrange in cluster |
className | string | - | 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"witharia-labelfor navigation regions. - Add
role="list"withrole="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: 4pxcompact: 8pxdefault: 16pxcomfortable: 24pxspacious: 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β
- Use semantic HTML via the
asprop for landmarks and lists - Use
spacingtokens for consistent rhythm across layouts - Combine
alignandjustifyfor precise positioning - Prefer Cluster over manual flex layouts for consistency
- Use
role="list"withrole="listitem"for tag collections
- Don't nest interactive elements (e.g., button inside a link)
- Don't add margins to Cluster children (use
spacingprop 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
Related Componentsβ
- 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