A navigation effect that draws the eye by pushing everything else back. When one link is hovered or focused, the other links in the same group fade to low opacity or go out of focus with a blur, and return when the pointer leaves. It is shown twice: in a small row of header links and in a full menu of large serif titles with a number, an arrow and a short description each. A switch changes the large menu between blur and dim. The effect itself is three CSS rules built on the `:has` selector, with no JavaScript. On phones the descriptions are hidden and the titles shrink.
// Adapted from Animata (https://animata.design/docs/container/sibling-focus-nav)
// Copyright (c) Animata. MIT License: https://we.inc/components/licenses
import { useEffect, useState } from "react";
import { ArrowUpRight } from "lucide-react";
const links = [
{ label: "Selected work", detail: "Fourteen projects, 2019 to now" },
{ label: "The studio", detail: "Six people above a bakery in Leith" },
{ label: "Journal", detail: "Notes on type, print and process" },
{ label: "Workshops", detail: "Letterpress weekends, spring dates" },
{ label: "Say hello", detail: "New projects from January" },
];
const quickLinks = ["Index", "Shop", "Press", "Contact"];
type Mode = "dim" | "blur";
export default function MenuLinksThatDimOrBlurSiblings() {
const [mode, setMode] = useState<Mode>("blur");
// Until someone points at the menu, a pretend hover walks down the list.
const [demo, setDemo] = useState<number | null>(0);
useEffect(() => {
if (demo === null || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const timer = setTimeout(() => setDemo((i) => (i === null ? null : (i + 1) % links.length)), 2400);
return () => clearTimeout(timer);
}, [demo]);
const stopDemo = () => setDemo(null);
return (
<section className="bg-[#10231c] px-5 py-8 text-[#f3ecdc] sm:px-10 sm:py-10" onPointerEnter={stopDemo} onFocus={stopDemo}>
<style>{`
.sibling-dim-menu a { transition: opacity 280ms ease-out, filter 280ms ease-out; }
/* The whole effect: when one link is hovered or focused, style every other link in the same group. */
.sibling-dim-menu[data-mode="dim"]:has(a:hover) a:not(:hover),
.sibling-dim-menu[data-mode="dim"]:has(a:focus-visible) a:not(:focus-visible),
.sibling-dim-menu[data-mode="dim"][data-demo] a:not([data-on]) { opacity: 0.28; }
.sibling-dim-menu[data-mode="blur"]:has(a:hover) a:not(:hover),
.sibling-dim-menu[data-mode="blur"]:has(a:focus-visible) a:not(:focus-visible),
.sibling-dim-menu[data-mode="blur"][data-demo] a:not([data-on]) { filter: blur(4px); opacity: 0.5; }
.sibling-dim-menu a .sibling-dim-menu-arrow { transition: transform 280ms ease-out, opacity 280ms ease-out; opacity: 0; transform: translate(-8px, 8px); }
.sibling-dim-menu a:hover .sibling-dim-menu-arrow,
.sibling-dim-menu a:focus-visible .sibling-dim-menu-arrow,
.sibling-dim-menu a[data-on] .sibling-dim-menu-arrow { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
.sibling-dim-menu a, .sibling-dim-menu a .sibling-dim-menu-arrow { transition: none; }
.sibling-dim-menu[data-mode="blur"]:has(a:hover) a:not(:hover),
.sibling-dim-menu[data-mode="blur"]:has(a:focus-visible) a:not(:focus-visible) { filter: none; opacity: 0.45; }
}
`}</style>
<div className="mx-auto max-w-5xl">
<div className="flex flex-wrap items-center justify-between gap-x-6 gap-y-3 border-b border-[#f3ecdc]/20 pb-5">
<a href="#" className="rounded font-serif text-xl italic focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#f3ecdc]">
Morrow & Pine
</a>
{/* Small row: links are spaced with padding, not gaps, so the hover never drops between them. */}
<nav aria-label="Quick links" data-mode="dim" className="sibling-dim-menu -mx-3 flex">
{quickLinks.map((label) => (
<a key={label} href="#" className="inline-flex min-h-[44px] items-center rounded px-3 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#f3ecdc]">
{label}
</a>
))}
</nav>
</div>
<div className="mt-6 flex flex-wrap items-center justify-between gap-3">
<p className="text-xs font-medium uppercase tracking-[0.2em] text-[#f3ecdc]/70">Menu</p>
<div role="group" aria-label="Effect on the other links" className="flex rounded-full border border-[#f3ecdc]/30 p-1">
{(["blur", "dim"] as Mode[]).map((m) => (
<button
key={m}
type="button"
aria-pressed={mode === m}
onClick={() => setMode(m)}
className={`h-10 rounded-full px-4 text-sm font-medium capitalize transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#f3ecdc] ${
mode === m ? "bg-[#f3ecdc] text-[#10231c]" : "text-[#f3ecdc] hover:bg-[#f3ecdc]/10"
}`}
>
{m}
</button>
))}
</div>
</div>
<nav aria-label="Main" data-mode={mode} data-demo={demo === null ? undefined : ""} className="sibling-dim-menu mt-2 flex flex-col">
{links.map((link, i) => (
<a
key={link.label}
href="#"
data-on={demo === i ? "" : undefined}
className="group flex items-baseline gap-4 rounded-lg py-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#f3ecdc] sm:gap-6 sm:py-2.5"
>
<span className="w-6 shrink-0 text-xs tabular-nums text-[#f3ecdc]/70 sm:w-8 sm:text-sm">0{i + 1}</span>
<span className="font-serif text-4xl leading-none tracking-tight sm:text-6xl">{link.label}</span>
<ArrowUpRight aria-hidden="true" className="sibling-dim-menu-arrow hidden h-8 w-8 shrink-0 self-center text-[#e8b86d] sm:block" />
<span className="ml-auto hidden max-w-[14rem] text-right text-sm text-[#f3ecdc]/80 md:block">{link.detail}</span>
</a>
))}
</nav>
<p className="mt-6 border-t border-[#f3ecdc]/20 pt-5 text-sm text-[#f3ecdc]/80">
Point at a link, or tab through the menu: the one you are on stays sharp and the rest step back.
</p>
</div>
</section>
);
}
Install with one command: npx shadcn@latest add https://we.inc/r/menu-links-that-dim-or-blur-their-siblings-on-hover.json
The rule reads: when the group has a link that is hovered, style every link in it that is not hovered. In CSS that is `.group:has(a:hover) a:not(:hover)`. The `:has` part lets the parent react to a child, which older CSS could not do.
All current versions of Chrome, Edge, Safari and Firefox support it. In an old browser without support the rule is ignored and the links simply stay at full strength, so nothing breaks.
A gap between links is empty space that belongs to no link, so the hover would drop for a moment as the pointer crosses it and every link would flash back. Padding keeps the pointer over a link the whole way.
It is a small demo that pretends to hover each link in turn so the effect is visible without a mouse. It stops for good as soon as a pointer or keyboard focus enters the section. Remove the `demo` state and the two `data` attributes to drop it.
A blur filter on a handful of text links is cheap. Avoid applying it to dozens of large elements or to images at once, where it can cost frames on low-powered phones; use the dim mode there.
Adapted from Sibling Focus Nav in Animata, used under its open-source licence. Licences and credits
Related: Menu Links with Rolling Letter Swap on Hover, Link Underline That Fills Into a Highlight on Hover, Navbar with Centered Links, CTA Button and Mobile Menu.
More tabs and navigation or all free UI components. Start for free to have a full site built around it.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.