24 CSS Tags & Chips08 / 24
CSS Liquid Fill Hover Tag Component
The classic liquid-rise effect, done the honest CSS way: a huge rotating blob (a square with ~42% uneven border-radius) hides below each pill; hover translates it upward while it keeps spinning, and the uneven radius sloshes across the top edge like a real waterline. The rotation runs as an animation on the standalone rotate property while the rise is a transition on translate — the two never fight, which is the whole trick.
Published Updated
The code
<section class="ctc-08" aria-label="Liquid fill hover tag demo">
<div class="ctc-08__stage">
<div class="ctc-08__wrap">
<header class="ctc-08__head">
<p class="ctc-08__kicker">Liquid fill · rotating blob + rising translate</p>
<h1>Hover to pour</h1>
</header>
<div class="ctc-08__panel">
<p class="ctc-08__lab">01 · Rise — the blob translates up while it spins</p>
<div class="ctc-08__row">
<a class="ctc-08__pill" style="--h:60" href="#ctc08">mango</a>
<a class="ctc-08__pill" style="--h:350" href="#ctc08">berry crush</a>
<a class="ctc-08__pill" style="--h:150" href="#ctc08">matcha</a>
<a class="ctc-08__pill" style="--h:250" href="#ctc08">blue lagoon</a>
<a class="ctc-08__pill" style="--h:30" href="#ctc08">cold brew</a>
</div>
</div>
<div class="ctc-08__panel">
<p class="ctc-08__lab">02 · Sweep — same blob, entering from the left</p>
<div class="ctc-08__row">
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:292" href="#ctc08">add to queue</a>
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:200" href="#ctc08">save for later</a>
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:150" href="#ctc08">follow topic</a>
</div>
</div>
<div class="ctc-08__panel">
<p class="ctc-08__lab">03 · Resting level — park the waterline as a quota chip</p>
<div class="ctc-08__row">
<span class="ctc-08__pill ctc-08__pill--level" style="--h:150;--fill:-32%">storage 32%</span>
<span class="ctc-08__pill ctc-08__pill--level" style="--h:60;--fill:-58%">credits 58%</span>
<span class="ctc-08__pill ctc-08__pill--level" style="--h:350;--fill:-86%">inbox 86%</span>
</div>
</div>
<p class="ctc-08__note">The spin is an <code>animation</code> on <code>rotate</code>; the rise is a <code>transition</code> on <code>translate</code>. Standalone transform properties compose — with classic <code>transform:</code> the hover would kill the spin.</p>
</div>
</div>
</section><section class="ctc-08" aria-label="Liquid fill hover tag demo">
<div class="ctc-08__stage">
<div class="ctc-08__wrap">
<header class="ctc-08__head">
<p class="ctc-08__kicker">Liquid fill · rotating blob + rising translate</p>
<h1>Hover to pour</h1>
</header>
<div class="ctc-08__panel">
<p class="ctc-08__lab">01 · Rise — the blob translates up while it spins</p>
<div class="ctc-08__row">
<a class="ctc-08__pill" style="--h:60" href="#ctc08">mango</a>
<a class="ctc-08__pill" style="--h:350" href="#ctc08">berry crush</a>
<a class="ctc-08__pill" style="--h:150" href="#ctc08">matcha</a>
<a class="ctc-08__pill" style="--h:250" href="#ctc08">blue lagoon</a>
<a class="ctc-08__pill" style="--h:30" href="#ctc08">cold brew</a>
</div>
</div>
<div class="ctc-08__panel">
<p class="ctc-08__lab">02 · Sweep — same blob, entering from the left</p>
<div class="ctc-08__row">
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:292" href="#ctc08">add to queue</a>
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:200" href="#ctc08">save for later</a>
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:150" href="#ctc08">follow topic</a>
</div>
</div>
<div class="ctc-08__panel">
<p class="ctc-08__lab">03 · Resting level — park the waterline as a quota chip</p>
<div class="ctc-08__row">
<span class="ctc-08__pill ctc-08__pill--level" style="--h:150;--fill:-32%">storage 32%</span>
<span class="ctc-08__pill ctc-08__pill--level" style="--h:60;--fill:-58%">credits 58%</span>
<span class="ctc-08__pill ctc-08__pill--level" style="--h:350;--fill:-86%">inbox 86%</span>
</div>
</div>
<p class="ctc-08__note">The spin is an <code>animation</code> on <code>rotate</code>; the rise is a <code>transition</code> on <code>translate</code>. Standalone transform properties compose — with classic <code>transform:</code> the hover would kill the spin.</p>
</div>
</div>
</section>.ctc-08,
.ctc-08 *,
.ctc-08 *::before,
.ctc-08 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ctc-08 {
--bg: oklch(0.965 0.012 85);
--ink: oklch(0.26 0.03 60);
--mut: oklch(0.5 0.025 60);
--line: oklch(0.88 0.015 80);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.ctc-08__stage {
width: 100%;
height: 100vh;
height: 100svh;
overflow-y: auto;
display: grid;
place-items: center;
padding: clamp(16px,4cqi,48px);
background: radial-gradient(70cqi 55cqi at 90% -10%,oklch(0.9 0.06 60/.55),transparent 62%),radial-gradient(60cqi 50cqi at -5% 112%,oklch(0.88 0.06 350/.4),transparent 60%),var(--bg);
container: ctc08/inline-size;
}
.ctc-08__wrap {
width: min(700px,100%);
display: flex;
flex-direction: column;
gap: 14px;
}
.ctc-08__kicker {
font-size: 11.5px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(0.6 0.16 60);
}
.ctc-08__head h1 {
font-size: clamp(24px,3.8cqi,38px);
font-weight: 800;
letter-spacing: -.03em;
margin: 8px 0 8px;
}
.ctc-08__panel {
background: oklch(0.998 0.003 85);
border: 1px solid var(--line);
border-radius: 18px;
padding: 16px 18px;
}
.ctc-08__lab {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
color: var(--mut);
margin-bottom: 12px;
}
.ctc-08__note code,
.ctc-08__lab code {
font-family: ui-monospace,Menlo,monospace;
font-size: .95em;
letter-spacing: 0;
text-transform: none;
background: oklch(0.92 0.012 80);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.ctc-08__row {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.ctc-08__pill {
--c: oklch(0.68 0.15 var(--h,60));
--deep: oklch(0.52 0.16 var(--h,60));
position: relative;
overflow: hidden;
display: inline-flex;
align-items: center;
min-height: 46px;
padding: 0 21px;
border-radius: 99px;
font-size: 14px;
font-weight: 750;
text-decoration: none;
color: color-mix(in oklch,var(--deep) 80%,var(--ink));
background: color-mix(in oklch,var(--c) 10%,oklch(1 0 0));
border: 1.5px solid color-mix(in oklch,var(--c) 35%,transparent);
isolation: isolate;
transition: border-color .3s,box-shadow .3s;
}
.ctc-08__pill::before {
content: "";
position: absolute;
z-index: -1;
left: -25%;
top: 108%;
width: 150%;
aspect-ratio: 1;
border-radius: 42% 44% 41% 43%;
background: linear-gradient(180deg,var(--c),var(--deep));
rotate: 0deg;
animation: ctc08-spin 5s linear infinite;
transition: translate .6s ease;
}
a.ctc-08__pill:hover,
a.ctc-08__pill:focus-visible {
border-color: transparent;
box-shadow: 0 10px 22px -10px var(--deep);
}
a.ctc-08__pill:hover::before,
a.ctc-08__pill:focus-visible::before {
translate: 0 -78%;
}
.ctc-08__pill span,
.ctc-08__pill {
transition: color .3s .18s,border-color .3s,box-shadow .3s;
}
a.ctc-08__pill:hover,
a.ctc-08__pill:focus-visible {
color: oklch(0.99 0.005 85);
}
a.ctc-08__pill:focus-visible {
outline: 2px solid var(--deep);
outline-offset: 3px;
}
@keyframes ctc08-spin {
to {
rotate: 360deg;
}
}
.ctc-08__pill--sweep::before {
left: auto;
right: 105%;
top: -25%;
width: 220%;
transition: translate .55s ease;
}
a.ctc-08__pill--sweep:hover::before,
a.ctc-08__pill--sweep:focus-visible::before {
translate: 78% 0;
}
.ctc-08__pill--level {
color: oklch(0.99 0.005 85);
border-color: transparent;
background: color-mix(in oklch,var(--c) 18%,oklch(0.3 0.02 60));
}
.ctc-08__pill--level::before {
translate: 0 var(--fill,-30%);
animation-duration: 7s;
}
.ctc-08__pill--level {
text-shadow: 0 1px 4px oklch(0 0 0/.3);
font-variant-numeric: tabular-nums;
}
.ctc-08__note {
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid oklch(0.6 0.16 60);
padding-left: 12px;
}
@media (prefers-reduced-motion: reduce) {
.ctc-08 * {
transition-duration: .01ms !important;
}
.ctc-08__pill::before {
animation: none;
}
}.ctc-08,
.ctc-08 *,
.ctc-08 *::before,
.ctc-08 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ctc-08 {
--bg: oklch(0.965 0.012 85);
--ink: oklch(0.26 0.03 60);
--mut: oklch(0.5 0.025 60);
--line: oklch(0.88 0.015 80);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.ctc-08__stage {
width: 100%;
height: 100vh;
height: 100svh;
overflow-y: auto;
display: grid;
place-items: center;
padding: clamp(16px,4cqi,48px);
background: radial-gradient(70cqi 55cqi at 90% -10%,oklch(0.9 0.06 60/.55),transparent 62%),radial-gradient(60cqi 50cqi at -5% 112%,oklch(0.88 0.06 350/.4),transparent 60%),var(--bg);
container: ctc08/inline-size;
}
.ctc-08__wrap {
width: min(700px,100%);
display: flex;
flex-direction: column;
gap: 14px;
}
.ctc-08__kicker {
font-size: 11.5px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(0.6 0.16 60);
}
.ctc-08__head h1 {
font-size: clamp(24px,3.8cqi,38px);
font-weight: 800;
letter-spacing: -.03em;
margin: 8px 0 8px;
}
.ctc-08__panel {
background: oklch(0.998 0.003 85);
border: 1px solid var(--line);
border-radius: 18px;
padding: 16px 18px;
}
.ctc-08__lab {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
color: var(--mut);
margin-bottom: 12px;
}
.ctc-08__note code,
.ctc-08__lab code {
font-family: ui-monospace,Menlo,monospace;
font-size: .95em;
letter-spacing: 0;
text-transform: none;
background: oklch(0.92 0.012 80);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.ctc-08__row {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.ctc-08__pill {
--c: oklch(0.68 0.15 var(--h,60));
--deep: oklch(0.52 0.16 var(--h,60));
position: relative;
overflow: hidden;
display: inline-flex;
align-items: center;
min-height: 46px;
padding: 0 21px;
border-radius: 99px;
font-size: 14px;
font-weight: 750;
text-decoration: none;
color: color-mix(in oklch,var(--deep) 80%,var(--ink));
background: color-mix(in oklch,var(--c) 10%,oklch(1 0 0));
border: 1.5px solid color-mix(in oklch,var(--c) 35%,transparent);
isolation: isolate;
transition: border-color .3s,box-shadow .3s;
}
.ctc-08__pill::before {
content: "";
position: absolute;
z-index: -1;
left: -25%;
top: 108%;
width: 150%;
aspect-ratio: 1;
border-radius: 42% 44% 41% 43%;
background: linear-gradient(180deg,var(--c),var(--deep));
rotate: 0deg;
animation: ctc08-spin 5s linear infinite;
transition: translate .6s ease;
}
a.ctc-08__pill:hover,
a.ctc-08__pill:focus-visible {
border-color: transparent;
box-shadow: 0 10px 22px -10px var(--deep);
}
a.ctc-08__pill:hover::before,
a.ctc-08__pill:focus-visible::before {
translate: 0 -78%;
}
.ctc-08__pill span,
.ctc-08__pill {
transition: color .3s .18s,border-color .3s,box-shadow .3s;
}
a.ctc-08__pill:hover,
a.ctc-08__pill:focus-visible {
color: oklch(0.99 0.005 85);
}
a.ctc-08__pill:focus-visible {
outline: 2px solid var(--deep);
outline-offset: 3px;
}
@keyframes ctc08-spin {
to {
rotate: 360deg;
}
}
.ctc-08__pill--sweep::before {
left: auto;
right: 105%;
top: -25%;
width: 220%;
transition: translate .55s ease;
}
a.ctc-08__pill--sweep:hover::before,
a.ctc-08__pill--sweep:focus-visible::before {
translate: 78% 0;
}
.ctc-08__pill--level {
color: oklch(0.99 0.005 85);
border-color: transparent;
background: color-mix(in oklch,var(--c) 18%,oklch(0.3 0.02 60));
}
.ctc-08__pill--level::before {
translate: 0 var(--fill,-30%);
animation-duration: 7s;
}
.ctc-08__pill--level {
text-shadow: 0 1px 4px oklch(0 0 0/.3);
font-variant-numeric: tabular-nums;
}
.ctc-08__note {
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid oklch(0.6 0.16 60);
padding-left: 12px;
}
@media (prefers-reduced-motion: reduce) {
.ctc-08 * {
transition-duration: .01ms !important;
}
.ctc-08__pill::before {
animation: none;
}
}Here's a working CSS Tags & Chip from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Liquid Fill Hover Tag Component
Source: https://codefronts.com/snippets/css-tags-chips/liquid-fill/
The classic liquid-rise effect, done the honest CSS way: a huge rotating blob (a square with ~42% uneven border-radius) hides below each pill; hover translates it upward while it keeps spinning, and the uneven radius sloshes across the top edge like a real waterline. The rotation runs as an animation on the standalone rotate property while the rise is a transition on translate — the two never fight, which is the whole trick.
## HTML
```html
<section class="ctc-08" aria-label="Liquid fill hover tag demo">
<div class="ctc-08__stage">
<div class="ctc-08__wrap">
<header class="ctc-08__head">
<p class="ctc-08__kicker">Liquid fill · rotating blob + rising translate</p>
<h1>Hover to pour</h1>
</header>
<div class="ctc-08__panel">
<p class="ctc-08__lab">01 · Rise — the blob translates up while it spins</p>
<div class="ctc-08__row">
<a class="ctc-08__pill" style="--h:60" href="#ctc08">mango</a>
<a class="ctc-08__pill" style="--h:350" href="#ctc08">berry crush</a>
<a class="ctc-08__pill" style="--h:150" href="#ctc08">matcha</a>
<a class="ctc-08__pill" style="--h:250" href="#ctc08">blue lagoon</a>
<a class="ctc-08__pill" style="--h:30" href="#ctc08">cold brew</a>
</div>
</div>
<div class="ctc-08__panel">
<p class="ctc-08__lab">02 · Sweep — same blob, entering from the left</p>
<div class="ctc-08__row">
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:292" href="#ctc08">add to queue</a>
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:200" href="#ctc08">save for later</a>
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:150" href="#ctc08">follow topic</a>
</div>
</div>
<div class="ctc-08__panel">
<p class="ctc-08__lab">03 · Resting level — park the waterline as a quota chip</p>
<div class="ctc-08__row">
<span class="ctc-08__pill ctc-08__pill--level" style="--h:150;--fill:-32%">storage 32%</span>
<span class="ctc-08__pill ctc-08__pill--level" style="--h:60;--fill:-58%">credits 58%</span>
<span class="ctc-08__pill ctc-08__pill--level" style="--h:350;--fill:-86%">inbox 86%</span>
</div>
</div>
<p class="ctc-08__note">The spin is an <code>animation</code> on <code>rotate</code>; the rise is a <code>transition</code> on <code>translate</code>. Standalone transform properties compose — with classic <code>transform:</code> the hover would kill the spin.</p>
</div>
</div>
</section>
```
## CSS
```css
.ctc-08,
.ctc-08 *,
.ctc-08 *::before,
.ctc-08 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ctc-08 {
--bg: oklch(0.965 0.012 85);
--ink: oklch(0.26 0.03 60);
--mut: oklch(0.5 0.025 60);
--line: oklch(0.88 0.015 80);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.ctc-08__stage {
width: 100%;
height: 100vh;
height: 100svh;
overflow-y: auto;
display: grid;
place-items: center;
padding: clamp(16px,4cqi,48px);
background: radial-gradient(70cqi 55cqi at 90% -10%,oklch(0.9 0.06 60/.55),transparent 62%),radial-gradient(60cqi 50cqi at -5% 112%,oklch(0.88 0.06 350/.4),transparent 60%),var(--bg);
container: ctc08/inline-size;
}
.ctc-08__wrap {
width: min(700px,100%);
display: flex;
flex-direction: column;
gap: 14px;
}
.ctc-08__kicker {
font-size: 11.5px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(0.6 0.16 60);
}
.ctc-08__head h1 {
font-size: clamp(24px,3.8cqi,38px);
font-weight: 800;
letter-spacing: -.03em;
margin: 8px 0 8px;
}
.ctc-08__panel {
background: oklch(0.998 0.003 85);
border: 1px solid var(--line);
border-radius: 18px;
padding: 16px 18px;
}
.ctc-08__lab {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
color: var(--mut);
margin-bottom: 12px;
}
.ctc-08__note code,
.ctc-08__lab code {
font-family: ui-monospace,Menlo,monospace;
font-size: .95em;
letter-spacing: 0;
text-transform: none;
background: oklch(0.92 0.012 80);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.ctc-08__row {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.ctc-08__pill {
--c: oklch(0.68 0.15 var(--h,60));
--deep: oklch(0.52 0.16 var(--h,60));
position: relative;
overflow: hidden;
display: inline-flex;
align-items: center;
min-height: 46px;
padding: 0 21px;
border-radius: 99px;
font-size: 14px;
font-weight: 750;
text-decoration: none;
color: color-mix(in oklch,var(--deep) 80%,var(--ink));
background: color-mix(in oklch,var(--c) 10%,oklch(1 0 0));
border: 1.5px solid color-mix(in oklch,var(--c) 35%,transparent);
isolation: isolate;
transition: border-color .3s,box-shadow .3s;
}
.ctc-08__pill::before {
content: "";
position: absolute;
z-index: -1;
left: -25%;
top: 108%;
width: 150%;
aspect-ratio: 1;
border-radius: 42% 44% 41% 43%;
background: linear-gradient(180deg,var(--c),var(--deep));
rotate: 0deg;
animation: ctc08-spin 5s linear infinite;
transition: translate .6s ease;
}
a.ctc-08__pill:hover,
a.ctc-08__pill:focus-visible {
border-color: transparent;
box-shadow: 0 10px 22px -10px var(--deep);
}
a.ctc-08__pill:hover::before,
a.ctc-08__pill:focus-visible::before {
translate: 0 -78%;
}
.ctc-08__pill span,
.ctc-08__pill {
transition: color .3s .18s,border-color .3s,box-shadow .3s;
}
a.ctc-08__pill:hover,
a.ctc-08__pill:focus-visible {
color: oklch(0.99 0.005 85);
}
a.ctc-08__pill:focus-visible {
outline: 2px solid var(--deep);
outline-offset: 3px;
}
@keyframes ctc08-spin {
to {
rotate: 360deg;
}
}
.ctc-08__pill--sweep::before {
left: auto;
right: 105%;
top: -25%;
width: 220%;
transition: translate .55s ease;
}
a.ctc-08__pill--sweep:hover::before,
a.ctc-08__pill--sweep:focus-visible::before {
translate: 78% 0;
}
.ctc-08__pill--level {
color: oklch(0.99 0.005 85);
border-color: transparent;
background: color-mix(in oklch,var(--c) 18%,oklch(0.3 0.02 60));
}
.ctc-08__pill--level::before {
translate: 0 var(--fill,-30%);
animation-duration: 7s;
}
.ctc-08__pill--level {
text-shadow: 0 1px 4px oklch(0 0 0/.3);
font-variant-numeric: tabular-nums;
}
.ctc-08__note {
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid oklch(0.6 0.16 60);
padding-left: 12px;
}
@media (prefers-reduced-motion: reduce) {
.ctc-08 * {
transition-duration: .01ms !important;
}
.ctc-08__pill::before {
animation: none;
}
}
```Here's a working CSS Tags & Chip from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Liquid Fill Hover Tag Component
Source: https://codefronts.com/snippets/css-tags-chips/liquid-fill/
The classic liquid-rise effect, done the honest CSS way: a huge rotating blob (a square with ~42% uneven border-radius) hides below each pill; hover translates it upward while it keeps spinning, and the uneven radius sloshes across the top edge like a real waterline. The rotation runs as an animation on the standalone rotate property while the rise is a transition on translate — the two never fight, which is the whole trick.
## HTML
```html
<section class="ctc-08" aria-label="Liquid fill hover tag demo">
<div class="ctc-08__stage">
<div class="ctc-08__wrap">
<header class="ctc-08__head">
<p class="ctc-08__kicker">Liquid fill · rotating blob + rising translate</p>
<h1>Hover to pour</h1>
</header>
<div class="ctc-08__panel">
<p class="ctc-08__lab">01 · Rise — the blob translates up while it spins</p>
<div class="ctc-08__row">
<a class="ctc-08__pill" style="--h:60" href="#ctc08">mango</a>
<a class="ctc-08__pill" style="--h:350" href="#ctc08">berry crush</a>
<a class="ctc-08__pill" style="--h:150" href="#ctc08">matcha</a>
<a class="ctc-08__pill" style="--h:250" href="#ctc08">blue lagoon</a>
<a class="ctc-08__pill" style="--h:30" href="#ctc08">cold brew</a>
</div>
</div>
<div class="ctc-08__panel">
<p class="ctc-08__lab">02 · Sweep — same blob, entering from the left</p>
<div class="ctc-08__row">
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:292" href="#ctc08">add to queue</a>
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:200" href="#ctc08">save for later</a>
<a class="ctc-08__pill ctc-08__pill--sweep" style="--h:150" href="#ctc08">follow topic</a>
</div>
</div>
<div class="ctc-08__panel">
<p class="ctc-08__lab">03 · Resting level — park the waterline as a quota chip</p>
<div class="ctc-08__row">
<span class="ctc-08__pill ctc-08__pill--level" style="--h:150;--fill:-32%">storage 32%</span>
<span class="ctc-08__pill ctc-08__pill--level" style="--h:60;--fill:-58%">credits 58%</span>
<span class="ctc-08__pill ctc-08__pill--level" style="--h:350;--fill:-86%">inbox 86%</span>
</div>
</div>
<p class="ctc-08__note">The spin is an <code>animation</code> on <code>rotate</code>; the rise is a <code>transition</code> on <code>translate</code>. Standalone transform properties compose — with classic <code>transform:</code> the hover would kill the spin.</p>
</div>
</div>
</section>
```
## CSS
```css
.ctc-08,
.ctc-08 *,
.ctc-08 *::before,
.ctc-08 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ctc-08 {
--bg: oklch(0.965 0.012 85);
--ink: oklch(0.26 0.03 60);
--mut: oklch(0.5 0.025 60);
--line: oklch(0.88 0.015 80);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.ctc-08__stage {
width: 100%;
height: 100vh;
height: 100svh;
overflow-y: auto;
display: grid;
place-items: center;
padding: clamp(16px,4cqi,48px);
background: radial-gradient(70cqi 55cqi at 90% -10%,oklch(0.9 0.06 60/.55),transparent 62%),radial-gradient(60cqi 50cqi at -5% 112%,oklch(0.88 0.06 350/.4),transparent 60%),var(--bg);
container: ctc08/inline-size;
}
.ctc-08__wrap {
width: min(700px,100%);
display: flex;
flex-direction: column;
gap: 14px;
}
.ctc-08__kicker {
font-size: 11.5px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(0.6 0.16 60);
}
.ctc-08__head h1 {
font-size: clamp(24px,3.8cqi,38px);
font-weight: 800;
letter-spacing: -.03em;
margin: 8px 0 8px;
}
.ctc-08__panel {
background: oklch(0.998 0.003 85);
border: 1px solid var(--line);
border-radius: 18px;
padding: 16px 18px;
}
.ctc-08__lab {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
color: var(--mut);
margin-bottom: 12px;
}
.ctc-08__note code,
.ctc-08__lab code {
font-family: ui-monospace,Menlo,monospace;
font-size: .95em;
letter-spacing: 0;
text-transform: none;
background: oklch(0.92 0.012 80);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.ctc-08__row {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.ctc-08__pill {
--c: oklch(0.68 0.15 var(--h,60));
--deep: oklch(0.52 0.16 var(--h,60));
position: relative;
overflow: hidden;
display: inline-flex;
align-items: center;
min-height: 46px;
padding: 0 21px;
border-radius: 99px;
font-size: 14px;
font-weight: 750;
text-decoration: none;
color: color-mix(in oklch,var(--deep) 80%,var(--ink));
background: color-mix(in oklch,var(--c) 10%,oklch(1 0 0));
border: 1.5px solid color-mix(in oklch,var(--c) 35%,transparent);
isolation: isolate;
transition: border-color .3s,box-shadow .3s;
}
.ctc-08__pill::before {
content: "";
position: absolute;
z-index: -1;
left: -25%;
top: 108%;
width: 150%;
aspect-ratio: 1;
border-radius: 42% 44% 41% 43%;
background: linear-gradient(180deg,var(--c),var(--deep));
rotate: 0deg;
animation: ctc08-spin 5s linear infinite;
transition: translate .6s ease;
}
a.ctc-08__pill:hover,
a.ctc-08__pill:focus-visible {
border-color: transparent;
box-shadow: 0 10px 22px -10px var(--deep);
}
a.ctc-08__pill:hover::before,
a.ctc-08__pill:focus-visible::before {
translate: 0 -78%;
}
.ctc-08__pill span,
.ctc-08__pill {
transition: color .3s .18s,border-color .3s,box-shadow .3s;
}
a.ctc-08__pill:hover,
a.ctc-08__pill:focus-visible {
color: oklch(0.99 0.005 85);
}
a.ctc-08__pill:focus-visible {
outline: 2px solid var(--deep);
outline-offset: 3px;
}
@keyframes ctc08-spin {
to {
rotate: 360deg;
}
}
.ctc-08__pill--sweep::before {
left: auto;
right: 105%;
top: -25%;
width: 220%;
transition: translate .55s ease;
}
a.ctc-08__pill--sweep:hover::before,
a.ctc-08__pill--sweep:focus-visible::before {
translate: 78% 0;
}
.ctc-08__pill--level {
color: oklch(0.99 0.005 85);
border-color: transparent;
background: color-mix(in oklch,var(--c) 18%,oklch(0.3 0.02 60));
}
.ctc-08__pill--level::before {
translate: 0 var(--fill,-30%);
animation-duration: 7s;
}
.ctc-08__pill--level {
text-shadow: 0 1px 4px oklch(0 0 0/.3);
font-variant-numeric: tabular-nums;
}
.ctc-08__note {
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid oklch(0.6 0.16 60);
padding-left: 12px;
}
@media (prefers-reduced-motion: reduce) {
.ctc-08 * {
transition-duration: .01ms !important;
}
.ctc-08__pill::before {
animation: none;
}
}
```How this works
One pseudo-element does everything. It is a 150%-wide square parked below the pill's clip area, with deliberately uneven corner radii — that asymmetry is what makes the top edge read as a wave when it spins:
.pill::before{ content:""; position:absolute; left:-25%;
width:150%; aspect-ratio:1; top:110%;
border-radius:42% 44% 41% 43%;
background:var(--c);
rotate:0deg; animation:spin 5s linear infinite; /* keeps spinning */
transition:translate .6s ease; } /* rises on hover */
.pill:hover::before{ translate:0 -78% }The critical detail is using the individual transform properties: rotate loops forever in a keyframe while translate transitions on hover. With classic transform: both would write to the same property and the hover would kill the spin — with standalone properties they compose. overflow:hidden on the pill crops the blob into a waterline.
Text stays legible with a color swap that is slower and delayed relative to the rise (transition:color .3s .18s), so the label flips just as the liquid passes it — that timing detail sells the physicality. The sweep variant is the same blob entering from the left (translate:-78% 0), and the "fill level" variant parks the blob permanently at a partial height as a quota indicator, waves still lapping.
Make it yours
- Liquid color: each pill's
--hhue drives blob, border and final text contrast via color-mix — one number per flavor. - Viscosity: the spin duration (5s) plus the rise duration (.6s). Slow both ~1.5× for honey; speed the spin to 3s for fizzy soda.
- Wave amplitude: how uneven the four radii are —
42%/44%/41%/43%is gentle; push to38%/47%/40%/45%for choppier water. - Resting fill: the quota variant sets
--fill:-30%inline; wire it to a real percentage from your backend for storage/usage chips.
Gotchas — read before shipping
- overflow:hidden on the pill is load-bearing — without it you have a spinning square, not a liquid.
- Keep the blob ≥150% of the pill's width; smaller and its corners peek in from the sides mid-spin, breaking the waterline illusion.
- The delayed text-color transition must be shorter than the rise or the label goes illegible mid-fill on dark liquids — test with your slowest easing.
- This composites two transforms per pill — cheap — but blur or box-shadow on the blob forces repaints per frame. Glow the pill, never the blob.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 113+ | 111+ |
Standalone rotate/translate properties are universal since 2022 (Chrome 104, Safari 14.1, Firefox 72). aspect-ratio likewise. The floor is oklch()/color-mix() theming; with hex tokens this runs years further back.