Bolt
import { useState } from 'react';
const css = `
@keyframes toggle-bolt-clack { 0%, 100% { transform: translateX(0); } 30% { transform: translateX(1.5px); } 60% { transform: translateX(-0.6px); } }
.toggle-bolt { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
.toggle-bolt__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-bolt__art { display: block; width: 128px; height: 52px; overflow: visible; border-radius: 8px; }
.toggle-bolt__input:focus-visible + .toggle-bolt__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
.toggle-bolt__door { fill: light-dark(#ebedf0, #232529); }
.toggle-bolt__plate { fill: light-dark(#cbced3, #393c41); }
.toggle-bolt__screw { fill: light-dark(#6d7179, #7d8087); }
.toggle-bolt__slot { fill: light-dark(#dbdde1, #2e3035); }
.toggle-bolt__keeper { fill: light-dark(#cbced3, #393c41); }
.toggle-bolt__hole { fill: light-dark(#f3f4f7, #1a1c1f); }
.toggle-bolt__bar { fill: light-dark(#50535b, #c6c8cd); }
.toggle-bolt__stem { fill: light-dark(#5f626a, #a7abb2); }
.toggle-bolt__knob { fill: light-dark(#42454c, #e5e6e9); }
/* Locking: slide across, then the knob turns down into its notch. Unlocking runs the other way. */
.toggle-bolt__slide { transition: transform 0.4s cubic-bezier(0.5, 0, 0.3, 1) 0.2s; }
.toggle-bolt__turn { transform-box: view-box; transform-origin: 31px 24px; transition: transform 0.2s ease-in; }
.toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__slide { transform: translateX(24px); transition: transform 0.4s cubic-bezier(0.6, 0, 0.8, 0.6); }
.toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__turn { transform: scaleY(-1); transition: transform 0.22s cubic-bezier(0.4, 0, 0.6, 1.4) 0.42s; }
.toggle-bolt[data-armed] .toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__frame { animation: toggle-bolt-clack 0.25s ease-out 0.38s; }
@media (prefers-reduced-motion: reduce) {
.toggle-bolt__slide, .toggle-bolt__turn, .toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__slide, .toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__turn { transition: none; }
.toggle-bolt[data-armed] .toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__frame { animation: none; }
}
`;
export function BoltToggle() {
// The keeper only jolts once someone has slid the bolt, never on load.
const [armed, setArmed] = useState(false);
return (
<>
<style>
{css}
</style>
<label className="toggle-bolt" data-armed={armed || undefined}>
<input className="toggle-bolt__input" type="checkbox" role="switch" onChange={() => setArmed(true)} />
<svg className="toggle-bolt__art" viewBox="0 0 124 50" aria-hidden="true">
<rect className="toggle-bolt__door" x="2" y="6" width="84" height="38" rx="3" />
<g className="toggle-bolt__frame">
<rect className="toggle-bolt__door" x="89" y="6" width="33" height="38" rx="3" />
<rect className="toggle-bolt__keeper" x="93" y="16" width="20" height="16" rx="3" />
<rect className="toggle-bolt__hole" x="93" y="21" width="15" height="6" rx="1.5" />
</g>
<rect className="toggle-bolt__plate" x="12" y="14" width="56" height="20" rx="3" />
{[
[16, 18],
[64, 18],
[16, 30],
[64, 30],
].map(([x, y]) => (
<circle key={`${x}-${y}`} className="toggle-bolt__screw" cx={x} cy={y} r="1.6" />
))}
{/* The slot the knob rides in, with the notch it drops into at the locked end. */}
<path className="toggle-bolt__slot" d="M24 15H58V18H24ZM52 30H58V33H52Z" />
<g className="toggle-bolt__slide">
<rect className="toggle-bolt__bar" x="20" y="21.5" width="56" height="5" rx="2.5" />
<g className="toggle-bolt__turn">
<rect className="toggle-bolt__stem" x="29.5" y="12" width="3" height="11" rx="1" />
<circle className="toggle-bolt__knob" cx="31" cy="11" r="4" />
</g>
</g>
</svg>
<span>Lock</span>
</label>
</>
);
}Markup where the component goes, CSS in your stylesheet, script before </body>. In Vue, Svelte or Angular, call initToggleBolt(el) on the mounted element instead.
<label class="toggle-bolt">
<input class="toggle-bolt__input" type="checkbox" role="switch"/>
<svg class="toggle-bolt__art" viewBox="0 0 124 50" aria-hidden="true">
<rect class="toggle-bolt__door" x="2" y="6" width="84" height="38" rx="3"></rect>
<g class="toggle-bolt__frame">
<rect class="toggle-bolt__door" x="89" y="6" width="33" height="38" rx="3"></rect>
<rect class="toggle-bolt__keeper" x="93" y="16" width="20" height="16" rx="3"></rect>
<rect class="toggle-bolt__hole" x="93" y="21" width="15" height="6" rx="1.5"></rect>
</g>
<rect class="toggle-bolt__plate" x="12" y="14" width="56" height="20" rx="3"></rect>
<circle class="toggle-bolt__screw" cx="16" cy="18" r="1.6"></circle>
<circle class="toggle-bolt__screw" cx="64" cy="18" r="1.6"></circle>
<circle class="toggle-bolt__screw" cx="16" cy="30" r="1.6"></circle>
<circle class="toggle-bolt__screw" cx="64" cy="30" r="1.6"></circle>
<path class="toggle-bolt__slot" d="M24 15H58V18H24ZM52 30H58V33H52Z"></path>
<g class="toggle-bolt__slide">
<rect class="toggle-bolt__bar" x="20" y="21.5" width="56" height="5" rx="2.5"></rect>
<g class="toggle-bolt__turn">
<rect class="toggle-bolt__stem" x="29.5" y="12" width="3" height="11" rx="1"></rect>
<circle class="toggle-bolt__knob" cx="31" cy="11" r="4"></circle>
</g>
</g>
</svg>
<span>Lock</span>
</label>@keyframes toggle-bolt-clack { 0%, 100% { transform: translateX(0); } 30% { transform: translateX(1.5px); } 60% { transform: translateX(-0.6px); } }
.toggle-bolt { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
.toggle-bolt__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-bolt__art { display: block; width: 128px; height: 52px; overflow: visible; border-radius: 8px; }
.toggle-bolt__input:focus-visible + .toggle-bolt__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
.toggle-bolt__door { fill: light-dark(#ebedf0, #232529); }
.toggle-bolt__plate { fill: light-dark(#cbced3, #393c41); }
.toggle-bolt__screw { fill: light-dark(#6d7179, #7d8087); }
.toggle-bolt__slot { fill: light-dark(#dbdde1, #2e3035); }
.toggle-bolt__keeper { fill: light-dark(#cbced3, #393c41); }
.toggle-bolt__hole { fill: light-dark(#f3f4f7, #1a1c1f); }
.toggle-bolt__bar { fill: light-dark(#50535b, #c6c8cd); }
.toggle-bolt__stem { fill: light-dark(#5f626a, #a7abb2); }
.toggle-bolt__knob { fill: light-dark(#42454c, #e5e6e9); }
/* Locking: slide across, then the knob turns down into its notch. Unlocking runs the other way. */
.toggle-bolt__slide { transition: transform 0.4s cubic-bezier(0.5, 0, 0.3, 1) 0.2s; }
.toggle-bolt__turn { transform-box: view-box; transform-origin: 31px 24px; transition: transform 0.2s ease-in; }
.toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__slide { transform: translateX(24px); transition: transform 0.4s cubic-bezier(0.6, 0, 0.8, 0.6); }
.toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__turn { transform: scaleY(-1); transition: transform 0.22s cubic-bezier(0.4, 0, 0.6, 1.4) 0.42s; }
.toggle-bolt[data-armed] .toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__frame { animation: toggle-bolt-clack 0.25s ease-out 0.38s; }
@media (prefers-reduced-motion: reduce) {
.toggle-bolt__slide, .toggle-bolt__turn, .toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__slide, .toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__turn { transition: none; }
.toggle-bolt[data-armed] .toggle-bolt__input:checked + .toggle-bolt__art .toggle-bolt__frame { animation: none; }
}function initToggleBolt(root) {
// The motion keyframes only run once the control has been used, never on page load.
function onChange() {
root.setAttribute('data-armed', '');
}
root.addEventListener('change', onChange);
return function () {
root.removeEventListener('change', onChange);
};
}
document.querySelectorAll('.toggle-bolt').forEach(initToggleBolt);Goal: Create a "Bolt" toggle switch.
Visual behavior: A lock switch drawn as a door bolt: the bolt slides across into the keeper and its knob turns down into the notch with a clack; unlocking runs the other way.
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 bolt slides with one transition and the knob flips with scaleY after a delay, so it slides first and drops second; unlocking reverses the delays.
- Keyframes that must not play on page load hang off a data-armed attribute set on the first change. The keeper jolts as the bolt goes home.
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 bolt moves 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 "Bolt" toggle switch.
Visual behavior: A lock switch drawn as a door bolt: the bolt slides across into the keeper and its knob turns down into the notch with a clack; unlocking runs the other way.
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 bolt slides with one transition and the knob flips with scaleY after a delay, so it slides first and drops second; unlocking reverses the delays.
- Keyframes that must not play on page load hang off a data-armed attribute set on the first change. The keeper jolts as the bolt goes home.
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 bolt moves 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 lock switch drawn as a door bolt: the bolt slides across into the keeper and its knob turns down into the notch with a clack; unlocking runs the other way.
- Dependencies
- None
- Difficulty
- Beginner
- Triggers
- click, state change
- Techniques
- css transition, keyframes, 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 bolt moves at once.