How to convert less to styled components

How do you convert less stylesheets to styled components?

Less is a popular language extension for CSS that allows a lot of cool stuff that CSS can only dream of. Less’ superpowers comes from three new abilities that it brings which CSS doesn’t have- namely nesting of selectors, variables, and mixins. Less along with Sass blew web styling out of the water when they were invented 10 years ago because it allowed developers to write styling closer to how they write javascript.

So if Less is so great, why I am writing an article about how to switch away from it? Well, React has been gaining more and more momentum recently for its ability to compose discrete components together to form the user interface.

React components are building blocks.
React components are building blocks.

The hallmark of React is that you can write your HTML together with your javascript, so that a single component has all the relevant code together in one spot. Styled-components is an alternative to Less and Sass that follows that same way of organizing. Now you can have composable parts for your javascript, html, and styling and you can write it all using javascript functions. It’s really the best of all worlds!

So if I have old Less code how can I get it converted over to styled components?

Step 1: Set a Global Style for all global stylesheets.

We take any global css styling and move it into the global style by wrapping it with a createGlobalStyle.

import { createGlobalStyle } from 'styled-components'

export const GlobalStyle = createGlobalStyle`
@font-face {
    font-family: "Roboto";
    font-weight: normal;
    src: local("Roboto"), url('https://fonts.google.com/...') format("truetype");
  }

  body {
      font-family: "Roboto", sans-serif;
      font-size: 12px;
      color: black;
  }

  h1 {
    font-size: 16px;
  }

  a {
      text-decoration: none;
      color: inherit;
  }

`

Then, once we have that <GlobalStyle /> component defined, we add it to the root component of our project:

const Layout = ({ children, title }) => (
  <>
    <GlobalStyle />
    <Header />
    <Page />
    </Footer>
  </>
)

Step 2: Move all of your variables into a theme style.

Styled-components provides a global theme component that you can define in the root of project and it will be accessible everywhere. Let’s say these are the less variables that we want to access site wide:

@theme-text: #cccccc;
@label-theme: #2d5a6b;
@label-text: @theme-text;
@label-border: 1px solid @theme-text;

We create a javascript object called theme that we can store all of our constants in. Each key in the object will represent the variable name and the value will be the value it is in less.

The trouble with javascript objects though is that you can't have one value reference a different value within the same object. So if I had a color value and I wanted to create a second variable called backgroundColor which is the inverse of color, I am not allowed to reference the value of color when it is in the same object as the place where I'm setting it.

To get around this limitation- we start by defining everything as separate strings. Since one string can use information from a different string, we don't have any problem. Then, once we have all our strings, we build one giant object called theme and we are ready to go.

So first we convert all of our less variables over to strings- be careful about switching any dashes (-) to underscores (_) because you can't hava a dash in a javascript variable name. When we create the theme object, we can have a dash in an object key name, so if we had a less variable named @theme-text and want to stick with snake case notation throughout our styled-components, we can by creating the key theme-text in the theme object: theme['theme-text']. A few lines of code are worth 1000 words, so this is the pattern I used:

const color: '#442d6b';
const theme_text = '#cccccc';
const label_theme = '#2d5a6b';
const label_text = theme_text;
const label_border = `1px solid ${theme_text}`;


const theme = {
  color, //I don't need color: color here because the names are the same
  "theme-text": theme_text,
  "label-theme": label_theme,
  "label-text": label_text,
  "label-border": label_border
}

export default theme;

We now have to import the theme object into the <ThemeProvider> object in the root of our project. Just make sure that we export the theme object and then import it into the root of the project and use it with the <ThemeProvider> component from the styled-components library:

import { ThemeProvider } from 'styled-components'
import theme from './theme' //This is the theme object that we defined above

const Layout = ({ children, title }) => (
  <>
    <ThemeProvider theme={myTheme}>
      <GlobalStyle />
      <Header />
      <Page />
      </Footer>
    </ThemeProvider>
  </>
)

Now we will have access to everything in the theme object as a parameter in all of our styled-components that are children of <ThemeProvider>. We'll show you how to do this in Step 4, but next we have to show you how to get the rest of the styling copied over.

Step 3: Break down the less components into chunks of styling that you can attach to react components.

Since a main benefit to using React is it’s composibility, we now have to break apart the single less stylesheet into discrete chunks. This isn’t as bad as it sounds, for example, if we had a class called blogItem that had a bunch of less that stylized the item card, we create a styled-component called BlogItem and we copy all the styling into it. You can keep all the nested styles and media queries in place.

.blogItem {
  font-size: 12px;
  margin-top: 24px;
  //..many more stylings excluded
}

goes to:

export const StyledBlogItem = styled.div`
  font-size: 12px;
  margin-top: 24px;
  //...many more stylings excluded
`

export const BlogItem = props => <StyledBlogItem props={props} />

Step 4: Convert any variables referenced in the less stylesheets

Now that we have the bulk of the style sheet code copied over, we now need to deal with variables. Since these variables are now defined in our theme, we can access them in any styled component by using the theme parameter like this:

import styled from 'styled-components'

export const BlogItem = styled.div`
  ${({ theme }) => `
      color: theme['color'];
    
    `}
`

The only gotcha here is that you need to watch out for is any place where a less variable is being multiplied or divided by a number. Examples of this include multiplying a font size, border radius, or border thickness by a constant. In this example below, we have a less variable named global-border-radius which we define as 12px. It's valid to get a smaller border radius by dividing it by a number in another spot in the stylesheet. Here, the blogItem class would have a border-radius of 6px even though the global setting is 12px.

@global-border-radius: 12px;

.blogItem {
  border-radius: @global-border-radius / 2;
}

Styled components can’t deal with this because you are essentially trying to divide the string 12px by the number 2. Here’s a function that you can copy and use which will handle this for you automatically:

export const modifySize = (fontString, modifyFrac) => {
  const fontNumber = parseFloat(fontString)
  const fontUnit = fontString.replace(/[0-9]/g, '')
  return `${fontNumber * modifyFrac}${fontUnit}`
}

const BlogItem = styled.div`
  ${({theme})=> `
    border-radius: `${modifySize(theme['global-border-radius'], 2)}`
  `}
`

Our function modifySize is breaking the string 12px down into the number 12 and the string px. It will size the number by the factor you pass in and return a string with the correct sizing.

Step 5: convert mixins to functions

Mixins are a way in less to make a little snippet of styles that you can apply to a number of items. Here’s a mixin that will set a variety of parameters for a heading:

.heading (@selector, @size, @color: @theme-title) {
  text-align: center;
  font-size: @size;
  font-weight: bold;
  color: @color;
}

We can capture this same essence by making a function that will return a string with all that styling information and styled-components will happily add it wherever you’d like:

export const heading = (theme, size, color) => {
  const setColor = color ? color : theme['theme-title']
  //I can set a default color if color isn't passed in

  return `
    text-align: center;
    font-size: ${size};
    font-weight: bold;
    color: ${setColor};
`
}

What’s cool about this is that if we want, we can even pass in variables to control the output of the function. The only gotcha is that if you need access to global variables, you need to pass in the theme parameter into the function- it won’t be automatically be a parameter like it is when you create styled components. We can call the heading function like this:

const BlogHeading = styled.div`
  ${({ theme }) => `
    ${heading(theme, '16px', 'black')}
  `}
`

Step 6: use && for and straggling styles

For most conversions the first 4 steps should be enough, but every once in a while you will have some styles that won’t come through. I ran into it in one case where I had a css stylesheet that was forming my base style and then when I was trying to override it with my styled component, the base styling was persistantly sticking around.

:not(pre) > code[class*='language-'],
pre[class*='language-'] {
  background-color: #fdf6e3;
}
:not(pre) > code[class*='language-'] {
  font-family: 'Roboto-Mono';
  padding: 0.1em;
  border-radius: @border-radius-small;
  background-color: @post-highlight;
  color: @post-highlight-text;
}

The solution is to add a && in front of the style that is sticking around. Each & will bump the generated className for each & that you add.

const StyledContent = styled.div`
  ${({ theme }) => `
        && :not(pre) > code[class*='language-'] {
            font-family: 'Roboto-Mono';
            padding: 0.1em;
            border-radius: ${theme['border-radius-small']};
            background-color: ${theme['post-highlight']};
            color: ${theme['post-highlight-text']};
            background-color: purple;
        }
    `}
`

Learn something new? Share it with the world!

There is more where that came from!

Drop your email in the box below and we'll let you know when we publish new stuff. We respect your email privacy, we will never spam you and you can unsubscribe anytime.