Highlight

Apply a temporary visual treatment whenever a React prop changes.

A component that watches for changes to its trigger prop, and flips its data-highlight attribute to "on" for duration milliseconds whenever the prop changes.

<Highlight
  trigger={visitors}
  duration={500}
  className="data-[highlight=on]:bg-sky-500 bg-gray-900 transition"
>
  <p>Visitors</p>
  <p>{visitors}</p>
</Highlight>

The demos above highlight changes in a cookie value from a React Server Component. The first two are built with CSS transitions and the third uses a CSS animation. They also all use an animated counter component which is separate from the highlighter. View the demo source on GitHub.

Using Tailwind

Use Tailwind's data attributes to style the highlighted state:

<Highlight
  trigger={visitors}
  duration={500}
  className="data-[highlight=on]:bg-sky-500 bg-gray-900"
>
  <p>Visitors</p>
  <p>{visitors}</p>
</Highlight>

Combine the group modifier with data attributes to style child elements individually:

<Highlight trigger={visitors} duration={500} className="group">
  <div className="group-data-[highlight=on]:bg-sky-500 bg-gray-900">
    <p className="text-white/50">Visitors</p>
    <p className="group-data-[highlight=on]:text-white text-sky-500">
      {visitors}
    </p>
  </div>
</Highlight>

Using CSS

Use the [data-highlight="on"] CSS selector to style elements in the highlighted state:

<Highlight trigger={visitors} duration={500}>
  <p>Visitors</p>
  <p>{visitors}</p>
</Highlight>
[data-highlight="on"] {
  background-color: blue;
}

Animation

To animate changes, use CSS transitions or animations. You can specify different enter and leave durations:

<div
  className="
    group-data-[highlight=on]:bg-sky-500 group-data-[highlight=on]:duration-150
    bg-gray-900 transition duration-1000
  "
>
  {/* ... */}
</div>

or use data-highlight="on" and data-highlight="off" to specify an enter and leave animation:

<div
  className="
    group-data-[highlight=on]:animate-[ripple_250ms_cubic-bezier(0.09,.6,.36,1)_forwards]
    group-data-[highlight=off]:animate-[fadeOut_750ms_forwards]
    aspect-square h-full rounded-full bg-green-500/50 opacity-0
  "
/>

Code

Here's the code for the <Highlight> component. The data-highlight attribute has three states:

  • null on initial render
  • on after the trigger prop changes (for duration milliseconds)
  • off after the duration has elapsed

The component is restartable, so data-highlight will remain on if the value changes again before the duration has elapsed.

<Highlight> can be used in both Server Components and Client Components.

"use client";

import { ComponentPropsWithoutRef, useEffect, useState } from "react";

type HighlightProps = ComponentPropsWithoutRef<"div"> & {
  trigger: unknown;
  duration: number;
};

export default function Highlight({
  trigger,
  duration,
  children,
  ...props
}: HighlightProps) {
  let [previous, setPrevious] = useState(trigger);
  let [didHighlight, setDidHighlight] = useState(false);

  useEffect(() => {
    const handler = setTimeout(() => {
      if (previous !== trigger) {
        setDidHighlight(true);
      }
      setPrevious(trigger);
    }, duration);

    return () => {
      clearTimeout(handler);
    };
  }, [duration, previous, trigger]);

  return (
    <div
      data-highlight={previous !== trigger ? "on" : didHighlight ? "off" : null}
      {...props}
    >
      {children}
    </div>
  );
}