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 renderon
after thetrigger
prop changes (forduration
milliseconds)off
after theduration
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>
);
}