Chess clock
import type { CSSProperties } from 'react';
const css = `
@keyframes toggle-chess-clock-tick { to { transform: rotate(360deg); } }
.toggle-chess-clock { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
.toggle-chess-clock__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-chess-clock__art { display: block; width: 132px; height: 84px; overflow: visible; border-radius: 8px; }
.toggle-chess-clock__input:focus-visible + .toggle-chess-clock__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
.toggle-chess-clock__case { fill: light-dark(#dbdde1, #2e3035); }
.toggle-chess-clock__panel { fill: light-dark(#ebedf0, #232529); }
.toggle-chess-clock__face { fill: light-dark(#f3f4f7, #1a1c1f); stroke: light-dark(#cbced3, #393c41); stroke-width: 1.2; }
.toggle-chess-clock__tick { stroke: light-dark(#6d7179, #7d8087); stroke-width: 1.2; stroke-linecap: round; }
.toggle-chess-clock__minute { stroke: light-dark(#50535b, #c6c8cd); stroke-width: 1.8; stroke-linecap: round; }
.toggle-chess-clock__second { stroke: light-dark(#373a40, #f1f2f3); stroke-width: 1; stroke-linecap: round; transform-box: view-box; transform-origin: var(--o); animation: toggle-chess-clock-tick 8s steps(60) infinite paused; }
.toggle-chess-clock__pin { fill: light-dark(#373a40, #f1f2f3); }
.toggle-chess-clock__stem { fill: light-dark(#cbced3, #393c41); }
.toggle-chess-clock__cap { fill: light-dark(#5f626a, #a7abb2); }
/* Pressing one plunger lifts the other: its own clock stops, the other side's starts. */
.toggle-chess-clock__plunger { transition: transform 0.18s cubic-bezier(0.3, 1.5, 0.5, 1); }
.toggle-chess-clock__plunger--b { transform: translateY(5px); }
.toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__plunger--a { transform: translateY(5px); }
.toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__plunger--b { transform: none; }
.toggle-chess-clock__second--a,
.toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__second--b { animation-play-state: running; }
.toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__second--a { animation-play-state: paused; }
.toggle-chess-clock__flag { fill: light-dark(#50535b, #c6c8cd); opacity: 0; transition: opacity 0.2s; }
.toggle-chess-clock__flag--a, .toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__flag--b { opacity: 1; }
.toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__flag--a { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
.toggle-chess-clock__second { animation: none; }
.toggle-chess-clock__plunger { transition: none; }
}
`;
const SIDES = [
{ k: 'a', cx: 40, minute: -50 },
{ k: 'b', cx: 80, minute: 70 },
];
export function ChessClockToggle() {
return (
<>
<style>
{css}
</style>
<label className="toggle-chess-clock">
<input className="toggle-chess-clock__input" type="checkbox" role="switch" />
<svg className="toggle-chess-clock__art" viewBox="0 0 120 76" aria-hidden="true">
{SIDES.map((s) => (
<g key={s.k} className={`toggle-chess-clock__plunger toggle-chess-clock__plunger--${s.k}`}>
<rect className="toggle-chess-clock__stem" x={s.cx - 3} y="14" width="6" height="10" />
<rect className="toggle-chess-clock__cap" x={s.cx - 10} y="10" width="20" height="7" rx="2.5" />
</g>
))}
<path className="toggle-chess-clock__case" d="M8 72L16 28Q17 24 21 24H99Q103 24 104 28L112 72Z" />
<rect className="toggle-chess-clock__panel" x="17" y="31" width="86" height="33" rx="5" />
{SIDES.map((s) => (
<g key={s.k}>
<circle className="toggle-chess-clock__face" cx={s.cx} cy="47" r="13" />
{[0, 90, 180, 270].map((a) => (
<line key={a} className="toggle-chess-clock__tick" x1={s.cx} y1="36" x2={s.cx} y2="38" transform={`rotate(${a} ${s.cx} 47)`} />
))}
{/* The flag stands up on the clock that is running. */}
<path className={`toggle-chess-clock__flag toggle-chess-clock__flag--${s.k}`} d={`M${s.cx + 3} 37L${s.cx + 7} 38L${s.cx + 3} 40Z`} />
<line className="toggle-chess-clock__minute" x1={s.cx} y1="47" x2={s.cx} y2="40" transform={`rotate(${s.minute} ${s.cx} 47)`} />
<line
className={`toggle-chess-clock__second toggle-chess-clock__second--${s.k}`}
style={{ '--o': `${s.cx}px 47px` } as CSSProperties}
x1={s.cx}
y1="49"
x2={s.cx}
y2="36.5"
/>
<circle className="toggle-chess-clock__pin" cx={s.cx} cy="47" r="1.3" />
</g>
))}
</svg>
<span>Black to move</span>
</label>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
<label class="toggle-chess-clock">
<input class="toggle-chess-clock__input" type="checkbox" role="switch"/>
<svg class="toggle-chess-clock__art" viewBox="0 0 120 76" aria-hidden="true">
<g class="toggle-chess-clock__plunger toggle-chess-clock__plunger--a">
<rect class="toggle-chess-clock__stem" x="37" y="14" width="6" height="10"></rect>
<rect class="toggle-chess-clock__cap" x="30" y="10" width="20" height="7" rx="2.5"></rect>
</g>
<g class="toggle-chess-clock__plunger toggle-chess-clock__plunger--b">
<rect class="toggle-chess-clock__stem" x="77" y="14" width="6" height="10"></rect>
<rect class="toggle-chess-clock__cap" x="70" y="10" width="20" height="7" rx="2.5"></rect>
</g>
<path class="toggle-chess-clock__case" d="M8 72L16 28Q17 24 21 24H99Q103 24 104 28L112 72Z"></path>
<rect class="toggle-chess-clock__panel" x="17" y="31" width="86" height="33" rx="5"></rect>
<g>
<circle class="toggle-chess-clock__face" cx="40" cy="47" r="13"></circle>
<line class="toggle-chess-clock__tick" x1="40" y1="36" x2="40" y2="38" transform="rotate(0 40 47)"></line>
<line class="toggle-chess-clock__tick" x1="40" y1="36" x2="40" y2="38" transform="rotate(90 40 47)"></line>
<line class="toggle-chess-clock__tick" x1="40" y1="36" x2="40" y2="38" transform="rotate(180 40 47)"></line>
<line class="toggle-chess-clock__tick" x1="40" y1="36" x2="40" y2="38" transform="rotate(270 40 47)"></line>
<path class="toggle-chess-clock__flag toggle-chess-clock__flag--a" d="M43 37L47 38L43 40Z"></path>
<line class="toggle-chess-clock__minute" x1="40" y1="47" x2="40" y2="40" transform="rotate(-50 40 47)"></line>
<line class="toggle-chess-clock__second toggle-chess-clock__second--a" style="--o:40px 47px" x1="40" y1="49" x2="40" y2="36.5"></line>
<circle class="toggle-chess-clock__pin" cx="40" cy="47" r="1.3"></circle>
</g>
<g>
<circle class="toggle-chess-clock__face" cx="80" cy="47" r="13"></circle>
<line class="toggle-chess-clock__tick" x1="80" y1="36" x2="80" y2="38" transform="rotate(0 80 47)"></line>
<line class="toggle-chess-clock__tick" x1="80" y1="36" x2="80" y2="38" transform="rotate(90 80 47)"></line>
<line class="toggle-chess-clock__tick" x1="80" y1="36" x2="80" y2="38" transform="rotate(180 80 47)"></line>
<line class="toggle-chess-clock__tick" x1="80" y1="36" x2="80" y2="38" transform="rotate(270 80 47)"></line>
<path class="toggle-chess-clock__flag toggle-chess-clock__flag--b" d="M83 37L87 38L83 40Z"></path>
<line class="toggle-chess-clock__minute" x1="80" y1="47" x2="80" y2="40" transform="rotate(70 80 47)"></line>
<line class="toggle-chess-clock__second toggle-chess-clock__second--b" style="--o:80px 47px" x1="80" y1="49" x2="80" y2="36.5"></line>
<circle class="toggle-chess-clock__pin" cx="80" cy="47" r="1.3"></circle>
</g>
</svg>
<span>Black to move</span>
</label>@keyframes toggle-chess-clock-tick { to { transform: rotate(360deg); } }
.toggle-chess-clock { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
.toggle-chess-clock__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-chess-clock__art { display: block; width: 132px; height: 84px; overflow: visible; border-radius: 8px; }
.toggle-chess-clock__input:focus-visible + .toggle-chess-clock__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
.toggle-chess-clock__case { fill: light-dark(#dbdde1, #2e3035); }
.toggle-chess-clock__panel { fill: light-dark(#ebedf0, #232529); }
.toggle-chess-clock__face { fill: light-dark(#f3f4f7, #1a1c1f); stroke: light-dark(#cbced3, #393c41); stroke-width: 1.2; }
.toggle-chess-clock__tick { stroke: light-dark(#6d7179, #7d8087); stroke-width: 1.2; stroke-linecap: round; }
.toggle-chess-clock__minute { stroke: light-dark(#50535b, #c6c8cd); stroke-width: 1.8; stroke-linecap: round; }
.toggle-chess-clock__second { stroke: light-dark(#373a40, #f1f2f3); stroke-width: 1; stroke-linecap: round; transform-box: view-box; transform-origin: var(--o); animation: toggle-chess-clock-tick 8s steps(60) infinite paused; }
.toggle-chess-clock__pin { fill: light-dark(#373a40, #f1f2f3); }
.toggle-chess-clock__stem { fill: light-dark(#cbced3, #393c41); }
.toggle-chess-clock__cap { fill: light-dark(#5f626a, #a7abb2); }
/* Pressing one plunger lifts the other: its own clock stops, the other side's starts. */
.toggle-chess-clock__plunger { transition: transform 0.18s cubic-bezier(0.3, 1.5, 0.5, 1); }
.toggle-chess-clock__plunger--b { transform: translateY(5px); }
.toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__plunger--a { transform: translateY(5px); }
.toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__plunger--b { transform: none; }
.toggle-chess-clock__second--a,
.toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__second--b { animation-play-state: running; }
.toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__second--a { animation-play-state: paused; }
.toggle-chess-clock__flag { fill: light-dark(#50535b, #c6c8cd); opacity: 0; transition: opacity 0.2s; }
.toggle-chess-clock__flag--a, .toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__flag--b { opacity: 1; }
.toggle-chess-clock__input:checked + .toggle-chess-clock__art .toggle-chess-clock__flag--a { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
.toggle-chess-clock__second { animation: none; }
.toggle-chess-clock__plunger { transition: none; }
}Goal: Create a "Chess clock" toggle switch.
Visual behavior: A whose-turn switch drawn as a chess clock: the plungers rock across, one clock stops dead and the other starts ticking from where it was, its flag standing up.
Technique:
- A native checkbox with role="switch", visually hidden but focusable, sits inside a <label> before an aria-hidden drawing and the visible text. Every on state is styled from the input's :checked state through the adjacent sibling combinator, so Space and clicks both work.
- Both second hands run the same looping keyframe, and animation-play-state pauses one and runs the other, so a stopped hand keeps its place instead of snapping back.
- The plungers swap heights with a transition and the flag stands up on the clock that is running. Pure CSS.
Accessibility: A native checkbox with role="switch" inside a <label>, so Space toggles it and the visible text names it; :focus-visible on the input outlines the drawing, which is aria-hidden. Under prefers-reduced-motion the hands stay still.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: neutral only. Black, white and grays, no hues. Use the color values above as written, or my project's closest neutral tokens.
Return a single self-contained component. No dependencies.Goal: Create a "Chess clock" toggle switch.
Visual behavior: A whose-turn switch drawn as a chess clock: the plungers rock across, one clock stops dead and the other starts ticking from where it was, its flag standing up.
Technique:
- A native checkbox with role="switch", visually hidden but focusable, sits inside a <label> before an aria-hidden drawing and the visible text. Every on state is styled from the input's :checked state through the adjacent sibling combinator, so Space and clicks both work.
- Both second hands run the same looping keyframe, and animation-play-state pauses one and runs the other, so a stopped hand keeps its place instead of snapping back.
- The plungers swap heights with a transition and the flag stands up on the clock that is running. Pure CSS.
Accessibility: A native checkbox with role="switch" inside a <label>, so Space toggles it and the visible text names it; :focus-visible on the input outlines the drawing, which is aria-hidden. Under prefers-reduced-motion the hands stay still.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: use my project's existing color tokens. The color values above are grayscale placeholders. Map each one by its role (surface, text, border, accent, glow) to my theme, and keep their relative lightness so contrast and the effect still read.
Return a single self-contained component. No dependencies.Paste into Claude, ChatGPT or Cursor after filling in {{USER_STACK}}, {{USER_STYLING}} and {{USER_CONSTRAINTS}}. Neutral keeps the greys you see here; Match my project asks the model to map them onto your own colour tokens.
Free to use in any project, commercial included. MIT-0, no attribution required. Licence
About this component
A whose-turn switch drawn as a chess clock: the plungers rock across, one clock stops dead and the other starts ticking from where it was, its flag standing up.
- Dependencies
- None
- Difficulty
- Intermediate
- Triggers
- click, state change, loop
- Techniques
- keyframes, css transition, svg
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
A native checkbox with role="switch" inside a <label>, so Space toggles it and the visible text names it; :focus-visible on the input outlines the drawing, which is aria-hidden. Under prefers-reduced-motion the hands stay still.