Branko Tomić

How to Handle Spacing Between UI Components

TL;DR as a rule of thumb, parent component should maintain spacing between child components in order to preserve modularity of the design system.

In my experience, improper spacing management is one of the most common ways a design system becomes misused.

In order to preserve modularity of UI components in a design system, you should properly separate spacing concerns in your layout.

When you are driving your car at a high speed on a highway, you need to maintain greater distance between your car and the car in front of you than if you are driving in a city. To ensure optimal space between them, imagine that each car on the highway had a long rod protruding from the front of it.

Now imagine if you dropped these cars with their rods in a busy city setting. The traffic would grind to a halt.

const Car = () => (
  <div style={{
    width: 50,
    height: 100,
    backgroundColor: 'red',
    marginTop: 200 // the “rod” of our car
  }} />
)

const Highway = ({children}) => (
  <div style={{display: 'flex', flexDirection: 'column'}}>
    {children}
  </div>
)

const City = ({children}) => (
  <div style={{display: 'flex', flexDirection: 'column'}}>
    {children}
  </div>
)
  
const World = () => (
  <Traffic>
    {/* All is good ✅ */}
    <Highway>
      <Car />
      <Car />
      <Car />
    </Highway>

    {/* Chaos! Panic! Mayhem! 💥 */}
    <City>
      <Car />
      <Car />
      <Car />
    </City>
  </Traffic>
)

It is the same with components (if you disregard all the ways cars and UI components differ).

If you have a button or a card component with built-in margins, you are likely going to run into a scenario where these margins are a hindrance to your layout.

Component should not blindly assume spacing in different contexts.

The trade-off of modularity is a little bit of extra work (and possibly, code).

So, what options do we have?

Solutions

As a rule of thumb, the parent which contains the component should worry about spacing between its children and not the child itself.

Use “Stack” component

The sole purpose of this component is to maintain proper spacing between its children. It is a very useful tool and easy to build if you are creating components from scratch.

An illustration of the stack component represented as a row of four buttons with equal spacing between them

<Stack spacing={20}>
  <Button/>
  <Button/>
  <Button/>
  <Button/>
</Stack>

Or, if you want to stick to the (this time, rodless) car analogy:

<Stack spacing={200}> // Highway
  <Car/>
  <Car/>
  <Car/>
</Stack>

<Stack spacing={15}> // City
  <Car/>
  <Car/>
  <Car/>
</Stack>

Here is how MUI, Ant and Bootstrap implement these components.

Pros

Cons

Use wrapper component

This works best when you have a single component that needs spacing.

An illustration showing three buttons. The first and the last are using a wrapper component with margins applied. This results in spacing between the buttons.

The purpose of the wrapper is just to position the nested component in the layout.

<Layout>
  <SomeComponent/>
  <Box marginRight={20}>
    <Component/>
  </Box>
  <Component/>
  <Box marginLeft={40}>
    <Component/>
  </Box>
</Layout>

Pros

Cons

Provide the component with optional spacing

Provide your component with a spacing prop that can be used to maintain spacing between components.

An illustration showing one button pushing the other

const Component = ({spacing}) => (
  <div style={{marginRight: spacing || false}}/>
)

const App = () => (
  <Layout>
    <Component spacing={20}/>
  </Layout>
)

Pros

Cons

Conclusion

There is no one-size-fits-all solution. Choose a solution depending on the use case. Sometimes, the best course of action is to use a combination of these solutions.

The rule of thumb is, let the parent component worry about spacing between its children.

Important note

Spacing concerns do not apply to the padding inside the component itself. Padding should provide permanent spacing between the content and the edge of the component.