06Repeat rule that becomes a sentence as you tap chips
Toggling weekday chips rewrites the sentence underneath, and switching the mode to monthly makes the same chips read as an ordinal week instead.
Best forSettings dialogs for recurring events and reminders where the rule must be confirmed in words
aria-checkedaria-livesteps(1, end)

<main class="stage">
<div class="rc is-demo">
<div class="rc__seg" id="rc-seg" data-m="w" role="radiogroup" aria-label="Repeat cycle">
<span class="rc__ind" aria-hidden="true"></span>
<button class="rc__opt" type="button" role="radio" aria-checked="true" data-m="w">Weekly</button>
<button class="rc__opt" type="button" role="radio" aria-checked="false" data-m="m">Monthly</button>
</div>
<div class="rc__days" role="group" aria-label="Repeat days">
<button class="rc__chip" type="button" role="checkbox" aria-checked="false" aria-label="Sunday" data-w="0">Su</button>
<button class="rc__chip" type="button" role="checkbox" aria-checked="true" aria-label="Monday" data-w="1">Mo</button>
<button class="rc__chip" type="button" role="checkbox" aria-checked="false" aria-label="Tuesday" data-w="2">Tu</button>
<button class="rc__chip" type="button" role="checkbox" aria-checked="true" aria-label="Wednesday" data-w="3">We</button>
<button class="rc__chip" type="button" role="checkbox" aria-checked="false" aria-label="Thursday" data-w="4">Th</button>
<button class="rc__chip" type="button" role="checkbox" aria-checked="true" aria-label="Friday" data-w="5">Fr</button>
<button class="rc__chip" type="button" role="checkbox" aria-checked="false" aria-label="Saturday" data-w="6">Sa</button>
</div>
<p class="rc__sum" aria-live="polite">
<span class="rc__cut" data-k="1">Weekly on Mon, Wed, Fri at 9:00 AM</span>
<span class="rc__cut" data-k="2" aria-hidden="true">Weekly on Mon, Wed, Thu, Fri at 9:00 AM</span>
<span class="rc__cut" data-k="3" aria-hidden="true">Weekly on Mon, Tue, Wed, Thu, Fri at 9:00 AM</span>
<span class="rc__cut" data-k="4" aria-hidden="true">Monthly on the 2nd Monday at 9:00 AM</span>
</p>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop;
$easing: $ease-spring;
$color: $subject-ink;
@include stage($stage-yellow);
.rc {
--seg-w: 72px; // 세그먼트 한 칸 폭 = 인디케이터가 옮겨가는 거리 (BD19: 칸은 flex: 0 0 auto)
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-3;
width: min(300px, 90vw);
color: $color;
}
// ── 매주 / 매월 세그먼트 두 칸 ──
.rc__seg {
position: relative;
display: flex;
gap: 0;
padding: $sp-1;
border-radius: $r-pill;
background-color: rgba($subject-ink, .16);
}
.rc__ind {
position: absolute;
left: $sp-1;
top: $sp-1;
width: var(--seg-w);
height: 28px;
border-radius: $r-pill;
background-color: $color;
transform: translateX(0);
transition: transform $dur-base $easing;
}
.rc__seg[data-m="m"] .rc__ind { transform: translateX(var(--seg-w)); }
.rc__opt {
position: relative;
flex: 0 0 auto;
width: var(--seg-w);
height: 28px;
margin: 0;
padding: 0;
border: 0;
border-radius: $r-pill;
background-color: transparent;
color: $color;
font-size: clamp(12px, 3.6vw, 13px);
line-height: 28px;
font-weight: 700;
cursor: pointer;
}
.rc__opt[aria-checked="true"] { color: $stage-yellow; }
.rc__opt:focus-visible { outline: 3px solid $color; outline-offset: 2px; }
// ── 요일 칩 일곱 개 ──
.rc__days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: $sp-1;
width: 100%;
}
.rc__chip {
margin: 0;
padding: 0;
height: 30px;
border: 2px solid $color;
border-radius: $r-pill;
background-color: transparent;
color: $color;
font-size: clamp(11px, 3.4vw, 13px);
line-height: 26px;
font-weight: 700;
cursor: pointer;
}
.rc__chip[aria-checked="true"] { background-color: $color; color: $stage-yellow; }
.rc__chip:focus-visible { outline: 3px solid $color; outline-offset: 2px; }
// ── 라이브 요약 문장 ──
.rc__sum {
position: relative;
margin: 0;
width: 100%;
height: 38px;
}
.rc__cut {
position: absolute;
left: 0;
right: 0;
top: 0;
text-align: center;
font-size: clamp(12px, 3.8vw, 14px);
line-height: 19px;
font-weight: 700;
opacity: 0;
}
.rc__cut[data-k="1"] { opacity: 1; }
// ── 자동 루프: 칩 두 개가 차례로 켜지고 문장이 컷으로 갈리며 세그먼트가 한 번 옮겨간다 ──
.rc.is-demo .rc__ind {
animation-name: rc-ind;
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
// 인디케이터가 매월로 옮겨 앉는 66% 가 이 데모의 주인공 장면이다 — 요약 문장도 그 프레임에 함께 갈린다
@keyframes rc-ind {
0%, 62% { transform: translateX(0); }
70%, 76% { transform: translateX(var(--seg-w)); }
86%, 100% { transform: translateX(0); }
}
.rc.is-demo .rc__opt {
animation-duration: $duration;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.rc.is-demo .rc__opt[data-m="w"] { animation-name: rc-ow; }
.rc.is-demo .rc__opt[data-m="m"] { animation-name: rc-om; }
// 라벨 색은 인디케이터가 그 칸을 덮는 순간에 맞춰 갈린다 — 어긋나면 먹 글자가 먹 알약 위에 얹혀 사라진다
@keyframes rc-ow { 0% { color: $stage-yellow; } 64% { color: $color; } 78% { color: $stage-yellow; } 100% { color: $stage-yellow; } }
@keyframes rc-om { 0% { color: $color; } 64% { color: $stage-yellow; } 78% { color: $color; } 100% { color: $color; } }
// 켜짐은 한 프레임에 컷 — 그 옆에 눌림 반동(scale)을 붙여 프레임을 채운다
.rc.is-demo .rc__chip[data-w="4"],
.rc.is-demo .rc__chip[data-w="2"] {
animation-duration: $duration, $duration;
animation-timing-function: steps(1, end), $ease-pop;
animation-iteration-count: infinite, infinite;
animation-fill-mode: backwards, backwards;
}
.rc.is-demo .rc__chip[data-w="4"] { animation-name: rc-on4, rc-pop4; }
.rc.is-demo .rc__chip[data-w="2"] { animation-name: rc-on2, rc-pop2; }
// 끄기는 한 프레임에 몰지 않고 74% · 82% 로 나눈다 (되돌림이 최대 변화가 되면 poster 가 빈 장면이 된다)
@keyframes rc-on4 {
0% { background-color: transparent; color: $color; }
20% { background-color: $color; color: $stage-yellow; }
74% { background-color: transparent; color: $color; }
100% { background-color: transparent; color: $color; }
}
@keyframes rc-on2 {
0% { background-color: transparent; color: $color; }
40% { background-color: $color; color: $stage-yellow; }
82% { background-color: transparent; color: $color; }
100% { background-color: transparent; color: $color; }
}
@keyframes rc-pop4 {
0%, 19% { transform: scale(1); }
22% { transform: scale(.86); }
29% { transform: scale(1.12); }
36%, 100% { transform: scale(1); }
}
@keyframes rc-pop2 {
0%, 39% { transform: scale(1); }
42% { transform: scale(.86); }
49% { transform: scale(1.12); }
56%, 100% { transform: scale(1); }
}
.rc.is-demo .rc__cut {
animation-duration: $duration;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.rc.is-demo .rc__cut[data-k="1"] { animation-name: rc-k1; }
.rc.is-demo .rc__cut[data-k="2"] { animation-name: rc-k2; }
.rc.is-demo .rc__cut[data-k="3"] { animation-name: rc-k3; }
.rc.is-demo .rc__cut[data-k="4"] { animation-name: rc-k4; }
@keyframes rc-k1 { 0% { opacity: 1; } 20% { opacity: 0; } 86% { opacity: 1; } 100% { opacity: 1; } }
@keyframes rc-k2 { 0% { opacity: 0; } 20% { opacity: 1; } 40% { opacity: 0; } 100% { opacity: 0; } }
@keyframes rc-k3 { 0% { opacity: 0; } 40% { opacity: 1; } 66% { opacity: 0; } 100% { opacity: 0; } }
@keyframes rc-k4 { 0% { opacity: 0; } 66% { opacity: 1; } 86% { opacity: 0; } 100% { opacity: 0; } }
.rc.is-demo .rc__sum {
animation-name: rc-drop;
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
@keyframes rc-drop {
0%, 19% { transform: translateY(0); }
22% { transform: translateY(6px); }
29% { transform: translateY(0); }
39% { transform: translateY(0); }
42% { transform: translateY(6px); }
49% { transform: translateY(0); }
65% { transform: translateY(0); }
68% { transform: translateY(6px); }
75%, 100% { transform: translateY(0); }
}
@media (max-width: 340px) {
.rc { --seg-w: 60px; gap: $sp-2; }
.rc__chip { height: 28px; line-height: 24px; }
.rc__sum { height: 36px; }
.rc__cut { line-height: 18px; }
}
@media (prefers-reduced-motion: reduce) {
.rc.is-demo .rc__ind,
.rc.is-demo .rc__opt,
.rc.is-demo .rc__chip,
.rc.is-demo .rc__cut,
.rc.is-demo .rc__sum { animation: none !important; }
.rc__ind { transition: none !important; }
.rc.is-demo .rc__cut[data-k="1"] { opacity: 1; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffd23f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.rc {
--seg-w: 72px;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
width: min(300px, 90vw);
color: #17141a;
}
.rc__seg {
position: relative;
display: flex;
gap: 0;
padding: 4px;
border-radius: 999px;
background-color: rgba(23, 20, 26, 0.16);
}
.rc__ind {
position: absolute;
left: 4px;
top: 4px;
width: var(--seg-w);
height: 28px;
border-radius: 999px;
background-color: #17141a;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.rc__seg[data-m=m] .rc__ind {
transform: translateX(var(--seg-w));
}
.rc__opt {
position: relative;
flex: 0 0 auto;
width: var(--seg-w);
height: 28px;
margin: 0;
padding: 0;
border: 0;
border-radius: 999px;
background-color: transparent;
color: #17141a;
font-size: clamp(12px, 3.6vw, 13px);
line-height: 28px;
font-weight: 700;
cursor: pointer;
}
.rc__opt[aria-checked=true] {
color: #ffd23f;
}
.rc__opt:focus-visible {
outline: 3px solid #17141a;
outline-offset: 2px;
}
.rc__days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
width: 100%;
}
.rc__chip {
margin: 0;
padding: 0;
height: 30px;
border: 2px solid #17141a;
border-radius: 999px;
background-color: transparent;
color: #17141a;
font-size: clamp(11px, 3.4vw, 13px);
line-height: 26px;
font-weight: 700;
cursor: pointer;
}
.rc__chip[aria-checked=true] {
background-color: #17141a;
color: #ffd23f;
}
.rc__chip:focus-visible {
outline: 3px solid #17141a;
outline-offset: 2px;
}
.rc__sum {
position: relative;
margin: 0;
width: 100%;
height: 38px;
}
.rc__cut {
position: absolute;
left: 0;
right: 0;
top: 0;
text-align: center;
font-size: clamp(12px, 3.8vw, 14px);
line-height: 19px;
font-weight: 700;
opacity: 0;
}
.rc__cut[data-k="1"] {
opacity: 1;
}
.rc.is-demo .rc__ind {
animation-name: rc-ind;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
@keyframes rc-ind {
0%, 62% {
transform: translateX(0);
}
70%, 76% {
transform: translateX(var(--seg-w));
}
86%, 100% {
transform: translateX(0);
}
}
.rc.is-demo .rc__opt {
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.rc.is-demo .rc__opt[data-m=w] {
animation-name: rc-ow;
}
.rc.is-demo .rc__opt[data-m=m] {
animation-name: rc-om;
}
@keyframes rc-ow {
0% {
color: #ffd23f;
}
64% {
color: #17141a;
}
78% {
color: #ffd23f;
}
100% {
color: #ffd23f;
}
}
@keyframes rc-om {
0% {
color: #17141a;
}
64% {
color: #ffd23f;
}
78% {
color: #17141a;
}
100% {
color: #17141a;
}
}
.rc.is-demo .rc__chip[data-w="4"],
.rc.is-demo .rc__chip[data-w="2"] {
animation-duration: 2s, 2s;
animation-timing-function: steps(1, end), cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite, infinite;
animation-fill-mode: backwards, backwards;
}
.rc.is-demo .rc__chip[data-w="4"] {
animation-name: rc-on4, rc-pop4;
}
.rc.is-demo .rc__chip[data-w="2"] {
animation-name: rc-on2, rc-pop2;
}
@keyframes rc-on4 {
0% {
background-color: transparent;
color: #17141a;
}
20% {
background-color: #17141a;
color: #ffd23f;
}
74% {
background-color: transparent;
color: #17141a;
}
100% {
background-color: transparent;
color: #17141a;
}
}
@keyframes rc-on2 {
0% {
background-color: transparent;
color: #17141a;
}
40% {
background-color: #17141a;
color: #ffd23f;
}
82% {
background-color: transparent;
color: #17141a;
}
100% {
background-color: transparent;
color: #17141a;
}
}
@keyframes rc-pop4 {
0%, 19% {
transform: scale(1);
}
22% {
transform: scale(0.86);
}
29% {
transform: scale(1.12);
}
36%, 100% {
transform: scale(1);
}
}
@keyframes rc-pop2 {
0%, 39% {
transform: scale(1);
}
42% {
transform: scale(0.86);
}
49% {
transform: scale(1.12);
}
56%, 100% {
transform: scale(1);
}
}
.rc.is-demo .rc__cut {
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.rc.is-demo .rc__cut[data-k="1"] {
animation-name: rc-k1;
}
.rc.is-demo .rc__cut[data-k="2"] {
animation-name: rc-k2;
}
.rc.is-demo .rc__cut[data-k="3"] {
animation-name: rc-k3;
}
.rc.is-demo .rc__cut[data-k="4"] {
animation-name: rc-k4;
}
@keyframes rc-k1 {
0% {
opacity: 1;
}
20% {
opacity: 0;
}
86% {
opacity: 1;
}
100% {
opacity: 1;
}
}
@keyframes rc-k2 {
0% {
opacity: 0;
}
20% {
opacity: 1;
}
40% {
opacity: 0;
}
100% {
opacity: 0;
}
}
@keyframes rc-k3 {
0% {
opacity: 0;
}
40% {
opacity: 1;
}
66% {
opacity: 0;
}
100% {
opacity: 0;
}
}
@keyframes rc-k4 {
0% {
opacity: 0;
}
66% {
opacity: 1;
}
86% {
opacity: 0;
}
100% {
opacity: 0;
}
}
.rc.is-demo .rc__sum {
animation-name: rc-drop;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
@keyframes rc-drop {
0%, 19% {
transform: translateY(0);
}
22% {
transform: translateY(6px);
}
29% {
transform: translateY(0);
}
39% {
transform: translateY(0);
}
42% {
transform: translateY(6px);
}
49% {
transform: translateY(0);
}
65% {
transform: translateY(0);
}
68% {
transform: translateY(6px);
}
75%, 100% {
transform: translateY(0);
}
}
@media (max-width: 340px) {
.rc {
--seg-w: 60px;
gap: 8px;
}
.rc__chip {
height: 28px;
line-height: 24px;
}
.rc__sum {
height: 36px;
}
.rc__cut {
line-height: 18px;
}
}
@media (prefers-reduced-motion: reduce) {
.rc.is-demo .rc__ind,
.rc.is-demo .rc__opt,
.rc.is-demo .rc__chip,
.rc.is-demo .rc__cut,
.rc.is-demo .rc__sum {
animation: none !important;
}
.rc__ind {
transition: none !important;
}
.rc.is-demo .rc__cut[data-k="1"] {
opacity: 1;
}
}(function () {
var root = document.querySelector('.rc'), seg = document.getElementById('rc-seg');
var sum = root.querySelector('.rc__cut[data-k="1"]');
var opts = [].slice.call(root.querySelectorAll('.rc__opt'));
var chips = [].slice.call(root.querySelectorAll('.rc__chip'));
var SH = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
var OR = ['1st', '2nd', '3rd', '4th', '5th'];
function on(c) { return c.getAttribute('aria-checked') === 'true'; }
// Monthly 모드의 몇째 주는 실제로 센다 — 기준일에서 그 요일이 처음 오는 날짜로 주차를 구한다
function ord(w) {
var d = new Date(2026, 8, 12);
d.setDate(d.getDate() + ((w - d.getDay() + 7) % 7));
return OR[Math.ceil(d.getDate() / 7) - 1];
}
function render() {
var m = seg.dataset.m, p = chips.filter(on).map(function (c) { return +c.dataset.w; });
if (!p.length) { sum.textContent = m === 'm' ? 'No monthly repeat' : 'No weekly repeat'; return; }
sum.textContent = m === 'm'
? 'Monthly ' + ord(p[0]) + ' week ' + SH[p[0]] + ' at 9:00 AM'
: p.length === 1
? 'Weekly ' + SH[p[0]] + ' at 9:00 AM'
: 'Weekly ' + p.map(function (w) { return SH[w]; }).join('·') + ' 9:00 AM';
}
function wake() { root.classList.remove('is-demo'); }
chips.forEach(function (c) {
c.addEventListener('click', function () { wake(); c.setAttribute('aria-checked', String(!on(c))); render(); });
});
opts.forEach(function (o) {
o.addEventListener('click', function () {
wake();
opts.forEach(function (x) { x.setAttribute('aria-checked', String(x === o)); });
seg.dataset.m = o.dataset.m; render();
});
});
seg.addEventListener('keydown', function (e) {
var d = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0;
if (!d) return;
e.preventDefault(); wake();
var i = (opts.indexOf(document.activeElement) + d + 2) % 2;
opts[i].focus({ preventScroll: true }); opts[i].click();
});
root.querySelector('.rc__days').addEventListener('keydown', function (e) {
var d = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0;
if (!d) return;
e.preventDefault();
var i = (chips.indexOf(document.activeElement) + d + 7) % 7;
chips[i].focus({ preventScroll: true });
});
render();
})();import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./RecurrenceWeekdayChips.module.scss";
export type RecurrenceWeekdayChipsProps = {
labels?: {
cycle: string; weekly: string; monthly: string;
days: string; abbr: string[]; full: string[]; ordinals: string[];
at: string; none: string;
};
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
type KeyEvt = { key: string; preventDefault(): void };
const DEMO_CUTS = [
[1, 3, 5],
[1, 3, 4, 5],
[1, 2, 3, 4, 5],
];
export default function RecurrenceWeekdayChips({
labels = {
cycle: "Repeat cycle",
weekly: "Weekly",
monthly: "Monthly",
days: "Repeat days",
abbr: ["S", "M", "T", "W", "T", "F", "S"],
full: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
ordinals: ["1st", "2nd", "3rd", "4th", "5th"],
at: "at 9:00 AM",
none: "no days selected",
},
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#17141a",
}: RecurrenceWeekdayChipsProps) {
const [demo, setDemo] = useState(true);
const [monthly, setMonthly] = useState(false);
const [picked, setPicked] = useState<number[]>([1, 3, 5]);
const vars: Vars = { "--duration": duration + "ms", "--easing": easing, "--color": color };
// 매월 모드의 몇째 주는 실제로 센다 — 기준일에서 그 요일이 처음 오는 날짜로 주차를 구한다
function ordinal(w: number): string {
const d = new Date(2026, 8, 12);
d.setDate(d.getDate() + ((w - d.getDay() + 7) % 7));
return labels.ordinals[Math.ceil(d.getDate() / 7) - 1];
}
function sentence(mode: boolean, p: number[]): string {
const head = mode ? labels.monthly : labels.weekly;
if (!p.length) return head + " — " + labels.none;
if (mode) return head + " on the " + ordinal(p[0]) + " " + labels.full[p[0]] + " " + labels.at;
if (p.length === 1) return head + " on " + labels.full[p[0]] + " " + labels.at;
return head + " on " + p.map((w) => labels.full[w].slice(0, 3)).join(", ") + " " + labels.at;
}
function wake() { setDemo(false); }
function toggle(w: number) {
wake();
setPicked((p) => (p.indexOf(w) < 0 ? p.concat([w]).sort((x, y) => x - y) : p.filter((x) => x !== w)));
}
function segKey(e: KeyEvt) {
const d = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : 0;
if (!d) return;
e.preventDefault();
wake();
setMonthly((m) => !m);
}
const cuts = [
sentence(false, DEMO_CUTS[0]),
sentence(false, DEMO_CUTS[1]),
sentence(false, DEMO_CUTS[2]),
sentence(true, [1]),
];
return (
<div className={styles.rc + (demo ? " " + styles.isDemo : "")} style={vars}>
<div className={styles.seg} data-m={monthly ? "m" : "w"} role="radiogroup"
aria-label={labels.cycle} onKeyDown={segKey}>
<span className={styles.ind} aria-hidden="true" />
<button className={styles.opt} type="button" role="radio" data-m="w"
aria-checked={!monthly} onClick={() => { wake(); setMonthly(false); }}>
{labels.weekly}
</button>
<button className={styles.opt} type="button" role="radio" data-m="m"
aria-checked={monthly} onClick={() => { wake(); setMonthly(true); }}>
{labels.monthly}
</button>
</div>
<div className={styles.days} role="group" aria-label={labels.days}>
{labels.abbr.map((a, w) => (
<button key={w} className={styles.chip} type="button" role="checkbox" data-w={w}
aria-checked={picked.indexOf(w) >= 0} aria-label={labels.full[w]}
onClick={() => toggle(w)}>
{a}
</button>
))}
</div>
<p className={styles.sum} aria-live="polite">
<span className={styles.cut} data-k="1">{demo ? cuts[0] : sentence(monthly, picked)}</span>
<span className={styles.cut} data-k="2" aria-hidden="true">{cuts[1]}</span>
<span className={styles.cut} data-k="3" aria-hidden="true">{cuts[2]}</span>
<span className={styles.cut} data-k="4" aria-hidden="true">{cuts[3]}</span>
</p>
</div>
);
}06 Repeat rule that becomes a sentence as you tap chips — Measured area 6.613% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · 9 time picker ui patterns, from a point to a span
The post this demo lives in
9 time picker ui patterns, from a point to a spanA time picker ui is the part of a screen where someone sets a time without typing it. These nine go past a single time o See all demos in this post


