Back to Styled Components

Coming From Css

sections/basics/coming-from-css.mdx

latest6.5 KB
Original Source

Coming from CSS

How do Styled Components work within a component?

If you're familiar with importing CSS into your components (e.g. like CSSModules) you'll be used to doing something like this:

tsx
import React from 'react';
import styles from './styles.css';

export default class Counter extends React.Component {
  state = { count: 0 };

  increment = () => this.setState({ count: this.state.count + 1 });
  decrement = () => this.setState({ count: this.state.count - 1 });

  render() {
    return (
      <div className={styles.counter}>
        <p className={styles.paragraph}>{this.state.count}</p>
        <button className={styles.button} onClick={this.increment}>
          +
        </button>
        <button className={styles.button} onClick={this.decrement}>
          -
        </button>
      </div>
    );
  }
}

Because a Styled Component is the combination of the element and the rules that style it, we'd write Counter like this:

tsx
import React from 'react';
import styled from 'styled-components';

const StyledCounter = styled.div`
  /* ... */
`;
const Paragraph = styled.p`
  /* ... */
`;
const Button = styled.button`
  /* ... */
`;

export default class Counter extends React.Component {
  state = { count: 0 };

  increment = () => this.setState({ count: this.state.count + 1 });
  decrement = () => this.setState({ count: this.state.count - 1 });

  render() {
    return (
      <StyledCounter>
        <Paragraph>{this.state.count}</Paragraph>
        <Button onClick={this.increment}>+</Button>
        <Button onClick={this.decrement}>-</Button>
      </StyledCounter>
    );
  }
}

Note that we added a "Styled" prefix to StyledCounter so that the React component Counter and the Styled Component StyledCounter don't clash names but remain easily identifiable in the React Developer Tools and Web Inspector.

Define Styled Components outside of the render method

It is important to define your styled components outside of the render method, otherwise it will be recreated on every single render pass. Defining a styled component within the render method will thwart caching and drastically slow down rendering speed, and should be avoided.

Write your styled components the recommended way:

tsx
const StyledWrapper = styled.div`
  /* ... */
`;

const Wrapper = ({ message }) => {
  return <StyledWrapper>{message}</StyledWrapper>;
};

Instead of:

tsx
const Wrapper = ({ message }) => {
  // WARNING: THIS IS VERY VERY BAD AND SLOW, DO NOT DO THIS!!!
  const StyledWrapper = styled.div`
    /* ... */
  `;

  return <StyledWrapper>{message}</StyledWrapper>;
};

Recommended reading: Talia Marcassa wrote a great review of real-world usage, featuring lots of solid practical insights and comparisons with alternatives, in Styled Components: To Use or Not to Use?

Pseudoelements, pseudoselectors, and nesting

The preprocessor we use, stylis, supports scss-like syntax for automatically nesting styles.

Through this preprocessing, styled-components supports some advanced selector patterns:

  • & a single ampersand refers to all instances of the component; it is used for applying broad overrides:

    react
    const Thing = styled.div.attrs((/* props */) => ({ tabIndex: 0 }))`
      color: blue;
    
      &:hover {
        color: red; // <Thing> when hovered
      }
    
      & ~ & {
        background: tomato; // <Thing> as a sibling of <Thing>, but maybe not directly next to it
      }
    
      & + & {
        background: lime; // <Thing> next to <Thing>
      }
    
      &.something {
        background: orange; // <Thing> tagged with an additional CSS class ".something"
      }
    
      .something-else & {
        border: 1px solid; // <Thing> inside another element labeled ".something-else"
      }
    `
    
    render(
      <React.Fragment>
        <Thing>Hello world!</Thing>
        <Thing>How ya doing?</Thing>
        <Thing className="something">The sun is shining...</Thing>
        <div>Pretty nice day today.</div>
        <Thing>Don't you think?</Thing>
        <div className="something-else">
          <Thing>Splendid.</Thing>
        </div>
      </React.Fragment>
    )
    
  • && a double ampersand refers to an instance of the component; this is useful if you're doing conditional styling overrides and don't want a style to apply to all instances of a particular component:

    react
    const Input = styled.input.attrs({ type: "checkbox" })``;
    
    const Label = styled.label`
      align-items: center;
      display: flex;
      gap: 8px;
      margin-bottom: 8px;
    `
    
    const LabelText = styled.span`
      ${(props) => {
        switch (props.$mode) {
          case "dark":
            return css`
              background-color: black;
              color: white;
              ${Input}:checked + && {
                color: blue;
              }
            `;
          default:
            return css`
              background-color: white;
              color: black;
              ${Input}:checked + && {
                color: red;
              }
            `;
        }
      }}
    `;
    
    render(
      <React.Fragment>
        <Label>
          <Input defaultChecked />
          <LabelText>Foo</LabelText>
        </Label>
        <Label>
          <Input />
          <LabelText $mode="dark">Foo</LabelText>
        </Label>
        <Label>
          <Input defaultChecked />
          <LabelText>Foo</LabelText>
        </Label>
        <Label>
          <Input defaultChecked />
          <LabelText $mode="dark">Foo</LabelText>
        </Label>
      </React.Fragment>
    )
    
  • && a double ampersand alone has a special behavior called a "precedence boost"; this can be useful if you are dealing with a mixed styled-components and vanilla CSS environment where there might be conflicting styles:

    react
     const Thing = styled.div`
       && {
         color: blue;
       }
     `
    
     const GlobalStyle = createGlobalStyle`
       div${Thing} {
         color: red;
       }
     `
    
     render(
       <React.Fragment>
         <GlobalStyle />
         <Thing>
           I'm blue, da ba dee da ba daa
         </Thing>
       </React.Fragment>
     )
    

If you put selectors in without the ampersand, they will refer to children of the component.

react
const Thing = styled.div`
  color: blue;

  .something {
    border: 1px solid; // an element labeled ".something" inside <Thing>
    display: block;
  }
`

render(
  <Thing>
    <label htmlFor="foo-button" className="something">Mystery button</label>
    <button id="foo-button">What do I do?</button>
  </Thing>
)