CSS Animations and Keyframes
@keyframes and attach them with the animation property.
Let me ask you something.
Have you ever landed on a website and the heading slides in from the left as the page loads? Or seen a loading spinner rotating in a circle? Or a notification that pulses gently to grab your attention?
None of that required JavaScript. It was pure CSS animations.
Transitions are great but they need something to trigger them — a hover, a click, a focus. Animations are different. They run on their own. The moment an element appears on the page the animation starts. No user interaction needed.
Transitions vs Animations
Before we go further let me make sure this distinction is clear.
- Transitions — animate between two states. They need a trigger. Hover, focus, active. You define the start and end and CSS handles the in between.
- Animations — run automatically. No trigger needed. You define multiple steps and CSS loops through them. They can repeat, reverse, and run forever if you want.
Think of a transition like a light switch. It goes from off to on when you flip it. An animation is like a traffic light. It cycles through states on its own continuously.
@keyframes — Defining the Animation
To create an animation you first define what it does using @keyframes. This is where you describe each
step of the animation.
@keyframes animation-name {
from {
/* starting state */
}
to {
/* ending state */
}
}
from is where the animation starts. to is where it ends. CSS handles everything in between
automatically.
Then you attach it to an element using the animation property.
Let us start with something simple. A pulsing red glow on the Netflix logo.
Netflix
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap'); body { background-color: #141414; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; } #logo { font-family: "Bebas Neue", sans-serif; font-size: 3rem; color: #E50914; letter-spacing: 4px; animation: pulse 2s ease-in-out infinite; } @keyframes pulse { from { opacity: 1; } to { opacity: 0.5; } }The Netflix logo now gently fades in and out continuously. Here is what is happening.
@keyframes pulse— defines an animation calledpulse. It starts at full opacity and ends at half opacity.animation: pulse 2s ease-in-out infinite— attaches the animation to the logo. Let us break down each value.
pulse— the name of the keyframes to use.2s— the animation takes 2 seconds to complete one cycle.ease-in-out— the timing function. It eases in and out making the pulse feel smooth and natural.infinite— the animation repeats forever. Remove this and it only plays once.
The animation property follows this order: name, duration, timing-function, delay, iteration-count,
direction. You do not need to write all of them every time. The most common pattern is just name, duration, and
infinite for looping animations. Everything else defaults to sensible values.
Using Percentage Steps
Instead of just from and to you can define multiple steps using percentages.
0% is the start, 100% is the end, and anything in between is a middle step.
This is how you create more complex animations with multiple stages.
Netflix
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap'); body { background-color: #141414; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; } #logo { font-family: "Bebas Neue", sans-serif; font-size: 3rem; color: #E50914; letter-spacing: 4px; animation: glow 3s ease-in-out infinite; } @keyframes glow { 0% { opacity: 1; text-shadow: none; } 50% { opacity: 0.8; text-shadow: 0 0 20px rgba(229, 9, 20, 0.8); } 100% { opacity: 1; text-shadow: none; } }The logo now pulses with a red glow at the halfway point of the animation and then fades back. Three steps — start, middle, end.
0%— the starting state. Full opacity, no glow.50%— halfway through. Slightly faded with a red text shadow glowing behind it.100%— back to the start. Full opacity, no glow.
Because 0% and 100% are identical the animation loops seamlessly. You cannot tell where one
cycle ends and the next begins.
animation-direction: alternate
Instead of jumping from 100% back to 0% at the end of each cycle you can make the
animation reverse smoothly using alternate.
Netflix
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap'); body { background-color: #141414; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; } #logo { font-family: "Bebas Neue", sans-serif; font-size: 3rem; color: #E50914; letter-spacing: 4px; animation: pulse 1.5s ease-in-out infinite alternate; } @keyframes pulse { from { opacity: 1; } to { opacity: 0.4; } }The logo fades out then fades back in smoothly. alternate makes the animation play forward then backward
then forward again in a continuous smooth loop.
Instead of jumping back to 0% when it hits 100% the animation reverses direction. It plays
from to to then to back to from again. This is much smoother than
manually making 0% and 100% match.
A Loading Spinner
Spinners are everywhere. Loading states, processing indicators, buffering icons. Here is how to make one with a pure CSS rotation animation.
A red spinning circle. Clean, simple, and exactly what you see on loading screens everywhere. Here is what is happening.
border: 4px solid rgba(255,255,255,0.2)— a light transparent border on all four sides. This is the gray track of the spinner.border-top-color: #E50914— only the top border is red. As the element rotates this red section spins around the track.border-radius: 50%— makes it a circle not a square.animation: spin 0.8s linear infinite— rotates it continuously.lineartiming is important here.transform: rotate(0deg)torotate(360deg)— rotates the element one full turn. Then it loops back and goes again.
linear is the right timing function for rotation animations. ease or
ease-in-out would make the spinner slow down and speed up which looks wrong. Spinners should feel
mechanical and constant. linear gives you that.
Slide In on Page Load
This is something you see on almost every modern landing page. Content slides in from below as the page loads. It feels welcoming and dynamic.
Unlimited movies, TV shows, and more.
Starts at USD 2.99. Cancel anytime.
When the page loads the content fades in while sliding up from 40px below its final position. It feels polished and intentional. Here is what is happening.
from— the content starts invisible (opacity: 0) and 40px below where it will end up (translateY(40px)).to— the content ends fully visible (opacity: 1) and in its normal position (translateY(0)).forwards— this isanimation-fill-mode. It tells the animation to stay at its final state when it finishes instead of snapping back to the starting state. Without this the content would slide up and then jump back to being invisible.ease-out— starts fast and slows down at the end. This feels like the content is settling into place.
The combination of opacity going from 0 to 1 and translateY
going from a positive value to 0 is the most used entrance animation on the entire web. Start with
30px to 50px of translateY and 0.6s to 0.8s duration for most hero content.
animation-delay — Staggering Multiple Elements
When multiple elements animate in at the same time it looks fine. But when each one enters slightly after the
previous one it looks intentional and choreographed. You do this with animation-delay.
Unlimited movies, TV shows, and more.
Starts at USD 2.99. Cancel anytime.
Ready to watch? Enter your email below.
The heading slides in first. Then the first paragraph 0.2 seconds later. Then the second paragraph 0.2 seconds after that. Each element enters in sequence like a choreographed reveal. Here is what is happening.
opacity: 0on.animate— all elements start invisible. Without this they would flash at full opacity before the animation starts.animation-delay: 0.2sand0.4s— each element waits a little longer before starting its animation. 0.2 seconds between each one creates a clean cascading effect.forwards— each element stays visible after its animation completes. Without this they would all snap back to invisible once done.
Keep stagger delays short. 0.1s to 0.2s between elements feels elegant. Longer than 0.3s starts to feel slow and impatient. The whole entrance sequence should complete within about 1 second total.
Building the Netflix Clone
Let us add animations to our Netflix clone. The hero content slides up on load. The logo pulses subtly. We add a loading spinner for when the page is buffering.
Unlimited movies, TV shows, and more.
Starts at USD 2.99. Cancel anytime.
Ready to watch? Enter your email to create or restart your membership.
Trending Now





Here is what every animation is doing.
logoGlow— the Netflix logo pulses with a soft red glow every 3 seconds. The0%, 100%pattern on the same state means it loops seamlessly.slideUpon hero content — each element in the hero slides up from 40px below and fades in. The heading goes first, then the paragraphs, then the form.opacity: 0on.animate— all animated elements start invisible so there is no flash before the animation begins.forwardson all entrance animations — every element stays visible after its animation completes. Without this they would all disappear the moment their animation finished.
Have anything to say about this lesson?
Your feedback helps improve these tutorials. If something was confusing or missing, let us know.