Kyle Shevlin

Software Engineer

Snippet

Margin

edit
0 strokes bestowed

Update: This component was formerly named Spacer. It was brought to my attention that a few design systems use Spacer in the way of old spacer.gifs, arbitrarily sized empty boxes used for pushing elements around. Thus, I've renamed this component to Margin. It's more accurate anyways.


It is a mistake for reusable components to export their own margins. Doing so makes it difficult to compose them with other components to build up more complex ones. For more on this concept, I encourage you to read Max Stoiber's article, "Margin considered harmful".

When we compose components, we can use other elements to affect their layout. This can be done with one-off elements with styles applied, or we can create some declarative layout components. One of the ones I find most useful is the Margin component you see below. I have used the component you see below in a number of applications. I use a Margin all over this blog, but I make a small modification which I will show later on.

I even added some jsdoc comments and some TypeScript types for you.

type TMargin = number | string

type MarginProps = {
  children: React.ReactNode
  /**
   * Shorthand for applying margin to all sides
   */
  all?: TMargin
  /**
   * Shorthand for applying margin to left and right sides
   */
  horz?: TMargin
  /**
   * Shorthand for applying margin to top and bottom sides
   */
  vert?: TMargin
  /**
   * Applies margin to the top
   */
  top?: TMargin
  /**
   * Applies margin to the right
   */
  right?: TMargin
  /**
   * Applies margin to the bottom
   */
  bottom?: TMargin
  /**
   * Applies margin to the left
   */
  left?: TMargin
}

function Margin({
  children,
  all = 0,
  horz = 0,
  vert = 0,
  top = 0,
  right = 0,
  bottom = 0,
  left = 0,
}: MarginProps) {
  const margins = {
    ...(all && {
      marginTop: all,
      marginRight: all,
      marginBottom: all,
      marginLeft: all,
    }),
    ...(horz && {
      marginLeft: horz,
      marginRight: horz,
    }),
    ...(vert && {
      marginTop: vert,
      marginBottom: vert,
    }),
    ...(top && { marginTop: top }),
    ...(right && { marginRight: right }),
    ...(bottom && { marginBottom: bottom }),
    ...(left && { marginLeft: left }),
  }

  return <div style={margins}>{children}</div>
}

Now we can compose components together. A simple example might be a Name and Avatar component that may have different layouts in different contexts. Perhaps in one layout they are adjacent to one another, as inline or flex elements:

<div style={{ display: 'flex' }}>
  <Margin right={8}>
    <Avatar src={avatar} />
  </Margin>
  <Name>{name}</Name>
</div>

And in another, perhaps a Bio component is introduced and some of them are stacked:

<div>
  <Margin bottom={8}>
    <Name>{name}</Name>
  </Margin>
  <div style={{ display: 'flex' }}>
    <Margin right={4}>
      <Avatar src={avatar} />
    </Margin>
    <Bio>{bio}</Bio>
  </div>
</div>

The Margin component makes it very easy to arbitrarily create new layouts for reusable components. I think you'll enjoy learning to use it.

My modification

Because I use shevyjs in my projects, I modify my Margin to use the baseSpacing function under the hood. Here is a direct copy/paste of what I'm using in this blog, right now:

import React from 'react'
import { bs } from '../shevy' // alias of the baseSpacing function I export

export default function Margin({
  children,
  all = 0,
  vert = 0,
  horz = 0,
  top = 0,
  right = 0,
  bottom = 0,
  left = 0,
}) {
  const margins = {
    ...(all && { margin: bs(all) }),
    ...(vert && { marginTop: bs(vert), marginBottom: bs(vert) }),
    ...(horz && { marginLeft: bs(horz), marginRight: bs(horz) }),
    ...(top && { marginTop: bs(top) }),
    ...(right && { marginRight: bs(right) }),
    ...(bottom && { marginBottom: bs(bottom) }),
    ...(left && { marginLeft: bs(left) }),
  }

  return <div css={margins}>{children}</div>
}

Now, when I pass a number in, it's being passed to bs and always returns a value that's in rhythm with my vertical spacing.

You might also notice that I use the margin shorthand for the all prop. Since I'm the only one working on kyleshevlin.com, I'm not worried about making a mistake combining props on this component. You may want to bake in stronger safety rails on your version of the component, disallowing overriding the all prop with other props. I leave that up to you.


Finished reading?

Liked the post? Give the author a dopamine boost with a few "beard strokes". Click it up to 50 times show your appreciation.


Are you, or the company you work for, struggling with something mentioned in this article?
Would you benefit from a live training session?
Let's Talk
Kyle Shevlin's face, which is mostly a beard with eyes
Kyle Shevlin is a software engineer who specializes in JavaScript, TypeScript, React and frontend web development.

Let's talk some more about JavaScript, TypeScript, React, and software engineering.

I write a newsletter to share my thoughts and the projects I'm working on. I would love for you to join the conversation. You can unsubscribe at any time.

Data Structures and Algorithms Logo
Data Structures and Algorithms

Check out my courses!

Liked the post? You might like my courses, too. Click the button to view this course or go to Courses for more information.
View on egghead.io
I would like give thanks to those who have contributed fixes and updates to this blog. If you see something that needs some love, you can join them. This blog is open sourced at https://github.com/kyleshevlin/blog
alexloudenjacobwsmithbryndymentJacobMGEvanseclectic-codingjhsukgcreativeerikvorhesHaroenvmarktnoonandependabotmarcuslyonsbrentmclarkfederico-fiorinimedayzTowardsDeathFanchGadjonoahmateenbrandonpittmanmattridley
©2023 Kyle Shevlin. All Rights Reserved.