Menu items float up a little on hover and a dot pops in underneath with a bounce, the same dot that marks the current page. The dot counter-moves so it stays on the baseline.
Also known as: nav dot under link, menu item lift hover, active dot navbar.
<nav class="hfx-nav-lift-dot"> <a href="#" aria-current="page">Home</a> <a href="#">Work</a> <a href="#">About</a> <a href="#">Contact</a> </nav>
.hfx-nav-lift-dot {
--c: var(--hfx-color, #3b82f6);
display: flex;
align-items: center;
font: 600 var(--hfx-size, 16px)/1.2 system-ui, sans-serif;
}
.hfx-nav-lift-dot a {
position: relative;
padding: 0.6em 0.9em 0.9em;
color: inherit;
text-decoration: none;
transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), color 0.25s ease;
}
.hfx-nav-lift-dot a::after {
content: "";
position: absolute;
left: 50%;
bottom: 0.25em;
width: 0.36em;
height: 0.36em;
margin-left: -0.18em;
border-radius: 50%;
background: var(--c);
transform: scale(0);
transition: transform 0.35s cubic-bezier(0.34, 1.8, 0.64, 1);
}
.hfx-nav-lift-dot a[aria-current="page"] { color: var(--c); }
.hfx-nav-lift-dot a[aria-current="page"]::after { transform: scale(1); }
.hfx-nav-lift-dot a:is(:hover, :focus-visible) {
color: var(--c);
transform: translateY(-0.25em);
}
.hfx-nav-lift-dot a:is(:hover, :focus-visible)::after { transform: translateY(0.25em) scale(1); }
@media (prefers-reduced-motion: reduce) {
.hfx-nav-lift-dot, .hfx-nav-lift-dot *, .hfx-nav-lift-dot::before, .hfx-nav-lift-dot::after, .hfx-nav-lift-dot *::before, .hfx-nav-lift-dot *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}The same effect using Tailwind utility classes. No custom CSS or config needed.
<nav class="flex font-semibold"> <a href="#" aria-current="page" class="relative px-3 pb-3 pt-2 text-blue-500 transition duration-300 after:absolute after:bottom-1 after:left-1/2 after:h-1.5 after:w-1.5 after:-translate-x-1/2 after:rounded-full after:bg-blue-500 after:transition after:duration-300 hover:-translate-y-1 hover:after:translate-y-1">Home</a> <a href="#" class="relative px-3 pb-3 pt-2 transition duration-300 after:absolute after:bottom-1 after:left-1/2 after:h-1.5 after:w-1.5 after:-translate-x-1/2 after:scale-0 after:rounded-full after:bg-blue-500 after:transition after:duration-300 hover:-translate-y-1 hover:text-blue-500 hover:after:translate-y-1 hover:after:scale-100">Work</a> <a href="#" class="relative px-3 pb-3 pt-2 transition duration-300 after:absolute after:bottom-1 after:left-1/2 after:h-1.5 after:w-1.5 after:-translate-x-1/2 after:scale-0 after:rounded-full after:bg-blue-500 after:transition after:duration-300 hover:-translate-y-1 hover:text-blue-500 hover:after:translate-y-1 hover:after:scale-100">About</a> <a href="#" class="relative px-3 pb-3 pt-2 transition duration-300 after:absolute after:bottom-1 after:left-1/2 after:h-1.5 after:w-1.5 after:-translate-x-1/2 after:scale-0 after:rounded-full after:bg-blue-500 after:transition after:duration-300 hover:-translate-y-1 hover:text-blue-500 hover:after:translate-y-1 hover:after:scale-100">Contact</a> </nav>
--hfx-color on the element or a parent.
Currently previewing #3b82f6.--hfx-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.