Cluster
A transparent flex container that distributes its design-system gap to children. Replaces cards, justify-between, and other opaque wrappers.
Configure your project settings and preferences.
'use client'
import { PresentationIcon } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Cluster, Filler } from '@/components/ui/cluster'
import { Kbd } from '@/components/ui/kbd'
import { SearchIcon } from 'lucide-react'
export default function ClusterDemo() {
return (
<div className="flex flex-col gap-10 p-6">
<div className="flex flex-col gap-2">
<span className="text-muted-foreground font-mono text-xs tracking-wide uppercase">
Row with Filler
</span>
<Cluster className="max-w-sm items-stretch">
<div className="flex items-center gap-2 px-3 py-2">
<span className="text-sm font-medium">Project Setup</span>
</div>
<div className="bg-fd-background flex flex-1 items-center justify-end px-2">
<Badge variant="accent">Draft</Badge>
</div>
</Cluster>
</div>
<div className="flex flex-col gap-2">
<span className="text-muted-foreground font-mono text-xs tracking-wide uppercase">
Column layout
</span>
<Cluster
direction="col"
align="stretch"
className="max-w-sm"
bg="muted"
>
<div className="p-3 pt-2">
<span className="text-sm font-medium">Project Setup</span>
<p className="text-muted-foreground text-sm">
Configure your project settings and preferences.
</p>
</div>
<label className="focus-within:bg-accent/70 flex items-center gap-3 p-3">
<PresentationIcon className="text-muted-foreground size-4 shrink-0" />
<input
placeholder="Project name"
className="min-w-0 text-sm outline-none"
/>
</label>
<Cluster bg="muted">
<Filler />
<Button variant="secondary" className="rounded-none">
Cancel
</Button>
<Button className="rounded-none">Create</Button>
</Cluster>
</Cluster>
</div>
<div className="flex flex-col gap-2">
<span className="text-muted-foreground font-mono text-xs tracking-wide uppercase">
Input variant (sidebar pattern)
</span>
<Cluster direction="col" align="stretch" className="h-48 max-w-sm">
<label className="focus-within:bg-accent/70 flex h-10 items-center gap-3 px-3">
<SearchIcon className="text-muted-foreground size-4 shrink-0" />
<input
type="text"
placeholder="Search"
className="text-foreground placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent font-mono text-sm tracking-wide uppercase outline-none"
/>
<Kbd className="h-[2em] rounded-none px-2">⌘K</Kbd>
</label>
<Filler />
<div className="flex items-center gap-2 px-3 py-2">
<span className="text-muted-foreground text-sm">Footer</span>
</div>
</Cluster>
</div>
</div>
)
}
Installation
Core Concept
A Cluster is an invisible layout scaffold. It has no background — it only provides gap and flex behavior. Its direct children automatically receive the Cluster's bg color via a zero-specificity CSS rule, which any utility class on the child can override.
This replaces the traditional "Card" pattern (wrapper with bg + padding). In JOYCO's design system, containers are transparent — children own their own backgrounds.
Usage
import { Cluster, Filler } from '@/components/ui/cluster'Basic row
<Cluster>
<div className="px-3 py-2">
<span className="text-sm font-medium">Label</span>
</div>
<Filler />
<Badge variant="accent">Status</Badge>
</Cluster>Children inherit bg-muted by default. The <Filler /> pushes the badge to the end — never use justify-between, always use Filler for space distribution.
Column layout
<Cluster direction="col" align="stretch" bg="muted">
<div className="p-3">
<span className="text-sm font-medium">Header</span>
<p className="text-muted-foreground text-sm">Description</p>
</div>
<div className="p-3">
<Input placeholder="Field" />
</div>
<Cluster bg="muted">
<Filler />
<Button variant="secondary">Cancel</Button>
<Button>Create</Button>
</Cluster>
</Cluster>Override child background
Children can override the inherited bg with any utility class:
<Cluster>
<div className="px-3 py-2">Uses inherited bg-muted</div>
<div className="bg-fd-background px-3 py-2">Overrides to bg-fd-background</div>
</Cluster>Inline display
Use asChild with a <span> for inline contexts:
<Cluster display="inline-flex" asChild>
<span>
<Badge>Active</Badge>
<Badge variant="muted">3 tasks</Badge>
</span>
</Cluster>API
Cluster
| Prop | Type | Default | Description |
|---|---|---|---|
display | 'flex' | 'inline-flex' | 'flex' | Display mode |
direction | 'row' | 'col' | 'row' | Flex direction |
align | 'start' | 'center' | 'end' | 'stretch' | 'baseline' | 'center' | Cross-axis alignment |
wrap | boolean | false | Enable flex wrap |
bg | 'muted' | 'accent' | 'muted' | Background color inherited by direct children |
asChild | boolean | false | Render as child element via Radix Slot |
Filler
A presentational spacer that fills available space along the main axis. Renders as flex-1 with aria-hidden="true".
Use <Filler /> between children to distribute space. Never use justify-between or justify-end.
Design Principles
- Clusters are transparent — they never have a visible background
- Children are solid — every direct child should have its own visual identity (background color, etc.) but never borders or rounded corners. Borders are almost never used in this design system — the only exception is the outline button variant. The theme radius is
0rem. Clusters, Fillers, inputs, and all layout elements rely on background color contrast and spacing to create visual separation, not border lines or rounded shapes. - Filler over justify — use
<Filler />for space distribution, neverjustify-between - bg cascades down — the
bgprop sets a CSS variable that children inherit at zero specificity, overridable with anybg-*class