24 CSS Tags & Chips07 / 24
Animated Border Outline Tag Effect
Borders that draw themselves. Hover any tag and a conic-gradient ring sweeps 0°→360° around it like a pen tracing the outline — a real transition on a registered @property angle, not a keyframe hack. A second variant keeps a comet segment orbiting forever, and a third fills the ring as a progress indicator. The ring itself is carved with the mask-composite trick, so the center stays perfectly transparent over any background.
Published Updated
The code
<section class="ctc-07" aria-label="Animated border outline tag demo">
<div class="ctc-07__stage">
<div class="ctc-07__wrap">
<header class="ctc-07__head">
<p class="ctc-07__kicker">Border draw · @property + conic + mask</p>
<h1>Hover. Watch the ink go around.</h1>
</header>
<div class="ctc-07__panel">
<p class="ctc-07__lab">01 · Draw on hover — <code>transition: --angle .7s</code></p>
<div class="ctc-07__row">
<a class="ctc-07__tag" style="--h:200" href="#ctc07">css</a>
<a class="ctc-07__tag" style="--h:292" href="#ctc07">houdini</a>
<a class="ctc-07__tag" style="--h:150" href="#ctc07">@property</a>
<a class="ctc-07__tag" style="--h:40" href="#ctc07">conic-gradient</a>
<a class="ctc-07__tag" style="--h:330" href="#ctc07">mask-composite</a>
</div>
</div>
<div class="ctc-07__panel">
<p class="ctc-07__lab">02 · Orbiting comet — keyframes on the gradient's <code>from</code> angle</p>
<div class="ctc-07__row">
<span class="ctc-07__tag ctc-07__tag--orbit" style="--h:200">scanning…</span>
<span class="ctc-07__tag ctc-07__tag--orbit" style="--h:292">syncing</span>
<span class="ctc-07__tag ctc-07__tag--orbit ctc-07__tag--slow" style="--h:150">background job</span>
</div>
</div>
<div class="ctc-07__panel">
<p class="ctc-07__lab">03 · Determinate ring — same CSS, angle set inline as progress</p>
<div class="ctc-07__row">
<span class="ctc-07__tag ctc-07__tag--set" style="--h:150;--ctc07a:90deg">25% uploaded</span>
<span class="ctc-07__tag ctc-07__tag--set" style="--h:200;--ctc07a:216deg">60% uploaded</span>
<span class="ctc-07__tag ctc-07__tag--set" style="--h:292;--ctc07a:342deg">95% uploaded</span>
</div>
</div>
<p class="ctc-07__note">The ring is a pseudo-element hollowed by <code>mask-composite:exclude</code> — the center stays truly transparent, so this works over photos and gradients, not just flat fills.</p>
</div>
</div>
</section><section class="ctc-07" aria-label="Animated border outline tag demo">
<div class="ctc-07__stage">
<div class="ctc-07__wrap">
<header class="ctc-07__head">
<p class="ctc-07__kicker">Border draw · @property + conic + mask</p>
<h1>Hover. Watch the ink go around.</h1>
</header>
<div class="ctc-07__panel">
<p class="ctc-07__lab">01 · Draw on hover — <code>transition: --angle .7s</code></p>
<div class="ctc-07__row">
<a class="ctc-07__tag" style="--h:200" href="#ctc07">css</a>
<a class="ctc-07__tag" style="--h:292" href="#ctc07">houdini</a>
<a class="ctc-07__tag" style="--h:150" href="#ctc07">@property</a>
<a class="ctc-07__tag" style="--h:40" href="#ctc07">conic-gradient</a>
<a class="ctc-07__tag" style="--h:330" href="#ctc07">mask-composite</a>
</div>
</div>
<div class="ctc-07__panel">
<p class="ctc-07__lab">02 · Orbiting comet — keyframes on the gradient's <code>from</code> angle</p>
<div class="ctc-07__row">
<span class="ctc-07__tag ctc-07__tag--orbit" style="--h:200">scanning…</span>
<span class="ctc-07__tag ctc-07__tag--orbit" style="--h:292">syncing</span>
<span class="ctc-07__tag ctc-07__tag--orbit ctc-07__tag--slow" style="--h:150">background job</span>
</div>
</div>
<div class="ctc-07__panel">
<p class="ctc-07__lab">03 · Determinate ring — same CSS, angle set inline as progress</p>
<div class="ctc-07__row">
<span class="ctc-07__tag ctc-07__tag--set" style="--h:150;--ctc07a:90deg">25% uploaded</span>
<span class="ctc-07__tag ctc-07__tag--set" style="--h:200;--ctc07a:216deg">60% uploaded</span>
<span class="ctc-07__tag ctc-07__tag--set" style="--h:292;--ctc07a:342deg">95% uploaded</span>
</div>
</div>
<p class="ctc-07__note">The ring is a pseudo-element hollowed by <code>mask-composite:exclude</code> — the center stays truly transparent, so this works over photos and gradients, not just flat fills.</p>
</div>
</div>
</section>@property --ctc07a {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
@property --ctc07f {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.ctc-07,
.ctc-07 *,
.ctc-07 *::before,
.ctc-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ctc-07 {
--bg: oklch(0.19 0.02 265);
--ink: oklch(0.95 0.008 265);
--mut: oklch(0.66 0.02 265);
--line: oklch(0.32 0.025 265);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.ctc-07__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 85% -10%,oklch(0.32 0.08 292/.5),transparent 62%),radial-gradient(60cqi 50cqi at -5% 110%,oklch(0.3 0.07 200/.5),transparent 60%),var(--bg);
container: ctc07/inline-size;
}
.ctc-07__wrap {
width: min(700px,100%);
display: flex;
flex-direction: column;
gap: 14px;
}
.ctc-07__kicker {
font-size: 11.5px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(0.75 0.12 200);
}
.ctc-07__head h1 {
font-size: clamp(24px,3.8cqi,38px);
font-weight: 800;
letter-spacing: -.03em;
margin: 8px 0 8px;
}
.ctc-07__panel {
background: oklch(1 0 0/.045);
border: 1px solid var(--line);
border-radius: 18px;
padding: 16px 18px;
}
.ctc-07__lab {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
color: var(--mut);
margin-bottom: 12px;
}
.ctc-07__lab code,
.ctc-07__note code {
font-family: ui-monospace,Menlo,monospace;
font-size: .95em;
letter-spacing: 0;
text-transform: none;
background: oklch(1 0 0/.09);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.ctc-07__row {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.ctc-07__tag {
--c: oklch(0.78 0.14 var(--h,200));
position: relative;
display: inline-flex;
align-items: center;
min-height: 44px;
padding: 0 20px;
border-radius: 99px;
font-family: ui-monospace,Menlo,monospace;
font-size: 13.5px;
font-weight: 600;
text-decoration: none;
color: var(--ink);
background: oklch(1 0 0/.05);
box-shadow: inset 0 0 0 1px var(--line);
transition: color .25s,box-shadow .25s;
}
.ctc-07__tag::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background: conic-gradient(from calc(var(--ctc07f) - 90deg),var(--c) var(--ctc07a),transparent 0);
-webkit-mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
transition: --ctc07a .7s ease;
}
a.ctc-07__tag:hover,
a.ctc-07__tag:focus-visible {
--ctc07a: 360deg;
color: var(--c);
box-shadow: inset 0 0 0 1px transparent;
}
a.ctc-07__tag:hover::before,
a.ctc-07__tag:focus-visible::before {
--ctc07a: 360deg;
}
a.ctc-07__tag:focus-visible {
outline: 2px solid var(--c);
outline-offset: 3px;
}
.ctc-07__tag--orbit {
color: var(--mut);
}
.ctc-07__tag--orbit::before {
background: conic-gradient(from var(--ctc07f),transparent 0 82%,var(--c) 96%,oklch(0.98 0.01 var(--h)) 100%);
animation: ctc07-orbit 2.6s linear infinite;
}
.ctc-07__tag--slow::before {
animation-duration: 5s;
}
@keyframes ctc07-orbit {
to {
--ctc07f: 360deg;
}
}
.ctc-07__tag--set {
color: var(--c);
}
.ctc-07__tag--set::before {
transition: none;
}
.ctc-07__note {
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid oklch(0.75 0.12 200);
padding-left: 12px;
}
@media (prefers-reduced-motion: reduce) {
.ctc-07 * {
transition-duration: .01ms !important;
}
.ctc-07__tag--orbit::before {
animation: none;
background: conic-gradient(var(--c) 30deg,transparent 0);
}
}@property --ctc07a {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
@property --ctc07f {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.ctc-07,
.ctc-07 *,
.ctc-07 *::before,
.ctc-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ctc-07 {
--bg: oklch(0.19 0.02 265);
--ink: oklch(0.95 0.008 265);
--mut: oklch(0.66 0.02 265);
--line: oklch(0.32 0.025 265);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.ctc-07__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 85% -10%,oklch(0.32 0.08 292/.5),transparent 62%),radial-gradient(60cqi 50cqi at -5% 110%,oklch(0.3 0.07 200/.5),transparent 60%),var(--bg);
container: ctc07/inline-size;
}
.ctc-07__wrap {
width: min(700px,100%);
display: flex;
flex-direction: column;
gap: 14px;
}
.ctc-07__kicker {
font-size: 11.5px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(0.75 0.12 200);
}
.ctc-07__head h1 {
font-size: clamp(24px,3.8cqi,38px);
font-weight: 800;
letter-spacing: -.03em;
margin: 8px 0 8px;
}
.ctc-07__panel {
background: oklch(1 0 0/.045);
border: 1px solid var(--line);
border-radius: 18px;
padding: 16px 18px;
}
.ctc-07__lab {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
color: var(--mut);
margin-bottom: 12px;
}
.ctc-07__lab code,
.ctc-07__note code {
font-family: ui-monospace,Menlo,monospace;
font-size: .95em;
letter-spacing: 0;
text-transform: none;
background: oklch(1 0 0/.09);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.ctc-07__row {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.ctc-07__tag {
--c: oklch(0.78 0.14 var(--h,200));
position: relative;
display: inline-flex;
align-items: center;
min-height: 44px;
padding: 0 20px;
border-radius: 99px;
font-family: ui-monospace,Menlo,monospace;
font-size: 13.5px;
font-weight: 600;
text-decoration: none;
color: var(--ink);
background: oklch(1 0 0/.05);
box-shadow: inset 0 0 0 1px var(--line);
transition: color .25s,box-shadow .25s;
}
.ctc-07__tag::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background: conic-gradient(from calc(var(--ctc07f) - 90deg),var(--c) var(--ctc07a),transparent 0);
-webkit-mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
transition: --ctc07a .7s ease;
}
a.ctc-07__tag:hover,
a.ctc-07__tag:focus-visible {
--ctc07a: 360deg;
color: var(--c);
box-shadow: inset 0 0 0 1px transparent;
}
a.ctc-07__tag:hover::before,
a.ctc-07__tag:focus-visible::before {
--ctc07a: 360deg;
}
a.ctc-07__tag:focus-visible {
outline: 2px solid var(--c);
outline-offset: 3px;
}
.ctc-07__tag--orbit {
color: var(--mut);
}
.ctc-07__tag--orbit::before {
background: conic-gradient(from var(--ctc07f),transparent 0 82%,var(--c) 96%,oklch(0.98 0.01 var(--h)) 100%);
animation: ctc07-orbit 2.6s linear infinite;
}
.ctc-07__tag--slow::before {
animation-duration: 5s;
}
@keyframes ctc07-orbit {
to {
--ctc07f: 360deg;
}
}
.ctc-07__tag--set {
color: var(--c);
}
.ctc-07__tag--set::before {
transition: none;
}
.ctc-07__note {
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid oklch(0.75 0.12 200);
padding-left: 12px;
}
@media (prefers-reduced-motion: reduce) {
.ctc-07 * {
transition-duration: .01ms !important;
}
.ctc-07__tag--orbit::before {
animation: none;
background: conic-gradient(var(--c) 30deg,transparent 0);
}
}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: Animated Border Outline Tag Effect
Source: https://codefronts.com/snippets/css-tags-chips/sliding-border/
Borders that draw themselves. Hover any tag and a conic-gradient ring sweeps 0°→360° around it like a pen tracing the outline — a real transition on a registered @property angle, not a keyframe hack. A second variant keeps a comet segment orbiting forever, and a third fills the ring as a progress indicator. The ring itself is carved with the mask-composite trick, so the center stays perfectly transparent over any background.
## HTML
```html
<section class="ctc-07" aria-label="Animated border outline tag demo">
<div class="ctc-07__stage">
<div class="ctc-07__wrap">
<header class="ctc-07__head">
<p class="ctc-07__kicker">Border draw · @property + conic + mask</p>
<h1>Hover. Watch the ink go around.</h1>
</header>
<div class="ctc-07__panel">
<p class="ctc-07__lab">01 · Draw on hover — <code>transition: --angle .7s</code></p>
<div class="ctc-07__row">
<a class="ctc-07__tag" style="--h:200" href="#ctc07">css</a>
<a class="ctc-07__tag" style="--h:292" href="#ctc07">houdini</a>
<a class="ctc-07__tag" style="--h:150" href="#ctc07">@property</a>
<a class="ctc-07__tag" style="--h:40" href="#ctc07">conic-gradient</a>
<a class="ctc-07__tag" style="--h:330" href="#ctc07">mask-composite</a>
</div>
</div>
<div class="ctc-07__panel">
<p class="ctc-07__lab">02 · Orbiting comet — keyframes on the gradient's <code>from</code> angle</p>
<div class="ctc-07__row">
<span class="ctc-07__tag ctc-07__tag--orbit" style="--h:200">scanning…</span>
<span class="ctc-07__tag ctc-07__tag--orbit" style="--h:292">syncing</span>
<span class="ctc-07__tag ctc-07__tag--orbit ctc-07__tag--slow" style="--h:150">background job</span>
</div>
</div>
<div class="ctc-07__panel">
<p class="ctc-07__lab">03 · Determinate ring — same CSS, angle set inline as progress</p>
<div class="ctc-07__row">
<span class="ctc-07__tag ctc-07__tag--set" style="--h:150;--ctc07a:90deg">25% uploaded</span>
<span class="ctc-07__tag ctc-07__tag--set" style="--h:200;--ctc07a:216deg">60% uploaded</span>
<span class="ctc-07__tag ctc-07__tag--set" style="--h:292;--ctc07a:342deg">95% uploaded</span>
</div>
</div>
<p class="ctc-07__note">The ring is a pseudo-element hollowed by <code>mask-composite:exclude</code> — the center stays truly transparent, so this works over photos and gradients, not just flat fills.</p>
</div>
</div>
</section>
```
## CSS
```css
@property --ctc07a {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
@property --ctc07f {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.ctc-07,
.ctc-07 *,
.ctc-07 *::before,
.ctc-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ctc-07 {
--bg: oklch(0.19 0.02 265);
--ink: oklch(0.95 0.008 265);
--mut: oklch(0.66 0.02 265);
--line: oklch(0.32 0.025 265);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.ctc-07__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 85% -10%,oklch(0.32 0.08 292/.5),transparent 62%),radial-gradient(60cqi 50cqi at -5% 110%,oklch(0.3 0.07 200/.5),transparent 60%),var(--bg);
container: ctc07/inline-size;
}
.ctc-07__wrap {
width: min(700px,100%);
display: flex;
flex-direction: column;
gap: 14px;
}
.ctc-07__kicker {
font-size: 11.5px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(0.75 0.12 200);
}
.ctc-07__head h1 {
font-size: clamp(24px,3.8cqi,38px);
font-weight: 800;
letter-spacing: -.03em;
margin: 8px 0 8px;
}
.ctc-07__panel {
background: oklch(1 0 0/.045);
border: 1px solid var(--line);
border-radius: 18px;
padding: 16px 18px;
}
.ctc-07__lab {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
color: var(--mut);
margin-bottom: 12px;
}
.ctc-07__lab code,
.ctc-07__note code {
font-family: ui-monospace,Menlo,monospace;
font-size: .95em;
letter-spacing: 0;
text-transform: none;
background: oklch(1 0 0/.09);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.ctc-07__row {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.ctc-07__tag {
--c: oklch(0.78 0.14 var(--h,200));
position: relative;
display: inline-flex;
align-items: center;
min-height: 44px;
padding: 0 20px;
border-radius: 99px;
font-family: ui-monospace,Menlo,monospace;
font-size: 13.5px;
font-weight: 600;
text-decoration: none;
color: var(--ink);
background: oklch(1 0 0/.05);
box-shadow: inset 0 0 0 1px var(--line);
transition: color .25s,box-shadow .25s;
}
.ctc-07__tag::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background: conic-gradient(from calc(var(--ctc07f) - 90deg),var(--c) var(--ctc07a),transparent 0);
-webkit-mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
transition: --ctc07a .7s ease;
}
a.ctc-07__tag:hover,
a.ctc-07__tag:focus-visible {
--ctc07a: 360deg;
color: var(--c);
box-shadow: inset 0 0 0 1px transparent;
}
a.ctc-07__tag:hover::before,
a.ctc-07__tag:focus-visible::before {
--ctc07a: 360deg;
}
a.ctc-07__tag:focus-visible {
outline: 2px solid var(--c);
outline-offset: 3px;
}
.ctc-07__tag--orbit {
color: var(--mut);
}
.ctc-07__tag--orbit::before {
background: conic-gradient(from var(--ctc07f),transparent 0 82%,var(--c) 96%,oklch(0.98 0.01 var(--h)) 100%);
animation: ctc07-orbit 2.6s linear infinite;
}
.ctc-07__tag--slow::before {
animation-duration: 5s;
}
@keyframes ctc07-orbit {
to {
--ctc07f: 360deg;
}
}
.ctc-07__tag--set {
color: var(--c);
}
.ctc-07__tag--set::before {
transition: none;
}
.ctc-07__note {
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid oklch(0.75 0.12 200);
padding-left: 12px;
}
@media (prefers-reduced-motion: reduce) {
.ctc-07 * {
transition-duration: .01ms !important;
}
.ctc-07__tag--orbit::before {
animation: none;
background: conic-gradient(var(--c) 30deg,transparent 0);
}
}
```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: Animated Border Outline Tag Effect
Source: https://codefronts.com/snippets/css-tags-chips/sliding-border/
Borders that draw themselves. Hover any tag and a conic-gradient ring sweeps 0°→360° around it like a pen tracing the outline — a real transition on a registered @property angle, not a keyframe hack. A second variant keeps a comet segment orbiting forever, and a third fills the ring as a progress indicator. The ring itself is carved with the mask-composite trick, so the center stays perfectly transparent over any background.
## HTML
```html
<section class="ctc-07" aria-label="Animated border outline tag demo">
<div class="ctc-07__stage">
<div class="ctc-07__wrap">
<header class="ctc-07__head">
<p class="ctc-07__kicker">Border draw · @property + conic + mask</p>
<h1>Hover. Watch the ink go around.</h1>
</header>
<div class="ctc-07__panel">
<p class="ctc-07__lab">01 · Draw on hover — <code>transition: --angle .7s</code></p>
<div class="ctc-07__row">
<a class="ctc-07__tag" style="--h:200" href="#ctc07">css</a>
<a class="ctc-07__tag" style="--h:292" href="#ctc07">houdini</a>
<a class="ctc-07__tag" style="--h:150" href="#ctc07">@property</a>
<a class="ctc-07__tag" style="--h:40" href="#ctc07">conic-gradient</a>
<a class="ctc-07__tag" style="--h:330" href="#ctc07">mask-composite</a>
</div>
</div>
<div class="ctc-07__panel">
<p class="ctc-07__lab">02 · Orbiting comet — keyframes on the gradient's <code>from</code> angle</p>
<div class="ctc-07__row">
<span class="ctc-07__tag ctc-07__tag--orbit" style="--h:200">scanning…</span>
<span class="ctc-07__tag ctc-07__tag--orbit" style="--h:292">syncing</span>
<span class="ctc-07__tag ctc-07__tag--orbit ctc-07__tag--slow" style="--h:150">background job</span>
</div>
</div>
<div class="ctc-07__panel">
<p class="ctc-07__lab">03 · Determinate ring — same CSS, angle set inline as progress</p>
<div class="ctc-07__row">
<span class="ctc-07__tag ctc-07__tag--set" style="--h:150;--ctc07a:90deg">25% uploaded</span>
<span class="ctc-07__tag ctc-07__tag--set" style="--h:200;--ctc07a:216deg">60% uploaded</span>
<span class="ctc-07__tag ctc-07__tag--set" style="--h:292;--ctc07a:342deg">95% uploaded</span>
</div>
</div>
<p class="ctc-07__note">The ring is a pseudo-element hollowed by <code>mask-composite:exclude</code> — the center stays truly transparent, so this works over photos and gradients, not just flat fills.</p>
</div>
</div>
</section>
```
## CSS
```css
@property --ctc07a {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
@property --ctc07f {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.ctc-07,
.ctc-07 *,
.ctc-07 *::before,
.ctc-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ctc-07 {
--bg: oklch(0.19 0.02 265);
--ink: oklch(0.95 0.008 265);
--mut: oklch(0.66 0.02 265);
--line: oklch(0.32 0.025 265);
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
}
.ctc-07__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 85% -10%,oklch(0.32 0.08 292/.5),transparent 62%),radial-gradient(60cqi 50cqi at -5% 110%,oklch(0.3 0.07 200/.5),transparent 60%),var(--bg);
container: ctc07/inline-size;
}
.ctc-07__wrap {
width: min(700px,100%);
display: flex;
flex-direction: column;
gap: 14px;
}
.ctc-07__kicker {
font-size: 11.5px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(0.75 0.12 200);
}
.ctc-07__head h1 {
font-size: clamp(24px,3.8cqi,38px);
font-weight: 800;
letter-spacing: -.03em;
margin: 8px 0 8px;
}
.ctc-07__panel {
background: oklch(1 0 0/.045);
border: 1px solid var(--line);
border-radius: 18px;
padding: 16px 18px;
}
.ctc-07__lab {
font-size: 11.5px;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
color: var(--mut);
margin-bottom: 12px;
}
.ctc-07__lab code,
.ctc-07__note code {
font-family: ui-monospace,Menlo,monospace;
font-size: .95em;
letter-spacing: 0;
text-transform: none;
background: oklch(1 0 0/.09);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.ctc-07__row {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.ctc-07__tag {
--c: oklch(0.78 0.14 var(--h,200));
position: relative;
display: inline-flex;
align-items: center;
min-height: 44px;
padding: 0 20px;
border-radius: 99px;
font-family: ui-monospace,Menlo,monospace;
font-size: 13.5px;
font-weight: 600;
text-decoration: none;
color: var(--ink);
background: oklch(1 0 0/.05);
box-shadow: inset 0 0 0 1px var(--line);
transition: color .25s,box-shadow .25s;
}
.ctc-07__tag::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background: conic-gradient(from calc(var(--ctc07f) - 90deg),var(--c) var(--ctc07a),transparent 0);
-webkit-mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
transition: --ctc07a .7s ease;
}
a.ctc-07__tag:hover,
a.ctc-07__tag:focus-visible {
--ctc07a: 360deg;
color: var(--c);
box-shadow: inset 0 0 0 1px transparent;
}
a.ctc-07__tag:hover::before,
a.ctc-07__tag:focus-visible::before {
--ctc07a: 360deg;
}
a.ctc-07__tag:focus-visible {
outline: 2px solid var(--c);
outline-offset: 3px;
}
.ctc-07__tag--orbit {
color: var(--mut);
}
.ctc-07__tag--orbit::before {
background: conic-gradient(from var(--ctc07f),transparent 0 82%,var(--c) 96%,oklch(0.98 0.01 var(--h)) 100%);
animation: ctc07-orbit 2.6s linear infinite;
}
.ctc-07__tag--slow::before {
animation-duration: 5s;
}
@keyframes ctc07-orbit {
to {
--ctc07f: 360deg;
}
}
.ctc-07__tag--set {
color: var(--c);
}
.ctc-07__tag--set::before {
transition: none;
}
.ctc-07__note {
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid oklch(0.75 0.12 200);
padding-left: 12px;
}
@media (prefers-reduced-motion: reduce) {
.ctc-07 * {
transition-duration: .01ms !important;
}
.ctc-07__tag--orbit::before {
animation: none;
background: conic-gradient(var(--c) 30deg,transparent 0);
}
}
```How this works
Two techniques stack. First, the ring: a pseudo-element painted with a conic gradient, then hollowed out so only a 2px band survives — the modern replacement for border-image:
.tag::before{ content:""; position:absolute; inset:0;
border-radius:inherit; padding:2px; /* ring thickness */
background:conic-gradient(var(--c) var(--ctc07a), transparent 0);
mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite:exclude; }The two mask layers cover the content-box and the full box; exclude XORs them, leaving only the padding band — the ring. Second, the sweep: --ctc07a is registered with @property … syntax:'<angle>', which is what makes it transitionable. Hover sets it from 0deg to 360deg and the browser interpolates the gradient's hard stop around the circle — a border literally drawing itself in one declaration: transition: --ctc07a .7s.
The orbiting variant animates the gradient's from angle instead (via a second registered property in a keyframe loop), so a short comet with a fading tail circles the tag; pair it with a dim static ring underneath for that HUD look. Without @property support the ring simply appears fully drawn on hover — a clean, intentional fallback that needs no @supports gate.
Make it yours
- Ring thickness is the pseudo-element's
padding(2px). At 1px it reads as hairline jewelry; 3px+ starts feeling like a loader. - Draw speed: the .7s on
transition: --ctc07a. Add a small negative rotation on the gradient'sfromangle to start the draw at 12 o'clock. - Comet length: the
15%color stop in the orbit gradient — longer tail = more motion energy. Slow the loop below 3s and it turns hypnotic; keep 4s+ for calm UIs. - Progress variant: drive
--ctc07afrom inline style (style="--ctc07a:270deg") instead of hover and the same ring becomes a determinate progress chip.
Gotchas — read before shipping
- Un-registered custom properties do not transition — the @property block is not optional decoration, it IS the animation. Without it the ring snaps (acceptable fallback, but know why).
- Safari needs the -webkit-mask lines alongside the standard ones; miss them and the whole pseudo-element paints solid, covering your tag's center.
- border-radius:inherit on the pseudo-element or the ring's corners drift from the tag's corners at large radii.
- Conic gradients render polygon-faceted on some Android GPUs at large sizes — at chip scale it is invisible, but do not scale this ring to hero cards without checking.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
@property lands in Firefox 128 (July 2024) — before that the ring appears without the drawing transition. mask-composite:exclude is universal since 2023 with -webkit- prefixes for older Safari. oklch()/color-mix() as everywhere.