04Checkbox kiểu switch
Một checkbox trông như switch nhưng núm đứng yên tại chỗ — track được tô bằng vòng tròn clip-path lớn dần để báo trạng thái bật.
Dùng choSwitch tắt tiếng thông báo, toggle tự động lưu
clip-path:circle():checked0.3초

<main class="stage">
<label class="switch is-demo">
<input type="checkbox" class="sr-input">
<span class="switch__track" aria-hidden="true">
<span class="switch__fill"></span>
<span class="switch__knob">
<svg viewBox="0 0 24 24" class="switch__icon switch__icon--on"><path d="M18 8a6 6 0 1 1-6-6 7 7 0 0 0 6 6Z" /></svg>
<svg viewBox="0 0 24 24" class="switch__icon switch__icon--off"><circle cx="12" cy="12" r="3.2" /><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1" stroke-width="1.6" stroke-linecap="round" /></svg>
</span>
</span>
<span class="switch__text">Tắt tiếng thông báo</span>
</label>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────
$duration: $dur-base;
$easing: $ease-out;
$color: $subject-blue;
// ── 데모 무대 ───────────────────────────────
@include stage($stage-paper);
.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;
}
.switch { display: inline-flex; align-items: center; gap: 12px; cursor: pointer;
font-weight: 600; font-size: clamp(14px, 4vw, 16px); line-height: 1.3; font-family: inherit; color: $subject-ink; }
.switch__track {
position: relative; width: 56px; height: 28px; border-radius: $r-pill;
background: rgba($subject-ink, .14); overflow: hidden; display: grid; place-items: center;
}
.switch__fill {
position: absolute; inset: 0; background: $color;
clip-path: circle(0% at 50% 50%); transition: clip-path $duration $easing;
}
.sr-input:checked ~ .switch__track .switch__fill { clip-path: circle(75% at 50% 50%); }
.sr-input:focus-visible ~ .switch__track { box-shadow: 0 0 0 3px rgba($subject-blue, .35); }
.switch__knob {
position: relative; z-index: 1; width: 22px; height: 22px; border-radius: 50%;
background: #fff; display: grid; place-items: center; box-shadow: $shadow-press;
}
.switch__icon {
position: absolute; width: 13px; height: 13px; fill: none; stroke: $subject-ink; stroke-width: 1.6;
transition: opacity $duration $easing, transform $duration $easing;
}
.switch__icon--on { opacity: 0; transform: scale(.6); }
.switch__icon--off { opacity: 1; transform: scale(1); }
.sr-input:checked ~ .switch__track .switch__icon--on { opacity: 1; transform: scale(1); }
.sr-input:checked ~ .switch__track .switch__icon--off { opacity: 0; transform: scale(.6); }
@media (prefers-reduced-motion: reduce) {
// 접근성: 움직임 줄이기 설정이면 트랜지션을 즉시 끝낸다
.switch__fill, .switch__icon { 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: #ffffff;
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;
}
.switch {
display: inline-flex;
align-items: center;
gap: 12px;
cursor: pointer;
font-weight: 600;
font-size: clamp(14px, 4vw, 16px);
line-height: 1.3;
font-family: inherit;
color: #17141a;
}
.switch__track {
position: relative;
width: 56px;
height: 28px;
border-radius: 999px;
background: rgba(23, 20, 26, 0.14);
overflow: hidden;
display: grid;
place-items: center;
}
.switch__fill {
position: absolute;
inset: 0;
background: #2f6df6;
clip-path: circle(0% at 50% 50%);
transition: clip-path 300ms ease-out;
}
.sr-input:checked ~ .switch__track .switch__fill {
clip-path: circle(75% at 50% 50%);
}
.sr-input:focus-visible ~ .switch__track {
box-shadow: 0 0 0 3px rgba(47, 109, 246, 0.35);
}
.switch__knob {
position: relative;
z-index: 1;
width: 22px;
height: 22px;
border-radius: 50%;
background: #fff;
display: grid;
place-items: center;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
.switch__icon {
position: absolute;
width: 13px;
height: 13px;
fill: none;
stroke: #17141a;
stroke-width: 1.6;
transition: opacity 300ms ease-out, transform 300ms ease-out;
}
.switch__icon--on {
opacity: 0;
transform: scale(0.6);
}
.switch__icon--off {
opacity: 1;
transform: scale(1);
}
.sr-input:checked ~ .switch__track .switch__icon--on {
opacity: 1;
transform: scale(1);
}
.sr-input:checked ~ .switch__track .switch__icon--off {
opacity: 0;
transform: scale(0.6);
}
@media (prefers-reduced-motion: reduce) {
.switch__fill, .switch__icon {
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, ChangeEvent } from "react";
import styles from "./SwitchLikeCheckbox.module.scss";
export type SwitchLikeCheckboxProps = {
label?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function SwitchLikeCheckbox({
label = "Mute notifications",
duration = 300,
easing = "ease-out",
color = "#2f6df6",
}: SwitchLikeCheckboxProps) {
const [checked, setChecked] = useState(false);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<label className={styles.switch} style={vars}>
<input
type="checkbox"
className={styles.srInput}
checked={checked}
onChange={(e: ChangeEvent<HTMLInputElement>) => setChecked(e.target.checked)}
/>
<span className={styles.track} aria-hidden="true">
<span className={styles.fill} />
<span className={styles.knob}>{checked ? "●" : "○"}</span>
</span>
<span>{label}</span>
</label>
);
}04 Checkbox kiểu switch — Diện tích đo được 0.803% · 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


