Jack
const css = `
@keyframes toggle-jack-pop {
0% { transform: scaleY(0.45); }
35% { transform: translateY(-8px) scaleY(1.15); }
55% { transform: translateY(3px) scaleY(0.92); }
75% { transform: translateY(-2px) scaleY(1.04); }
100% { transform: translateY(0) scaleY(1); }
}
@keyframes toggle-jack-wobble { 0% { transform: rotate(0deg); } 20% { transform: rotate(-14deg); } 40% { transform: rotate(10deg); } 60% { transform: rotate(-6deg); } 80% { transform: rotate(3deg); } 100% { transform: rotate(0deg); } }
.toggle-jack { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
.toggle-jack__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-jack__art { display: block; width: 84px; height: 100px; overflow: visible; border-radius: 8px; }
.toggle-jack__input:focus-visible + .toggle-jack__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
.toggle-jack__box { fill: light-dark(#cbced3, #393c41); }
.toggle-jack__trim { fill: light-dark(#6d7179, #7d8087); }
.toggle-jack__lid { fill: light-dark(#6d7179, #7d8087); }
.toggle-jack__crank { fill: none; stroke: light-dark(#5f626a, #a7abb2); stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.toggle-jack__knob { fill: light-dark(#50535b, #c6c8cd); }
.toggle-jack__spring { fill: none; stroke: light-dark(#5f626a, #a7abb2); stroke-width: 2; stroke-linejoin: round; }
.toggle-jack__head { fill: light-dark(#42454c, #e5e6e9); }
.toggle-jack__hat { fill: light-dark(#5f626a, #a7abb2); }
.toggle-jack__bobble { fill: light-dark(#373a40, #f1f2f3); }
.toggle-jack__feature { fill: light-dark(#ebedf0, #232529); }
.toggle-jack__smile { fill: none; stroke: light-dark(#ebedf0, #232529); stroke-width: 1.3; stroke-linecap: round; }
/* On: the crank turns twice, the lid springs open and he pops out. Off: he ducks in and the lid shuts on him. */
.toggle-jack__handle { transform-box: view-box; transform-origin: 66px 74px; transition: transform 0.2s; }
.toggle-jack__hinge { transform-box: view-box; transform-origin: 13px 52px; transition: transform 0.3s cubic-bezier(0.5, 0, 0.6, 1.6) 0.25s; }
.toggle-jack__rise { transform-box: view-box; transform-origin: 38px 88px; transform: scaleY(0.45); transition: transform 0.25s ease-in; }
.toggle-jack__sway { transform-box: view-box; transform-origin: 38px 88px; }
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__handle { transform: rotate(720deg); transition: transform 0.8s cubic-bezier(0.4, 0, 0.6, 1); }
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__hinge { transform: rotate(-118deg); transition: transform 0.35s cubic-bezier(0.3, 1.5, 0.5, 1) 0.75s; }
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__rise { transform: none; animation: toggle-jack-pop 0.6s ease-out 0.78s backwards; }
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__sway { animation: toggle-jack-wobble 1.4s ease-out 1.1s; }
@media (prefers-reduced-motion: reduce) {
.toggle-jack__handle, .toggle-jack__hinge, .toggle-jack__rise,
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__handle,
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__hinge { transition: none; }
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__rise, .toggle-jack__input:checked + .toggle-jack__art .toggle-jack__sway { animation: none; }
}
`;
export function JackToggle() {
return (
<>
<style>
{css}
</style>
<label className="toggle-jack">
<input className="toggle-jack__input" type="checkbox" role="switch" />
<svg className="toggle-jack__art" viewBox="0 0 82 98" aria-hidden="true">
<g className="toggle-jack__sway">
<g className="toggle-jack__rise">
<path className="toggle-jack__spring" d="M38 88L31 84L45 79L31 74L45 69L31 64L45 59L38 55" />
<path className="toggle-jack__hat" d="M30 40L38 22L46 40Z" />
<circle className="toggle-jack__bobble" cx="38" cy="21" r="2.5" />
<circle className="toggle-jack__head" cx="38" cy="46" r="9" />
<circle className="toggle-jack__feature" cx="34.5" cy="44.5" r="1.2" />
<circle className="toggle-jack__feature" cx="41.5" cy="44.5" r="1.2" />
<path className="toggle-jack__smile" d="M34 49Q38 52.5 42 49" />
</g>
</g>
{/* The box front is drawn over him, so while he is folded down he is out of sight. */}
<rect className="toggle-jack__box" x="14" y="54" width="48" height="38" rx="3" />
<path className="toggle-jack__trim" d="M14 64H62V67H14ZM14 79H62V82H14Z" />
<path className="toggle-jack__trim" d="M38 69L42 73L38 77L34 73Z" />
<g className="toggle-jack__hinge">
<rect className="toggle-jack__lid" x="12" y="49" width="52" height="6" rx="2" />
</g>
<g className="toggle-jack__handle">
<path className="toggle-jack__crank" d="M62 74H66V66H72" />
<circle className="toggle-jack__knob" cx="73" cy="66" r="2.5" />
</g>
</svg>
<span>Surprise</span>
</label>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
<label class="toggle-jack">
<input class="toggle-jack__input" type="checkbox" role="switch"/>
<svg class="toggle-jack__art" viewBox="0 0 82 98" aria-hidden="true">
<g class="toggle-jack__sway">
<g class="toggle-jack__rise">
<path class="toggle-jack__spring" d="M38 88L31 84L45 79L31 74L45 69L31 64L45 59L38 55"></path>
<path class="toggle-jack__hat" d="M30 40L38 22L46 40Z"></path>
<circle class="toggle-jack__bobble" cx="38" cy="21" r="2.5"></circle>
<circle class="toggle-jack__head" cx="38" cy="46" r="9"></circle>
<circle class="toggle-jack__feature" cx="34.5" cy="44.5" r="1.2"></circle>
<circle class="toggle-jack__feature" cx="41.5" cy="44.5" r="1.2"></circle>
<path class="toggle-jack__smile" d="M34 49Q38 52.5 42 49"></path>
</g>
</g>
<rect class="toggle-jack__box" x="14" y="54" width="48" height="38" rx="3"></rect>
<path class="toggle-jack__trim" d="M14 64H62V67H14ZM14 79H62V82H14Z"></path>
<path class="toggle-jack__trim" d="M38 69L42 73L38 77L34 73Z"></path>
<g class="toggle-jack__hinge">
<rect class="toggle-jack__lid" x="12" y="49" width="52" height="6" rx="2"></rect>
</g>
<g class="toggle-jack__handle">
<path class="toggle-jack__crank" d="M62 74H66V66H72"></path>
<circle class="toggle-jack__knob" cx="73" cy="66" r="2.5"></circle>
</g>
</svg>
<span>Surprise</span>
</label>@keyframes toggle-jack-pop {
0% { transform: scaleY(0.45); }
35% { transform: translateY(-8px) scaleY(1.15); }
55% { transform: translateY(3px) scaleY(0.92); }
75% { transform: translateY(-2px) scaleY(1.04); }
100% { transform: translateY(0) scaleY(1); }
}
@keyframes toggle-jack-wobble { 0% { transform: rotate(0deg); } 20% { transform: rotate(-14deg); } 40% { transform: rotate(10deg); } 60% { transform: rotate(-6deg); } 80% { transform: rotate(3deg); } 100% { transform: rotate(0deg); } }
.toggle-jack { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
.toggle-jack__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-jack__art { display: block; width: 84px; height: 100px; overflow: visible; border-radius: 8px; }
.toggle-jack__input:focus-visible + .toggle-jack__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
.toggle-jack__box { fill: light-dark(#cbced3, #393c41); }
.toggle-jack__trim { fill: light-dark(#6d7179, #7d8087); }
.toggle-jack__lid { fill: light-dark(#6d7179, #7d8087); }
.toggle-jack__crank { fill: none; stroke: light-dark(#5f626a, #a7abb2); stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.toggle-jack__knob { fill: light-dark(#50535b, #c6c8cd); }
.toggle-jack__spring { fill: none; stroke: light-dark(#5f626a, #a7abb2); stroke-width: 2; stroke-linejoin: round; }
.toggle-jack__head { fill: light-dark(#42454c, #e5e6e9); }
.toggle-jack__hat { fill: light-dark(#5f626a, #a7abb2); }
.toggle-jack__bobble { fill: light-dark(#373a40, #f1f2f3); }
.toggle-jack__feature { fill: light-dark(#ebedf0, #232529); }
.toggle-jack__smile { fill: none; stroke: light-dark(#ebedf0, #232529); stroke-width: 1.3; stroke-linecap: round; }
/* On: the crank turns twice, the lid springs open and he pops out. Off: he ducks in and the lid shuts on him. */
.toggle-jack__handle { transform-box: view-box; transform-origin: 66px 74px; transition: transform 0.2s; }
.toggle-jack__hinge { transform-box: view-box; transform-origin: 13px 52px; transition: transform 0.3s cubic-bezier(0.5, 0, 0.6, 1.6) 0.25s; }
.toggle-jack__rise { transform-box: view-box; transform-origin: 38px 88px; transform: scaleY(0.45); transition: transform 0.25s ease-in; }
.toggle-jack__sway { transform-box: view-box; transform-origin: 38px 88px; }
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__handle { transform: rotate(720deg); transition: transform 0.8s cubic-bezier(0.4, 0, 0.6, 1); }
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__hinge { transform: rotate(-118deg); transition: transform 0.35s cubic-bezier(0.3, 1.5, 0.5, 1) 0.75s; }
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__rise { transform: none; animation: toggle-jack-pop 0.6s ease-out 0.78s backwards; }
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__sway { animation: toggle-jack-wobble 1.4s ease-out 1.1s; }
@media (prefers-reduced-motion: reduce) {
.toggle-jack__handle, .toggle-jack__hinge, .toggle-jack__rise,
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__handle,
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__hinge { transition: none; }
.toggle-jack__input:checked + .toggle-jack__art .toggle-jack__rise, .toggle-jack__input:checked + .toggle-jack__art .toggle-jack__sway { animation: none; }
}Goal: Create a "Jack" toggle switch.
Visual behavior: A surprise switch drawn as a jack-in-the-box: the crank turns twice, the lid springs open and the jack pops out wobbling; off ducks him back in and shuts the lid.
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.
- The crank turns 720 degrees, then the lid swings open and the jack, folded down to almost nothing inside the box, springs up with a pop keyframe and a wobble.
- The box front is drawn over him, so while he is folded he is out of sight. 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 lid opens and he appears at once.
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 "Jack" toggle switch.
Visual behavior: A surprise switch drawn as a jack-in-the-box: the crank turns twice, the lid springs open and the jack pops out wobbling; off ducks him back in and shuts the lid.
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.
- The crank turns 720 degrees, then the lid swings open and the jack, folded down to almost nothing inside the box, springs up with a pop keyframe and a wobble.
- The box front is drawn over him, so while he is folded he is out of sight. 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 lid opens and he appears at once.
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 surprise switch drawn as a jack-in-the-box: the crank turns twice, the lid springs open and the jack pops out wobbling; off ducks him back in and shuts the lid.
- Dependencies
- None
- Difficulty
- Intermediate
- Triggers
- click, state change, spring, keyframe
- Techniques
- keyframes, svg, transform only
- 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 lid opens and he appears at once.