CSS anchor positioning is a groundbreaking feature that allows elements to be sized and positioned relative to others without requiring a parent-child relationship. Introduced in May 2024 with Chrome 125, it currently has around 70% browser support. While adoption is growing, it’s not yet recommended for critical features on websites
In this article, we’ll explore how to use CSS anchor positioning to create smoother UI animations without relying on JavaScript. We’ll also ensure compatibility with unsupported browsers so the site remains functional.
If you are only interested in the code, check out the sections: animation on click, animation on hover, GitHub.
How it works
CSS anchor positioning revolves around two main concepts:
- Anchor: The element relative to which the position and size of a target are defined.
- Target: The element positioned relative to the anchor. Typically, this is a pseudo-element styled with
position: absolute
.
If we apply this terminology to the usual positioning of elements, then anchor is an element with position: absolute
, and target is an element with position: absolute
that is inside anchor. But the magic of CSS anchor positioning allows these elements not to be parents and children, in most cases, these elements are siblings.
Core Properties and Functions:
anchor-name
: Defines the anchor and uses a--
prefix.
Example:anchor-name: --example;
anchor()
: Positions the target relative to the anchor.
Syntax:top: anchor(--example top);
Options:top
,bottom
,left
,right
, etc.anchor-size()
: Sets the size of the target based on the anchor.
Syntax:width: anchor-size(--example width);
Options:width
,height
, etc.
Adding Animation on Click with Anchor Positioning
Now let's start figuring out anchor positioning using a real-life use case. Let's add animation to tab buttons using anchor positioning.
We will use the Next.js project with shadcn tab component and Tailwind CSS as the project. to streamline the process. This lets us focus on the animation logic rather than building tabs from scratch.
0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
export function TabsSection() {
return (
<Tabs defaultValue="rabbit" className="container w-full sm:max-w-[800px] my-5 sm:my-8">
<TabsList className="flex w-full bg-[#f4f4f4] tabs-list">
<TabsTrigger className="w-[25%] data-[state=active]:bg-white tab" value="rabbit">
Rabbit
</TabsTrigger>
<TabsTrigger className="w-[25%] data-[state=active]:bg-white tab" value="squirrel">
Squirrel
</TabsTrigger>
<TabsTrigger className="w-[25%] data-[state=active]:bg-white tab" value="hamster">
Hamster
</TabsTrigger>
<TabsTrigger className="w-[25%] data-[state=active]:bg-white tab" value="kitten">
Kitten
</TabsTrigger>
</TabsList>
<TabsContent value="rabbit">
<Card>
<CardHeader>
<CardTitle>Rabbit Samurai</CardTitle>
<CardDescription>
With swift leaps and razor-sharp instincts, the rabbit samurai uses its long ears to
sense danger and its nimble paws to outmaneuver even the fiercest foes. Armed with a
katana as light as moonlight, it defends its warren with unwavering honor. By day, it
trains in the art of evasion, honing its speed and agility in open fields. By night,
it patrols the meadow under the stars, alert to every rustle and shadow. The rabbit
samurai is a master of stealth, blending into the tall grasses as if it were never
there. It moves so gracefully that even the wind cannot hear its steps. Despite its
small size, its courage rivals that of the fiercest warriors. Its loyalty to its
fellow rabbits is unshakable, always ready to defend them from predators. The rabbit
samurai believes that strength comes not from size but from the heart. It serves as a
reminder that even the smallest creature can have a heroic spirit.
</CardDescription>
</CardHeader>
</Card>
</TabsContent>
<TabsContent value="squirrel">
<Card>
<CardHeader>
<CardTitle>Squirrel Samurai</CardTitle>
<CardDescription>
Agile and daring, the squirrel samurai leaps from treetop to treetop, wielding its
tiny blade with unmatched precision while guarding the forest from intruders. Its
bushy tail serves as a balancing tool and a signal to its allies in the canopy. No
invader can escape its watchful eyes, as it defends its woodland home with clever
ambushes and lightning-fast strikes. Its armor is woven from leaves and bark, offering
protection while keeping it light enough to move swiftly. The squirrel samurai spends
its mornings training with the rising sun, perfecting its acrobatic moves. In the
afternoon, it scouts the forest, ensuring its safety for all creatures that live
there. When the wind whispers of danger, it gathers its squirrel clan to strategize
and prepare for battle. Its sharp claws double as tools and weapons, making it
versatile in any fight. It cherishes the harmony of the forest, fighting not for glory
but for the balance of nature. Brave, clever, and nimble, the squirrel samurai is a
true protector of the treetops.
</CardDescription>
</CardHeader>
</Card>
</TabsContent>
<TabsContent value="hamster">
<Card>
<CardHeader>
<CardTitle>Hamster Samurai</CardTitle>
<CardDescription>
Small but mighty, the hamster samurai carries a heart as big as its courage, rolling
into action inside its magical armored ball to protect the village. By day, it appears
harmless, munching on seeds and storing supplies in its cheeks. But when danger looms,
it becomes a spinning whirlwind of bravery, surprising foes with its unmatched
determination. Its armor is forged from fragments of forgotten treasures, giving it
both style and strength. The hamster samurai trains tirelessly in secret, mastering
techniques passed down through generations. It is known for its ability to think
quickly under pressure, often outsmarting enemies many times its size. Its tiny paws
are deceptively strong, capable of wielding miniature weapons with precision. Despite
its size, it inspires awe and respect among the other warriors. Its burrow serves as
both a home and a hidden dojo, filled with scrolls of ancient wisdom. The hamster
samurai proves that even the smallest hero can make the biggest difference.
</CardDescription>
</CardHeader>
</Card>
</TabsContent>
<TabsContent value="kitten">
<Card>
<CardHeader>
<CardTitle>Kitten Samurai</CardTitle>
<CardDescription>
Graceful and fearless, the kitten samurai blends charm with stealth, its silky fur
hiding the sharpest claws and the keenest of minds. It practices silent stalking in
the moonlight, its amber eyes glowing with focus. Though young, it carries the wisdom
of generations, always ready to pounce and defend its home with a delicate yet fierce
touch. Its sword, small but sharp, gleams like a star in the dim light. The kitten
samurai’s playful demeanor masks its disciplined nature, surprising those who
underestimate it. Each morning, it sharpens its reflexes by chasing leaves and
shadows, perfecting its speed and accuracy. It listens to the whispers of the wind,
using them to guide its movements. With a soft paw and a steady heart, it walks the
line between innocence and power. The kitten samurai values both honor and kindness,
ensuring its blade is used only for protection. Its journey is just beginning, but its
potential is as boundless as the horizon.{' '}
</CardDescription>
</CardHeader>
</Card>
</TabsContent>
</Tabs>
);
}
0123456789101112131415161718192021222324
@supports (anchor-name: --test) {
.tabs-list {
@apply relative;
}
.tabs-list .tab {
@apply relative z-20;
}
.tabs-list .tab[data-state='active'] {
anchor-name: --active-tab;
@apply bg-transparent shadow-none;
}
.tabs-list::before {
content: "";
@apply absolute z-10 bg-white shadow rounded transition-all;
top: anchor(--active-tab top);
bottom: anchor(--active-tab bottom);
left: anchor(--active-tab left);
right: anchor(--active-tab right);
width: anchor-size(--active-tab width);
height: anchor-size(--active-tab height);
}
}
Tabs are built in the TabsSection
component and shown in the first tab. Components provided by shadcn were used for this purpose:
Tabs
: wrapper for tabs;TabsList
: grid for buttons that switch tabs;TabsTrigger
: button that toggles the tabs;TabsContent
: the content of a particular tab. The name of the other components speaks for itself;Card
;CardHeader
;CardTitle
;CardDescription
.
From what anchor positioning in TabsSection
markup: added CSS classes "tabs-list" and "tab". Styles applied to them are responsible for the whole anchor positioning magic.
The CSS styles responsible for anchor positioning are shown in the second tab. To ensure that the added styles do not break styles on browsers that do not support this feature, all styles have been wrapped in @supports (anchor-name: --test)
. In order to remove the default styles of the active tab button (selector .tabs-list .tab[data-state='active']
), styles that remove the background color and box shadow (@apply bg-transparent shadow-none;
) were added.
Back to the terminology described above:
- Anchor: selector
.tabs-list .tab[data-state='active']
- selected tab button. Addedanchor-name: --active-tab;
. - Target: The
::before
pseudo-element in.tabs-list
represents a white block moving to the active tab.
Target styles include:
- Positioning:
anchor(--active-tab <side>);
- Sizing:
anchor-size(--active-tab <size>);
- Transitions for smooth movement.
The result is shown in the video below:
Adding Animation on Hover
To extend functionality, we’ll modify the code to enable hover-based animations.
0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
'use client'
import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useState } from 'react';
import { cn } from '@/utils/cn';
export function TabsSection() {
const [animationTrigger, setAnimationTrigger] = useState<'click' | 'hover'>('click');
const toggleEffect = () => {
if (animationTrigger === 'click') {
setAnimationTrigger('hover');
return;
}
setAnimationTrigger('click');
}
return (
<Tabs defaultValue="rabbit" className="container w-full sm:max-w-[800px] my-5 sm:my-8">
<div className="flex mb-3 gap-4 items-center">
<button
className="py-2.5 px-5 shrink-0 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100"
onClick={toggleEffect}>
Switch effect
</button>
Current animation trigger: {animationTrigger}
</div>
<TabsList className={cn('flex w-full bg-[#f4f4f4] tabs-list', {'tabs-list-hover': animationTrigger === 'hover'})}>
<TabsTrigger className="w-[25%] data-[state=active]:bg-white tab" value="rabbit">
Rabbit
</TabsTrigger>
<TabsTrigger className="w-[25%] data-[state=active]:bg-white tab" value="squirrel">
Squirrel
</TabsTrigger>
<TabsTrigger className="w-[25%] data-[state=active]:bg-white tab" value="hamster">
Hamster
</TabsTrigger>
<TabsTrigger className="w-[25%] data-[state=active]:bg-white tab" value="kitten">
Kitten
</TabsTrigger>
</TabsList>
<TabsContent value="rabbit">
<Card>
<CardHeader>
<CardTitle>Rabbit Samurai</CardTitle>
<CardDescription>
With swift leaps and razor-sharp instincts, the rabbit samurai uses its long ears to
sense danger and its nimble paws to outmaneuver even the fiercest foes. Armed with a
katana as light as moonlight, it defends its warren with unwavering honor. By day, it
trains in the art of evasion, honing its speed and agility in open fields. By night,
it patrols the meadow under the stars, alert to every rustle and shadow. The rabbit
samurai is a master of stealth, blending into the tall grasses as if it were never
there. It moves so gracefully that even the wind cannot hear its steps. Despite its
small size, its courage rivals that of the fiercest warriors. Its loyalty to its
fellow rabbits is unshakable, always ready to defend them from predators. The rabbit
samurai believes that strength comes not from size but from the heart. It serves as a
reminder that even the smallest creature can have a heroic spirit.
</CardDescription>
</CardHeader>
</Card>
</TabsContent>
<TabsContent value="squirrel">
<Card>
<CardHeader>
<CardTitle>Squirrel Samurai</CardTitle>
<CardDescription>
Agile and daring, the squirrel samurai leaps from treetop to treetop, wielding its
tiny blade with unmatched precision while guarding the forest from intruders. Its
bushy tail serves as a balancing tool and a signal to its allies in the canopy. No
invader can escape its watchful eyes, as it defends its woodland home with clever
ambushes and lightning-fast strikes. Its armor is woven from leaves and bark, offering
protection while keeping it light enough to move swiftly. The squirrel samurai spends
its mornings training with the rising sun, perfecting its acrobatic moves. In the
afternoon, it scouts the forest, ensuring its safety for all creatures that live
there. When the wind whispers of danger, it gathers its squirrel clan to strategize
and prepare for battle. Its sharp claws double as tools and weapons, making it
versatile in any fight. It cherishes the harmony of the forest, fighting not for glory
but for the balance of nature. Brave, clever, and nimble, the squirrel samurai is a
true protector of the treetops.
</CardDescription>
</CardHeader>
</Card>
</TabsContent>
<TabsContent value="hamster">
<Card>
<CardHeader>
<CardTitle>Hamster Samurai</CardTitle>
<CardDescription>
Small but mighty, the hamster samurai carries a heart as big as its courage, rolling
into action inside its magical armored ball to protect the village. By day, it appears
harmless, munching on seeds and storing supplies in its cheeks. But when danger looms,
it becomes a spinning whirlwind of bravery, surprising foes with its unmatched
determination. Its armor is forged from fragments of forgotten treasures, giving it
both style and strength. The hamster samurai trains tirelessly in secret, mastering
techniques passed down through generations. It is known for its ability to think
quickly under pressure, often outsmarting enemies many times its size. Its tiny paws
are deceptively strong, capable of wielding miniature weapons with precision. Despite
its size, it inspires awe and respect among the other warriors. Its burrow serves as
both a home and a hidden dojo, filled with scrolls of ancient wisdom. The hamster
samurai proves that even the smallest hero can make the biggest difference.
</CardDescription>
</CardHeader>
</Card>
</TabsContent>
<TabsContent value="kitten">
<Card>
<CardHeader>
<CardTitle>Kitten Samurai</CardTitle>
<CardDescription>
Graceful and fearless, the kitten samurai blends charm with stealth, its silky fur
hiding the sharpest claws and the keenest of minds. It practices silent stalking in
the moonlight, its amber eyes glowing with focus. Though young, it carries the wisdom
of generations, always ready to pounce and defend its home with a delicate yet fierce
touch. Its sword, small but sharp, gleams like a star in the dim light. The kitten
samurai’s playful demeanor masks its disciplined nature, surprising those who
underestimate it. Each morning, it sharpens its reflexes by chasing leaves and
shadows, perfecting its speed and accuracy. It listens to the whispers of the wind,
using them to guide its movements. With a soft paw and a steady heart, it walks the
line between innocence and power. The kitten samurai values both honor and kindness,
ensuring its blade is used only for protection. Its journey is just beginning, but its
potential is as boundless as the horizon.{' '}
</CardDescription>
</CardHeader>
</Card>
</TabsContent>
</Tabs>
);
}
0123456789101112
...
.tabs-list:not(.tabs-list-hover) .tab[data-state='active'] {
anchor-name: --active-tab;
}
.tabs-list.tabs-list-hover:not(:has(.tab:hover)) .tab[data-state='active'] {
anchor-name: --active-tab;
}
.tabs-list.tabs-list-hover .tab:hover {
anchor-name: --active-tab;
}
...
In the markup (first tab), these changes have been added:
'use client'
directive was added: the component is now client since it usesuseState
.useState
animationTrigger
was added: this is needed to add the possibility to switch animation on click (as described above) or on hover. Also,toggleEffec
t function was added to toggle this state.- The class
tabs-list-hover
is added in caseanimationTrigger === 'hover'
. This is done to control the type of animation.
All of the above is done for demonstration purposes, and the key is the last point: if tabs-list
contains CSS class tabs-list-hover
, then animation will be applied on hover, else - on click.
The second tab demonstrates the CSS changes. They are all related to the target element definition (anchor-name: --active-tab;
).
.tabs-list:not(.tabs-list-hover) .tab[data-state='active']
: if click animation is selected, target is the selected tab button..tabs-list.tabs-list-hover:not(:has(.tab:hover)) .tab[data-state='active']
: if hover animation is selected and none of the tab buttons are hovered, the target is also the selected tab button..tabs-list.tabs-list-hover .tab:hover
: if hover animation is selected and one of the tab buttons is hovered, the target is the hovered tab button.
The result is shown in the video below:
Conclusion
This article demonstrated the power of CSS anchor positioning to build fluid and responsive animations. Two key examples were implemented:
- Animation on click: A moving element highlights the selected tab button.
- Animation on hover: The moving element follows the hovered tab button.
Both animations are CSS-only and can be applied to various UI elements, ensuring better performance and smoother user experiences.
All the code from the article is available on GitHub. Feel free to use.