smithery/dylantarre

layout-primitives

Builds 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.

Installation

$ npx skills add smithery/dylantarre --skill layout-primitives

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/dylantarre · top by installs.

npx skills add smithery/dylantarre

Browse all from smithery/dylantarre

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 15,600 B
  • docs SUMMARY.md 244 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Layout Primitives

Overview

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.

When to Use

  • Setting up layout components for a design system
  • Creating composable layout building blocks
  • Replacing repetitive flexbox/grid patterns
  • Building layouts that adapt without media queries
  • Implementing consistent spacing throughout an app

Quick Reference: Core Primitives

Primitive Purpose Primary CSS
Stack Vertical spacing flex-direction: column + gap
Cluster Horizontal wrapping groups flex-wrap: wrap + gap
Sidebar Two-column with minmax flex-wrap: wrap + basis
Switcher Stack/row based on width flex-wrap + container query
Center Centered max-width max-width + margin-inline: auto
Cover Vertically centered hero min-height + flex
Grid Auto-fill responsive grid grid-template-columns: repeat(auto-fill)
Frame Aspect ratio container aspect-ratio
Reel Horizontal scroll overflow-x: auto + flex-wrap: nowrap
Box Spacing/padding container padding

Stack

Vertical rhythm with consistent spacing.

CSS

.stack {
  display: flex;
  flex-direction: column;
}

.stack > * + * {
  margin-block-start: var(--stack-space, 1.5rem);
}

/* Or with gap (modern, simpler) */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--stack-space, 1.5rem);
}

/* Variants */
.stack[data-space="sm"] { --stack-space: 0.5rem; }
.stack[data-space="md"] { --stack-space: 1rem; }
.stack[data-space="lg"] { --stack-space: 2rem; }
.stack[data-space="xl"] { --stack-space: 4rem; }

React Component

interface StackProps {
  space?: 'sm' | 'md' | 'lg' | 'xl';
  as?: React.ElementType;
  children: React.ReactNode;
  className?: string;
}

export function Stack({
  space = 'md',
  as: Component = 'div',
  children,
  className,
}: StackProps) {
  return (
    <Component
      className={clsx('stack', className)}
      data-space={space}
    >
      {children}
    </Component>
  );
}

// Usage
<Stack space="lg">
  <h1>Title</h1>
  <p>Paragraph</p>
  <p>Another paragraph</p>
</Stack>

Tailwind Utility

// Tailwind approach
<div className="flex flex-col gap-4">
  {/* children */}
</div>

// With custom component
function Stack({ space = 4, children }) {
  return (
    <div className={`flex flex-col gap-${space}`}>
      {children}
    </div>
  );
}

Cluster

Horizontal grouping that wraps naturally.

CSS

.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cluster-space, 1rem);
  align-items: var(--cluster-align, center);
  justify-content: var(--cluster-justify, flex-start);
}

/* Variants */
.cluster[data-justify="center"] { justify-content: center; }
.cluster[data-justify="space-between"] { justify-content: space-between; }
.cluster[data-justify="end"] { justify-content: flex-end; }

.cluster[data-align="start"] { align-items: flex-start; }
.cluster[data-align="end"] { align-items: flex-end; }
.cluster[data-align="stretch"] { align-items: stretch; }

React Component

interface ClusterProps {
  space?: 'xs' | 'sm' | 'md' | 'lg';
  justify?: 'start' | 'center' | 'end' | 'space-between';
  align?: 'start' | 'center' | 'end' | 'stretch';
  children: React.ReactNode;
  className?: string;
}

export function Cluster({
  space = 'md',
  justify = 'start',
  align = 'center',
  children,
  className,
}: ClusterProps) {
  return (
    <div
      className={clsx('cluster', className)}
      data-space={space}
      data-justify={justify}
      data-align={align}
    >
      {children}
    </div>
  );
}

// Usage
<Cluster space="sm" justify="space-between">
  <Tag>React</Tag>
  <Tag>TypeScript</Tag>
  <Tag>CSS</Tag>
</Cluster>

Sidebar

Two-column layout with a sidebar that stacks below breakpoint.

CSS

.with-sidebar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sidebar-gap, 1rem);
}

.with-sidebar > :first-child {
  flex-basis: var(--sidebar-width, 20rem);
  flex-grow: 1;
}

.with-sidebar > :last-child {
  flex-basis: 0;
  flex-grow: 999;
  min-inline-size: var(--sidebar-content-min, 50%);
}

/* Sidebar on right */
.with-sidebar[data-side="right"] {
  flex-direction: row-reverse;
}

React Component

interface SidebarProps {
  sideWidth?: string;
  contentMin?: string;
  gap?: string;
  side?: 'left' | 'right';
  children: React.ReactNode;
}

export function Sidebar({
  sideWidth = '20rem',
  contentMin = '50%',
  gap = '1rem',
  side = 'left',
  children,
}: SidebarProps) {
  return (
    <div
      className="with-sidebar"
      style={{
        '--sidebar-width': sideWidth,
        '--sidebar-content-min': contentMin,
        '--sidebar-gap': gap,
      } as React.CSSProperties}
      data-side={side}
    >
      {children}
    </div>
  );
}

// Usage
<Sidebar sideWidth="250px" contentMin="60%">
  <nav>Sidebar content</nav>
  <main>Main content</main>
</Sidebar>

Switcher

Switches between horizontal and vertical based on available width.

CSS

.switcher {
  display: flex;
  flex-wrap: wrap;
  gap: var(--switcher-gap, 1rem);
}

.switcher > * {
  flex-grow: 1;
  flex-basis: calc((var(--switcher-threshold, 30rem) - 100%) * 999);
}

/* Limit number of items per row */
.switcher > :nth-last-child(n+4),
.switcher > :nth-last-child(n+4) ~ * {
  flex-basis: 100%;
}

React Component

interface SwitcherProps {
  threshold?: string;
  gap?: string;
  limit?: number;
  children: React.ReactNode;
}

export function Switcher({
  threshold = '30rem',
  gap = '1rem',
  limit = 4,
  children,
}: SwitcherProps) {
  return (
    <div
      className="switcher"
      style={{
        '--switcher-threshold': threshold,
        '--switcher-gap': gap,
      } as React.CSSProperties}
    >
      {children}
    </div>
  );
}

// Usage - items stay horizontal until container < 30rem
<Switcher threshold="500px">
  <Card>First</Card>
  <Card>Second</Card>
  <Card>Third</Card>
</Switcher>

Center

Horizontally center with max-width.

CSS

.center {
  box-sizing: content-box;
  max-inline-size: var(--center-max, 60ch);
  margin-inline: auto;
  padding-inline: var(--center-gutter, 1rem);
}

/* Text centering variant */
.center[data-text] {
  text-align: center;
}

/* Intrinsic centering (shrink-wrap) */
.center[data-intrinsic] {
  display: flex;
  flex-direction: column;
  align-items: center;
}

React Component

interface CenterProps {
  max?: string;
  gutter?: string;
  text?: boolean;
  intrinsic?: boolean;
  children: React.ReactNode;
}

export function Center({
  max = '60ch',
  gutter = '1rem',
  text = false,
  intrinsic = false,
  children,
}: CenterProps) {
  return (
    <div
      className="center"
      style={{
        '--center-max': max,
        '--center-gutter': gutter,
      } as React.CSSProperties}
      data-text={text || undefined}
      data-intrinsic={intrinsic || undefined}
    >
      {children}
    </div>
  );
}

// Usage
<Center max="800px">
  <article>Centered content with max width</article>
</Center>

Cover

Full-height section with vertically centered content.

CSS

.cover {
  display: flex;
  flex-direction: column;
  min-block-size: var(--cover-min-height, 100vh);
  padding: var(--cover-padding, 1rem);
}

.cover > * {
  margin-block: var(--cover-space, 1rem);
}

.cover > :first-child:not([data-centered]) {
  margin-block-start: 0;
}

.cover > :last-child:not([data-centered]) {
  margin-block-end: 0;
}

.cover > [data-centered] {
  margin-block: auto;
}

React Component

interface CoverProps {
  minHeight?: string;
  padding?: string;
  children: React.ReactNode;
}

export function Cover({ minHeight = '100vh', padding = '1rem', children }: CoverProps) {
  return (
    <div
      className="cover"
      style={{
        '--cover-min-height': minHeight,
        '--cover-padding': padding,
      } as React.CSSProperties}
    >
      {children}
    </div>
  );
}

export function CoverCenter({ children }: { children: React.ReactNode }) {
  return <div data-centered>{children}</div>;
}

// Usage - header at top, content centered, footer at bottom
<Cover>
  <header>Logo</header>
  <CoverCenter>
    <h1>Hero Content</h1>
  </CoverCenter>
  <footer>Footer</footer>
</Cover>

Grid

Responsive grid without media queries.

CSS

.grid {
  display: grid;
  gap: var(--grid-gap, 1rem);
  grid-template-columns: repeat(
    auto-fill,
    minmax(min(var(--grid-min, 250px), 100%), 1fr)
  );
}

/* Fixed columns variant */
.grid[data-columns="2"] {
  grid-template-columns: repeat(2, 1fr);
}
.grid[data-columns="3"] {
  grid-template-columns: repeat(3, 1fr);
}
.grid[data-columns="4"] {
  grid-template-columns: repeat(4, 1fr);
}

React Component

interface GridProps {
  min?: string;
  gap?: string;
  children: React.ReactNode;
}

export function Grid({ min = '250px', gap = '1rem', children }: GridProps) {
  return (
    <div
      className="grid"
      style={{
        '--grid-min': min,
        '--grid-gap': gap,
      } as React.CSSProperties}
    >
      {children}
    </div>
  );
}

// Usage
<Grid min="300px" gap="1.5rem">
  <Card>1</Card>
  <Card>2</Card>
  <Card>3</Card>
  <Card>4</Card>
</Grid>

Frame

Aspect ratio container for media.

CSS

.frame {
  aspect-ratio: var(--frame-ratio, 16 / 9);
  overflow: hidden;
}

.frame > img,
.frame > video,
.frame > iframe {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* Common ratios */
.frame[data-ratio="1:1"] { --frame-ratio: 1; }
.frame[data-ratio="4:3"] { --frame-ratio: 4 / 3; }
.frame[data-ratio="16:9"] { --frame-ratio: 16 / 9; }
.frame[data-ratio="21:9"] { --frame-ratio: 21 / 9; }

React Component

type FrameRatio = '1:1' | '4:3' | '16:9' | '21:9' | string;

interface FrameProps {
  ratio?: FrameRatio;
  children: React.ReactNode;
}

const ratioMap: Record<string, string> = {
  '1:1': '1',
  '4:3': '4 / 3',
  '16:9': '16 / 9',
  '21:9': '21 / 9',
};

export function Frame({ ratio = '16:9', children }: FrameProps) {
  return (
    <div
      className="frame"
      style={{
        '--frame-ratio': ratioMap[ratio] || ratio,
      } as React.CSSProperties}
    >
      {children}
    </div>
  );
}

// Usage
<Frame ratio="16:9">
  <img src="/hero.jpg" alt="Hero" />
</Frame>

Reel

Horizontal scrolling container.

CSS

.reel {
  display: flex;
  gap: var(--reel-gap, 1rem);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-color: var(--color-border-primary) transparent;
}

.reel > * {
  flex: 0 0 var(--reel-item-width, auto);
}

/* Hide scrollbar (optional) */
.reel[data-hide-scrollbar] {
  scrollbar-width: none;
}

.reel[data-hide-scrollbar]::-webkit-scrollbar {
  display: none;
}

/* Snap scrolling */
.reel[data-snap] {
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--reel-gap, 1rem);
}

.reel[data-snap] > * {
  scroll-snap-align: start;
}

React Component

interface ReelProps {
  gap?: string;
  itemWidth?: string;
  snap?: boolean;
  hideScrollbar?: boolean;
  children: React.ReactNode;
}

export function Reel({
  gap = '1rem',
  itemWidth,
  snap = false,
  hideScrollbar = false,
  children,
}: ReelProps) {
  return (
    <div
      className="reel"
      style={{
        '--reel-gap': gap,
        '--reel-item-width': itemWidth,
      } as React.CSSProperties}
      data-snap={snap || undefined}
      data-hide-scrollbar={hideScrollbar || undefined}
    >
      {children}
    </div>
  );
}

// Usage
<Reel itemWidth="300px" snap>
  <Card>1</Card>
  <Card>2</Card>
  <Card>3</Card>
  <Card>4</Card>
</Reel>

Box

Padding and background container.

CSS

.box {
  padding: var(--box-padding, 1rem);
  border: var(--box-border, 0);
  background-color: var(--box-bg, transparent);
  border-radius: var(--box-radius, 0);
}

/* Invert colors */
.box[data-invert] {
  background-color: var(--color-bg-inverse);
  color: var(--color-text-inverse);
}

React Component

interface BoxProps {
  padding?: string;
  bg?: string;
  invert?: boolean;
  children: React.ReactNode;
}

export function Box({ padding = '1rem', bg, invert, children }: BoxProps) {
  return (
    <div
      className="box"
      style={{
        '--box-padding': padding,
        '--box-bg': bg,
      } as React.CSSProperties}
      data-invert={invert || undefined}
    >
      {children}
    </div>
  );
}

Composition Examples

Page Layout

<Stack space="xl">
  <Center max="1200px">
    <header>
      <Cluster justify="space-between">
        <Logo />
        <nav>
          <Cluster space="md">
            <a href="#">Home</a>
            <a href="#">About</a>
            <a href="#">Contact</a>
          </Cluster>
        </nav>
      </Cluster>
    </header>
  </Center>

  <Cover minHeight="80vh">
    <CoverCenter>
      <Center text>
        <Stack space="lg">
          <h1>Welcome</h1>
          <p>Hero content here</p>
        </Stack>
      </Center>
    </CoverCenter>
  </Cover>

  <Center max="1200px">
    <Sidebar sideWidth="300px">
      <aside>
        <Stack space="md">
          <h2>Sidebar</h2>
          {/* sidebar content */}
        </Stack>
      </aside>
      <main>
        <Stack space="lg">
          <Grid min="300px">
            <Card>1</Card>
            <Card>2</Card>
            <Card>3</Card>
          </Grid>
        </Stack>
      </main>
    </Sidebar>
  </Center>
</Stack>

Card Grid

<Grid min="280px" gap="1.5rem">
  {items.map(item => (
    <Box key={item.id} padding="1.5rem">
      <Stack space="md">
        <Frame ratio="16:9">
          <img src={item.image} alt="" />
        </Frame>
        <h3>{item.title}</h3>
        <p>{item.description}</p>
        <Cluster justify="space-between">
          <span>{item.date}</span>
          <a href={item.url}>Read more</a>
        </Cluster>
      </Stack>
    </Box>
  ))}
</Grid>

Tailwind Implementation

// tailwind.config.js - custom utilities
module.exports = {
  theme: {
    extend: {
      // Custom layout utilities can use @apply or plugins
    },
  },
  plugins: [
    function({ addComponents }) {
      addComponents({
        '.stack': {
          display: 'flex',
          flexDirection: 'column',
          gap: 'var(--stack-space, 1rem)',
        },
        '.cluster': {
          display: 'flex',
          flexWrap: 'wrap',
          gap: 'var(--cluster-space, 1rem)',
          alignItems: 'center',
        },
        // ... etc
      });
    },
  ],
};

Or use utility classes directly:

<!-- Stack -->
<div class="flex flex-col gap-4">

<!-- Cluster -->
<div class="flex flex-wrap gap-2 items-center">

<!-- Center -->
<div class="max-w-prose mx-auto px-4">

<!-- Grid -->
<div class="grid gap-4 grid-cols-[repeat(auto-fill,minmax(250px,1fr))]">

Resources