• 18-19 College Green, Dublin 2
  • 01 685 9088
  • info@cunninghamwebsolutions.com
  • cunninghamwebsolutions
    Cunningham Web Solutions
    • Home
    • About Us
    • Our Services
      • Web Design
      • Digital Marketing
      • SEO Services
      • E-commerce Websites
      • Website Redevelopment
      • Social Media Services
    • Digital Marketing
      • Adwords
      • Social Media Services
      • Email Marketing
      • Display Advertising
      • Remarketing
    • Portfolio
    • FAQ’s
    • Blog
    • Contact Us
    MENU CLOSE back  

    Building A Component Library With React And Emotion

    You are here:
    1. Home
    2. Web Design
    3. Building A Component Library With React And Emotion
    Thumbnail for 25550

    According to Clearleft, a component library is:

    “A collection of components, organised in a meaningful manner, and often (but not necessarily) providing some way to browse and preview those components and their associated assets.”

    — “On Building Component Libraries,” Clearleft

    We’ll learn how to build a component library by making one that comprises four components:

    1. Button
      A wrapper around the default HTML button
    2. Box
      A container (HTML div) with custom properties
    3. Columns
      A container whose children are spaced evenly across the x-axis
    4. Stack
      A container whose children are spaced evenly across the y-axis

    These components could then be used in whatever application we are working on. We’ll build the component library using React and Emotion.

    At the end of this piece, you should be able to create a component library that fits whatever use case you have in mind. This knowledge will come handy when you’re working with a team that needs to make use of reusable components.

    First, let’s get started by establishing what the Emotion library is. The documentation explains:

    “Emotion is a library designed for writing CSS styles with JavaScript. It provides powerful and predictable style composition in addition to a great developer experience with features such as source maps, labels, and testing utilities.”

    — “Introduction,” Emotion Docs

    In essence, Emotion is a CSS-in-JavaScript library, and an interesting thing about CSS-in-JavaScript libraries is that they enable you to collocate components with styles. Being able to tie them up together in a scope ensures that some component styles don’t interfere with others, which is crucial to our component library.

    Emotion exposes two APIs for React:

    • @emotion/core
    • @emotion/styled

    Before we dive into how these APIs work, note that they both support the styling of components with template strings and objects.

    The core API is actually like the regular style property we currently use today when building apps with React, with the addition of vendor prefixing, nested selectors, media queries, and more.

    Using the object approach with the core API would typically look like this:

    import { jsx } from '@emotion/core'
    
    let Box = props => {
      return (
        <div
          css={{
            backgroundColor: 'grey'
          }}
          {...props}
        />
      )
    }
    

    This is a rather contrived example that shows how we could style a Box component with Emotion. It’s like swapping out the style property for a css property, and then we’re good to go.

    Now, let’s see how we could use the template string approach with the same core API:

    import { jsx, css } from '@emotion/core'
    
    let Box = props => {
      return (
        <div
          css={css`
            background-color: grey
          `}
          {...props}
        />
      )
    }
    

    All we did was wrap the template string with the css tag function, and Emotion handles the rest.

    The styled API, which is built on the core API, takes a slightly different approach to styling components. This API is called with a particular HTML element or React component, and that element is called with an object or a template string that contains the styles for that element.

    Let’s see how we could use the object approach with the styled API:

    import styled from '@emotion/styled'
    
    const Box = styled.div({
            backgroundColor: 'grey'
    });
    

    Here is one way to use the styled API, which is an alternative to using the core API. The rendered outputs are the same.

    Now, let’s see how we could use the template string approach using the styled API:

    import styled from '@emotion/styled'
    
    const Box = styled.div`
            background-color: grey
    `
    

    This achieves the same thing as the object approach, only with a template string this time.

    We could use either the core API or the styled API when building components or an application. I prefer the styled approach for a component library for a couple of reasons:

    • It achieves a lot with few keystrokes.
    • It takes in an as prop, which helps with dynamically changing the HTML element from the call site. Let’s say we default to a paragraph element, and we need a header element because of semantics; we can pass the header element as a value to the as property.

    Getting Started

    To get started, let’s clone the setup scripts on GitHub, which we can do on the command line:

    git clone git@github.com:smashingmagazine/component-library.git

    This command copies the code in that repository to the component-library‘s folder. It contains the code required to set up a component library, which includes Rollup to help bundle our library.

    We currently have a components folder with an index.js file, which does nothing. We’ll be creating new folders under the components folder for each component we build in our library. Each component’s folder will expose the following files:

    • Component.js
      This is the component we’re building.
    • index.js
      This exports the component from Component.js and makes referencing components from a different location easier.
    • Component.story.js
      This essentially renders our component in its multiple states using Storybook.

    It also ships with a utils folder, which defines certain properties that would be used in our components. The folder contains several files:

    • helpers.js
      This contains helper functions that we are going to be using across our application.
    • units.js
      This defines spacing and font-size units, which we will use later.
    • theme.js
      This defines our component library’s palette, shadows, typography, and shape.

    Let’s look at what we’ve defined in the units.js file:

    export const spacing = {
      none: 0,
      xxsmall: '4px',
      xsmall: '8px',
      small: '12px',
      medium: '20px',
      gutter: '24px',
      large: '32px',
      xlarge: '48px',
      xxlarge: '96px',
    };
    
    export const fontSizes = {
      xsmall: '0.79rem',
      small: '0.889rem',
      medium: '1rem',
      large: '1.125rem',
      xlarge: '1.266rem',
      xxlarge: '1.424rem',
    };
    

    This defines the spacing and fontSizes rules. The spacing rule was inspired by the Braid design system, which is based on multiples of four. The fontSizes are derived from the major second (1.125) type scale, which is a good scale for product websites. If you’re curious to learn more about type scale, “Exploring Responsive Type Scales” explains the value of knowing the scales appropriate for different websites.

    Next, let’s through the theme.js file!

    import { spacing } from './units';
    
    const white = '#fff';
    const black = '#111';
    
    const palette = {
      common: {
        black,
        white,
      },
      primary: {
        main: '#0070F3',
        light: '#146DD6',
        contrastText: white,
      },
      error: {
        main: '#A51C30',
        light: '#A7333F',
        contrastText: white,
      },
      grey: {
        100: '#EAEAEA',
        200: '#C9C5C5',
        300: '#888',
        400: '#666',
      },
    };
    
    const shadows = {
      0: 'none',
      1: '0px 5px 10px rgba(0, 0, 0, 0.12)',
      2: '0px 8px 30px rgba(0, 0, 0, 0.24)',
    };
    
    const typography = {
      fontFamily:
        "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', sans-serif",
    };
    
    const shape = {
      borderRadius: spacing['xxsmall'],
    };
    
    export const theme = {
      palette,
      shadows,
      typography,
      shape,
    };
    

    In the theme file, we’ve defined our palette, which is essentially the colors we’re going to be using across all components in our library. We also have a shadows object, where we define our box-shadow values. There’s also the typography object, which currently just defines our fontFamily. Finally, shape is used for properties such as border-radius. This theme’s structure is inspired by Material-UI.

    Next, our helpers.js file!

    export const isObjectEmpty = (obj) => {
      return Object.keys(obj).length === 0;
    };
    

    Here, we only expose the isObjectEmpty function, which takes in an object and returns true if the object is empty. It returns false if it has any values. We’re going to make use of this function later.

    Now that we’ve gone through all of the files in the utils folder, it’s about time to start building our components!

    Buttons

    Buttons are one of the most used components on the web. They’re used everywhere and can take different forms, shapes, sizes, and more.

    Here are the buttons we’re going to build in Figma.

    These subtle variations are going to be applied as properties to our button. We would like the buttons in our component library to accept properties such as variant, size, enableElevation (i.e. box-shadow), and color.

    Starting with the button component, let’s create a Button folder, where we will define everything related to buttons, as discussed earlier.

    Let’s create our button component:

    import styled from '@emotion/styled';
    import isPropValid from '@emotion/is-prop-valid';
    
    const StyledButton = () => {};
    
    const IGNORED_PROPS = ['color'];
    
    const buttonConfig = {
      shouldForwardProp: (prop) =>
        isPropValid(prop) && !IGNORED_PROPS.includes(prop),
    };
    
    export const Button = styled('button', buttonConfig)(StyledButton);
    

    Here, we’ve started off by setting up our button component with a buttonConfig. The buttonConfig contains shouldForwardProp, which is used to control the properties that should be forwarded to the DOM, because properties such as color show up on the rendered element by default.

    Next, let’s define our button sizes, which we’re going to use in the button component!

    const buttonSizeProps = {
      small: {
        fontSize: fontSizes['xsmall'],
        padding: `${spacing['xsmall']} ${spacing['small']}`,
      },
      medium: {
        fontSize: fontSizes['small'],
        padding: `${spacing['small']} ${spacing['medium']}`,
      },
      large: {
        fontSize: fontSizes['medium'],
        padding: `${spacing['medium']} ${spacing['large']}`,
      },
    };
    

    buttonSizeProps is a map of our size values (small, medium, and large), and it returns fontSize and padding values based on the sizes. For a small button, we’d need a small font with small padding. The same goes for the medium and large sizes to scale them appropriately.

    Next, let’s define a function that provides valid CSS properties based on the passed variant:

    const getPropsByVariant = ({ variant, color, theme }) => {
    
      const colorInPalette = theme.palette[color];
    
      const variants = {
        outline: colorInPalette
          ? outlineVariantPropsByPalette
          : defaultOutlineVariantProps,
        solid: colorInPalette
          ? solidVariantPropsByPalette
          : defaultSolidVariantProps,
      };
    
      return variants[variant] || variants.solid;
    };
    

    Here, the getPropsByVariant function takes in variant, color, and theme properties and returns the properties of the specified variant; if no variant is specified, it defaults to solid. colorInPalette retrieves the palette assigned to the specified color if found, and undefined if not found in our theme object.

    In each variant, we check whether a palette actually exists for the color specified; if we don’t, then we use colors from the common and grey objects of our theme, which we will apply in defaultOutlineVariantProps and defaultSolidVariantProps.

    Next, let’s define our variant properties!

    const defaultSolidVariantProps = {
      main: {
        border: `1px solid ${theme.palette.grey[100]}`,
        backgroundColor: theme.palette.grey[100],
        color: theme.palette.common.black,
      },
      hover: {
        border: `1px solid ${theme.palette.grey[200]}`,
        backgroundColor: theme.palette.grey[200],
      },
    };
    
    const defaultOutlineVariantProps = {
      main: {
        border: `1px solid ${theme.palette.common.black}`,
        backgroundColor: theme.palette.common.white,
        color: theme.palette.common.black,
      },
      hover: {
        border: `1px solid ${theme.palette.common.black}`,
        backgroundColor: theme.palette.common.white,
        color: theme.palette.common.black,
      },
    };
    
    const solidVariantPropsByPalette = colorInPalette && {
      main: {
        border: `1px solid ${colorInPalette.main}`,
        backgroundColor: colorInPalette.main,
        color: colorInPalette.contrastText,
      },
      hover: {
        border: `1px solid ${colorInPalette.light}`,
        backgroundColor: colorInPalette.light,
      },
    };
    
    const outlineVariantPropsByPalette = colorInPalette && {
      main: {
        border: `1px solid ${colorInPalette.main}`,
        backgroundColor: theme.palette.common.white,
        color: colorInPalette.main,
      },
      hover: {
        border: `1px solid ${colorInPalette.light}`,
        backgroundColor: theme.palette.common.white,
        color: colorInPalette.light,
      },
    };
    

    Here, we define the properties that are going to be applied to our button based on the selected variants. And, as discussed earlier, defaultSolidVariantProps and defaultOutlineVariantProps use colors from our common and grey objects as fallbacks for when the color specified isn’t in our palette or when no color is specified for what we put in place.

    By the way, the solidVariantPropsByPalette and outlineVariantPropsByPalette objects use the color from our palette as specified by the button. They both have main and hover properties that differentiate the button’s default and hover styles, respectively.

    The button design we’ve used accounts for two variants, which we can check out in our component library design.

    Next, let’s create our StyledButton function, which combines all we’ve done so far.

    const StyledButton = ({
      color,
      size,
      variant,
      enableElevation,
      disabled,
      theme,
    }) => {
      if (isObjectEmpty(theme)) {
        theme = defaultTheme;
      }
    
      const fontSizeBySize = buttonSizeProps[size]?.fontSize;
      const paddingBySize = buttonSizeProps[size]?.padding;
      const propsByVariant = getPropsByVariant({ variant, theme, color });
    
      return {
        fontWeight: 500,
        cursor: 'pointer',
        opacity: disabled && 0.7,
        transition: 'all 0.3s linear',
        padding: buttonSizeProps.medium.padding,
        fontSize: buttonSizeProps.medium.fontSize,
        borderRadius: theme.shape.borderRadius,
        fontFamily: theme.typography.fontFamily,
        boxShadow: enableElevation && theme.shadows[1],
        ...(propsByVariant && propsByVariant.main),
        ...(paddingBySize && { padding: paddingBySize }),
        ...(fontSizeBySize && { fontSize: fontSizeBySize }),
        '&:hover': !disabled && {
          boxShadow: enableElevation && theme.shadows[2],
          ...(propsByVariant && propsByVariant.hover),
        },
      };
    };
    

    In the StyledButton function, we’re assigning defaultTheme to the theme if the theme object is empty which makes it optional for the consumers of our library to use Emotion’s ThemeProvider in order to make use of the library. We assigned fontSize and padding based on the buttonSizeProps object. We defined several default button properties, such as fontWeight and cursor, which aren’t tied to any property, and we also derived color, backgroundColor, and border values based on the result of propsByVariant.

    Now that we’ve created our Button component, let’s see how we can use it:

    <Button
        variant="solid"
        color="primary"
        size="small"
        enableElevation
        disabled
    >
        Small Outline Elevated Button
    </Button>
    

    We can check what that looks like on CodeSandbox:

    That’s how to use the Button component. We define the following properties:

    • We define a variant with a solid value. We could have specified outline instead. If the variant prop isn’t provided, we would also default to solid.
    • We define color, with a value of primary. We also support error as a color value or a color from a theme object. If the color property isn’t specified, we would fall back to our default color state.
    • We define size, with a value of small. It could be medium (the default) or large.
    • We define EnableElevation because we want some box-shadow on our button. We could have chosen not to use it.
    • Finally, we define disabled because we want our button to be disabled. The additional thing we do to a disabled button is reduce its opacity.

    The button doesn’t need to take any property. It defaults to a solid medium-sized button.

    Box Component

    A box component is a container that can hold any component or HTML element. It accepts but is not limited to properties such as padding, margin, display, and width. It can also be used as a base component for some of the other components we’ll get into later.

    Here’s what it looks like on Figma:

    Before diving into the code, let’s not forget to create a new folder for this component.

    Now, let’s create our Box component:

    
    import styled from '@emotion/styled';
    import isPropValid from '@emotion/is-prop-valid';
    import { spacing, theme as defaultTheme } from '../../utils';
    
    const StyledBox = ({
      paddingX,
      paddingY,
      marginX,
      marginY,
      width,
      display,
      theme,
      ...props
    }) => {
    
      if (isObjectEmpty(theme)) {
        theme = defaultTheme;
      }
    
      const padding = spacing[props.padding];
      let paddingTop = spacing[props.paddingTop];
      let paddingRight = spacing[props.paddingRight];
      let paddingBottom = spacing[props.paddingBottom];
      let paddingLeft = spacing[props.paddingLeft];
      if (paddingX) {
        paddingLeft = spacing[paddingX];
        paddingRight = spacing[paddingX];
      }
      if (paddingY) {
        paddingTop = spacing[paddingY];
        paddingBottom = spacing[paddingY];
      }
      let margin = spacing[props.margin];
      let marginTop = spacing[props.marginTop];
      let marginRight = spacing[props.marginRight];
      let marginBottom = spacing[props.marginBottom];
      let marginLeft = spacing[props.marginLeft];
      if (marginX) {
        marginLeft = spacing[marginX];
        marginRight = spacing[marginX];
      }
      if (marginY) {
        marginTop = spacing[marginY];
        marginBottom = spacing[marginY];
      }
      return {
        padding,
        paddingTop,
        paddingRight,
        paddingBottom,
        paddingLeft,
        margin,
        marginTop,
        marginRight,
        marginBottom,
        marginLeft,
        width,
        display,
        fontFamily: theme.typography.fontFamily,
      };
    };
    
    const IGNORED_PROPS = ['display', 'width'];
    
    const boxConfig = {
      shouldForwardProp: (prop) =>
        isPropValid(prop) && !IGNORED_PROPS.includes(prop),
    };
    
    export const Box = styled('div', boxConfig)(StyledBox);
    

    The spacing rule we defined earlier is being applied to both padding and margin, as we can see in the Box component. We receive contextual values for padding and margin, and we look up their actual values from the spacing object.

    We accept paddingX and paddingY props to update padding across the horizontal and vertical axis, respectively. We do the same for marginX and marginY as well.

    Also, we don’t want the display and width props to get forwarded to the DOM because we only need them in CSS. So, we add them to our list of props to ignore, and pass that on to our config.

    Here’s how we could use the Box component:

    <Box
      padding="small"
      paddingTop="medium"
      paddingBottom="medium"
    >
      Simple Box Component
    </Box>
    

    We can see what this looks like on CodeSandbox.

    In this Box component, we’ve assigned small as a value to our padding property, and medium to the paddingTop and paddingBottom properties. When rendered, the Box component will have its padding-left and padding-right properties set to 12px each, and its padding-top and padding-bottom properties set to 20px. We could have replaced paddingTop and paddingBottom with paddingY and gotten the same result.

    Columns Component

    The Columns component is a variation of our Box component, with a display type of flex and with children spaced evenly across the x-axis.

    Here is a representation of the Columns component in Figma:

    Let’s build our Columns component!

    import React from 'react';
    import { Box } from '../Box';
    
    export const Columns = ({ children, space, ...props }) => {
      return (
        <Box display="flex" {...props}>
          {React.Children.map(children, (child, index) => {
            if (child.type !== Box) {
              console.warn(
                'Each child in a Columns component should be a Box component'
              );
            }
    
            if (index > 0) {
              return React.cloneElement(child, {
                marginLeft: space,
                width: '100%',
              });
            }
    
            return React.cloneElement(child, { width: '100%' });
          })}
        </Box>
      );
    };
    

    We’re using React.Children to map over the Columns component’s children. And we’re adding marginLeft and width properties to each of the children, except the first child, which doesn’t need a marginLeft property because it’s the leftmost child in the column. We expect each child to be a Box element to ensure that the necessary styles are applied to it.

    Here’s how we could use the Columns component:

    <Columns space="small">
      <Box> Item 1</Box>
      <Box> Item 2</Box>
      <Box> Item 3</Box>
    </Columns>
    

    We can see what that looks like on CodeSandbox.

    The Columns children here are spaced evenly across the x-axis by 12 pixels because that’s what the value of small resolves to, as we’ve defined earlier. Because the Columns component is literally a Box component, it can take in other Box component properties, and we can customize it as much as we want.

    Stack Component

    This is also a variation of our Box component that takes the full width of the parent element and whose children are spaced evenly across the y-axis.

    Here is a representation of the Stack component in Figma:

    Let’s build our Stack component:

    import React from 'react';
    import { Box } from '../Box';
    import { Columns } from '../Columns';
    
    const StackChildrenTypes = [Box, Columns];
    const UnsupportedChildTypeWarning =
      'Each child in a Stack component should be one of the types: Box, Columns';
    
    export const Stack = ({ children, space, ...props }) => {
      return (
        <Box {...props}>
          {React.Children.map(children, (child, index) => {
            if (!StackChildrenTypes.includes(child.type)) {
              console.warn(UnsupportedChildTypeWarning);
            }
    
            if (index > 0) {
              return React.cloneElement(child, { marginTop: space });
            }
    
            return child;
          })}
        </Box>
      );
    };
    

    Here, we map over each child with React.Children and apply a paddingTop property to it with the value of the space argument. As for the first child, we need it to take its original position, so we skip adding a marginTop property to it. We also accept each child to be a Box so that we can apply the necessary properties to it.

    Here’s how we could use the Stack component:

    <Stack space="small">
      <Box marginTop="medium"> Item 1</Box>
      <Box> Item 2</Box>
      <Box> Item 3</Box>
    </Stack>
    

    We can see what that looks like on CodeSandbox.

    Here, the Box elements are spaced evenly with the small unit, and the first Box takes a separate marginTop property. This shows that you can customize components however you wish.

    Conclusion

    We’ve gone through the basics of using Emotion to create components in React using the APIs that it provides. This is just one of many ways to go about building a component library. There are some nuances to building it for a brand because you might not have to take theming and some other things into consideration. But if you plan to release the library to the public one day, then you’ll have to deal with requests for those missing pieces, so consider that possibility and make the library a little flexible ahead of time.

    If you have any questions, feel free to drop them as comments.

    The repository for this article is on GitHub, and the button designs we’ve used are on Figma.

    References

    • “On Building Component Libraries”, Mark Perkins, Clearleft
    • “Exploring Responsive Type Scales”, Joseph Mueller
    • “Design Systems With React and Storybook”, Emma Bostian, Frontend Masters
    • Emotion official documentation

    From our sponsors: Building A Component Library With React And Emotion

    Posted on 4th September 2020Web Design
    FacebookshareTwittertweetGoogle+share

    Related posts

    Archived
    22nd March 2023
    Archived
    18th March 2023
    Archived
    20th January 2023
    Thumbnail for 25788
    Handling Continuous Integration And Delivery With GitHub Actions
    19th October 2020
    Thumbnail for 25778
    A Monthly Update With New Guides And Community Resources
    19th October 2020
    Thumbnail for 25781
    Supercharge Testing React Applications With Wallaby.js
    19th October 2020
    Latest News
    • Archived
      22nd March 2023
    • Archived
      18th March 2023
    • Archived
      20th January 2023
    • 20201019 ML Brief
      19th October 2020
    • Thumbnail for 25788
      Handling Continuous Integration And Delivery With GitHub Actions
      19th October 2020
    • Thumbnail for 25786
      The Future of CX with Larry Ellison
      19th October 2020
    News Categories
    • Digital Marketing
    • Web Design

    Our services

    Website Design
    Website Design

    A website is an important part of any business. Professional website development is an essential element of a successful online business.

    We provide website design services for every type of website imaginable. We supply brochure websites, E-commerce websites, bespoke website design, custom website development and a range of website applications. We love developing websites, come and talk to us about your project and we will tailor make a solution to match your requirements.

    You can contact us by phone, email or send us a request through our online form and we can give you a call back.

    More Information

    Digital Marketing
    Digital Marketing

    Our digital marketeers have years of experience in developing and excuting digital marketing strategies. We can help you promote your business online with the most effective methods to achieve the greatest return for your marketing budget. We offer a full service with includes the following:

    1. Social Media Marketing

    2. Email & Newsletter Advertising

    3. PPC - Pay Per Click

    4. A range of other methods are available

    More Information

    SEO
    SEO Services

    SEO is an essential part of owning an online property. The higher up the search engines that your website appears, the more visitors you will have and therefore the greater the potential for more business and increased profits.

    We offer a range of SEO services and packages. Our packages are very popular due to the expanse of on-page and off-page SEO services that they cover. Contact us to discuss your website and the SEO services that would best suit to increase your websites ranking.

    More Information

    E-commerce
    E-commerce Websites

    E-commerce is a rapidly growing area with sales online increasing year on year. A professional E-commerce store online is essential to increase sales and is a reflection of your business to potential customers. We provide professional E-commerce websites custom built to meet our clients requirements.

    Starting to sell online can be a daunting task and we are here to make that journey as smooth as possible. When you work with Cunningham Web Solutions on your E-commerce website, you will benefit from the experience of our team and every detail from the website design to stock management is carefully planned and designed with you in mind.

    More Information

    Social Media Services
    Social Media Services

    Social Media is becoming an increasingly effective method of marketing online. The opportunities that social media marketing can offer are endless and when managed correctly can bring great benefits to every business.

    Social Media Marketing is a low cost form of advertising that continues to bring a very good ROI for our clients. In conjuction with excellent website development and SEO, social media marketing should be an essential part of every digital marketing strategy.

    We offer Social Media Management packages and we also offer Social Media Training to individuals and to companies. Contact us to find out more.

    More Information

    Cunningham Web Solutions
    © Copyright 2025 | Cunningham Web Solutions
    • Home
    • Our Services
    • FAQ's
    • Account Services
    • Privacy Policy
    • Contact Us