Segmented control

세그먼티드 컨트롤

A button group where a pill-shaped background slides behind whichever segment is selected — functionally a radio group.

Also known as: Button groupToggle button groupiOS segmented control
···
html
<div class="seg" role="radiogroup" aria-label="기간">
  <i class="pill"></i>
  <button role="radio" aria-checked="true">일간</button>
  <button role="radio" aria-checked="false">주간</button>
  <button role="radio" aria-checked="false">월간</button>
</div>
css
.seg{position:relative;display:flex;width:min(220px,88%);background:var(--bg);border:1px solid var(--line);border-radius:11px;padding:3px}
.seg button{flex:1;position:relative;z-index:1;border:0;background:none;padding:8px 0;font-size:12px;font-weight:600;color:var(--muted);cursor:pointer}
.seg button[aria-checked=true]{color:#fff}
.pill{position:absolute;top:3px;left:3px;width:calc(100%/3 - 2px);height:calc(100% - 6px);background:var(--accent);border-radius:8px;transition:transform .25s cubic-bezier(.3,.8,.4,1)}
js
const seg = document.querySelector('.seg');
const btns = [...seg.querySelectorAll('button')];
const pill = seg.querySelector('.pill');
function select(i) {
  btns.forEach((b, idx) => b.setAttribute('aria-checked', String(idx === i)));
  pill.style.transform = 'translateX(' + (i * 100) + '%)';
}
let auto = true, cur = 0;
btns.forEach((b, i) => b.addEventListener('click', () => { auto = false; cur = i; select(i); }));
setInterval(() => { if (!auto) return; cur = (cur + 1) % btns.length; select(cur); }, 1600);

Functionally identical to a radio group — exactly one segment is selected at a time. Wrap it in role="radiogroup" with role="radio" and aria-checked on each button, or hide native radio inputs visually and style their labels as buttons.

The difference from tabs was covered above: tabs read as navigation that fully swaps a content area and sit at the top of a container, while a segmented control floats independently like a button group and switches a view state on the same screen — "Daily / Weekly / Monthly."

The sliding pill background only needs transform: translateX, but if the segment count can change, each segment's width (100% / N) has to be recalculated too.