Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    Why CSS-in-JS is winning, an example

    Stepper component built with CSS-in-JS

    Modern CSS-in-JS is the best thing that's happened to the web since components. Hands down. Let me explain. 👇

    First we had the style prop and that sucked. I hated CSS-in-JS and thought it was stupid. You can pry my CSS from my dead cold hands! shakes fist

    Then came experiments.

    Can we make the style prop easier to use? Can we still use class names to style with BEM like we used to? What about CSS files with fat semantic classes? Scoped styles maybe?

    None of it quite worked. Everything was awkward in some way.

    The style prop is great for individual components and breaks down when you want to change your general font size.

    Class names give you all the power of CSS and BEM (tiny classes like .font1 .bold .bla). And it breaks down when you reuse components like building blocks. A component has the color defined and if you want to overwrite that you need something that understands classes and decides which one takes power and ugh don't get me started.

    I may have once tried to make that work ...

    Scoped CSS is better. That's what Vue uses. But it still sucks, if you ask me. You get a weird mix of global styles with local overwrites and mixing BEM and semantic classes and it gets messy fast. Leads to weird code too.

    But then, then some beautiful soul came up with styled-components. I was in love.

    All the simplicity of scoped CSS with the power of the style prop. You write CSS, yes, but you can inject JavaScript too.

    Why styled-components are win, an example

    The best way to make my argument is with an example. I recently implemented a colorful React stepper component for a client project.

    Styled components and a couple libraries @Diegohaz built on top made it so easy it feels like cheating.

    You have 9 steps. Each a different color once done. Blank with a grey outline otherwise.

    Here's a CodeSandbox you can try.

    Pretty isn't it? I think it is.

    Here's all the component code 👇

    const Step = ({ index, done, last }) => (
      <Circle index={index} done={done} last={last}>
        {last ? "⭐️" : index + 1}
      </Circle>
    )
    
    export default ({ step, maxSteps }) => (
      <Steps row>
        {range(maxSteps).map((i) => (
          <Step index={i} key={i} last={i === maxSteps - 1} done={i < step} />
        ))}
      </Steps>
    )
    

    Yep that's it. 12 lines of code.

    A <Stepper> component, the default export, takes the current step and max number of steps, maxSteps. It renders a <Steps> component which deals with layouting. Inside goes an array of <Step>s.

    Each <Step> gets an index, a flag saying whether it's last, and whether it's been done yet. Highlighted vs. unhighlighted state.

    Steps render a <Circle> component, pass-in the props, and inside goes either a number or a star emoji. Star is last.

    All style logic goes in styles

    Moving your visual UI logic out of your components and into styles is where CSS-in-JS shines. Focus your components on business logic, user experience, and hierarchy. Let your styles take care of your styling.

    Just like our CSS Garden forebears envisioned. Remember CSS Gardens?

    You might be too young. The world has moved on. CSS's initial promise is that we would completely decouple styling from document hierarchy and live a Zen stress free life.

    Now look at us.

    BUT ALL IS NOT LOST! Look at this beauty.

    import { Box, Flex, Heading, styled } from "reakit"
    
    const Steps = styled(Flex)`
      align-items: center;
      justify-content: center;
      padding-top: 40px;
    `
    

    I'm using Diegohaz's reakit for base CSS stuff. He built a bunch of components on top of styled-components that have sane defaults and great composability.

    No styles, just some basics. Flex, for example, is a div with display: flex. That's all.

    <Steps> then is a flexbox div that centers its children and adds a 40px padding on top.

    Easy 👌

    And then comes the <Circle> component. The individual steps.

    import { theme, palette, withProp, ifProp } from "styled-tools"
    
    const Circle = styled(Box)`
            border-radius: 100%;
            border: 1px solid;
            border-color: ${ifProp(
              "done",
              withProp("index", (index) => palette("steps", index)),
              palette("grey", 2)
            )}
            color: ${ifProp("done", "white", palette("grey", 2))};
            background: ${ifProp(
              "done",
              withProp("index", (index) => palette("steps", index)),
              "white"
            )}
            font-size: ${palette("size", 1)};
            ${theme("bold")}
            width: 30px;
            height: 30px;
            line-height: 30px;
            text-align: center;
            margin-right: 20px;
    
            &:last-child {
                margin-right: 0px;
            }
        `
    

    A few things are happening here. Let me explain.

    1. I'm using another of Diegohaz's libraries styled-tools. It's a suite of helper functions that make it easier to use component props from your CSS-in-JS styles
    2. I'm using a theme behind the scenes. A concept built into Reakit that lets you define global styles. Things like UI colors, font sizes, stuff like that.
    3. ALL of my visual logic is in this style

    Let's break it down.

    We start with a Box, which is just a div.

    const Circle = styled(Box)`
    

    Then we turn it into a circle with a border.

    border-radius: 100%;
    border: 1px solid;
    

    Border and background color come from our theme, based on the combination of index and done props.

        border-color: ${ifProp(
                "done",
                withProp("index", index => palette("steps", index)),
                palette("grey", 2)
            )}
            background: ${ifProp(
                "done",
                withProp("index", index => palette("steps", index)),
                "white"
            )}
    

    Here's how you read that:

    1. If prop done is true, get first argument, otherwise the second.
    2. With prop index run method.
    3. Get the steps palette from theme and take the index color.
    4. Otherwise, get the grey palette and take the 2nd color

    Palettes are arrays in your theme object. Pure JavaScript. You can see it in the CodeSandbox above.

    A similar process gives you the font color.

        color: ${ifProp("done", "white", palette("grey", 2))};
    

    If step is done, color is white, otherwise the 2nd color from the grey palette.

    Then we have font size, bold fonts, widths and heights, and other normal CSS stuff to round off each step.

        font-size: ${palette("size", 1)};
            ${theme("bold")}
            width: 30px;
            height: 30px;
            line-height: 30px;
            text-align: center;
            margin-right: 20px;
    
            &:last-child {
                margin-right: 0px;
            }
        `;
    

    You can still use CSS nesting and meta classes just like you're used to. &:last-child matches the last <Step> and removes its margin so it doesn't mess with the layout.

    👌

    Conclusion

    This flavor of CSS-in-JS is the best most ridiculously easy way I've ever seen to style modern JavaScript components. I pray that it works for Vue as well as it does for React because I want to get my frontend peeps at the dayjob excited about it.

    To recap:

    1. Components deal with business logic and UX
    2. Styles handle all visual logic
    3. Global theme handles generics

    Yes that means you ca rebrand your whole app just by changing theme.js.

    Published on January 30th, 2019 in Front End, Technical

    Did you enjoy this article?

    Continue reading about Why CSS-in-JS is winning, an example

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

    Created by Swizec with ❤️