Getting Sassy with styled-components

Adam GruberOriginally published on Medium

Progressively migrating your app from Sass to styled-components seems daunting, but with a couple of quick tips and the right tooling it's a joy!

Getting Sassy with styled-components

The Basic Button Component

Initial Sass Implementation

.btn {
  border: none;
  border-radius: $base-radius;
  cursor: pointer;
  display: inline-block;
  font-size: $base-font-size;
  font-weight: $font-semibold;
  line-height: 2.5;
  padding: 0 12px;
  position: relative;
  text-align: center;
}

Button variants

Converting to styled-components

The conversion process involves three steps: importing styled-components, defining the component using styled.button, and copying base styles into a template literal:

import styled from 'styled-components';


const Button = styled.button`
  border: none;
  border-radius: ${props => props.theme.baseRadius};
  cursor: pointer;
  display: inline-block;
  font-size: ${props => props.theme.baseFontSize};
  font-weight: ${props => props.theme.fontSemibold};
  line-height: 2.5;
  padding: 0 12px;
  position: relative;
  text-align: center;
`;

Usage: <Button>Click Me</Button>

Button Variants with Interpolations

Styled-components enables dynamic styling through interpolated functions receiving component props:

const Button = styled.button`
  background-color: ${props =>
    (props.primary && props.theme.primary)
    || (props.danger && props.theme.danger)
    || ((props.inverted || props.link) && '#fff')
    || (props.disabled && props.theme.brandGrey)
  };
  color: ${props =>
    (props.inverted && props.theme.primary)
    || (props.link && props.theme.baseFontColor)
    || '#fff'
  };
  cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};


  &:hover {
    ${props => props.link && 'text-decoration: underline;'}
  }
`;

Reusing Variables with Themes

The Problem

The component references Sass variables that aren't valid CSS values. Rather than hardcoding values, the solution preserves variable reusability through styled-components theming.

Solution: ThemeProvider

Styled-components provides a <ThemeProvider> wrapper accepting a theme object with key-value pairs, making theme data available to all child components.

Extracting Sass Variables

The sass-extract and sass-extract-js packages convert Sass variables into JavaScript objects:

npm install -D sass-extract sass-extract-loader node-sass sass-extract-js
import React from 'react';
import ReactDOM from 'react-dom';
import { ThemeProvider } from 'styled-components';
import Button from './components/Button';


const theme = require(
  'sass-extract-loader?{"plugins": ["sass-extract-js"]}!./vars.scss'
);


const App = () => (
  <ThemeProvider theme={theme}>
    <div>
      <Button primary>Click Me</Button>
    </div>
  </ThemeProvider>
);


ReactDOM.render(<App />, document.getElementById('root'));

When a Button Isn't a Button

The withComponent() Method

To render the Button as an anchor tag without duplicating styles, use the withComponent() method:

import Button from './components/Button';


export const BtnLink = Button.withComponent('a');

This creates a new component that retains all the styles defined for our Button but simply renders an <a> tag.

Conclusion

While migrating a full application to styled-components represents significant effort, with its support for themes and great tools like sass-extract and sass-extract-js we don't need to transition all at once. Teams can incrementally adopt styled-components for new components while maintaining consistency with existing Sass-based styles.