09Toggle dạng chip
Nhấn checkbox dạng chip thì nền được tô từ giữa ra ngoài bằng scaleX, kèm phản hồi scale hơi lún khi nhấn.
Dùng choChọn nhiều tag, chọn chip lọc
scaleXpress scale0.22초

<main class="stage">
<div class="chips is-demo">
<label class="chip">
<input type="checkbox" class="sr-input" checked>
<span class="chip__fill" aria-hidden="true"></span>
<span class="chip__label">Miễn phí ship</span>
</label>
<label class="chip">
<input type="checkbox" class="sr-input">
<span class="chip__fill" aria-hidden="true"></span>
<span class="chip__label">Giao trong ngày</span>
</label>
<label class="chip">
<input type="checkbox" class="sr-input">
<span class="chip__fill" aria-hidden="true"></span>
<span class="chip__label">Bán chạy</span>
</label>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────
$duration: $dur-quick;
$easing: $ease-pop;
$color: $subject-blue-deep;
// ── 데모 무대 ───────────────────────────────
@include stage($stage-ink);
.sr-input {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.chips { display: flex; flex-wrap: wrap; gap: clamp(6px, 2vw, 10px); justify-content: center; }
.chip {
position: relative; overflow: hidden; display: inline-flex; align-items: center;
padding: 8px clamp(12px, 3.6vw, 18px); border-radius: $r-pill; border: 1.5px solid rgba($subject-cream, .35);
cursor: pointer; transition: transform 120ms $ease-out;
}
.chip:active { transform: scale(.96); }
.chip__fill {
position: absolute; inset: 0; background: $color; z-index: 0;
transform: scaleX(0); transform-origin: center; transition: transform $duration $easing;
}
.sr-input:checked ~ .chip__fill { transform: scaleX(1); }
.sr-input:focus-visible ~ .chip__fill { box-shadow: inset 0 0 0 2px rgba($subject-cream, .6); }
.chip__label {
position: relative; z-index: 1; font-weight: 600; font-size: clamp(12px, 3.4vw, 13px); line-height: 1; font-family: inherit; color: $subject-cream;
transition: color $duration $easing;
}
.sr-input:checked ~ .chip__label { color: $subject-cream; }
@media (prefers-reduced-motion: reduce) {
// 접근성: 움직임 줄이기 설정이면 트랜지션을 즉시 끝낸다
.chip__fill, .chip__label, .chip { transition-duration: 1ms !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.sr-input {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.chips {
display: flex;
flex-wrap: wrap;
gap: clamp(6px, 2vw, 10px);
justify-content: center;
}
.chip {
position: relative;
overflow: hidden;
display: inline-flex;
align-items: center;
padding: 8px clamp(12px, 3.6vw, 18px);
border-radius: 999px;
border: 1.5px solid rgba(255, 247, 230, 0.35);
cursor: pointer;
transition: transform 120ms ease-out;
}
.chip:active {
transform: scale(0.96);
}
.chip__fill {
position: absolute;
inset: 0;
background: #0a4fe8;
z-index: 0;
transform: scaleX(0);
transform-origin: center;
transition: transform 220ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.sr-input:checked ~ .chip__fill {
transform: scaleX(1);
}
.sr-input:focus-visible ~ .chip__fill {
box-shadow: inset 0 0 0 2px rgba(255, 247, 230, 0.6);
}
.chip__label {
position: relative;
z-index: 1;
font-weight: 600;
font-size: clamp(12px, 3.4vw, 13px);
line-height: 1;
font-family: inherit;
color: #fff7e6;
transition: color 220ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.sr-input:checked ~ .chip__label {
color: #fff7e6;
}
@media (prefers-reduced-motion: reduce) {
.chip__fill, .chip__label, .chip {
transition-duration: 1ms !important;
}
}// .is-demo 데모용 자동 토글. 실제로는 클릭이 :checked 를 바꾼다 (4줄)
document.querySelectorAll('.is-demo .sr-input').forEach(function (el) {
var t = function () { el.checked = !el.checked; };
t(); setInterval(t, 600);
});import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./ChipToggle.module.scss";
export type Chip = { value: string; label: string };
export type ChipToggleProps = {
chips?: Chip[];
defaultSelected?: string[];
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function ChipToggle({
chips = [
{ value: "free-shipping", label: "Free shipping" },
{ value: "ships-today", label: "Ships today" },
{ value: "best", label: "Best seller" },
],
defaultSelected = ["free-shipping"],
duration = 220,
easing = "cubic-bezier(.2,1.4,.4,1)",
color = "#0a4fe8",
}: ChipToggleProps) {
const [selected, setSelected] = useState<string[]>(defaultSelected);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const toggle = (v: string) => {
setSelected((prev) => (prev.includes(v) ? prev.filter((x) => x !== v) : [...prev, v]));
};
return (
<div className={styles.chips} style={vars}>
{chips.map((c) => {
const on = selected.includes(c.value);
return (
<label className={styles.chip} key={c.value}>
<input
type="checkbox"
className={styles.srInput}
checked={on}
onChange={() => toggle(c.value)}
/>
<span className={styles.fill} data-checked={on} aria-hidden="true" />
<span className={styles.label} data-checked={on}>{c.label}</span>
</label>
);
})}
</div>
);
}09 Toggle dạng chip — Diện tích đo được 4.197% · khung hình chuyển động 12/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 CSS Checkbox Radio — dán 1 dòng là chạy
Bài viết chứa demo này
9 CSS Checkbox Radio — dán 1 dòng là chạyCSS checkbox radio tùy biến là cách vẽ lại hình dạng cạnh input gốc bằng một pseudo-class :checked, input thật vẫn còn n Xem tất cả demo trong bài này


