24 CSS Tags & Chips06 / 24

CSS onlyMIT licensed

Interactive Hover Scale Pill Tag

Hover feel is a spec, not an accident. This demo ships three tuned scale recipes side by side — Subtle (1.03, product UIs), Springy (1.07 on an overshooting cubic-bezier, marketing pages) and Playful (scale + tilt, editorial) — plus a sheen-sweep and a shadow-lift layer you can stack on any of them. Every pill also presses down to .96 on :active, fires identically on :focus-visible, and stays put under prefers-reduced-motion.

Published Updated

Live Demo
Try it

The code

<section class="ctc-06" aria-label="Hover scale pill tag demo">
  <div class="ctc-06__stage">
    <div class="ctc-06__wrap">
      <header class="ctc-06__head">
        <p class="ctc-06__kicker">Hover scale · three tuned recipes</p>
        <h1>The bounce is a bezier, not a library</h1>
      </header>
      <div class="ctc-06__panel">
        <p class="ctc-06__lab">01 · Subtle — <code>scale:1.03, ease</code> — for product UI</p>
        <div class="ctc-06__row">
          <a class="ctc-06__pill" style="--h:255" href="#ctc06">typescript</a>
          <a class="ctc-06__pill" style="--h:150" href="#ctc06">node</a>
          <a class="ctc-06__pill" style="--h:40" href="#ctc06">rust</a>
          <a class="ctc-06__pill" style="--h:320" href="#ctc06">swift</a>
          <a class="ctc-06__pill" style="--h:200" href="#ctc06">go</a>
        </div>
      </div>
      <div class="ctc-06__panel">
        <p class="ctc-06__lab">02 · Springy — <code>scale:1.07, cubic-bezier(.34,1.56,.64,1)</code> — the overshoot IS the spring</p>
        <div class="ctc-06__row">
          <a class="ctc-06__pill ctc-06__pill--spring" style="--h:255" href="#ctc06">design tokens</a>
          <a class="ctc-06__pill ctc-06__pill--spring" style="--h:150" href="#ctc06">motion</a>
          <a class="ctc-06__pill ctc-06__pill--spring" style="--h:40" href="#ctc06">micro-interactions</a>
          <a class="ctc-06__pill ctc-06__pill--spring" style="--h:320" href="#ctc06">easing</a>
        </div>
      </div>
      <div class="ctc-06__panel">
        <p class="ctc-06__lab">03 · Playful — scale + <code>rotate:-2deg</code> + sheen sweep + color-matched lift</p>
        <div class="ctc-06__row">
          <a class="ctc-06__pill ctc-06__pill--play" style="--h:255" href="#ctc06">weekend reads ✺</a>
          <a class="ctc-06__pill ctc-06__pill--play" style="--h:150" href="#ctc06">fresh drops</a>
          <a class="ctc-06__pill ctc-06__pill--play" style="--h:40" href="#ctc06">hot right now</a>
          <a class="ctc-06__pill ctc-06__pill--play" style="--h:320" href="#ctc06">staff picks</a>
        </div>
      </div>
      <p class="ctc-06__note">Press any pill — <code>:active</code> drops to <code>scale:.96</code> in 80ms while the release keeps the slow spring. Keyboard focus fires the exact same motion via <code>:focus-visible</code>.</p>
    </div>
  </div>
</section>
.ctc-06,
.ctc-06 *,
.ctc-06 *::before,
.ctc-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.ctc-06 {
  --bg: oklch(0.975 0.008 85);
  --ink: oklch(0.25 0.02 60);
  --mut: oklch(0.5 0.02 60);
  --line: oklch(0.89 0.012 80);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--ink);
  container-type: inline-size;
}

.ctc-06__stage {
  width: 100%;
  height: 100vh;
  height: 100svh;
  overflow-y: auto;
  display: grid;
  place-items: center;
  padding: clamp(16px,4cqi,48px);
  background: radial-gradient(65cqi 50cqi at 10% -10%,oklch(0.92 0.05 40/.6),transparent 62%),radial-gradient(60cqi 50cqi at 105% 112%,oklch(0.9 0.05 255/.5),transparent 60%),var(--bg);
  container: ctc06/inline-size;
}

.ctc-06__wrap {
  width: min(720px,100%);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.ctc-06__kicker {
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: oklch(0.58 0.15 40);
}

.ctc-06__head h1 {
  font-size: clamp(24px,3.8cqi,38px);
  font-weight: 800;
  letter-spacing: -.03em;
  margin: 8px 0 8px;
}

.ctc-06__panel {
  background: oklch(0.998 0.002 85);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 16px 18px;
}

.ctc-06__lab {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--mut);
  margin-bottom: 12px;
}

.ctc-06__lab code,
.ctc-06__note code {
  font-family: ui-monospace,Menlo,monospace;
  font-size: .95em;
  letter-spacing: 0;
  text-transform: none;
  background: oklch(0.93 0.01 80);
  padding: 1px 5px;
  border-radius: 4px;
  color: var(--ink);
}

.ctc-06__row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.ctc-06__pill {
  --c: oklch(0.6 0.14 var(--h,255));
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 19px;
  border-radius: 99px;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  color: color-mix(in oklch,var(--c) 70%,var(--ink));
  background: color-mix(in oklch,var(--c) 12%,oklch(1 0 0));
  border: 1px solid color-mix(in oklch,var(--c) 26%,transparent);
  transition: scale .25s ease,translate .25s,box-shadow .25s,rotate .25s;
}

.ctc-06__pill:hover,
.ctc-06__pill:focus-visible {
  scale: 1.03;
  z-index: 1;
}

.ctc-06__pill:active {
  scale: .96;
  transition-duration: .08s;
}

.ctc-06__pill:focus-visible {
  outline: 2px solid var(--c);
  outline-offset: 2px;
}

.ctc-06__pill--spring {
  transition: scale .3s cubic-bezier(.34,1.56,.64,1),box-shadow .25s,rotate .3s cubic-bezier(.34,1.56,.64,1);
}

.ctc-06__pill--spring:hover,
.ctc-06__pill--spring:focus-visible {
  scale: 1.07;
  box-shadow: 0 10px 22px -10px var(--c);
}

.ctc-06__pill--play {
  transition: scale .3s cubic-bezier(.34,1.7,.64,1),rotate .3s cubic-bezier(.34,1.7,.64,1),box-shadow .3s;
}

.ctc-06__pill--play::before {
  content: "";
  position: absolute;
  inset: -30% auto;
  width: 34%;
  translate: -160% 0;
  rotate: 18deg;
  background: linear-gradient(90deg,transparent,oklch(1 0 0/.75),transparent);
  transition: translate .55s ease .05s;
}

.ctc-06__pill--play:hover,
.ctc-06__pill--play:focus-visible {
  scale: 1.07;
  rotate: -2deg;
  box-shadow: 0 14px 26px -12px var(--c);
}

.ctc-06__pill--play:hover::before,
.ctc-06__pill--play:focus-visible::before {
  translate: 400% 0;
}

.ctc-06__note {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--mut);
  border-left: 3px solid oklch(0.58 0.15 40);
  padding-left: 12px;
}

@media (prefers-reduced-motion: reduce) {
  .ctc-06 * {
    transition-duration: .01ms !important;
  }

  .ctc-06__pill:hover,
    .ctc-06__pill:focus-visible {
    scale: 1;
    rotate: 0deg;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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: Interactive Hover Scale Pill Tag
Source: https://codefronts.com/snippets/css-tags-chips/magnetic-hover/

Hover feel is a spec, not an accident. This demo ships three tuned scale recipes side by side — Subtle (1.03, product UIs), Springy (1.07 on an overshooting cubic-bezier, marketing pages) and Playful (scale + tilt, editorial) — plus a sheen-sweep and a shadow-lift layer you can stack on any of them. Every pill also presses down to .96 on :active, fires identically on :focus-visible, and stays put under prefers-reduced-motion.
## HTML
```html
<section class="ctc-06" aria-label="Hover scale pill tag demo">
  <div class="ctc-06__stage">
    <div class="ctc-06__wrap">
      <header class="ctc-06__head">
        <p class="ctc-06__kicker">Hover scale · three tuned recipes</p>
        <h1>The bounce is a bezier, not a library</h1>
      </header>
      <div class="ctc-06__panel">
        <p class="ctc-06__lab">01 · Subtle — <code>scale:1.03, ease</code> — for product UI</p>
        <div class="ctc-06__row">
          <a class="ctc-06__pill" style="--h:255" href="#ctc06">typescript</a>
          <a class="ctc-06__pill" style="--h:150" href="#ctc06">node</a>
          <a class="ctc-06__pill" style="--h:40" href="#ctc06">rust</a>
          <a class="ctc-06__pill" style="--h:320" href="#ctc06">swift</a>
          <a class="ctc-06__pill" style="--h:200" href="#ctc06">go</a>
        </div>
      </div>
      <div class="ctc-06__panel">
        <p class="ctc-06__lab">02 · Springy — <code>scale:1.07, cubic-bezier(.34,1.56,.64,1)</code> — the overshoot IS the spring</p>
        <div class="ctc-06__row">
          <a class="ctc-06__pill ctc-06__pill--spring" style="--h:255" href="#ctc06">design tokens</a>
          <a class="ctc-06__pill ctc-06__pill--spring" style="--h:150" href="#ctc06">motion</a>
          <a class="ctc-06__pill ctc-06__pill--spring" style="--h:40" href="#ctc06">micro-interactions</a>
          <a class="ctc-06__pill ctc-06__pill--spring" style="--h:320" href="#ctc06">easing</a>
        </div>
      </div>
      <div class="ctc-06__panel">
        <p class="ctc-06__lab">03 · Playful — scale + <code>rotate:-2deg</code> + sheen sweep + color-matched lift</p>
        <div class="ctc-06__row">
          <a class="ctc-06__pill ctc-06__pill--play" style="--h:255" href="#ctc06">weekend reads ✺</a>
          <a class="ctc-06__pill ctc-06__pill--play" style="--h:150" href="#ctc06">fresh drops</a>
          <a class="ctc-06__pill ctc-06__pill--play" style="--h:40" href="#ctc06">hot right now</a>
          <a class="ctc-06__pill ctc-06__pill--play" style="--h:320" href="#ctc06">staff picks</a>
        </div>
      </div>
      <p class="ctc-06__note">Press any pill — <code>:active</code> drops to <code>scale:.96</code> in 80ms while the release keeps the slow spring. Keyboard focus fires the exact same motion via <code>:focus-visible</code>.</p>
    </div>
  </div>
</section>
```
## CSS
```css
.ctc-06,
.ctc-06 *,
.ctc-06 *::before,
.ctc-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.ctc-06 {
  --bg: oklch(0.975 0.008 85);
  --ink: oklch(0.25 0.02 60);
  --mut: oklch(0.5 0.02 60);
  --line: oklch(0.89 0.012 80);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--ink);
  container-type: inline-size;
}

.ctc-06__stage {
  width: 100%;
  height: 100vh;
  height: 100svh;
  overflow-y: auto;
  display: grid;
  place-items: center;
  padding: clamp(16px,4cqi,48px);
  background: radial-gradient(65cqi 50cqi at 10% -10%,oklch(0.92 0.05 40/.6),transparent 62%),radial-gradient(60cqi 50cqi at 105% 112%,oklch(0.9 0.05 255/.5),transparent 60%),var(--bg);
  container: ctc06/inline-size;
}

.ctc-06__wrap {
  width: min(720px,100%);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.ctc-06__kicker {
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: oklch(0.58 0.15 40);
}

.ctc-06__head h1 {
  font-size: clamp(24px,3.8cqi,38px);
  font-weight: 800;
  letter-spacing: -.03em;
  margin: 8px 0 8px;
}

.ctc-06__panel {
  background: oklch(0.998 0.002 85);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 16px 18px;
}

.ctc-06__lab {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--mut);
  margin-bottom: 12px;
}

.ctc-06__lab code,
.ctc-06__note code {
  font-family: ui-monospace,Menlo,monospace;
  font-size: .95em;
  letter-spacing: 0;
  text-transform: none;
  background: oklch(0.93 0.01 80);
  padding: 1px 5px;
  border-radius: 4px;
  color: var(--ink);
}

.ctc-06__row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.ctc-06__pill {
  --c: oklch(0.6 0.14 var(--h,255));
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 19px;
  border-radius: 99px;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  color: color-mix(in oklch,var(--c) 70%,var(--ink));
  background: color-mix(in oklch,var(--c) 12%,oklch(1 0 0));
  border: 1px solid color-mix(in oklch,var(--c) 26%,transparent);
  transition: scale .25s ease,translate .25s,box-shadow .25s,rotate .25s;
}

.ctc-06__pill:hover,
.ctc-06__pill:focus-visible {
  scale: 1.03;
  z-index: 1;
}

.ctc-06__pill:active {
  scale: .96;
  transition-duration: .08s;
}

.ctc-06__pill:focus-visible {
  outline: 2px solid var(--c);
  outline-offset: 2px;
}

.ctc-06__pill--spring {
  transition: scale .3s cubic-bezier(.34,1.56,.64,1),box-shadow .25s,rotate .3s cubic-bezier(.34,1.56,.64,1);
}

.ctc-06__pill--spring:hover,
.ctc-06__pill--spring:focus-visible {
  scale: 1.07;
  box-shadow: 0 10px 22px -10px var(--c);
}

.ctc-06__pill--play {
  transition: scale .3s cubic-bezier(.34,1.7,.64,1),rotate .3s cubic-bezier(.34,1.7,.64,1),box-shadow .3s;
}

.ctc-06__pill--play::before {
  content: "";
  position: absolute;
  inset: -30% auto;
  width: 34%;
  translate: -160% 0;
  rotate: 18deg;
  background: linear-gradient(90deg,transparent,oklch(1 0 0/.75),transparent);
  transition: translate .55s ease .05s;
}

.ctc-06__pill--play:hover,
.ctc-06__pill--play:focus-visible {
  scale: 1.07;
  rotate: -2deg;
  box-shadow: 0 14px 26px -12px var(--c);
}

.ctc-06__pill--play:hover::before,
.ctc-06__pill--play:focus-visible::before {
  translate: 400% 0;
}

.ctc-06__note {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--mut);
  border-left: 3px solid oklch(0.58 0.15 40);
  padding-left: 12px;
}

@media (prefers-reduced-motion: reduce) {
  .ctc-06 * {
    transition-duration: .01ms !important;
  }

  .ctc-06__pill:hover,
    .ctc-06__pill:focus-visible {
    scale: 1;
    rotate: 0deg;
  }
}
```

How this works

All three recipes are the same two lines with different numbers — the feel lives entirely in the easing curve:

.pill{ transition: scale .25s cubic-bezier(.34,1.56,.64,1),
                   translate .25s, box-shadow .25s }
.pill:hover, .pill:focus-visible{ scale:1.07 }
.pill:active{ scale:.96; transition-duration:.08s }

The magic number is 1.56 — any y-value above 1 in the second control point makes the curve overshoot its target and settle back, which reads as springiness without a keyframe animation. Subtle uses plain ease at 1.03 (no overshoot, no bounce — calm), Playful adds rotate:-2deg so the pill cocks like a sticker. The press state shortens transition-duration to 80ms because a press must feel instant while the release stays soft.

The two stackable layers are pseudo-element work: the sheen is a skewed white gradient strip on ::before that translates from -130% to 230% on hover (clipped by overflow:hidden), and the lift is a growing color-mix shadow keyed to each pill's own --c, so the glow always matches the pill. Both animate only transform and shadow — compositor work, no layout, 60fps on a wall of fifty tags.

Make it yours

  • Spring stiffness: raise 1.56 toward 2 for cartoonish bounce, drop to 1.2 for barely-there overshoot.
  • Scale ceiling: 1.03–1.08 is the shippable band. Above 1.1, neighboring text visibly reflows around the pill and it starts occluding its row.
  • Sheen speed: the .55s translate on ::before — slow it to .8s for luxury-brand feel; add transition-delay:.05s so it trails the scale.
  • Per-pill color: each pill sets --h; the tint, text and glow derive. Point them at your brand hues and the whole wall re-themes.

Gotchas — read before shipping

  • Scale the pill, never its font-size — font-size transitions trigger reflow of the whole line and stutter on low-end devices.
  • A scaled pill overlaps neighbors: if pills sit flush in a dense toolbar, add z-index:1 on hover or the sheen clips under the next pill.
  • Do not put will-change on fifty pills — it promotes fifty layers and hurts. Modern browsers promote on transition automatically.
  • Remember :active AND :focus-visible: hover-only feedback makes keyboard and touch users second-class. Every recipe here wires all three.

Browser support

ChromeSafariFirefoxEdge
111+16.4+113+111+

The individual transform properties (scale/rotate/translate as standalone CSS properties) are universal since 2022; the floor is oklch()/color-mix() tokens. Swap tokens to hex and this runs on anything from 2020.

Search CodeFronts

Loading…