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?

Here are a few options for what to do next.

Like
Liked the post? Click the beard up to 50 times to show it
Share
Sharing this post on Twitter & elsewhere is a great way to help me out

Kyle Shevlin's face, which is mostly a beard with eyes
Kyle Shevlin is a software engineer who specializes in JavaScript, React and front end web development.

Let's talk some more about JavaScript, 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.

Just Enough Functional Programming Logo
Just Enough Functional Programming

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.
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-fiorinimedayzDoNormalFanchGadjonoahmateenbrandonpittman
©2022 Kyle Shevlin. All Rights Reserved.