Menu

Tailwind CSS Will Change

The will-change property in CSS serves as a hint to browsers, allowing them to pre-optimize certain elements on a webpage before they change. This proactive behavior can lead to better performance and reduced visual glitches during animations or transitions.

Tailwind CSS provides set of utilities, simplifying the implementation of the will-change property across your projects. In this document, we’ll explore its usage, conditional application, customization, and advanced implementations within Tailwind.

ClassPropertiesExample
will-change-autowill-change: auto;<div className="will-change-auto"></div>
will-change-scrollwill-change: scroll-position;<div className="will-change-scroll"></div>
will-change-contentswill-change: contents;<div className="will-change-contents"></div>
will-change-transformwill-change: transform;<div className="will-change-transform"></div>

Overview of Will Change

Adding the Will Change

Tailwind provides four predefined will-change utilities- will-change-auto, will-change-scroll, will-change-contents, and will-change-transform. Use these utilities to help the browser anticipate upcoming changes to an element, improving animation performance.

This is a live editor. Play around with it!
export default function MovingImage() {
  return (
    <div className="h-screen w-screen bg-gray-50 flex items-center justify-center">
      {/* Preloading optimization with will-change */}
      <img
        src="https://images.unsplash.com/photo-1487017159836-4e23ece2e4cf"
        alt="Dynamic Animation"
        className="transform transition-transform duration-1000 ease-linear will-change-transform hover:scale-110"
      />
    </div>
  );
}

Only apply these classes immediately before the element updates, and revert to will-change-auto once the update is finished. Remember that will-change is meant to address specific performance concerns, so don’t overuse it or apply it preemptively—doing so can actually harm your page’s performance.

States and Responsiveness

Hover and Focus States

Tailwind allows you to conditionally apply the utility class on certain states like hover and focus. Use Tailwind's state modifiers like- hover, focus, etc. to apply the utility only when these states are active, e.g., hover:will-change-transform.

This is a live editor. Play around with it!
export default function InteractiveButton() {
  return (
    <div className="h-screen w-screen bg-gray-50 flex items-center justify-center">
      {/* Preloading optimization on hover */}
      <img
        src="https://images.unsplash.com/photo-1487017159836-4e23ece2e4cf"
        alt="Dynamic Animation"
        className="transform transition-transform duration-1000 ease-linear hover:will-change-transform hover:scale-110"
      />
    </div>
  );
}

Breakpoint Modifiers

Tailwind CSS provides breakpoint modifiers to conditionally apply will-change only when the screen hits the defined breakpoint. This is especially helpful for applying effects only on specific screens. Use Tailwind's breakpoint modifiers like- sm, md, etc., to apply the utility only on these breakpoints and above.

This is a live editor. Play around with it!
export default function ResponsiveCard() {
  return (
    <div className="h-screen w-screen bg-gray-50 flex items-center justify-center">
      {/* Preloading optimization on "md" and above */}
      <img
        src="https://images.unsplash.com/photo-1487017159836-4e23ece2e4cf"
        alt="Dynamic Animation"
        className="transform transition-transform duration-1000 ease-linear md:will-change-transform hover:scale-110"
      />
    </div>
  );
}

Custom Will Change

Extending the Theme

Tailwind lets you customize the theme file to create new utilities beyond the default ones. Inside your tailwind.config.js, modify the willChange property under the theme.extend configuration.

In the below example, you have access to the custom utility- will-change-opacity:

This is a live editor. Play around with it!
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

  export default function CustomAnimation() {
  return (
    <div className="h-screen w-screen bg-gray-50 flex items-center justify-center">
      {/* Custom will-change utility */}
      <img
        src="https://images.unsplash.com/photo-1487017159836-4e23ece2e4cf"
        alt="Dynamic Animation"
        className="will-change-opacity transition-opacity duration-700 opacity-50 hover:opacity-100 hover:scale-110"
      />
    </div>
  );
}

Using Arbitrary Values

Tailwind also lets you define arbitrary values directly in your classes for one-off use cases. Just use the square bracket syntax [value] wherever you need it, e.g., will-change-[opacity].

This is a live editor. Play around with it!
export default function CustomAnimation() {
  return (
    <div className="h-screen w-screen bg-gray-50 flex items-center justify-center">
      {/* Arbitrary will-change utility */}
      <img
        src="https://images.unsplash.com/photo-1487017159836-4e23ece2e4cf"
        alt="Dynamic Animation"
        className="will-change-[opacity] transition-opacity duration-700 opacity-50 hover:opacity-100 hover:scale-110"
      />
    </div>
  );
}

Real World Examples

Animated Product Cards

This example demonstrates a grid of product cards with smooth hover animations using will-change for better performance.

This is a live editor. Play around with it!
export default function ProductGrid() {
  const products = [
    {
      id: 1,
      name: "Premium Leather Backpack",
      price: "$129.99",
      src: "https://images.unsplash.com/photo-1553062407-98eeb64c6a62",
      alt: "Brown leather backpack"
    },
    {
      id: 2,
      name: "Wireless Noise-Canceling Headphones",
      price: "$249.99",
      src: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e",
      alt: "Black wireless headphones"
    },
    {
      id: 3,
      name: "Smart Fitness Watch",
      price: "$199.99",
      src: "https://images.unsplash.com/photo-1579586337278-3befd40fd17a",
      alt: "Modern fitness watch"
    },
    {
      id: 4,
      name: "Mechanical Keyboard",
      price: "$159.99",
      src: "https://images.unsplash.com/photo-1511467687858-23d96c32e4ae",
      alt: "RGB mechanical keyboard"
    },
    {
      id: 5,
      name: "Ultra-Wide Monitor",
      price: "$499.99",
      src: "https://images.unsplash.com/photo-1527443224154-c4a3942d3acf",
      alt: "Curved gaming monitor"
    },
    {
      id: 6,
      name: "Gaming Mouse",
      price: "$79.99",
      src: "https://images.unsplash.com/photo-1527814050087-3793815479db",
      alt: "RGB gaming mouse"
    }
  ];

  return (
    <div className="grid grid-cols-1 md:grid-cols-3 gap-6 p-6">
      {products.map((product) => (
        <div
          key={product.id}
          className="group relative overflow-hidden rounded-lg shadow-lg will-change-transform hover:scale-105 transition-transform duration-300"
        >
          <img
            src={product.src}
            alt={product.alt}
            className="w-full h-48 object-cover"
          />
          <div className="absolute bottom-0 w-full bg-black bg-opacity-50 p-4 transform translate-y-full group-hover:translate-y-0 transition-transform duration-300 will-change-transform">
            <h3 className="text-white font-bold">{product.name}</h3>
            <p className="text-white">{product.price}</p>
          </div>
        </div>
      ))}
    </div>
  );
}

Vertical Timeline Cards

This example shows a vertical timeline with smooth hover on cover images using will-change for better performance.

This is a live editor. Play around with it!
const Timeline = () => {
  const timelineEvents = [
    {
      id: 1,
      date: "Jan 2024",
      title: "Company Foundation",
      description: "Launched our startup with a vision to revolutionize tech",
      icon: "https://images.unsplash.com/photo-1533750349088-cd871a92f312",
      alt: "Rocket launch icon"
    },
    {
      id: 2,
      date: "Mar 2024",
      title: "First Major Client",
      description: "Secured partnership with Fortune 500 company",
      icon: "https://images.unsplash.com/photo-1552581234-26160f608093",
      alt: "Handshake icon"
    },
    {
      id: 3,
      date: "May 2024",
      title: "Product Launch",
      description: "Successfully launched our flagship product",
      icon: "https://images.unsplash.com/photo-1460925895917-afdab827c52f",
      alt: "Product launch icon"
    },
    {
      id: 4,
      date: "Jul 2024",
      title: "International Expansion",
      description: "Opened offices in Europe and Asia",
      icon: "https://images.unsplash.com/photo-1526778548025-fa2f459cd5c1",
      alt: "Globe icon"
    },
    {
      id: 5,
      date: "Sep 2024",
      title: "Team Growth",
      description: "Reached 100 employee milestone",
      icon: "https://images.unsplash.com/photo-1521737604893-d14cc237f11d",
      alt: "Team icon"
    },
    {
      id: 6,
      date: "Dec 2024",
      title: "Innovation Award",
      description: "Received Industry Innovation Award 2024",
      icon: "https://images.unsplash.com/photo-1579548122080-c35fd6820ecb",
      alt: "Award icon"
    }
  ];

  return (
    <div className="max-w-4xl mx-auto p-8">
      <div className="relative">
        {timelineEvents.map((event, index) => (
          <div
            key={event.id}
            className="flex items-center mb-8 opacity-100 translate-x-0 animate-fade-in-right"
          >
            <div className="w-16 h-16 rounded-full overflow-hidden will-change-transform transition-transform duration-300 hover:scale-110">
              <img
                src={event.icon}
                alt={event.alt}
                className="w-full h-full object-cover"
              />
            </div>
            <div className="ml-6 flex-1">
              <span className="text-sm text-gray-500">{event.date}</span>
              <h3 className="text-xl font-bold mt-1">{event.title}</h3>
              <p className="text-gray-600 mt-2">{event.description}</p>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

export default Timeline;

Interactive Team Member Cards

This example showcases team member cards with smooth hover effects using will-change for better performance.

This is a live editor. Play around with it!
export default function TeamGrid() {
  const team = [
    {
      id: 1,
      name: "Sarah Johnson",
      role: "CEO",
      src: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
      alt: "Sarah Johnson profile picture",
      social: { twitter: "#", linkedin: "#" }
    },
    {
      id: 2,
      name: "Michael Chen",
      role: "CTO",
      src: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e",
      alt: "Michael Chen profile picture",
      social: { twitter: "#", linkedin: "#" }
    },
    {
      id: 3,
      name: "Emma Williams",
      role: "Design Director",
      src: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80",
      alt: "Emma Williams profile picture",
      social: { twitter: "#", linkedin: "#" }
    },
    {
      id: 4,
      name: "David Kim",
      role: "Lead Developer",
      src: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e",
      alt: "David Kim profile picture",
      social: { twitter: "#", linkedin: "#" }
    },
    {
      id: 5,
      name: "Lisa Garcia",
      role: "Marketing Head",
      src: "https://images.unsplash.com/photo-1534528741775-53994a69daeb",
      alt: "Lisa Garcia profile picture",
      social: { twitter: "#", linkedin: "#" }
    },
    {
      id: 6,
      name: "James Wilson",
      role: "Product Manager",
      src: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d",
      alt: "James Wilson profile picture",
      social: { twitter: "#", linkedin: "#" }
    }
  ];

  return (
    <div className="grid grid-cols-1 md:grid-cols-3 gap-8 p-8">
      {team.map((member) => (
        <div
          key={member.id}
          className="relative group will-change-transform hover:-translate-y-2 transition-transform duration-300"
        >
          <div className="overflow-hidden rounded-lg shadow-lg">
            <img
              src={member.src}
              alt={member.alt}
              className="w-full h-64 object-cover will-change-transform group-hover:scale-110 transition-transform duration-500"
            />
            <div className="p-4 bg-white">
              <h3 className="font-bold text-xl">{member.name}</h3>
              <p className="text-gray-600">{member.role}</p>
            </div>
            <div className="absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center space-x-4">
              <a href={member.social.twitter} className="text-white hover:text-blue-400">
                Twitter
              </a>
              <a href={member.social.linkedin} className="text-white hover:text-blue-400">
                LinkedIn
              </a>
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

Animated Feature Showcase

This example demonstrates an animated feature showcase with hover animations using will-change will-change for better performance.

This is a live editor. Play around with it!
const FeatureCards = () => {
  const features = [
    {
      id: 1,
      title: "Cloud Storage",
      description: "Secure and scalable cloud storage solutions",
      icon: "https://images.unsplash.com/photo-1544197150-b99a580bb7a8",
      alt: "Cloud storage icon"
    },
    {
      id: 2,
      title: "Analytics Dashboard",
      description: "Real-time data analytics and visualization",
      icon: "https://images.unsplash.com/photo-1551288049-bebda4e38f71",
      alt: "Analytics dashboard icon"
    },
    {
      id: 3,
      title: "AI Integration",
      description: "Smart automation powered by artificial intelligence",
      icon: "https://images.unsplash.com/photo-1555255707-c07966088b7b",
      alt: "AI integration icon"
    },
    {
      id: 4,
      title: "Mobile Apps",
      description: "Cross-platform mobile application development",
      icon: "https://images.unsplash.com/photo-1512941937669-90a1b58e7e9c",
      alt: "Mobile apps icon"
    },
    {
      id: 5,
      title: "API Solutions",
      description: "Robust API development and integration services",
      icon: "https://images.unsplash.com/photo-1516116216624-53e697fedbea",
      alt: "API solutions icon"
    },
    {
      id: 6,
      title: "24/7 Support",
      description: "Round-the-clock technical support and assistance",
      icon: "https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d",
      alt: "Support icon"
    }
  ];

  return (
    <div className="bg-gray-100 p-8">
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {features.map((feature) => (
          <div
            key={feature.id}
            className="bg-white rounded-xl p-6 shadow-lg 
                       will-change-transform hover:-translate-y-2 
                       hover:shadow-xl transition-all duration-300 group"
          >
            <div className="w-16 h-16 rounded-full overflow-hidden mb-4 
                          will-change-transform group-hover:animate-pulse group-hover:scale-110 
                          transition-transform">
              <img
                src={feature.icon}
                alt={feature.alt}
                className="w-full h-full object-cover"
              />
            </div>
            <h3 className="text-xl font-bold mb-2">{feature.title}</h3>
            <p className="text-gray-600">{feature.description}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

export default FeatureCards;

Animated Pricing Table

This example shows a pricing table with hover animations using will-change for better performance.

This is a live editor. Play around with it!
export default function PricingTable() {
  const plans = [
    {
      id: 1,
      name: "Starter",
      price: "$19",
      period: "monthly",
      features: [
        "5 Projects",
        "10GB Storage",
        "Basic Analytics",
        "Email Support",
        "2 Team Members",
        "API Access"
      ],
      icon: "https://images.unsplash.com/photo-1526379095098-d400fd0bf935"
    },
    {
      id: 2,
      name: "Professional",
      price: "$49",
      period: "monthly",
      features: [
        "15 Projects",
        "50GB Storage",
        "Advanced Analytics",
        "Priority Support",
        "5 Team Members",
        "Custom Integrations"
      ],
      icon: "https://images.unsplash.com/photo-1434626881859-194d67b2b86f"
    },
    {
      id: 3,
      name: "Enterprise",
      price: "$99",
      period: "monthly",
      features: [
        "Unlimited Projects",
        "1TB Storage",
        "Custom Analytics",
        "24/7 Support",
        "Unlimited Team Members",
        "White Label Solution"
      ],
      icon: "https://images.unsplash.com/photo-1661956602116-aa6865609028"
    }
  ];

  return (
    <div className="flex flex-col md:flex-row gap-8 p-8 justify-center">
      {plans.map((plan) => (
        <div
          key={plan.id}
          className="flex-1 max-w-sm bg-white rounded-lg shadow-lg overflow-hidden will-change-transform hover:-translate-y-2 transition-transform duration-300"
        >
          <div className="h-32 overflow-hidden">
            <img
              src={plan.icon}
              alt=""
              className="w-full h-full object-cover will-change-transform hover:scale-110 transition-transform duration-500"
            />
          </div>
          <div className="p-6">
            <h3 className="text-2xl font-bold mb-2">{plan.name}</h3>
            <div className="mb-4">
              <span className="text-4xl font-bold">{plan.price}</span>
              <span className="text-gray-500">/{plan.period}</span>
            </div>
            <ul className="space-y-2 mb-6">
              {plan.features.map((feature, index) => (
                <li key={index} className="flex items-center">
                  <svg
                    className="w-4 h-4 mr-2 text-green-500"
                    fill="currentColor"
                    viewBox="0 0 20 20"
                  >
                    <path
                      fillRule="evenodd"
                      d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
                      clipRule="evenodd"
                    />
                  </svg>
                  {feature}
                </li>
              ))}
            </ul>
            <button className="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition-colors">
              Choose Plan
            </button>
          </div>
        </div>
      ))}
    </div>
  );
}

Customization Examples

Smooth Card Transitions

This example demonstrates a card component with smooth transitions and custom will-change utility.

This is a live editor. Play around with it!
// App.js
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

const AnimatedCardGrid = () => {
  const cards = [
    { title: 'Mountain Vista', image: 'https://images.unsplash.com/photo-1519681393784-d120267933ba' },
    { title: 'Ocean Waves', image: 'https://images.unsplash.com/photo-1518837695005-2083093ee35b' },
    { title: 'Forest Path', image: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e' },
  ];

  return (
    <div className="grid grid-cols-3 gap-4 p-8">
      {cards.map((card, index) => (
        <div
          key={index}
          className="relative overflow-hidden rounded-lg shadow-lg transform transition-all duration-300 hover:scale-105"
        >
          <img
            src={card.image}
            alt={card.title}
            className="opacity-80 hover:opacity-100 will-change-opacity w-full h-64 object-cover"
          />
          <div className="absolute bottom-0 left-0 right-0 p-4 bg-black/50 text-white">
            <h3 className="text-xl font-bold">{card.title}</h3>
          </div>
        </div>
      ))}
    </div>
  );
};

export default AnimatedCardGrid;

Floating Navigation Menu

This example shows a navigation menu with smooth position transitions and custom will-change utility.

This is a live editor. Play around with it!
// App.js
import { useState } from "react"
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

const FloatingNav = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <nav className="fixed top-4 right-4 will-change-nav">
      <button
        onClick={() => setIsOpen(!isOpen)}
        className="bg-blue-500 text-white p-4 rounded-full shadow-lg"
      >
        Menu
      </button>
      
      <div className={`absolute right-0 top-16 bg-white rounded-lg shadow-xl p-4 transition-all duration-300 ${
        isOpen ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-4 pointer-events-none'
      }`}>
        <ul className="space-y-2">
          <li className="hover:bg-gray-100 p-2 rounded">Home</li>
          <li className="hover:bg-gray-100 p-2 rounded">About</li>
          <li className="hover:bg-gray-100 p-2 rounded">Contact</li>
        </ul>
      </div>
    </nav>
  );
};

export default FloatingNav;

Animated Profile Cards

This example shows profile cards with smooth opacity, position transitions, and custom will-change utility.

This is a live editor. Play around with it!
// App.js
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;
import { useState, useEffect } from 'react';

const ProfileCards = () => {
  const profiles = [
    {
      name: 'Alex Thompson',
      role: 'Designer',
      image: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e',
    },
    {
      name: 'Sarah Williams',
      role: 'Developer',
      image: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80',
    },
    {
      name: 'Michael Chen',
      role: 'Product Manager',
      image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e',
    },
  ];

  return (
    <div className="flex flex-wrap gap-8 justify-center p-8">
      {profiles.map((profile, index) => (
        <div
          key={index}
          className="group relative w-64 rounded-xl overflow-hidden shadow-lg"
        >
          <img
            src={profile.image}
            alt={profile.name}
            className="w-full h-72 object-cover transition-transform group-hover:scale-110"
          />
          <div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity will-change-profile">
            <div className="absolute bottom-0 left-0 right-0 p-4 text-white">
              <h3 className="text-xl font-bold">{profile.name}</h3>
              <p>{profile.role}</p>
            </div>
          </div>
        </div>
      ))}
    </div>
  );
};

export default ProfileCards;

Best Practices

Use with Caution

Applying will-change should be a carefully considered decision within your design and animation strategy. It should not be used indiscriminately, as excessive application can lead to unintended performance issues rather than optimizations. Instead of applying will-change broadly, limit its usage to elements that genuinely require rendering optimizations.

Before integrating will-change, ensure that animations, transitions, and layout adjustments align with a well-defined motion design system. Elements should transition smoothly, avoiding abrupt changes that might create inconsistencies within the interface. Rather than treating will-change as a default solution, establish a structured approach to animations—prioritizing standard CSS optimizations first and resorting to will-change only when all other techniques have been exhausted.

Leverage Utility Combinations

When applying will-change-transform, it pairs well with transition-transform and duration-* classes to ensure optimized GPU rendering. Similarly, using will-change-[opacity] alongside opacity-* classes enables smooth fading effects.

For hover interactions, group-hover or hover: variants can be used in conjunction with will-change to ensure optimized responsiveness. For instance, when hovering over a card, you may want to trigger subtle scaling or shadow effects. In such cases, pairing will-change with hover:scale-* or hover:shadow-* ensures that animations remain fluid and efficient.

Accessibility Considerations

Enhance Readability and Navigability

If applied to critical elements, users may experience unexpected rendering issues, making content harder to read. Ensure that will-change is only used where necessary and does not interfere with essential content.

For users who rely on assistive technologies, such as screen readers, avoid applying will-change to structural elements that contribute to content flow. The improper use of will-change can cause delays in rendering, which may disrupt the user's ability to navigate a page efficiently. To mitigate this, apply will-change sparingly and primarily to elements that enhance interaction rather than those that contain primary content.

Focus on High Contrast

When using will-change with opacity or color transitions in Tailwind CSS, be mindful that it does not directly control transitions but instead hints to the browser about expected changes. This means will-change itself does not guarantee smooth opacity transitions or color shifts—it simply allows the browser to optimize rendering in advance.

For accessibility, ensure that opacity and color changes maintain a sufficient contrast ratio throughout the animation. Sudden shifts in transparency may temporarily reduce text or element visibility, which can be problematic for users with low vision.