Create Styled Components

Let’s learn how to use CSS in JS to make styled-components.

We'll cover the following

Creating a styled component

Currently, the <img> created by CarouselSlide is unstyled, which means that it scales to whatever the original size of the image is. That means that the carousel jarringly expands and contracts as users move from slide to slide. Worse, it’ll push other page content around in the process.

  • To do that, we’ll replace the unstyled <img> element with a component generated by styled-components:

    // src/CarouselSlide.js
    import React from 'react';
    import PropTypes from 'prop-types';
    import styled from 'styled-components';
    
    const Img = styled.img`
      object-fit: cover;
      width: 100%;
      height: 500px;
    `;
    
    const CarouselSlide = ({ imgUrl, 
    description, attribution, ...rest }) => (
      <figure {...rest}>
        <Img src={imgUrl} />
        <figcaption>
          <strong>{description}</strong> {attribution}
        </figcaption>
      </figure>
    );
    ...
    

styled.img is a function that generates a component that renders an <img> tag with the given styles. When an instance of that Img component is mounted, styled-components will dynamically insert a style rule with the styles you provided, using a class name selector based on the hash of those styles.

There’s some syntactic fanciness here in the form of an ES6 feature called tagged templates: If you put a function directly in front of a template string (the kind delimited by backticks), that function is called with the template string as an argument.

In the case of Img, you could use the normal function call syntax since the string with the styles is a constant. The tagged template syntax unlocks new possibilities when the string has interpolations (the ${...} syntax). Each piece of the interpolated string is passed into the function as a separate argument. That gives the tag function the chance to process interpolated variables. As we’ll soon see, styled-components take advantage of this power.

As soon as you hit save, you should see the difference in your browser. Before, the size of the <img> tag was determined by the image file it loaded. Now, it takes up the full width of its container and has 500px of height. The object-fit: cover rule means that the image keeps its aspect ratio as it expands or contracts to those dimensions, getting clipped as needed.

Why 500px? Really, the height of the image should be determined by the app rendering the carousel component.

  • So let’s make these styles dynamic:

    // src/CarouselSlide.js
    import React from 'react';
    import PropTypes from 'prop-types';
    import styled from 'styled-components';
    
    const Img = styled.img`
      object-fit: cover;
      width: 100%;
      height: ${props => // 1
        typeof props.imgHeight === 'number'
          ? `${props.imgHeight}px`
          : props.imgHeight};
    `;
    
    const CarouselSlide = ({
      imgUrl,
      imgHeight,
      description,
      attribution,
      ...rest
    }) => (
      <figure {...rest}>
        <Img src={imgUrl} imgHeight={imgHeight} />
        <figcaption>
          <strong>{description}</strong> {attribution}
        </figcaption>
      </figure>
    );
    
    CarouselSlide.propTypes = {
      imgHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), // 2
      imgUrl: PropTypes.string.isRequired,
      description: PropTypes.node.isRequired,
      attribution: PropTypes.node,
    };
    
    CarouselSlide.defaultProps = { // 3
      imgHeight: 500,
    };
    
    export default CarouselSlide;
    
  1. This is where styled-components really get exciting. Interpolated values in the style template can be a function of the component’s props. Whereas ordinary CSS is static, these styles are completely dynamic. If the imgHeight prop changes, the styles update automatically.

  2. This code declares imgHeight as a prop that can be either a number (indicating a px value) or a string (such as '100vh'). Since it doesn’t have isRequired, it can also be null. In that case, styled-components would simply omit the height rule from the generated styles.

  3. A React component’s defaultProps are used as fallbacks when the prop’s given value is undefined. Whereas propTypes are only used during development, defaultProps are always applied.

Note that the src prop passed to Img is passed through to the <img> element it renders. Styled-components filter out props like imgHeight that aren’t valid DOM attributes. This means you should be careful what prop names you use for styling. If, for example, we’d named the prop height instead of imgHeight, then it would’ve been passed down as the height DOM attribute of the <img>.

Right now, imgHeight can be overridden on a slide-by-slide basis since Carousel passes the whole slide data object down to CarouselSlide as props. But in most cases, the Carousel consumer will want it to have a consistent height.

  • So let’s add a prop to Carousel that overrides the default imgHeight on CarouselSlide:

    // src/Carousel.js
    ...
    export default class Carousel extends 
    React.PureComponent {
      static propTypes = {
        defaultImgHeight: CarouselSlide.propTypes.imgHeight,
        slides: PropTypes.arrayOf(PropTypes.shape(CarouselSlide.propTypes))
          .isRequired,
      };
    
      static defaultProps = {
        defaultImgHeight: CarouselSlide.defaultProps.imgHeight, // 1
      };
      ...
      render() {
        const { defaultImgHeight, slides, ...rest } = this.props;
        return (
          <div {...rest}>
            <CarouselSlide
            imgHeight={defaultImgHeight} // 2
              {...slides[this.state.slideIndex]}
            />
            <CarouselButton data-action="prev" onClick={this.handlePrevClick}>
              Prev
            </CarouselButton>
            <CarouselButton data-action="next" onClick={this.handleNextClick}>
              Next
            </CarouselButton>
          </div>
        );
      }
    }
    
  1. The default value from CarouselSlide’s imgHeight is reused as the default value for defaultImgHeight. Functionally, this is redundant, but defaultProps is also commonly used for documentation, as we’ll see in the chapter “Continuous Integration and Collaboration”.

  2. Importantly, defaultImgHeight is passed down as imgHeight before the slide data object spreads, giving the latter precedence. If it were the other way around, individual slides would be unable to override imgHeight.

If you feel naked without test coverage for all of these changes, you can skip ahead to Testing Components, then come back for a detour into styled-components tooling.

So, How did styled-components get our styles into the <img> tag? If you inspect one of the <img> tags in the browser, as in the next screenshot, you’ll see that its class attribute is full of gibberish. Something like class="sc-bdVaJa hhfYDU". The styled-components library generated these class names for you and injected a corresponding style rule into a <style> tag in the <head> of the page.

Get hands-on with 1200+ tech skills courses.