Essential Layout Components For Your Design System
Tags: design, react, css
Published: 2022-03-20 09:25:20

If your app uses a component based library/framework like React, Vue, or Svelte, leverage the power of re-usable layout components. Go a level higher in the abstraction ladder.

In this article, I'll show you a system of solid layout components with their features and API. These are common _and_ versatile enough to be used for most products and teams. I promise you once you meet these layout, you'll start seeing them everywhere.

In the end, there's a CodeSandbox that uses all these components to build a real use case: a responsive listing of product cards.

---

I'd like to credit upfront the excellent [Braid Design System](https://seek-oss.github.io/braid-design-system/) by [Seek-OSS](https://github.com/seek-oss). All the components are heavily inspired from it. You should definitely check it out.

---

## 1. Stack

The most common way to layout your interface elements is a vertical **Stack**. They are everywhere.

```tsx
<Stack space="large">
  <Placeholder height={48} />
  <Placeholder height={48} />
  <Placeholder height={48} />
</Stack>
```

![A vertical stack with 3 placeholders.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0fcqdq30zvt0kuo67lu.png)

### API

```tsx
type StackProps = {
  // The element used for the root node.
  as?: 'div' | 'span' | 'ol' | 'ul' // Feel free to change these as per your uses
  // Defines the spacing between the items.
  space?: 'small' | 'medium' | 'large' // All of your spacing tokens
  // Sets the horizontal alignment of the items.
  align?: 'start' | 'center' | 'end' | 'stretch'
  // The items to layout in the stack.
  children: React.ReactNode
}
```

## 2. Columns and Column

The next most common layout is a single row of **Columns**.

```tsx
<Columns space="medium">
  <Column><Placeholder height={60} /></Column>
  <Column><Placeholder height={60} /></Column>
</Columns>
```
![Two columns with placeholders side-by-side](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oriwwkes35yktd3bvju8.png)

### Collapsing

A common need is to collapse the columns to a stack below a certain size. This can be implemented using `ResizeObserver` (and hopefully soon using [**Container Queries**](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries)).

![A video of columns collapsing to a stack below certain size.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqj4w7yh2e7o1y8wj1l2.gif) 

### Alignments

**Columns** support alignments in both directions.

![Columns with all three possible vertical alignments: start, center, and end](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1u7h1a65mdddava4vqp1.png)

![Columns with all three possible horizontal alignments: start, center, and end](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ee4nzn7z7go5bgbxtbb.png)
 

### Individual column sizing

We have a **Column** component to wrap each column that provides ability to size itself in different implicit and explicit sizes.

```tsx
<Stack space="medium">
  <Columns space="xsmall">
    <Column width="content">
      <Placeholder height={30} label="content" />
    </Column>
    <Column>
      <Placeholder height={30} label="fluid" />
    </Column>
  </Columns>
  <Columns space="xsmall">
    <Column width="1/5">
      <Placeholder height={30} label="1/5" />
    </Column>
    <Column>
      <Placeholder height={30} label="fluid" />
    </Column>
  </Columns>
  <Columns space="xsmall">
    <Column width="1/4">
      <Placeholder height={30} label="1/4" />
    </Column>
    <Column>
      <Placeholder height={30} label="fluid" />
    </Column>
  </Columns>
  <Columns space="xsmall">
    <Column width="1/3">
      <Placeholder height={30} label="1/3" />
    </Column>
    <Column>
      <Placeholder height={30} label="fluid" />
    </Column>
  </Columns>
  ...
</Stack>
```

![Multiple rows of columns with different implicit and explicit sizes.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5goopa38da3an0ce3rbj.png)
 
### API

```tsx
type ColumnsProps = {
  // The element used for the root node.
  as?: 'div' | 'span' | 'ol' | 'ul'
  // Defines the spacing between the items.
  space?: 'small' | 'medium' | 'large' // All of your spacing tokens
  // Collapse items to a stack below this size.
  collapseBelow?: number
  // Sets the horizontal alignment of the items.
  align?: 'start' | 'center' | 'end' | 'stretch'
  // Sets the vertical alignment of the items.
  alignY?: 'start' | 'center' | 'end'
  // The columns.
  children: React.ReactNode
}
```

## Cluster

This is very similar to **Columns** but it allows the items to wrap if needed. Most commonly used to show a list of badges.

```tsx
<Cluster space="small">
  <Tag tone="positive" weight="subtle" label="Confirmed" icon="IconCalendar" />
  <Tag tone="cautional" weight="subtle" label="Pending" icon="IconApple" />
  <Tag tone="informational" weight="subtle" label="Hired" icon="IconRemote" />
  <Tag tone="neutral" weight="subtle" label="Refunded" icon="IconLike" />
  <Tag tone="promotional" weight="subtle" label="New" icon="IconHeart" />
</Cluster>
```
 
![A list of badges within a cluster that wrap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k612s6zi9zniv9o6frpl.png)

**Clusters** also support collapsing and alignments in both directions just like **Columns**.

### API

```tsx
type ClusterProps = {
  // The element used for the root node.
  as?: 'div' | 'span' | 'ol' | 'ul'
  // Defines the spacing between the items.
  space?: 'small' | 'medium' | 'large'
  // Collapse items to a stack below this size.
  collapseBelow?: number
  // Sets the horizontal alignment of the items.
  align?: 'start' | 'center' | 'end'
  // Sets the vertical alignment of the items.
  alignY?: 'start' | 'center' | 'end'
  // The items to lay out in this cluster.
  children: React.ReactNode
}
```

## AutoGrid

Auto grid is basically a component abstraction for [**repeat-auto-minmax**](https://web.dev/patterns/layout/repeat-auto-minmax/).

```tsx
<AutoGrid space="medium" minItemWidth={120}>
  <Placeholder />
  <Placeholder />
  <Placeholder />
  <Placeholder />
  <Placeholder />
  <Placeholder />
  <Placeholder />
</AutoGrid>
```

![An AutoGrid that responsively lays out its items.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pqa4yk7vzk0aqwn486th.gif)
 
### API

```tsx
type AutoGridProps = {
  // The element used for the root node.
  as?: 'div' | 'section' | 'ul' | 'ol'
  // The minimum width for items to shrink to before the grid starts wrapping to make space.
  minItemWidth: number
  // The gap between the items or the column gap if spaceY is present.
  space?: 'small' | 'medium' | 'large'
  // The row gap between the items.
  spaceY?: 'small' | 'medium' | 'large'
  // Items inside the AutoGrid.
  children: React.ReactNode
}
```

## Layout Components In Action

Now that we've seen the components. Let's see them in action.

I've used NextJS and Vanilla-Extract to style the components. But you can, of course, use any solution down to plain CSS.

{% embed https://codesandbox.io/embed/jolly-darkness-d7v17x?fontsize=14&hidenavigation=1&theme=dark %}

Let's appreciate all the functionality going on in the demo:
1. We have a sidebar that collapses on smaller screens.
2. The product grid is responsive
3. The card itself has two layouts: horizontal and vertical and it responds based on available space.
4. The badges inside the **Cluster can wrap if required**.
5. The buttons also respond to available size and collapse if too narrow.

All this with just a system of well thought out components. Totally declarative, readable, and re-usable.

And we've only covered the most basic common denominator of layout components. You can do much more depending on your use case. Like:

- Add a **Spacer** component to create flexible space between **Stack** or **Column** items.
- Add a **prop** to make column items fill the available space inside the **Column**.
- Add a **FixedGrid** with explicit column and row counts to create a rigid grid.

But I hope that you find this system inspiring and already have ideas to build on top of this. Feel free to fork the sandbox or the repo to use it as a starting point.

I'll see you next time! 👋🏻