25 CSS Button Groups02 / 25
CSS Segmented Control with Motion-Path Comet Thumb (offset-path Arc Travel)
A segmented control whose selection thumb doesn't slide in a straight line — it arcs along a curved CSS motion path like a comet, trailing an aurora glow that lags behind and catches up. A never-seen 'CSS offset-path segmented control' that swaps the tired linear thumb for real motion-path travel, on accessible radio inputs.
Published
The code
<section class="btn-02" aria-label="Motion-path comet segmented control demo">
<div class="btn-02__wrap">
<p class="btn-02__label">Signal mode</p>
<div class="btn-02__seg" role="radiogroup" aria-label="Signal mode">
<input type="radio" name="btn-02" id="btn-02-1" class="btn-02__in" checked>
<label for="btn-02-1" class="btn-02__opt">Live</label>
<input type="radio" name="btn-02" id="btn-02-2" class="btn-02__in">
<label for="btn-02-2" class="btn-02__opt">Pulse</label>
<input type="radio" name="btn-02" id="btn-02-3" class="btn-02__in">
<label for="btn-02-3" class="btn-02__opt">Wave</label>
<input type="radio" name="btn-02" id="btn-02-4" class="btn-02__in">
<label for="btn-02-4" class="btn-02__opt">Idle</label>
<span class="btn-02__tail" aria-hidden="true"></span>
<span class="btn-02__thumb" aria-hidden="true"></span>
</div>
</div>
</section><section class="btn-02" aria-label="Motion-path comet segmented control demo">
<div class="btn-02__wrap">
<p class="btn-02__label">Signal mode</p>
<div class="btn-02__seg" role="radiogroup" aria-label="Signal mode">
<input type="radio" name="btn-02" id="btn-02-1" class="btn-02__in" checked>
<label for="btn-02-1" class="btn-02__opt">Live</label>
<input type="radio" name="btn-02" id="btn-02-2" class="btn-02__in">
<label for="btn-02-2" class="btn-02__opt">Pulse</label>
<input type="radio" name="btn-02" id="btn-02-3" class="btn-02__in">
<label for="btn-02-3" class="btn-02__opt">Wave</label>
<input type="radio" name="btn-02" id="btn-02-4" class="btn-02__in">
<label for="btn-02-4" class="btn-02__opt">Idle</label>
<span class="btn-02__tail" aria-hidden="true"></span>
<span class="btn-02__thumb" aria-hidden="true"></span>
</div>
</div>
</section>.btn-02,
.btn-02 *,
.btn-02 *::before,
.btn-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.btn-02 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(120% 120% at 50% 0%,#0d1024,#06070f);
padding: 44px 20px;
}
.btn-02__wrap {
width: min(420px,100%);
}
.btn-02__label {
font-size: 12px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: #7fe3c4;
margin: 0 0 14px 4px;
}
.btn-02__seg {
--d: 0%;
position: relative;
display: grid;
grid-template-columns: repeat(4,80px);
width: calc(80px*4 + 12px);
padding: 6px;
background: #12142a;
border: 1px solid #222647;
border-radius: 16px;
box-shadow: inset 0 2px 8px rgba(0,0,0,.5);
}
.btn-02__in {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.btn-02__opt {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
min-height: 44px;
font-size: 14.5px;
font-weight: 700;
color: #8891bd;
cursor: pointer;
transition: color .3s;
}
.btn-02__thumb,
.btn-02__tail {
position: absolute;
left: 0;
top: 0;
width: 74px;
height: 44px;
border-radius: 11px;
offset-path: path("M43 28 C 123 6, 203 6, 283 28");
offset-rotate: 0deg;
offset-distance: var(--d);
}
.btn-02__thumb {
z-index: 1;
background: linear-gradient(120deg,#38f2c0,#3aa0ff);
box-shadow: 0 4px 18px -4px #38f2c0;
transition: offset-distance .5s cubic-bezier(.5,0,.2,1);
}
.btn-02__tail {
z-index: 0;
background: linear-gradient(120deg,#38f2c0,#8a5bff);
filter: blur(9px);
opacity: .6;
transition: offset-distance .8s cubic-bezier(.5,0,.2,1);
}
.btn-02__seg:has(#btn-02-2:checked) {
--d: 33.3%;
}
.btn-02__seg:has(#btn-02-3:checked) {
--d: 66.6%;
}
.btn-02__seg:has(#btn-02-4:checked) {
--d: 100%;
}
.btn-02__in:checked + .btn-02__opt {
color: #04121c;
}
.btn-02__in:focus-visible + .btn-02__opt {
outline: 3px solid #7fe3c4;
outline-offset: 3px;
border-radius: 8px;
}
@media (prefers-reduced-motion: reduce) {
.btn-02__thumb,
.btn-02__tail {
offset-path: path("M43 28 L 283 28");
transition: offset-distance .2s;
}
}.btn-02,
.btn-02 *,
.btn-02 *::before,
.btn-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.btn-02 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(120% 120% at 50% 0%,#0d1024,#06070f);
padding: 44px 20px;
}
.btn-02__wrap {
width: min(420px,100%);
}
.btn-02__label {
font-size: 12px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: #7fe3c4;
margin: 0 0 14px 4px;
}
.btn-02__seg {
--d: 0%;
position: relative;
display: grid;
grid-template-columns: repeat(4,80px);
width: calc(80px*4 + 12px);
padding: 6px;
background: #12142a;
border: 1px solid #222647;
border-radius: 16px;
box-shadow: inset 0 2px 8px rgba(0,0,0,.5);
}
.btn-02__in {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.btn-02__opt {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
min-height: 44px;
font-size: 14.5px;
font-weight: 700;
color: #8891bd;
cursor: pointer;
transition: color .3s;
}
.btn-02__thumb,
.btn-02__tail {
position: absolute;
left: 0;
top: 0;
width: 74px;
height: 44px;
border-radius: 11px;
offset-path: path("M43 28 C 123 6, 203 6, 283 28");
offset-rotate: 0deg;
offset-distance: var(--d);
}
.btn-02__thumb {
z-index: 1;
background: linear-gradient(120deg,#38f2c0,#3aa0ff);
box-shadow: 0 4px 18px -4px #38f2c0;
transition: offset-distance .5s cubic-bezier(.5,0,.2,1);
}
.btn-02__tail {
z-index: 0;
background: linear-gradient(120deg,#38f2c0,#8a5bff);
filter: blur(9px);
opacity: .6;
transition: offset-distance .8s cubic-bezier(.5,0,.2,1);
}
.btn-02__seg:has(#btn-02-2:checked) {
--d: 33.3%;
}
.btn-02__seg:has(#btn-02-3:checked) {
--d: 66.6%;
}
.btn-02__seg:has(#btn-02-4:checked) {
--d: 100%;
}
.btn-02__in:checked + .btn-02__opt {
color: #04121c;
}
.btn-02__in:focus-visible + .btn-02__opt {
outline: 3px solid #7fe3c4;
outline-offset: 3px;
border-radius: 8px;
}
@media (prefers-reduced-motion: reduce) {
.btn-02__thumb,
.btn-02__tail {
offset-path: path("M43 28 L 283 28");
transition: offset-distance .2s;
}
}Here's a working CSS Button Group 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 Segmented Control with Motion-Path Comet Thumb (offset-path Arc Travel)
Source: https://codefronts.com/components/css-button-groups/css-segmented-control-with-motion-path-comet-thumb-offset-path-arc-travel/
A segmented control whose selection thumb doesn't slide in a straight line — it arcs along a curved CSS motion path like a comet, trailing an aurora glow that lags behind and catches up. A never-seen 'CSS offset-path segmented control' that swaps the tired linear thumb for real motion-path travel, on accessible radio inputs.
## HTML
```html
<section class="btn-02" aria-label="Motion-path comet segmented control demo">
<div class="btn-02__wrap">
<p class="btn-02__label">Signal mode</p>
<div class="btn-02__seg" role="radiogroup" aria-label="Signal mode">
<input type="radio" name="btn-02" id="btn-02-1" class="btn-02__in" checked>
<label for="btn-02-1" class="btn-02__opt">Live</label>
<input type="radio" name="btn-02" id="btn-02-2" class="btn-02__in">
<label for="btn-02-2" class="btn-02__opt">Pulse</label>
<input type="radio" name="btn-02" id="btn-02-3" class="btn-02__in">
<label for="btn-02-3" class="btn-02__opt">Wave</label>
<input type="radio" name="btn-02" id="btn-02-4" class="btn-02__in">
<label for="btn-02-4" class="btn-02__opt">Idle</label>
<span class="btn-02__tail" aria-hidden="true"></span>
<span class="btn-02__thumb" aria-hidden="true"></span>
</div>
</div>
</section>
```
## CSS
```css
.btn-02,
.btn-02 *,
.btn-02 *::before,
.btn-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.btn-02 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(120% 120% at 50% 0%,#0d1024,#06070f);
padding: 44px 20px;
}
.btn-02__wrap {
width: min(420px,100%);
}
.btn-02__label {
font-size: 12px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: #7fe3c4;
margin: 0 0 14px 4px;
}
.btn-02__seg {
--d: 0%;
position: relative;
display: grid;
grid-template-columns: repeat(4,80px);
width: calc(80px*4 + 12px);
padding: 6px;
background: #12142a;
border: 1px solid #222647;
border-radius: 16px;
box-shadow: inset 0 2px 8px rgba(0,0,0,.5);
}
.btn-02__in {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.btn-02__opt {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
min-height: 44px;
font-size: 14.5px;
font-weight: 700;
color: #8891bd;
cursor: pointer;
transition: color .3s;
}
.btn-02__thumb,
.btn-02__tail {
position: absolute;
left: 0;
top: 0;
width: 74px;
height: 44px;
border-radius: 11px;
offset-path: path("M43 28 C 123 6, 203 6, 283 28");
offset-rotate: 0deg;
offset-distance: var(--d);
}
.btn-02__thumb {
z-index: 1;
background: linear-gradient(120deg,#38f2c0,#3aa0ff);
box-shadow: 0 4px 18px -4px #38f2c0;
transition: offset-distance .5s cubic-bezier(.5,0,.2,1);
}
.btn-02__tail {
z-index: 0;
background: linear-gradient(120deg,#38f2c0,#8a5bff);
filter: blur(9px);
opacity: .6;
transition: offset-distance .8s cubic-bezier(.5,0,.2,1);
}
.btn-02__seg:has(#btn-02-2:checked) {
--d: 33.3%;
}
.btn-02__seg:has(#btn-02-3:checked) {
--d: 66.6%;
}
.btn-02__seg:has(#btn-02-4:checked) {
--d: 100%;
}
.btn-02__in:checked + .btn-02__opt {
color: #04121c;
}
.btn-02__in:focus-visible + .btn-02__opt {
outline: 3px solid #7fe3c4;
outline-offset: 3px;
border-radius: 8px;
}
@media (prefers-reduced-motion: reduce) {
.btn-02__thumb,
.btn-02__tail {
offset-path: path("M43 28 L 283 28");
transition: offset-distance .2s;
}
}
```Here's a working CSS Button Group 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 Segmented Control with Motion-Path Comet Thumb (offset-path Arc Travel)
Source: https://codefronts.com/components/css-button-groups/css-segmented-control-with-motion-path-comet-thumb-offset-path-arc-travel/
A segmented control whose selection thumb doesn't slide in a straight line — it arcs along a curved CSS motion path like a comet, trailing an aurora glow that lags behind and catches up. A never-seen 'CSS offset-path segmented control' that swaps the tired linear thumb for real motion-path travel, on accessible radio inputs.
## HTML
```html
<section class="btn-02" aria-label="Motion-path comet segmented control demo">
<div class="btn-02__wrap">
<p class="btn-02__label">Signal mode</p>
<div class="btn-02__seg" role="radiogroup" aria-label="Signal mode">
<input type="radio" name="btn-02" id="btn-02-1" class="btn-02__in" checked>
<label for="btn-02-1" class="btn-02__opt">Live</label>
<input type="radio" name="btn-02" id="btn-02-2" class="btn-02__in">
<label for="btn-02-2" class="btn-02__opt">Pulse</label>
<input type="radio" name="btn-02" id="btn-02-3" class="btn-02__in">
<label for="btn-02-3" class="btn-02__opt">Wave</label>
<input type="radio" name="btn-02" id="btn-02-4" class="btn-02__in">
<label for="btn-02-4" class="btn-02__opt">Idle</label>
<span class="btn-02__tail" aria-hidden="true"></span>
<span class="btn-02__thumb" aria-hidden="true"></span>
</div>
</div>
</section>
```
## CSS
```css
.btn-02,
.btn-02 *,
.btn-02 *::before,
.btn-02 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.btn-02 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(120% 120% at 50% 0%,#0d1024,#06070f);
padding: 44px 20px;
}
.btn-02__wrap {
width: min(420px,100%);
}
.btn-02__label {
font-size: 12px;
font-weight: 800;
letter-spacing: .16em;
text-transform: uppercase;
color: #7fe3c4;
margin: 0 0 14px 4px;
}
.btn-02__seg {
--d: 0%;
position: relative;
display: grid;
grid-template-columns: repeat(4,80px);
width: calc(80px*4 + 12px);
padding: 6px;
background: #12142a;
border: 1px solid #222647;
border-radius: 16px;
box-shadow: inset 0 2px 8px rgba(0,0,0,.5);
}
.btn-02__in {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.btn-02__opt {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
min-height: 44px;
font-size: 14.5px;
font-weight: 700;
color: #8891bd;
cursor: pointer;
transition: color .3s;
}
.btn-02__thumb,
.btn-02__tail {
position: absolute;
left: 0;
top: 0;
width: 74px;
height: 44px;
border-radius: 11px;
offset-path: path("M43 28 C 123 6, 203 6, 283 28");
offset-rotate: 0deg;
offset-distance: var(--d);
}
.btn-02__thumb {
z-index: 1;
background: linear-gradient(120deg,#38f2c0,#3aa0ff);
box-shadow: 0 4px 18px -4px #38f2c0;
transition: offset-distance .5s cubic-bezier(.5,0,.2,1);
}
.btn-02__tail {
z-index: 0;
background: linear-gradient(120deg,#38f2c0,#8a5bff);
filter: blur(9px);
opacity: .6;
transition: offset-distance .8s cubic-bezier(.5,0,.2,1);
}
.btn-02__seg:has(#btn-02-2:checked) {
--d: 33.3%;
}
.btn-02__seg:has(#btn-02-3:checked) {
--d: 66.6%;
}
.btn-02__seg:has(#btn-02-4:checked) {
--d: 100%;
}
.btn-02__in:checked + .btn-02__opt {
color: #04121c;
}
.btn-02__in:focus-visible + .btn-02__opt {
outline: 3px solid #7fe3c4;
outline-offset: 3px;
border-radius: 8px;
}
@media (prefers-reduced-motion: reduce) {
.btn-02__thumb,
.btn-02__tail {
offset-path: path("M43 28 L 283 28");
transition: offset-distance .2s;
}
}
```How this works
The thumb is placed on a curved offset-path: path(...) that bows gently above the track, and its position is a single animatable value — offset-distance. Each segment maps to a distance stop (0% / 33.3% / 66.6% / 100%); :has() sets the stop and the browser interpolates along the curve, so the thumb swoops between segments instead of sliding flat.
The comet tail is a second element on the same path with a longer transition-duration, so it trails then snaps in — a genuine motion lag, not a fake blur. Because offset-distance transitions natively, the whole effect is declarative and GPU-composited.
.thumb,.tail{ offset-path:path('M50 34 C 138 10,226 10,314 34'); offset-rotate:0deg }
.seg:has(#s3:checked){ --d:66.6% } .thumb{ offset-distance:var(--d); transition:offset-distance .5s }Techniques used: CSS Motion Path (offset-path + offset-distance) for curved travel · comet tail via a lagged second element on the same path · :has() distance mapping · offset-rotate control · aurora gradient thumb · reduced-motion straight fallback
Make it yours
- Arc shape = the
path()control points (flatten or exaggerate the bow). - Comet lag = the tail's longer
transition-duration. - Thumb/tail palette = the aurora gradient stops.
- Add segments by extending the path and adding distance stops.
Gotchas — read before shipping
offset-pathcoordinates are in the element's own box space — keep the control a known width so the stops line up (this demo fixes segment widths).- Only
offset-distancetransitions smoothly along the curve; don't animateleftalongside it or the paths fight. - Set
offset-rotate:0degunless you want the thumb to bank into the curve. - Under
prefers-reduced-motion, drop to a flat path/instant move so the swoop doesn't trigger vestibular discomfort.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 105+ | 15.4+ | 121+ | 105+ |
Floor set by :has() as this demo is written. The core technique itself goes back further — Chrome all.
CSS Motion Path (offset-path/offset-distance) is Baseline 2023. The :has() distance mapping needs :has() (Baseline 2023); without it the thumb rests on the first segment.