johno

Building a beautiful, reusable button with Styled System

If you've used a component with style functions from the styled-system library you might want to bring the API to all your projects. If that's the case we'll walk through how to build a button component using Styled System.

In this tutorial we'll go over using a styled function, theming providers, and variants.

See the CodeSandbox

Install the dependencies

For this example we'll be using Emotion, but any CSS-in-JS library will work.

yarn add styled-system @emotion/core @emotion/styled emotion-theming

Next, we create a basic styled button

import styled from '@emotion/styled'

const Button = styled('button')({})

And add some basic styles:

const Button = styled('button')({
  appearance: 'button',
  border: 0,
  outline: 0
})

Add some color

Now that we have some styling it's time to introduce some color. We can do that using the color function from Styled System. We do this by passing it as another argument after the style object:

import styled from '@emotion/styled'
import { color } from 'styled-system'

const Button = styled('button')(
  {
    appearance: 'button',
    border: 0,
    outline: 0
  },
  color
)

This exposes to props to the Button component: color and backgroundColor so you can use the component like so:

<Button color="white" backgroundColor="tomato">
  Hello, world!
</Button>

Then, if we want to add defaults we can use defaultProps:

Button.defaultProps = {
  color: 'white',
  backgroundColor: 'blue'
}

Use a theme

Now that we're setting colors on the Button component we want to use a ThemeProvider to use a globally defined theme for all components. We can achieve that with emotion-theming and then wrapping the app. Styled System will automatically tie into the theme and return a value if it exists.

import styled from '@emotion/styled'
import { ThemeProvider } from 'emotion-theming'
import { color } from 'styled-system'

const theme = {
  space: [0, 2, 4, 8, 16, 32],
  colors: {
    blue: '#07c',
    tomato: 'tomato',
    purple: 'purple'
  }
}

function Demo() {
  return (
    <ThemeProvider theme={theme}>
      <Button>Styled System Button!</Button>
    </ThemeProvider>
  )
}

Add padding and font size

Similarly to color, we can add in space and fontSize to add more style props to the Button.

import styled from '@emotion/styled'
import { ThemeProvider } from 'emotion-theming'
import { color, space, fontSize } from 'styled-system'

const Button = styled('button')(
  {
    appearance: 'button',
    border: 0,
    outline: 0
  },
  color,
  space,
  fontSize
)

Button.defaultProps = {
  color: 'white',
  backgroundColor: 'blue',
  px: 4,
  py: 3,
  fontSize: 2
}

const theme = {
  space: [0, 2, 4, 8, 16, 32],
  fontSizes: [14, 16, 18, 24, 32],
  colors: {
    blue: '#07c',
    tomato: 'tomato',
    purple: 'purple'
  }
}

function Demo() {
  return (
    <ThemeProvider theme={theme}>
      <Button>Styled System Button!</Button>
    </ThemeProvider>
  )
}

Now the button can have padding, margin, and fontSize set.

buttonStyle

For buttons it's common to have a need for a variant prop which can determine interrelated styles like color, box shadow, or anything else.

You can use a special buttonStyle key in your theme and then import the buttonStyle function from styled-system.

import styled from '@emotion/styled'
import { ThemeProvider } from 'emotion-theming'
import { buttonStyle, space, fontSize } from 'styled-system'

const Button = styled('button')(
  {
    appearance: 'button',
    border: 0,
    outline: 0
  },
  buttonStyle,
  space,
  fontSize
)

Button.defaultProps = {
  variant: 'primary',
  px: 4,
  py: 3,
  fontSize: 2
}

const baseTheme = {
  space: [0, 2, 4, 8, 16, 32],
  fontSizes: [14, 16, 18, 24, 32],
  colors: {
    blue: '#07c',
    tomato: 'tomato',
    purple: 'purple'
  }
}

const theme = {
  ...baseTheme,
  buttons: {
    primary: {
      color: 'white',
      backgroundColor: baseTheme.colors.blue
    },
    secondary: {
      color: 'white',
      backgroundColor: baseTheme.colors.purple
    },
    danger: {
      color: 'white',
      backgroundColor: baseTheme.colors.tomato
    }
  }
}

Now you can use primary, secondary, and danger variants in your Button like so:

<Button variant="danger">I'm tomato!</Button>

Custom variants

You can specify any number of other style variants using the variant function. With it you can specify custom keys in the theme object and even a custom prop. Below is an example with a size variant for Button:

import { buttonStyle, space, fontSize, variant } from 'styled-system'

const buttonSize = variant({
  prop: 'size',
  key: 'buttonSizes'
})

const theme = {
  ...baseTheme,
  buttons: {
    primary: {
      color: 'white',
      backgroundColor: baseTheme.colors.blue
    },
    secondary: {
      color: 'white',
      backgroundColor: baseTheme.colors.purple
    },
    danger: {
      color: 'white',
      backgroundColor: baseTheme.colors.tomato
    }
  },
  buttonSizes: {
    medium: {
      fontSize: baseTheme.fontSizes[2],
      padding: `8px 16px`
    },
    large: {
      fontSize: baseTheme.fontSizes[4],
      padding: `16px 32px`
    }
  }
}

Now you can change the button sizing like so:

<Button size="large">I'm large</Button>

Border radius

If you want to add a border radius prop to your component you can first create a radii key in your theme.

const baseTheme = {
  space: [0, 2, 4, 8, 16, 32],
  fontSizes: [14, 16, 18, 24, 32],
  colors: {
    blue: '#07c',
    tomato: 'tomato',
    purple: 'purple'
  },
  radii: [0, 2, 4, 8]
}

Then you can add import the borderRadius function from Styled System and pass it to the Button component definition. You can also set a default so that all buttons will take on the same border radius value.

import {
  borderRadius,
  buttonStyle,
  space,
  fontSize,
  variant
} from 'styled-system'

// ...

const Button = styled('button')(
  {
    appearance: 'button',
    border: 0,
    outline: 0
  },
  borderRadius,
  buttonStyle,
  buttonSize,
  space,
  fontSize
)

Button.defaultProps = {
  borderRadius: 2
}

That's not all

Since Button uses the space function from Styled System you can set custom margin or padding. This is nice for adjacent buttons when you want to add a bit of margin on the left of the second.

<Button>Button 1</Button>
<Button marginLeft={2}>Button 2</Button>

This is a great workflow for when you want to add spacing quickly and efficiently to a bit of markup. By default, it will ensure that design system scales will be used so that things remain consistent.

What else?

You can continue going further with other style functions for box-shadow, line-height, and more.

See the Styled System table of functions

Conclusion

Using this approach you can create components that can do essentially anything. Especially with the variant prop you can create enums that map to groups of styling which ensure a great developer experience.

See the CodeSandbox