All Articles

15 min read • By Kyle Truong • Published 1 Jun 2017

Animating React Components with CSS and Styled Components

There are a lot of great resources on how to do animations with CSS and there are a lot of great resources on how to do styling in React but there are relatively few resources on how to do them both together. One would think that if I knew one and the other that I’d smush them together to make two. That’s turned out harder than expected and here’s what I learned.

Disclaimer: The following does not necessarily represent best practices or even the recommended approach to styling and/or animating react components. They’re just possibilities of how one can style and animate react components.

Animations have great potential to improve user experience and give it that ‘wow’ factor, but they also have the potential to frustrate and make your app look tacky, so proceed cautiously when animating.

Which Tools and Why?

I’m choosing CSS animations over JS animations because CSS animations are often simpler and more performant than most JS libraries. JS animation libraries have different APIs while CSS animations are a standard that have been with us since CSS3. This is not to say that you should always choose one over the other because they can be combined and there are certain animations where CSS just falls flat compared to JS.

As for styled-components, I’m choosing this library for its ease and convenience. It gives me the isolation that css-modules brings, requires no build step, allows for the use of React props and JS variables and expressions to give me that extra flex with CSS, while also letting me write plain CSS (although it does get wrapped in a funky wrapper). It’s also nice that styled-components makes regular React components because you can toss them around as you would with any other React component.

Here’s a quick comparison between CSS solutions in React:

Solutions other than styled-components:

CSS in JS tools comparison

Styled-components:

Styled-components checklist https://github.com/styled-components/comparison/tree/master/examples/styled-components

Using CSS Transitions

Probably the most basic of all the options, we use CSS transitions by specifying the ‘transition’ property onto the element we wish to have transition. Once on, it acts like an event listener and it listens for the one property to change on the element, and once changed the element will transition.

There are many properties that can be transitioned and it generally seems that if the property can be represented by a number then it can be transitioned. But for performance reasons you should generally stick to transitioning the following properties:

CSS Animation Infographic https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/

Making Our First Animated Component

With create-react-app, create a new react application and create a couple folders and files like this:

create-react-app css-animations-styled-components

File tree

// App/index.js

import React from 'react';
import Box from './styles/Box

export class App extends React.Component {
  render() {
    return (
      <Box />
    );
  }
}

export default App;
// App/styles/Box.js

import styled from 'styled-components';

export const Box = styled.div`
  display: inline-block;
  background: pink;
  width: 200px;
  height: 200px;
  transition: transform 300ms ease-in-out;

  &:hover {
    transform: translate(200px, 150px) rotate(20deg)
  }
`

export default Box;

Transitions are like event listeners and you can tack them onto the element you want to listen along with the property to listen for. In the ‘App/styles/Box.js’ example above, the ‘Box’ div is listening for changes on the ‘transform’ property so that when ‘transform’ does change it will execute the transition.

We trigger the change when the ‘Box’ div is hovered:

Tipsy Turny

Tipsy Turny

You may notice that pink box will only transition while you’re mouse is directly hovering over it and as the box transforms into a new position your mouse will no longer be hovering it and it will stop transitioning and create this jerky movement. Here’s how to fix that.

// App/index.js

import React from 'react';
import Box from './styles/Box
import Trigger from './styles/Trigger;

export class App extends React.Component {
  render() {
    return (
      <Trigger>
        <Box />
      </Trigger>
    );
  }
}

export default App;
// App/styles/Box.js

import styled from 'styled-components';

export const Box = styled.div`
  display: inline-block;
  background: pink;
  width: 200px;
  height: 200px;
  transition: transform 300ms ease-in-out;
`

export default Box;
// App/styles/Trigger.js

import styled from 'styled-components';

import Box from './Box;

export const Trigger = styled.div`
  width: 200px;
  height: 200px;
  border: 20px solid #999;
  background: #ddd;

  &:hover ${Box} {
    transform: translate(200px, 150px) rotate(20deg);
  }
`

export default Trigger;

We create a new ‘Trigger’ component (represented as a div) that holds the ‘Box’ component. We still make the ‘Box’ component listen to the transform property but now we change the trigger to happen when the user hovers over the ‘Trigger’ component instead of the ‘Box’ component.

&:hover ${Box} {
  transform: translate(200px, 150px) rotate(20deg);
}

‘${Box}’ is how you select other components in styled-components (make sure you import ‘Box’). The above hover line says: “Select all ‘Box’ components within ‘Trigger’ components in the state of hover and apply this transform on said ‘Box’”.

Note that the pink box still transitions as you hover it. If you want the box to only transition when your mouse hovers the ‘Trigger’ box and not the pink ‘Box’, then set ‘pointer-events: none’ on ‘Box’. setting ‘pointer-events’ to none, means ‘:hover’ won’t work on Box, but it will work on ‘Trigger’, which then applies the transform onto ‘Box’.

Demo 2

Using KeyFrames and the CSS Animation Property

Keyframes are what the animation does and the animation is how it’s done. (the name, the duration, ease, direction, amount of time…). — Travis Neilson

Normal keyframes do not exist within selectors but they are given a name that will later be called by a selector. In a sense, they’re kind of global as they can be used by any selector as long as the selector calls the correct name of the keyframe. Styled-components has a helper, ‘keyframes’, that generates a unique name for your keyframe so they won’t clash in common namespaces.

Keyframes go hand in hand with the CSS ‘animation’ property. The ‘animation’ property is a short-form property and can be substituted by the following:

  • animation-name
  • animation-duration
  • animation-timing-function
  • animation-delay
  • animation-iteration-count
  • animation-direction
  • animation-fill-mode
  • animation-play-state

https://developer.mozilla.org/en-US/docs/Web/CSS/animation?v=control

And together, keyframes with the animation property can give you finer control over your animations than CSS transitions, although being more verbose.

Keyframes tell your animation what to do by defining hooks or frames at which you want things to happen. For example, imagine the life of your animation is specified by a timeline of keyframes from 0% to 100%, you could specify from 0% to 100% that you want the animation to animate a height of 200px to 600px:

// App/styles/Box.js

import styled from 'styled-components';

import { keyFrameExampleOne } from './KeyFrames';

export const Box = styled.div`
  display: inline-block;
  background: pink;
  width: 200px;
  height: 200px;
  position: relative;
  animation: ${keyFrameExampleOne} 2s ease-in-out 0s infinite;
`

export default Box;
// App/styles/KeyFrames.js

import styled, { keyframes } from 'styled-components';

export const keyFrameExampleOne = keyframes`
  0% {
    height: 200px;
  }
  100% {
    height: 600px;
    background: orange;
  }
`
// App/index.js

import React from 'react';
import Box from './styles/Box'
import Trigger from './styles/Trigger';

export class App extends React.Component {
  render() {
    return (
      <Trigger>
        <Box />
      </Trigger>
    );
  }
}

export default App;

Demo 3

If you want even more control over your keyframes, you can hook into more keyframes and properties too. In the following, the box will grow in height from 200px to 600px from 0% to 100% of the keyframe timeline, but it will also grow in width from 0px to 400px from 0% to 30% of the keyframe timeline, and maintain that width until the end of the keyframe timeline:

// App/styles/KeyFrames.js

import styled, { keyframes } from 'styled-components';

export const keyFrameExampleOne = keyframes`
  0% {
    height: 200px;
  }
  30%, 100% {
    width: 400px;
  }
  100% {
    height: 600px;
    background: orange;
  }
`

Demo 4

Any property you can animate with ‘transition’ is a property you can animate with ‘animation’ and ‘keyframes’, but now you can pick and choose choose what happens on a timeline of 0% to 100%. I hope you can see how much more leverage you can potentially get from keyframes and animations over regular transitions.

A Final Example

Let’s make a reusable React component that’s animated when rendered in. We’ll have a clickable button that renders the React component, called CoolBox.

We make two new folders for our components, Final and CoolBox:

File tree 2

// src/CoolBox/index.js

import styled, { keyframes } from 'styled-components';

const coolBoxKeyframes = keyframes`
  0% {
    height: 0px;
    background: green;
  }
  100% {
    height: 200px;
    background: blue;
  }
`

export const CoolBox = styled.div`
  display: inline-block;
  background: green;
  width: 100px;
  position: relative;
  animation-name: ${coolBoxKeyframes};
  animation-duration: 2s;
  animation-timing-function: ease;
  animation-delay: 0s;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: forwards;
  animation-play-state: running;
`

export default CoolBox

There are a couple things different here. First, I made a whole new folder just for this styled-component, CoolBox, because it acts just like a component, whereas in previous examples I put the styled-components in their own ‘styles’ folder out of habit.

Use what makes sense to you and your team. In most cases, I put styled-components in their own ‘styles’ folder because I use them exclusively as styles for the corresponding component that the folder sits in, but in this example I use them as stand alone components, so I give them their own folder, separate from any other components.

We’ve also separated the short-hand ‘animation’ property into its constituents. Nothing out of the ordinary, but we do change the animation iteration count to 1 (default is infinite) because we want it to animate only once, and we change the animation-fill-mode to ‘forwards’, which specifies how a CSS animation should apply styles before/after execution. In short, by setting this value to forward we are saying, “When this element finishes its animation, I want it to have the same CSS styles as the final keyframe of its animation”.

// src/Final/index.js

import React from 'react';

import CoolBox from '../CoolBox';

export class Final extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isCool: false }
  }

  toggleCoolness = () => {
    this.setState({ isCool: !this.state.isCool })
  }

  render() {
    const { isCool } = this.state;
    return (
      <div>
        <button onClick={this.toggleCoolness}>Click Me</button>
        {isCool ? (
          <CoolBox />
        ) : (
          <div></div>
        )}
      </div>
    );
  }
}

export default Final;

Using React’s local state and the double-frowney-face ternary operator, we set a state that’s toggled every time we click a button and we render the CoolBox if that state is toggled on.

// src/App/index.js

import React from 'react';
// import Box from './styles/Box'
// import Trigger from './styles/Trigger';

import Final from '../Final';

export class App extends React.Component {
  render() {
    return (
      <Final />
      // <Trigger>
      //   <Box />
      // </Trigger>
    );
  }
}

export default App;

And this is what we get:

Demo 5

Conclusion

We went over the basics of using CSS transitions, keyframes, and animations within React and styled-components but there’s a lot more you can do in this realm of animation.

Styled-components, by itself, allows you to interpolate React props or JS variables right into your styles, meaning you can essentially use programming logic in CSS! We’ve seen a bit of this in play in the final example, but you can also now do things like:

  • Store CSS variables as if they were JS variables
  • Use JS expressions to dynamically apply CSS properties, values, and animations
  • Apply a CSS theme and default styles
  • Incorporate custom logic with functions into your CSS
  • Not worry about what selector names to use or how to implement BEM or how to auto-prefix because it’s automatically done for you

Are these features absolutely necessary? Probably not, but it sure does make styling easier.

https://styled-components.com/

Or if CSS animations just aren’t cutting it for you and you need a bit more ‘oomph’ when animating React components you can try something like React-Motion.

https://github.com/chenglou/react-motion

And maybe you want to learn more about native CSS transitions and animations without React or styled-components, then I’d suggest watching DevTips on Youtube because that’s where I got my core examples from:

https://youtu.be/8kK-cA99SA0

Hope this helped in learning a thing or two about using CSS animations in React with styled-components.