Skip to main content
April 1, 2026Dan Rodney/12 min read

CSS Transitions: Free HTML & CSS Tutorial

Master Smooth CSS Transitions for Professional Web Animations

CSS Transition Fundamentals

Transition Property

Defines which CSS properties should animate when changed. Can target specific properties like color, opacity, or use 'all' for comprehensive coverage.

Transition Duration

Controls how long the animation takes to complete. Can be specified in seconds (s) or milliseconds (ms) for precise timing control.

Timing Functions

Determines the speed curve of the transition. Options include linear, ease-in, ease-out, and custom cubic-bezier functions for advanced control.

Topics Covered in This HTML & CSS Tutorial:

Transition-property & Transition-duration, Transition Shorthand & the All Keyword, Transitioning Position Coordinates, Adding Easing with Transition-timing-function, Custom Easing with Ceaser

Exercise Preview

preview transitions

Before and After

Notice how hover effects immediately snap to their final state. This exercise transforms abrupt changes into smooth, professional transitions that enhance user experience.

Exercise Overview

In the portfolio page we've been developing, hover states change abruptly when users interact with elements—a jarring experience that screams amateur design. In this exercise, you'll master CSS3 transitions to create smooth, professional interactions that rival those found on enterprise-level websites. Transitions work by interpolating between property values over time, essentially creating micro-animations that guide users' attention and provide visual feedback. Think of them as the digital equivalent of easing into a conversation rather than shouting—they make interfaces feel responsive and thoughtfully crafted.

Getting Started

  1. We'll be switching to a new folder of prepared files for this exercise. In your code editor, close all open files to avoid confusion between different project directories.
  2. For this exercise we'll be working with the Transitions folder located in Desktop > Class Files > Advanced HTML CSS Class. Open that folder in your code editor if it supports directory-based workflows (like Visual Studio Code, Sublime Text, or Atom).
  3. Open index.html from the Transitions folder.
  4. Preview index.html in a browser.

    Hover over the navigation elements (John Schmidt logo and navbar links) to observe the instant color changes. These immediate state changes feel mechanical and unpolished. We'll transform them into smooth, engaging transitions that enhance the user experience.

  5. Keep the page open in your browser for live testing as we implement changes.

Setup Process

1

Close Current Files

Close all open files in your code editor to avoid confusion between different project folders.

2

Open Transitions Folder

Navigate to Desktop > Class Files > Advanced HTML CSS Class > Transitions and open in your editor.

3

Preview Initial State

Open index.html and preview in browser to see the abrupt hover changes we'll be smoothing out.

Transition-Property & Transition-Duration

Understanding CSS transitions requires grasping a fundamental principle: transitions are declared on the element itself, not on the trigger state (like :hover). This might seem counterintuitive, but it's architectural genius—the browser needs advance notice of what properties can transition and how long those transitions should take. This approach also enables you to trigger the same transition from multiple pseudo-classes (:focus, :active) or even via JavaScript, making your code more flexible and maintainable.

  1. Switch back to your code editor and navigate to your project files.
  2. Open main.css from the css folder (nested within the Transitions folder).
  3. In the header a rule, add the transition properties shown below in bold:

    header a {

    Code Omitted To Save Space

    text-transform: uppercase;
       transition-property: color;
       transition-duration: 2s;
    }

    NOTE: These properties establish the transition foundation:

    • transition-property specifies which CSS property the browser should animate—in this case, the text color.
    • transition-duration defines the time span for the animation. You can specify values in seconds (s) or milliseconds (ms): 2s equals 2000ms. We're starting with 2 seconds for testing purposes, then we'll optimize for production-ready timing.
  4. Save the file, then reload the page in your browser to test the changes.
  5. If you're using Chrome or Safari, you may notice an unwanted color transition when the page initially loads. This is a known browser quirk that we'll address shortly with a professional workaround.
  6. Hover over any navigation link to observe the smooth 2-second color transition. Move your cursor away to see the reverse transition back to the original state.
  7. Now hover over the John Schmidt logo. The text color transitions smoothly, but notice how the text-shadow snaps into position abruptly—this inconsistency breaks the polished effect we're aiming for.
  8. Return to main.css in your code editor to fix this issue.
  9. CSS transitions support multiple properties by separating them with commas. Modify the transition-property to include text-shadow:

    header a {

    Code Omitted To Save Space

    transition-property: color, text-shadow;
       transition-duration: 2s;
    }
  10. Save the file and reload the page in your browser.
  11. Hover over John Schmidt again. Both the color and text-shadow now transition simultaneously, creating a cohesive, professional interaction.

Declaration Location

Transitions are declared in the base element rule, not the hover rule. The browser needs to know what to do before it starts transitioning.

Time Units Comparison

FeatureSecondsMilliseconds
2 seconds2s2000ms
Half second0.5s500ms
Quick transition0.3s300ms
Recommended: Use milliseconds for precise control of fast animations under 1 second

Fixing a Problem on Page Load

Webkit-based browsers (Chrome and Safari) have a longstanding bug where CSS transitions inadvertently fire during page load, creating an unprofessional flash of animated content. Firefox handles this correctly by not triggering transitions on load. This inconsistency requires a defensive coding approach that's become standard practice among professional front-end developers.

The most reliable solution involves temporarily disabling all transitions during page initialization, then re-enabling them after the DOM has fully loaded. This technique uses a combination of CSS and minimal JavaScript to ensure consistent behavior across all browsers.

  1. Navigate back to your code editor and locate the project files.
  2. Open prevent-transition-flicker.html from the snippets folder (within the Transitions directory).
  3. This file contains a complete solution with step-by-step instructions. Copy the CSS code under STEP #1:

    <style>.preload * {transition:none!important;}</style>
  4. Switch to index.html in your editor.
  5. Paste the CSS at the end of the head section:

    <link rel="stylesheet" href="css/main.css">
       <style>.preload * {transition:none!important;}</style>
    </head>
  6. Add the preload class to the body tag:

    <body class="preload">
  7. Return to prevent-transition-flicker.html and copy the JavaScript code under STEP #3:
  8. Copy the JavaScript snippet:

    <script>document.getElementsByTagName("body")[0].classList.remove("preload");</script>
  9. Close the prevent-transition-flicker.html file.
  10. In index.html, paste the JavaScript immediately after the opening body tag:
  11. Your code should look like this:

    <body class="preload">
       <script>document.getElementsByTagName("body")[0].classList.remove("preload");</script>
    
       <header>
  12. Save the file and reload the page in your browser.

    • Chrome and Safari users should no longer see any transition flickering on page load.
    • Hover interactions should continue working as expected.
  13. Understanding the mechanism:

    • The preload class applies transition: none !important to all elements during initial page load, overriding any transition declarations.
    • Once the DOM is ready, JavaScript removes the preload class, restoring normal transition functionality.
    • This approach is non-intrusive and degrades gracefully—if JavaScript fails to load, users simply see no transitions rather than broken animations.
Browser Bug Workaround

Chrome and Safari incorrectly fire transitions on page load. This JavaScript solution temporarily disables transitions during initial load.

Transition Flicker Fix

1

Add Disable Style

Insert CSS that disables all transitions with !important in the document head.

2

Apply Preload Class

Add class='preload' to the body tag to activate the transition-disabling CSS.

3

Remove After Load

JavaScript removes the preload class after page load, re-enabling transitions for user interactions.

Transition Shorthand & the All Keyword

Professional CSS development emphasizes maintainable, concise code. CSS transition shorthand reduces redundancy while improving readability—essential skills for working on large-scale projects where code clarity directly impacts development velocity and team collaboration.

  1. Return to main.css in your code editor.
  2. Delete the transition-duration declaration from the header a rule.
  3. Modify the transition-property to use shorthand syntax, incorporating duration values:

    header a {

    Code Omitted To Save Space

    text-transform: uppercase;
       transition: color 2s, text-shadow 4s;
    }

    NOTE: Transition shorthand follows the pattern: [property] [duration] [timing-function] [delay]. We're using different durations to demonstrate how individual properties can have distinct timing characteristics within a single element.

  4. Save the file and reload the page in your browser.
  5. Hover over John Schmidt to observe the color transitioning over 2 seconds while the text-shadow takes 4 seconds—this creates a sophisticated layered animation effect.

    While granular control is powerful, managing multiple properties individually becomes unwieldy in complex interfaces. The all keyword provides an elegant solution for comprehensive property transitions.

  6. Return to main.css and locate the header transition rule.
  7. Simplify the transition using the all keyword with optimized timing:

    transition: all 300ms;

    NOTE: 300 milliseconds represents the sweet spot for interface transitions—fast enough to feel responsive, slow enough to be perceived as smooth rather than jarring.

  8. Save the file and test in your browser. The interactions now feel crisp and professional, with significantly cleaner code.

All Keyword Usage

Pros
Dramatically reduces code when animating multiple properties
Automatically includes new animated properties without updates
Consistent timing across all transitioning elements
Cleaner, more maintainable CSS
Cons
Less precise control over individual property timing
May animate unintended properties
Can be less performant with many properties

Animating the Description Overlays

Now we'll apply transition principles to a more complex interaction pattern—image overlays that reveal descriptive content. This technique is fundamental in modern web design, from portfolio galleries to e-commerce product showcases.

  1. Hover over any of the four featured artwork images to see the current overlay behavior—an abrupt opacity change from invisible to fully visible.
  2. Opacity is an ideal candidate for transitions because it uses numeric values that can be interpolated smoothly, unlike discrete properties such as display or visibility.
  3. In main.css, locate the .category.description rule and add a transition:

    .category.description {

    Code Omitted To Save Space

    opacity: 0;
       transition: all 1s;
    }
  4. Save the file and reload the page to test the smooth fade-in effect.
  5. Hover over the featured images to experience the improved interaction. However, this hover-dependent design presents an accessibility and usability problem: mobile users have no equivalent to hover states, making content discovery impossible on touch devices.

    Let's implement a more inclusive solution that enhances both desktop and mobile experiences. We'll position overlays at the bottom of images by default, then expand them to full coverage on hover—providing immediate content visibility while preserving the enhanced interaction for pointer-capable devices.

  6. Return to main.css to refactor the overlay system.
  7. Remove the opacity properties from both the base and hover states, since we want overlays visible by default:

    .category.description {

    Code Omitted To Save Space

    padding-top: calc(33.33%—4rem/2);
       transition: all 1s;
    }.category a:hover.description {
    
    }
  8. Position the overlay at the bottom of each image by adjusting the top coordinate. Since our line-height is 4rem, we'll offset the top edge to show only one line of text:

    .category.description {

    Code Omitted To Save Space

    top: calc(100%—4rem);
       right: 0;
       bottom: 0;
       left: 0;
       padding-top: calc(33.33%—4rem/2);
       transition: all 1s;
    }
  9. Configure the hover state to expand the overlay to full image coverage:

    .category a:hover.description {
       top: 0;
    }
  10. Move the vertical centering padding from the base state to the hover state, since it's only needed when the overlay covers the entire image:
  11. Cut padding-top: calc(33.33%—4rem/2); from .category.description and paste it into the hover rule:

    .category.description {

    Code Omitted To Save Space

    top: calc(100%—4rem);
       right: 0;
       bottom: 0;
       left: 0;
       transition: all 1s;
    }.category a:hover.description {
       top: 0;
       padding-top: calc(33.33%—4rem/2);
    }
  12. Save and test your changes. The interface now provides immediate content accessibility while maintaining sophisticated hover enhancements—a hallmark of thoughtful UX design.

    • Labels are visible at the bottom of each image, ensuring content discoverability across all device types.
    • Hover interactions create an elegant expansion effect that provides additional visual emphasis.

NOTE: Legacy versions of Microsoft Edge (prior to adopting Chromium) don't support CSS transitions with calc() functions, causing the overlay animation to fail silently. However, the layout remains functional and visually acceptable. Since Edge now uses the Chromium engine (as of 2020), this compatibility issue affects a negligible user base. For projects requiring broader legacy support, you'd need to implement fixed pixel values with corresponding media queries—a tradeoff between modern CSS capabilities and legacy compatibility.

Mobile-First Approach

By positioning labels at the bottom initially, mobile users can always see the descriptions, while desktop users get the enhanced hover animation.

Overlay Animation Setup

1

Position at Bottom

Use calc(100% - 4rem) to position overlay showing only one line of text at the bottom of images.

2

Expand on Hover

Move top position to 0 on hover, expanding the overlay to cover the full image height.

3

Add Center Padding

Include vertical centering padding only in the hover state when overlay is full height.

Adding Easing with Transition-Timing-Function

Easing functions control the acceleration and deceleration of transitions, dramatically impacting how animations feel to users. The right easing can make interactions feel natural and responsive, while poor easing choices can make even well-designed interfaces feel sluggish or mechanical.

CSS provides several built-in timing functions: ease (default), linear, ease-in, ease-out, ease-in-out, and cubic-bezier for custom curves. Understanding when to apply each type is crucial for creating professional-grade user experiences.

  1. In main.css, locate the .category.description rule.
  2. Add a linear timing function to observe its mechanical characteristics:

    .category.description {

    Code Omitted To Save Space

    transition: all 1s;
       transition-timing-function: linear;
    }
  3. Save the file and test the overlay animations. Linear easing maintains constant velocity throughout the transition, creating a robotic, unnatural feeling that lacks the subtle acceleration patterns humans expect from real-world motion.
  4. Remove the timing function line and incorporate easing directly into the shorthand syntax:
  5. Replace the transition with an ease-in timing function:

    .category.description {

    Code Omitted To Save Space

    transition: all 1s ease-in;
    }
  6. Test the updated animation. ease-in starts slowly and accelerates toward the end, creating anticipation before the payoff. However, the built-in CSS easing functions offer limited expressiveness for sophisticated interface design.

Built-in Easing Options

Linear

Constant speed throughout the animation. Creates a robotic, mechanical feel that's rarely ideal for user interfaces.

Ease-in

Starts slow and accelerates toward the end. Good for elements moving off-screen or disappearing from view.

Ease-out

Starts fast and decelerates toward the end. Ideal for elements appearing or moving into final position.

Custom Easing with Ceaser

Professional-grade animations require custom easing curves that match your interface's personality and brand. Creating cubic-bezier coordinates manually is mathematically complex and time-consuming. Fortunately, developer Matthew Lein (a former Noble Desktop instructor) created Ceaser—an elegant tool that generates CSS-ready cubic-bezier functions with visual preview capabilities.

Ceaser includes presets based on Robert Penner's renowned easing equations, which have become industry standards across animation platforms from jQuery to GreenSock (GSAP) to CSS transitions.

  1. Open a new browser tab and navigate to matthewlein.com/tools/ceaser
  2. From the Easing dropdown menu, select easeOutExpo.

    This preset approximates one of Penner's most popular equations, creating a fast start with a smooth deceleration that feels both snappy and natural—ideal for UI transitions that need to feel responsive without being jarring.

    Ceaser Tool Benefits

    Matthew Lein's Ceaser tool eliminates the complexity of manually calculating cubic-bezier coordinates, providing visual feedback and preset Penner equation approximations.

Penner Equations

Robert Penner revolutionized digital animation by codifying mathematical easing functions that mimic natural motion patterns. Originally developed for Adobe Flash, these equations now power animation libraries across platforms—from CSS transitions to JavaScript frameworks like GreenSock, from mobile app development to game engines. Their widespread adoption reflects their effectiveness in creating animations that feel intuitively correct to users. For comprehensive documentation and examples, visit robertpenner.com/easing

  • Set the Duration to 300 milliseconds (0.3 seconds)—optimal for responsive UI feedback.
  • Click the Height button repeatedly to preview the animation curve and timing characteristics.
  • Select and copy the generated transition value from the output panel:

    copy easing value

  • Return to main.css in your code editor.
  • Replace the current transition with the custom cubic-bezier values:

    .category.description {

    Code Omitted To Save Space

    transition: all 300ms cubic-bezier(0.190,1,000,0.220,1,000);
    }
  • Save the file and test the refined animations. The custom easing creates a distinctly more polished feel—responsive and energetic without sacrificing smoothness.
  • Hover over the artwork images to experience the improved interaction quality. The animation now has character and sophistication that elevates the entire interface.

  • Industry Standard

    Robert Penner's easing functions have become the de facto standard across animation platforms, from Flash to jQuery, GSAP, and modern CSS implementations.

    Optional Bonus: Adding Focus Selectors to Hovers

    Accessibility isn't optional in professional web development—it's a fundamental requirement. Users who navigate with keyboards rather than pointer devices deserve equivalent interaction feedback. Adding :focus states alongside :hover states ensures your transitions work for everyone while meeting WCAG accessibility guidelines.

    1. Locate the .category a:hover.description rule and expand the selector to include focus states:

      .category a:hover.description,.category a:focus.description {

      Code Omitted To Save Space

      }

      NOTE: The comma creates a grouped selector, applying the same styles to both hover and focus interactions.

    2. Apply the same principle to the logo hover effect:

      .logo-wrapper a:hover,.logo-wrapper a:focus {

      Code Omitted To Save Space

      }

    3. Save your changes and test keyboard navigation using the Tab key. Focus indicators now receive the same polished transition treatment as hover states, creating a cohesive experience regardless of input method.

    This completes your CSS transitions implementation. You've transformed a static interface into a dynamic, accessible experience that demonstrates professional front-end development skills—from technical execution to inclusive design thinking.

    Accessibility Best Practice

    Adding focus selectors to hover effects ensures keyboard navigation users get the same visual feedback as mouse users, improving overall accessibility.

    Accessibility Enhancement Checklist

    0/3

    Key Takeaways

    1CSS transitions must be declared on the base element, not the trigger state, so the browser knows how to handle property changes before they occur.
    2The transition shorthand syntax combines property, duration, and timing function into a single declaration for cleaner, more maintainable code.
    3Using the 'all' keyword dramatically reduces code complexity when animating multiple properties, though it offers less granular control than individual property declarations.
    4Chrome and Safari have a bug that fires transitions on page load, requiring a JavaScript workaround that temporarily disables transitions during initial rendering.
    5Custom easing functions using cubic-bezier provide more natural, engaging animations than the basic linear, ease-in, and ease-out options.
    6Positioning overlays to be initially visible at the bottom ensures mobile accessibility while still providing enhanced hover animations for desktop users.
    7Tools like Ceaser simplify the creation of complex cubic-bezier timing functions by providing visual feedback and preset Penner equation approximations.
    8Adding focus selectors to hover effects is an accessibility best practice that ensures keyboard navigation users receive the same visual feedback as mouse users.

    RELATED ARTICLES