DEV Community

Cover image for Essential Layout Components For Your Design System
Nayaab Khan
Nayaab Khan

Posted on

Essential Layout Components For Your Design System

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

<Stack space="large">
  <Placeholder height={48} />
  <Placeholder height={48} />
  <Placeholder height={48} />
</Stack>
Enter fullscreen mode Exit fullscreen mode

A vertical stack with 3 placeholders.

API

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
}
Enter fullscreen mode Exit fullscreen mode

2. Columns and Column

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

<Columns space="medium">
  <Column><Placeholder height={60} /></Column>
  <Column><Placeholder height={60} /></Column>
</Columns>
Enter fullscreen mode Exit fullscreen mode

Two columns with placeholders side-by-side

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

A video of columns collapsing to a stack below certain size.

Alignments

Columns support alignments in both directions.

Columns with all three possible vertical alignments: start, center, and end

Columns with all three possible horizontal alignments: start, center, and end

Individual column sizing

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

<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>
Enter fullscreen mode Exit fullscreen mode

Multiple rows of columns with different implicit and explicit sizes.

API

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
}
Enter fullscreen mode Exit fullscreen mode

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.

<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>
Enter fullscreen mode Exit fullscreen mode

A list of badges within a cluster that wrap

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

API

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
}
Enter fullscreen mode Exit fullscreen mode

AutoGrid

Auto grid is basically a component abstraction for repeat-auto-minmax.

<AutoGrid space="medium" minItemWidth={120}>
  <Placeholder />
  <Placeholder />
  <Placeholder />
  <Placeholder />
  <Placeholder />
  <Placeholder />
  <Placeholder />
</AutoGrid>
Enter fullscreen mode Exit fullscreen mode

An AutoGrid that responsively lays out its items.

API

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
}
Enter fullscreen mode Exit fullscreen mode

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.

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! 👋🏻

Latest comments (3)

Collapse
 
philw_ profile image
Phil Wolstenholme

Anyone who is trying to make as much of their layout code use only CSS rather than CSS and JavaScript might be interested to know that the Collapsing component here can be built with CSS only, no ResizeObserver required!

Here's an example: every-layout.dev/demos/switcher-basic . The example is taken from a very good ebook called Every Layout: every-layout.dev/layouts/switcher.

heydonworks.com/article/the-flexbo... is a free-to-all blog post that describes the same technique, just in an older version. The blog post uses margin to add spacing, but the Every Layout version uses gap now that browsers support gap in Flexbox layouts.

Collapse
 
nayaabkhan profile image
Nayaab Khan

That’s right. I initially tried to use the “Switcher” CSS but don’t remember why I had to abandon it. Might be worth going back a retry.

Also, EveryLayout.dev is an excellent resource and worth getting a copy. The name “Cluster” comes right from it too. 😄

Collapse
 
philw_ profile image
Phil Wolstenholme

I thought there some similarity around your shared use of the name 'Cluster' but wasn't sure if that was a coincidence or not! 😄