A sale tag on a cord drops on hover, then bobs on it like a bungee while the cord stretches to match. The tag and the cord run twin animations with the same timing, so they never drift apart.
Also known as: hvr-hang, hanging tag hover css, bungee drop hover.
<a class="hfx-hang" href="#"> <span class="hfx-hang-cord" aria-hidden="true"></span> <span class="hfx-hang-tag">Summer sale</span> </a>
.hfx-hang {
--c: var(--hfx-color, #3b82f6);
display: inline-flex;
flex-direction: column;
align-items: center;
font: 700 var(--hfx-size, 16px) system-ui, sans-serif;
text-decoration: none;
}
/* the nail */
.hfx-hang::before {
content: "";
width: 0.55em;
height: 0.55em;
border-radius: 50%;
background: var(--c);
box-shadow: 0 0 0 0.18em color-mix(in srgb, var(--c) 25%, transparent);
}
.hfx-hang-cord {
position: relative;
z-index: 1;
width: 2px;
height: 1.8em;
margin-top: -0.2em;
background: color-mix(in srgb, var(--c) 70%, transparent);
transform-origin: 50% 0;
}
.hfx-hang-tag {
position: relative;
margin-top: -0.5em;
padding: 0.95em 1.3em 0.7em;
border-radius: 0.45em;
background: var(--c);
color: #fff;
letter-spacing: 0.02em;
box-shadow: 0 0.5em 0.9em -0.45em color-mix(in srgb, var(--c) 70%, transparent);
}
/* the punched hole the cord runs through */
.hfx-hang-tag::before {
content: "";
position: absolute;
top: 0.3em;
left: 50%;
width: 0.4em;
height: 0.4em;
margin-left: -0.2em;
border-radius: 50%;
background: rgb(255 255 255 / 0.85);
}
.hfx-hang:is(:hover, :focus-visible) .hfx-hang-tag {
animation: hfx-hang-drop 0.3s ease-out forwards, hfx-hang-bob 1.5s ease-in-out 0.3s infinite alternate;
}
.hfx-hang:is(:hover, :focus-visible) .hfx-hang-cord {
animation: hfx-hang-stretch 0.3s ease-out forwards, hfx-hang-spring 1.5s ease-in-out 0.3s infinite alternate;
}
/* the cord is 1.8em: dropping 0.6em stretches it to 2.4em, 0.25em to 2.05em */
@keyframes hfx-hang-drop {
to { transform: translateY(0.6em); }
}
@keyframes hfx-hang-bob {
from { transform: translateY(0.6em); }
to { transform: translateY(0.25em); }
}
@keyframes hfx-hang-stretch {
to { transform: scaleY(1.333); }
}
@keyframes hfx-hang-spring {
from { transform: scaleY(1.333); }
to { transform: scaleY(1.139); }
}
@media (prefers-reduced-motion: reduce) {
.hfx-hang, .hfx-hang *, .hfx-hang::before, .hfx-hang::after, .hfx-hang *::before, .hfx-hang *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--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.