Shell
const css = `
.toggle-shell { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
.toggle-shell__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-shell__art { display: block; width: 112px; height: 68px; overflow: visible; border-radius: 8px; }
.toggle-shell__input:focus-visible + .toggle-shell__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
.toggle-shell__ground { fill: light-dark(#dbdde1, #2e3035); }
.toggle-shell__skin { fill: light-dark(#5f626a, #a7abb2); }
.toggle-shell__eye { fill: light-dark(#f3f4f7, #1a1c1f); }
.toggle-shell__dome { fill: light-dark(#6d7179, #7d8087); }
.toggle-shell__rim { fill: light-dark(#cbced3, #393c41); }
.toggle-shell__scute { fill: none; stroke: light-dark(#cbced3, #393c41); stroke-width: 1.4; stroke-linejoin: round; }
/* Coming out: the shell lifts, the head eases out, then the legs. Going in it all happens the other way round, faster. */
.toggle-shell__head { transition: transform 0.6s cubic-bezier(0.3, 1.3, 0.5, 1) 0.25s; }
.toggle-shell__leg { transition: transform 0.35s ease-out 0.4s; }
.toggle-shell__tail { transition: transform 0.35s ease-out 0.4s; }
.toggle-shell__body { transition: transform 0.3s ease-out; }
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__head { transform: translateX(-17px); transition: transform 0.25s cubic-bezier(0.6, 0, 0.8, 0.4) 0.05s; }
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__leg { transform: translateY(-9px); transition: transform 0.2s ease-in; }
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__tail { transform: translate(7px, -2px); transition: transform 0.2s ease-in; }
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__body { transform: translateY(5px); transition: transform 0.3s cubic-bezier(0.5, 0, 0.6, 1.4) 0.2s; }
@media (prefers-reduced-motion: reduce) {
.toggle-shell__head, .toggle-shell__leg, .toggle-shell__tail, .toggle-shell__body,
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__head,
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__leg,
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__tail,
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__body { transition: none; }
}
`;
export function ShellToggle() {
return (
<>
<style>
{css}
</style>
<label className="toggle-shell">
<input className="toggle-shell__input" type="checkbox" role="switch" />
<svg className="toggle-shell__art" viewBox="0 0 108 66" aria-hidden="true">
<rect className="toggle-shell__ground" x="4" y="61" width="100" height="3" rx="1.5" />
<g className="toggle-shell__body">
{/* Everything that tucks away is drawn first, so the shell covers it as it goes in. */}
<g className="toggle-shell__head">
<path className="toggle-shell__skin" d="M74 42Q84 30 93 33Q101 36 98 43Q95 48 86 47H74Z" />
<circle className="toggle-shell__eye" cx="92" cy="38" r="1.4" />
</g>
<path className="toggle-shell__skin toggle-shell__tail" d="M28 46L17 49L28 50Z" />
<rect className="toggle-shell__skin toggle-shell__leg" x="33" y="44" width="9" height="17" rx="4" />
<rect className="toggle-shell__skin toggle-shell__leg" x="64" y="44" width="9" height="17" rx="4" />
<path className="toggle-shell__dome" d="M26 47C26 20 80 20 82 47Z" />
<path className="toggle-shell__scute" d="M36 46L40 34L54 29L68 34L72 46M40 34L32 40M68 34L77 40M54 29V22M40 34L48 46M68 34L60 46" />
<rect className="toggle-shell__rim" x="23" y="45" width="62" height="5" rx="2.5" />
</g>
</svg>
<span>Private</span>
</label>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
<label class="toggle-shell">
<input class="toggle-shell__input" type="checkbox" role="switch"/>
<svg class="toggle-shell__art" viewBox="0 0 108 66" aria-hidden="true">
<rect class="toggle-shell__ground" x="4" y="61" width="100" height="3" rx="1.5"></rect>
<g class="toggle-shell__body">
<g class="toggle-shell__head">
<path class="toggle-shell__skin" d="M74 42Q84 30 93 33Q101 36 98 43Q95 48 86 47H74Z"></path>
<circle class="toggle-shell__eye" cx="92" cy="38" r="1.4"></circle>
</g>
<path class="toggle-shell__skin toggle-shell__tail" d="M28 46L17 49L28 50Z"></path>
<rect class="toggle-shell__skin toggle-shell__leg" x="33" y="44" width="9" height="17" rx="4"></rect>
<rect class="toggle-shell__skin toggle-shell__leg" x="64" y="44" width="9" height="17" rx="4"></rect>
<path class="toggle-shell__dome" d="M26 47C26 20 80 20 82 47Z"></path>
<path class="toggle-shell__scute" d="M36 46L40 34L54 29L68 34L72 46M40 34L32 40M68 34L77 40M54 29V22M40 34L48 46M68 34L60 46"></path>
<rect class="toggle-shell__rim" x="23" y="45" width="62" height="5" rx="2.5"></rect>
</g>
</svg>
<span>Private</span>
</label>.toggle-shell { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
.toggle-shell__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-shell__art { display: block; width: 112px; height: 68px; overflow: visible; border-radius: 8px; }
.toggle-shell__input:focus-visible + .toggle-shell__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
.toggle-shell__ground { fill: light-dark(#dbdde1, #2e3035); }
.toggle-shell__skin { fill: light-dark(#5f626a, #a7abb2); }
.toggle-shell__eye { fill: light-dark(#f3f4f7, #1a1c1f); }
.toggle-shell__dome { fill: light-dark(#6d7179, #7d8087); }
.toggle-shell__rim { fill: light-dark(#cbced3, #393c41); }
.toggle-shell__scute { fill: none; stroke: light-dark(#cbced3, #393c41); stroke-width: 1.4; stroke-linejoin: round; }
/* Coming out: the shell lifts, the head eases out, then the legs. Going in it all happens the other way round, faster. */
.toggle-shell__head { transition: transform 0.6s cubic-bezier(0.3, 1.3, 0.5, 1) 0.25s; }
.toggle-shell__leg { transition: transform 0.35s ease-out 0.4s; }
.toggle-shell__tail { transition: transform 0.35s ease-out 0.4s; }
.toggle-shell__body { transition: transform 0.3s ease-out; }
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__head { transform: translateX(-17px); transition: transform 0.25s cubic-bezier(0.6, 0, 0.8, 0.4) 0.05s; }
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__leg { transform: translateY(-9px); transition: transform 0.2s ease-in; }
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__tail { transform: translate(7px, -2px); transition: transform 0.2s ease-in; }
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__body { transform: translateY(5px); transition: transform 0.3s cubic-bezier(0.5, 0, 0.6, 1.4) 0.2s; }
@media (prefers-reduced-motion: reduce) {
.toggle-shell__head, .toggle-shell__leg, .toggle-shell__tail, .toggle-shell__body,
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__head,
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__leg,
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__tail,
.toggle-shell__input:checked + .toggle-shell__art .toggle-shell__body { transition: none; }
}Goal: Create a "Shell" toggle switch.
Visual behavior: A private switch drawn as a turtle: on, it pulls its head, legs and tail into its shell and settles down; off, the shell lifts and it peeks back out.
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.
- Head, legs and tail are drawn under the shell and translate in with transitions, delayed in one order going in and the reverse coming out, and the shell drops as it settles. 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 it tucks in 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 "Shell" toggle switch.
Visual behavior: A private switch drawn as a turtle: on, it pulls its head, legs and tail into its shell and settles down; off, the shell lifts and it peeks back out.
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.
- Head, legs and tail are drawn under the shell and translate in with transitions, delayed in one order going in and the reverse coming out, and the shell drops as it settles. 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 it tucks in 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 private switch drawn as a turtle: on, it pulls its head, legs and tail into its shell and settles down; off, the shell lifts and it peeks back out.
- Dependencies
- None
- Difficulty
- Beginner
- Triggers
- click, state change
- Techniques
- css transition, 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 it tucks in at once.