Intro to CSS animations: a tutorial for dynamic web designs

⚠️ A word of caution: if you suffer from epilepsy or visually-induced seizures, please note that this article has animated embedded content that may contain flashing elements and intense motion. Ahead in the article, we’ll show the best practice to stop animated content for users who prefer reduced motion on their electronic devices.

The web has evolved dramatically since the first bytes of HTML were displayed on a computer browser. Rich user experiences with animated visuals have been sought after ever since Macromedia Flash entered the picture. Right after the iPhone entered the scene, Flash support started waning, as developers and designers started adopting native, web-first technologies to produce stunning, engaging experiences.

Nowadays, introducing animations for websites and web apps is a fun challenge. The sheer amount of beautiful websites seen daily on places like Awwwards serves as a great source of inspiration for front-end and creative developers who seek to make animated content.

Also, speaking of Awwwards, check out our 10th-anniversary website - we got an Awwwards Honorable Mention!

The tutorial

In this tutorial, we’re gonna take a look at an animated element from our 10th-anniversary website and break down exactly how it was made.

See the Pen Pulsating Glow Effect by João P. Marques (@jp-marques) on CodePen.

We’ll cover the following in this tutorial:


Chefs work in clean, organized kitchens as they whip up remarkable dishes. Ingredients must be cleaned, prepped, and placed in containers around the kitchen counter so the cookery is diligent and rigorous.

Animation for the web, though a creative endeavor, is much the same. We need to make sure that our raw ingredients (henceforth “assets”) are correctly exported and ready for animation.

The animated piece we’re building is comprised of a cloud core and two light beams.

To animate these items, think about how the elements interact with each other. Which parts move independently? How are things grouped? Where do items go and what are they doing?

In this example, we want to have a soft rotation with alternating motion on the core cloud element, while the light beams rotate around the core, like a lighthouse.

Prep the assets for animation

Working closely with designers, developers help prep the assets for animation. In this case, it’s useful to decompose all visual elements into exportable assets.

Notice factors that can be optimized like the asset’s bounding box, its dimensions, and how the asset is aligned in the box. Whatever you can do to normalize the assets at this stage is best to ensure that your code does less heavy lifting, which will be easier later down the road.

In this case, we want to pivot the light beams around an item, so it’s best to export them in the same orientation and configuration.

Decomposition of the visual elements into exportable assets.

Plate the assets

With these exports done (and we’ve exported these as @2x images so they look nice on Retina and HiDPI displays 😉), we can start assembling the animation.

For this tutorial, we’re using CodePen. This free platform is excellent for iterating and quickly making experiments for components, UI elements, and other simple features. Use it to test techniques and to create proof of concept prototypes for more challenging components and animations.

See the Pen Pulsating Glow Effect - Starting position by João P. Marques (@jp-marques) on CodePen.

Our ingredients are on the plate and we’re now ready to strive for the Michelin star.

OK, we’re done with the cooking metaphors. For now. 😂

Transforming elements

We’re going to harness the power of CSS transforms and opacity to animate these elements individually and deliver a smooth animation with good performance.

Following the decomposition logic we employed before to export assets, let’s start with the core cloud element. We want to add a subtle rotation to this element. We can do this by making use of the animation and @keyframes properties:

.core-cloud {
	/* ...CSS styles for this element */

	animation: 3s linear infinite rotate-core;

@keyframes rotate-core {
	0% {
		transform: rotate(0deg);
	100% {
		transform: rotate(180deg);

The animation property is a CSS shorthand for several animation properties - I recommend looking up the MDN Web Docs page on the topic for more info. It’s an excellent resource that’s invaluable for all web developers.

In this example, we’re stating that the animation should last for 3 seconds, at a constant speed, and it should repeat forever.

The @keyframes property uses percentages to define the total duration of a timeline. Think of these percentages as the keyframes on a video or audio editing software suite. 0% corresponds to the very beginning of the timeline (0 seconds), and 100% is the end (at 3 seconds, for this example). Anything in between is another keyframe that will correspond to that point in time.

The code above yields a basic rotation effect:

It works! However, it looks a bit janky. 🥲

To make this piece transition smoothly, let’s introduce better timings and refine the easing function to smooth out things.

Expressiveness through timing functions

A quick way to add flavor and interest to this animation is to use timing functions. The linear keyword in the example above means that the animation progresses at a constant speed from start to finish. Changing this to ease or ease-in-out makes the animation start slowly, pick up speed in the middle of the timeline, and slow down again by the end.

You can even go beyond this and use cubic-bezier, a technique that allows for four individual points of control:

animation: 3s cubic-bezier(.44,.28,.33,.95) infinite rotate-core;

See the Pen Pulsating Glow Effect - Cubic Bézier by João P. Marques (@jp-marques) on CodePen.

To better understand how cubic-bezier works, check out this excellent interactive Cubic Bezier playground by Lea Verou. If you just want a good set of default values for acceleration and deceleration curves, take a look at the super useful Easing Functions Cheat Sheet. You can build a ton of interesting animations just by using these values.

Now we need to adjust the timing, as it’s running a bit too fast right now. Also, the way the animation just “snaps” back to the start isn’t the desired effect for this case. (It could be if you’re making a snappier, more jittery piece - feel free to always experiment!)

Adjust the timings and use the alternate keyword so that the timeline plays backward once it completes. We now have a more chilled-out core cloud element:


Now that we understand how to make our animations smoother (or more expressive), we can make use of layering techniques to make animated items even more visually engaging. Let’s try this out on the light beams.

Let’s add a similar rotation animation to one of the light beams. However, it’s obvious that something isn’t right - the animation is off-center.

We can fix this by correcting the item’s transform-origin property so it’s pinned to its bottom right corner. This is how a great mise-en-place makes our life easier down the line! 💁

Try it out by toggling the option on the CodePen below:

A quick tip: notice how the border attributes on the edges of the element make things easier to understand when you’re working with transforms. This can be a real time-saver when you’re trying to align elements for animation.

Running more than one timeline on an element

Let’s make the light beam fade in and out intermittently. Imagine the light rays beneath the shaded leaves of a tree bouncing around. Isn’t it a lovely sight?

We can replicate this effect by concatenating another statement to our animation property - this time to independently control another timeline that deals exclusively with fading elements in and out:

Notice two details about this implementation:

  1. The second fade timeline has different values for timing, including a second value. This is the delay value, which delays the animation playback and lets you layer effects with ease.
  2. Both instances of the light beams have slightly different values for timing, which means that their timeline plays back in different order. You can use this technique to avoid creating more @keyframes when you only want to give your elements a more organic feel.

If you’ve reached this part, congratulations! 🥳

The hardest part is done. You now have an animation that works and looks decent.


If you’re interested in making it just a bit better, read on. As you’ve seen before, refining and tweaking values is extremely important to achieve a great feeling animation. The code itself is terse and straightforward. Take some time to review the timing functions and easings - it will pay off as your work looks more polished.

Another extra effect that would look nice here would be a slight color-shifting effect. We can add this by reusing the technique we’ve just seen, with an extra timeline for a filter property:

@keyframes color-shift {
	from {
		filter: hue-rotate(0deg);
	to {
		filter: hue-rotate(80deg);

For the sake of fun, let’s try making this effect extra potent. Enable the toggle below to make the colors really pop. 🤩

⚠️ Again, if you’re sensitive to bright lights, colors, or motion, skip the toggle altogether.

See the Pen Pulsating Glow Effect - Hue shift controls by João P. Marques (@jp-marques) on CodePen.

This may look over the top but it shows exactly why refining and tweaking values is super important to achieve the right look.

A piece of folk wisdom: “Too much ketchup ruins the fries”. 🍟

Being mindful of users

One important part of animating is also respecting users who do not want to see animations and prefer their devices to have reduced motion. One way to achieve this is by using media queries - in this case, by taking into account the user’s device accessibility settings:

@media (prefers-reduced-motion: no-preference) {
	/* Add your animation code here */

Should users have a reduced motion setting enabled on their device, the styles inside this media query will not be applied. This is ideal for optional animations that may distract users or provoke unintended side effects.


I hope you now have an idea of how to make your animations with ease.

All of the techniques shown here are native CSS and work across a myriad of browsers and devices, which means this animated goodness is available virtually anywhere you may access the web. Use this newfound superpower to add some spark to your web experiences! 🦸

The key takeaways:

João P. Marques
Creative Developer