$npx -y skills add dylantarre/design-system-skills --skill layout-primitivesBuilds composable layout components (Stack, Cluster, Grid, Sidebar, Center, Cover). Use when creating layout systems, spacing compositions, or Every Layout-style intrinsic design patterns that adapt without breakpoints.
| 1 | # Layout Primitives |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Build reusable layout primitives (Stack, Cluster, Sidebar, Grid) that compose to create any layout. Based on Every Layout patterns - intrinsic design that adapts without breakpoints. |
| 6 | |
| 7 | ## When to Use |
| 8 | |
| 9 | - Setting up layout components for a design system |
| 10 | - Creating composable layout building blocks |
| 11 | - Replacing repetitive flexbox/grid patterns |
| 12 | - Building layouts that adapt without media queries |
| 13 | - Implementing consistent spacing throughout an app |
| 14 | |
| 15 | ## Quick Reference: Core Primitives |
| 16 | |
| 17 | | Primitive | Purpose | Primary CSS | |
| 18 | |-----------|---------|-------------| |
| 19 | | Stack | Vertical spacing | `flex-direction: column` + gap | |
| 20 | | Cluster | Horizontal wrapping groups | `flex-wrap: wrap` + gap | |
| 21 | | Sidebar | Two-column with minmax | `flex-wrap: wrap` + basis | |
| 22 | | Switcher | Stack/row based on width | `flex-wrap` + container query | |
| 23 | | Center | Centered max-width | `max-width` + `margin-inline: auto` | |
| 24 | | Cover | Vertically centered hero | `min-height` + flex | |
| 25 | | Grid | Auto-fill responsive grid | `grid-template-columns: repeat(auto-fill)` | |
| 26 | | Frame | Aspect ratio container | `aspect-ratio` | |
| 27 | | Reel | Horizontal scroll | `overflow-x: auto` + `flex-wrap: nowrap` | |
| 28 | | Box | Spacing/padding container | `padding` | |
| 29 | |
| 30 | --- |
| 31 | |
| 32 | ## Stack |
| 33 | |
| 34 | Vertical rhythm with consistent spacing. |
| 35 | |
| 36 | ### CSS |
| 37 | |
| 38 | ```css |
| 39 | .stack { |
| 40 | display: flex; |
| 41 | flex-direction: column; |
| 42 | } |
| 43 | |
| 44 | .stack > * + * { |
| 45 | margin-block-start: var(--stack-space, 1.5rem); |
| 46 | } |
| 47 | |
| 48 | /* Or with gap (modern, simpler) */ |
| 49 | .stack { |
| 50 | display: flex; |
| 51 | flex-direction: column; |
| 52 | gap: var(--stack-space, 1.5rem); |
| 53 | } |
| 54 | |
| 55 | /* Variants */ |
| 56 | .stack[data-space="sm"] { --stack-space: 0.5rem; } |
| 57 | .stack[data-space="md"] { --stack-space: 1rem; } |
| 58 | .stack[data-space="lg"] { --stack-space: 2rem; } |
| 59 | .stack[data-space="xl"] { --stack-space: 4rem; } |
| 60 | ``` |
| 61 | |
| 62 | ### React Component |
| 63 | |
| 64 | ```tsx |
| 65 | interface StackProps { |
| 66 | space?: 'sm' | 'md' | 'lg' | 'xl'; |
| 67 | as?: React.ElementType; |
| 68 | children: React.ReactNode; |
| 69 | className?: string; |
| 70 | } |
| 71 | |
| 72 | export function Stack({ |
| 73 | space = 'md', |
| 74 | as: Component = 'div', |
| 75 | children, |
| 76 | className, |
| 77 | }: StackProps) { |
| 78 | return ( |
| 79 | <Component |
| 80 | className={clsx('stack', className)} |
| 81 | data-space={space} |
| 82 | > |
| 83 | {children} |
| 84 | </Component> |
| 85 | ); |
| 86 | } |
| 87 | |
| 88 | // Usage |
| 89 | <Stack space="lg"> |
| 90 | <h1>Title</h1> |
| 91 | <p>Paragraph</p> |
| 92 | <p>Another paragraph</p> |
| 93 | </Stack> |
| 94 | ``` |
| 95 | |
| 96 | ### Tailwind Utility |
| 97 | |
| 98 | ```tsx |
| 99 | // Tailwind approach |
| 100 | <div className="flex flex-col gap-4"> |
| 101 | {/* children */} |
| 102 | </div> |
| 103 | |
| 104 | // With custom component |
| 105 | function Stack({ space = 4, children }) { |
| 106 | return ( |
| 107 | <div className={`flex flex-col gap-${space}`}> |
| 108 | {children} |
| 109 | </div> |
| 110 | ); |
| 111 | } |
| 112 | ``` |
| 113 | |
| 114 | --- |
| 115 | |
| 116 | ## Cluster |
| 117 | |
| 118 | Horizontal grouping that wraps naturally. |
| 119 | |
| 120 | ### CSS |
| 121 | |
| 122 | ```css |
| 123 | .cluster { |
| 124 | display: flex; |
| 125 | flex-wrap: wrap; |
| 126 | gap: var(--cluster-space, 1rem); |
| 127 | align-items: var(--cluster-align, center); |
| 128 | justify-content: var(--cluster-justify, flex-start); |
| 129 | } |
| 130 | |
| 131 | /* Variants */ |
| 132 | .cluster[data-justify="center"] { justify-content: center; } |
| 133 | .cluster[data-justify="space-between"] { justify-content: space-between; } |
| 134 | .cluster[data-justify="end"] { justify-content: flex-end; } |
| 135 | |
| 136 | .cluster[data-align="start"] { align-items: flex-start; } |
| 137 | .cluster[data-align="end"] { align-items: flex-end; } |
| 138 | .cluster[data-align="stretch"] { align-items: stretch; } |
| 139 | ``` |
| 140 | |
| 141 | ### React Component |
| 142 | |
| 143 | ```tsx |
| 144 | interface ClusterProps { |
| 145 | space?: 'xs' | 'sm' | 'md' | 'lg'; |
| 146 | justify?: 'start' | 'center' | 'end' | 'space-between'; |
| 147 | align?: 'start' | 'center' | 'end' | 'stretch'; |
| 148 | children: React.ReactNode; |
| 149 | className?: string; |
| 150 | } |
| 151 | |
| 152 | export function Cluster({ |
| 153 | space = 'md', |
| 154 | justify = 'start', |
| 155 | align = 'center', |
| 156 | children, |
| 157 | className, |
| 158 | }: ClusterProps) { |
| 159 | return ( |
| 160 | <div |
| 161 | className={clsx('cluster', className)} |
| 162 | data-space={space} |
| 163 | data-justify={justify} |
| 164 | data-align={align} |
| 165 | > |
| 166 | {children} |
| 167 | </div> |
| 168 | ); |
| 169 | } |
| 170 | |
| 171 | // Usage |
| 172 | <Cluster space="sm" justify="space-between"> |
| 173 | <Tag>React</Tag> |
| 174 | <Tag>TypeScript</Tag> |
| 175 | <Tag>CSS</Tag> |
| 176 | </Cluster> |
| 177 | ``` |
| 178 | |
| 179 | --- |
| 180 | |
| 181 | ## Sidebar |
| 182 | |
| 183 | Two-column layout with a sidebar that stacks below breakpoint. |
| 184 | |
| 185 | ### CSS |
| 186 | |
| 187 | ```css |
| 188 | .with-sidebar { |
| 189 | display: flex; |
| 190 | flex-wrap: wrap; |
| 191 | gap: var(--sidebar-gap, 1rem); |
| 192 | } |
| 193 | |
| 194 | .with-sidebar > :first-child { |
| 195 | flex-basis: var(--sidebar-width, 20rem); |
| 196 | flex-grow: 1; |
| 197 | } |
| 198 | |
| 199 | .with-sidebar > :last-child { |
| 200 | flex-basis: 0; |
| 201 | flex-grow: 999; |
| 202 | min-inline-size: var(--sidebar-content-min, 50%); |
| 203 | } |
| 204 | |
| 205 | /* Sidebar on right */ |
| 206 | .with-sidebar[data-side="right"] { |
| 207 | flex-direction: row-reverse; |
| 208 | } |
| 209 | ``` |
| 210 | |
| 211 | ### React Component |
| 212 | |
| 213 | ```tsx |
| 214 | interface SidebarProps { |
| 215 | sideWidth?: string; |
| 216 | contentMin?: string; |
| 217 | gap?: string; |
| 218 | side?: 'left' | 'right'; |
| 219 | children: React.ReactNode; |
| 220 | } |
| 221 | |
| 222 | export function Sidebar({ |
| 223 | sideWidth = '20rem', |
| 224 | contentMin = '50%', |
| 225 | gap = '1rem', |
| 226 | side = 'left', |
| 227 | children, |
| 228 | }: SidebarProps) { |