Stop using margin, use Spacer component instead!

A better way of handling spacing between components in React

Thor Chen
JavaScript in Plain English

--

When coding for UI layout, we need something to represent the spacing between elements, and yes, we have been using margin for a long time.

However, when using a component-based framework such as React, we may need to rethink if margin is still the best choice.

Here are my thoughts:

The problem of margin

First of all, I would say it is not good to use margin on the component’s top-level, because it breaks the isolation of components.

// A React component with margin set on top-level
const MyComponent = () => {
return <div style="margin: 1rem">...</div>;
};
  • margin here is not just affecting the component itself, but also affecting other components (e.g., it is pushing off another component / element sitting next to it)
  • If we want to place this component in a different context or layout, margin would get in the way of resuse

Then, what about using margin inside a component (not on the top-level)? It seems to be fine at the first glance, but when you extracting a part of the existing component to assemble a new component for whatever reason (e.g., reuse / isolate / simplify / performance), it will bring us back to the problem of having margin on the top-level again :(

Solution: Spacer component

Instead of regarding empty spaces between components or elements as margin, let’s imagine that they are actually components — components that are dedicated to represent the empty space, and we can call them Spacer.

It is not something completely new to the UI programming, but something already widely used in many UI frameworks (especially on mobile):

Why not having one for the Web / React? Here we come:

Once we abstract the spacing as a decidated component, we won’t have the problem of breaking isolation by using margin! All components don’t need to care about what is the required spacing around them — the parent component will take care of that via using Spacer components, which makes every component really self-contained and reusable.

A simple exaple would be using Spacer to represent the vertical gaps between parts of an article:

<Article>
<Header />
<Spacer y={2} />
<Section1 />
<Spacer y={1} />
<Section2 />
<Spacer y={2} />
<Footer />
</Article>

A possible implementation of the Spacer component (based on the styled system of Material-UI) could be:

Spacer component based on the styled system of Material-UI

That is, you could either specify the width , height or flexBasis as the value that would be multiplied by theme.spacing (same as other spacing props of Box)

Going further: layout component using Spacer internally

There is a lot of chances that we may want to make spacing between components / elements even, and it is not elegant to drop in many Spacer components with the same props. In this case, a Container component can definitely help.

For example, we may have a Flexbox component as the container, which accepts a spacing prop to config the Spacer components inserted between its children items (some other articles may reword it as Stack components).

<Article>
<Flexbox flexDirection="column" spacing={1}>
<Header />
<MainContent1 />
<MainContent2 />
<Footer />
</Flexbox>
</Article>

A possible implementation of the Flexbox component (based on the styled system of Material-UI) could be:

Flexbox component based on the styled system of Material-UI

Bonus: dynamic spacing in flexbox

Apart from making the component self-contained and reusable, Spacer component brings us one more benefit that we are not able to get from margin easily— dynamic spacing in flexbox.

This is the natural outcome of turning spacing into components — we can now treat them as flexbox items which can grow and shrink!

For exampe, imagine we have a layout below (the number in picture indicates the width of the box):

Original layout

Given the conditions:

  • the width of two left items are fixed
  • the right item and spacing can be shrinked
  • we want to make the spacing shrink 2x faster than the right item while resizing the container.

It would be straightforward to get something like this:

<Flexbox>
<Box flexBasis={100} flexShrink={0} />
<Box flexBasis={100} flexShrink={0} />
<Spacer flexBasis={200} flexShrink={2} />
<Box flexBasis={200} flexShrink={1} />
</Flexbox>

Based on that, when the right Box shrink to 150, the Spacer would shrink to 100.

Shrinked layout

Conclusion

Let’s stop using margin!

It breaks the isolation of components and makes them harder to be reused in different contexts.

Spacer component and layout components based on it (e.g., Flexbox / Stack) will solve these issues, and they can bring extra benefits when you want more flexibility on spacing!

Updates after the original post

  1. Flexbox now has a gap property to define the spacing between its children elements, but it is not supported in all major browsers yet (no IE, no Safari < 14.1). Once it is widely adopted by mordern browsers and users, using gap with flexbox should be the recommended solution in most use cases.
    (Tip: if you use the Flexbox component approach, then it should be easy to switch from <Spacer/> to gap in the future as it is just an internal implementation change, and <Spacer /> will still be useful when you have extra and/or special needs to represent the spacing between components)
  2. Re: Performance, the essential way to avoid performance issues with long list of items is virtualization (or “render in window”) whether you use empty div elements or not — if you don’t virtualize, the long list hurts the performance whatsoever; if you do virtualize, then why bother with a few more DOM elements in a short list?

Related articles

--

--