Storybook & Atomic Design – 1.6. – Styling Our Button

At the moment our Button component doesn't have any styles, other than that of browsers default applied to it.

In this lesson, we are going to cover how we can use the styled-components package to write isolated styles that apply to any instance of our Button component, with dynamic CSS properties generated based on the props that are provided to a component.

Style File

To continue with our separation of component file focus, we will create a new styles file to live within our button directory next to our documentation and markup files.

I've used the name button.styles.jsx for our Button component styles.

- __Storybook__
   - __components__
     - __atoms__
       - __button__
         - button.jsx
         - button.stories.js
         - button.styles.jsx

Inside this file, we will write the following code and then go through what is happening -

import styled from "styled-components"

export const StyledButton = styled.button`
  display: inline-flex;
  padding: 16px;

  background-color: ${props => props.theme.primary};
  border: none;
  box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
  color: white;
  cursor: pointer;
  font-weight: 700;
  line-height: 1;
  outline: none;
  text-decoration: none;
  transition: all 0.15s ease;
  white-space: nowrap;
`;

export default StyledButton;

So, first of all, we are importing the styled package from styled-components allowing us to tap into the CSS-in-JS library.

We next create a constant value named StyledButton and assign it to be a styled component. We then specify the HTML element we want to return to our component with a .followed by the name of the HTML element, in our case, this will be a button HTML element.

We then use template literal string (`)characters to open up the file for our CSS. Then we start writing our CSS for our component inside of these characters.

In my CSS I've included a display property and a padding property to give the button some room to breathe.

I've also included some colour values, specifically a white text colour and a background colour that taps into the values we provided our ThemeProvider component at the start of this chapter when setting up our Storybook global wrapper component.

We can tap into the values as the CSS we are writing is in a template literal string, by using the ${JS HERE} syntax we can break in and out of writing a string or using JavaScript functions and properties.

The StyledButton string value has access to props anytime we interpolate within the string and forward the value of props into what we want to return in our CSS.

For anyone unfamiliar with styled-components this can take some getting used to but it gives us the great ability in writing isolated CSS that doesn't break out into other components.

With our new Styled-Component, we now need to import it into our Button component to make use of the CSS rules we've created.

import React from 'react';
import { func, node, string } from "prop-types";

import StyledButton from "./button.styles.jsx"

const Button = ({children, href, onClick}) => {
  if(!href) return <StyledButton onClick={onClick}>{children}</StyledButton>
  return <a href={href}>{children}</a>
);

// Expected prop values
Button.propTypes = {
  children: node.isRequired,
  href: string,
  onClick: func,
}

// Default prop values
Button.defaultProps = {
  children: 'Button text',
};

export default Button;

Accessing Component Props in our Styles

In the example above, I've shown how we can tap into our global theme prop values and use them within our components CSS.

We can take it a step further and instead of accessing global props, we can access the props fed directly to the Button component itself.

The syntax for doing so is very similar to that of accessing the global theme object.

But before we do that, let's add a new prop value to our Button component called variant.

import React from 'react';
import { func, node, string } from "prop-types";

import StyledButton from "./button.styles.jsx"

const Button = ({children, href, onClick, variant}) => {
  if(!href) return <StyledButton variant={variant} onClick={onClick}>{children}</StyledButton>
  return <a href={href}>{children}</a>
);

// Expected prop values
Button.propTypes = {
  children: node.isRequired,
  href: string,
  onClick: func,
  variant: string,
}

// Default prop values
Button.defaultProps = {
  children: 'Button text',
  variant: "primary"
};

export default Button;

The variant prop will be used to switch between component styles.

We may have a single Button Component that shares different colour instances. For example a primary, secondary, and tertiary button.

The reason we use the prop name variant instead of theme is that the theme prop is already defined by our ThemeProvider component and so the Button component already has this prop name being used.

We could try the prop name type but again this is already used by the HTML button element to determine if a button is a standard button, reset button or submit button.

Instead, we use the term variant as you have different variants of a button.

With our new prop available to our component, we will revisit our Button style file and extend it with a function which accepts the props available to our Button component and dynamically returns a background colour based on the values it finds.

Note: only the props we make available with ThemeProvider or by directly passing to our Styled-Component will be available in the props object. Which in this situation would only include onClick, theme and variant.

import styled from "styled-components"

const buttonBackground = (props) => {
  // Fallback value if we can't get access to props
  if(!props || !props.theme || !props.theme.primary) return "#00FFFF";
  // If no variant is specified, return the primary colour in our theme
  if(!props.variant) return props.theme.primary;
	
  // Dynamically determine the background colour based on props
  let colour;
  switch (props.variant)) {
    case "primary":
      colour = props.theme.primary;
      break;
    case "secondary":
      colour = props.theme.secondary;
      break;
    case "tertiary":
      colour = props.theme.tertiary;
      break;
    default:
      colour = props.theme.primary;
      break;
  }

  return colour;
}

export const StyledButton = styled.button`
  display: inline-flex;
  padding: 16px;

  background-color: ${props => buttonBackground(props)};
  border: none;
  box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
  color: white;
  cursor: pointer;
  font-weight: 700;
  line-height: 1;
  outline: none;
  text-decoration: none;
  transition: all 0.15s ease;
  white-space: nowrap;
`;

export default StyledButton;

Now when we call on our Button Component, if we have specified the prop variant with a value secondary, the Styled-Component will dynamically return the background-colour associated with the value found in our ThemeProvider under the key 'secondary'.

Dynamically Extending Our Styled Button to an Anchor Tag

We're almost done creating our styled React Button component, but we've got one task left to complete.

At the moment, we are only returning a styled HTML button with our Styled-Components logic.

However, as mentioned in the previous video, we won't always be returning a button. When we provide a value to the href prop we instead want to return an a HTML element to our application.

Fortunately, we can extend the styled-component defined as 'StyledButton' with the following code to return a new styled-component defined as 'StyledLinkButton'.

export const StyledButton = styled.button`
  ...
`;

export const StyledLinkButton = styled(StyledButton).attrs({
  as: "a"
})`
  text-deocration: none;
`;

export default StyledButton;

We now have three exports from our button.styles.jsx file.

Two named exports of StyledButton and StyledLinkButton.

With a default export of StyledButton.

We can import these into our Button React component using -

import StyledButton, { StyledLinkButton } from "./button.styles.jsx"

By replacing our anchor element with a StyledLinkButton we can now return an anchor element that shares the same styles as our button element. Creating consistency between the two elements whilst still making benefit of the browser's support for each elements functionality.

import React from 'react';
import { func, node, string } from "prop-types";

import StyledButton, { StyledLinkButton } from "./button.styles.jsx"

const Button = ({children, href, onClick, variant}) => {
  (!href) return <StyledButton variant={variant} onClick={onClick}>{children}</StyledButton>
  return <StyledLinkButton variant={variant} href={href}>{children}</StyledLinkButton>
);

// Expected prop values
Button.propTypes = {
  children: node.isRequired,
  href: string,
  onClick: func,
  variant: string,
}

// Default prop values
Button.defaultProps = {
  children: 'Button text',
  variant: "primary"
};

export default Button;

We now have a styled React Button component which dynamically changes colour based on the props we pass in at build-time. We're also making use of our ThemeProvider to supply consistent colour values throughout our application to keep the maintenance of brand colours easier.

The component will swap between using either an anchor element or button element based on the href prop value being defined, keeping the application accessible.

In the next lesson, we'll be covering how we an import SVGs to our React Components in Storybook and include a dynamic icon for our Button Component.

Continue Reading 📚