Animating Styled Components using Style Objects or Tagged Template Literals

Charlie Thomas
5 min readApr 15, 2022
Photo by Pankaj Patel on Unsplash

Don’t you just love styled-components? One of the best features I have found is the ability to easily compose various base components to get the styles and functionality I need. This feature will come in handy in a few moments (spoiler?).

When writing styled-components, I tend to prefer using Style Objects (defining CSS as a JS object) over Tagged Template Literals (defining CSS as an interpolated string).

// Style Object
const StyleObject = styled.div({
width: '100%',
height: '100%',
backgroundColor: 'red'
})
// Tagged Template Literal
const TaggedTemplateLiteral = styled.div`
width: 100%;
height: 100%;
background-color: red;
`

It’s a personal preference. I find that Style Objects tend to play better with Typescript to catch errors in my CSS. Also, with Style Objects, I get to write EVERYTHING in Javascript and not blend the two languages in a single file.

However, the use of Style Objects can pose a problem when trying to animate components.

Let’s take a look at how to make a simple rotating box using Tagged Template Literals, then again with Style Objects. Then, we will take a quick peek at how to animate components that can change at runtime by passing in props.

How I hypnotize my readers into clapping and following.

Static Animation with Tagged Template Literals

If you prefer using Tagged Template Literals and you are not doing anything special — you do not need to change the animation at runtime or create a shared animation to use with multiple components — then the animations section of the styled-components documentation lays out how to do this using the keyframes helper.

Here is the code that was used to generate the rotating box above.

import styled, { keyframes } from 'styled-components';const rotate = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
export const AnimatedBox = styled.div`
width: 50px;
height: 50px;
background-color: pink;
animation: ${rotate} 2s linear infinite;
`

Static Animation with Style Objects

Try to follow the same string interpolation approach and, well…

// THIS WILL NOT WORKexport const AnimatedBox = styled.div({
width: '50px',
height: '50px',
backgroundColor: 'pink',
animation: `${rotate} 2s linear infinite`
})

You should expect the following error:

It seems you are interpolating a keyframe declaration (%s) into an untagged string. This was supported in styled-components v3, but is not longer supported in v4 as keyframes are now injected on-demand. Please wrap your string in the css`` helper which ensures the styles are injected correctly. See https://www.styled-components.com/docs/api#css

Blah blah, “injected on-demand”, blah blah. Okay, so clearly we should use the css helper and try to shoehorn a solution without compromising our need to use Style Objects.

// THIS WILL NOT WORK EITHER!import styled, { keyframes, css } from 'styled-components';const rotateAnimation = css`
${rotate} 2s linear infinite
`
export const AnimatedBox = styled.div({
...
animation: `${rotateAnimation}`
})

Nah, thats not going to work either.

What is going on here? Well, instead of ‘blah blah’ing past that error message, if I had more closely read “It seems you are interpolating a keyframe declaration (%s) into an untagged string”, I may have correctly interpreted that as meaning “tagged strings are required for keyframe animations”.

Why? Because styled-components wants to lazily evaluate keyframe animations, the use of Tagged Template Literals means that the ${rotate} interpolation gets passed in as an argument to the tagged string function to be evaluated at the pleasure of the program, instead of immediately evaluated at compile time when used in a Style Object.

Add that to the list of cons for Style Objects I guess.

Solution: I hinted at it earlier, but we can simply compose two styled components, one using Tagged Template Literals and the other using a Style Object.

import styled, { keyframes, css } from 'styled-components';const rotate = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
// Create an Animation component using a Tagged Template Literal
const Animation = styled.div`
animation: ${rotate} 2s linear infinite;
`
// Compose the Animation component with your
export const AnimatedBox = styled(Animation)({
width: '50px',
height: '50px',
backgroundColor: 'pink',
})

If much of your code is already written using Style Objects, this is a quick way to get the animations you need.

Dynamic Animation with Tagged Template Literals

Now, I want to be able to change the background color of the box and the speed of the animation at runtime by passing in some props.

With Tagged Template Literals, just call on the props:

import styled, { keyframes } from 'styled-components';const rotate = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
export const AnimatedBox = styled.div`
width: 50px;
height: 50px;
background-color: ${props => props.backgroundColor};
animation: ${rotate} ${props => props.animationLength} linear infinite;
`
// render it somewhere else
render(
<AnimatedBox backgroundColor={'red'} animationLength={'1s'}/>
)

Maybe a bit cleaner if we use the css helper.

import styled, { keyframes, css } from 'styled-components';//...const animation = props => css`
${rotate} ${props.animationLength} linear infinite;
`
const AnimatedBox = styled.div`
width: 50px;
height: 50px;
background-color: ${props => props.backgroundColor};
animation: ${animation};
`

Dynamic Animation with Style Objects

The approach is very similar to dynamic animations with Tagged Template Literals, with some syntax changes.

import styled, { keyframes } from 'styled-components';const rotate = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
const Animation = styled.div`
animation: ${rotate} ${props => props.animationLength} linear infinite;
`
const AnimatedBox = styled(Animation)(({backgroundColor}) => ({
margin: '100px',
width: '50px',
height: '50px',
backgroundColor: backgroundColor,
}))
// render it somewhere else
render(
<AnimatedBox backgroundColor={'red'} animationLength={'1s'}/>
)

Takeaways

Implementing simple animations in styled-components can be accomplished with relatively little code and ultimately is pretty straight forward. However, the choice between Tagged Template Literals and Style Objects is not purely a choice of personal style — they are not functionally equivalent for all styled-component features such as keyframe animations.

--

--