Home

Tabs

Vercel's tab component with
animated highlight on hover.

I decided to challenge myself and recreate real UI elements from the web. Vercel's tabs component will be first, with more coming soon. If you have any ideas on what UI elements I can cover next, let me know on Twitter!

I will explain only the trickiest parts of the implementation. To see how the code works in details you can visit the codesandbox link.

How it works

The bounding box of the highlighted tab & wrapper are being stored in the state. Each time the highlighted tab changes, the component re-renders and applies new styles to the highlighter.

const [tabBoundingBox, setTabBoundingBox] = useState(null);
const [wrapperBoundingBox, setWrapperBoundingBox] = useState(null);
const [highlightedTab, setHighlightedTab] = useState(null);

To calculate highlighter's position we subtract the left property of the wrapper from the left property of the highlighted tab. That way we get the distance between them and we can then use that value to move the highlighter like this:

`translate(${tabBoundingBox.left - wrapperBoundingBox.left}px)`;

First hover

If we look closely, the animation duration on first hover is set to 0 which makes it even more sophisticated. Look at the gif below to see how it would look if the duration didn't change.

Gif explaining the hover issue

Notice how we can see the highlighter animating from it's last position

To tackle this issue we declare a state variable called isHoveredFromNull, when highlightedTab is not set (first hover) we set it to true.

const repositionHighlight = (e, tab) => {
setTabBoundingBox(e.target.getBoundingClientRect());
setWrapperBoundingBox(wrapperRef.current.getBoundingClientRect());
// If highlighted tab is not set, set to false.
setIsHoveredFromNull(!highlightedTab);
setHighlightedTab(tab);
};

We can now assign the correct transition duration like this:

transitionDuration = hoveredFromNull ? '0ms' : '150ms';

Changing direction

To change the direction of our highlighter all we need to do is calculate the distance from top instead of the left side.

`translateY(${tabBoundingBox.top - wrapperBoundingBox.top}px)`;

Other use cases

You can use the highlighter we just created to polish your User Interfaces. Vercel uses it in their Command Menu too.

Command Menu that uses the highlighter

Preview of Vercel's command menu
from Rauno's Twitter