Gate
const css = `
.toggle-gate { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
.toggle-gate__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-gate__art { display: block; width: 136px; height: 66px; overflow: visible; border-radius: 8px; }
.toggle-gate__input:focus-visible + .toggle-gate__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
.toggle-gate__road { fill: light-dark(#dbdde1, #2e3035); }
.toggle-gate__line { fill: light-dark(#ebedf0, #232529); }
.toggle-gate__post { fill: light-dark(#dbdde1, #2e3035); }
.toggle-gate__box { fill: light-dark(#cbced3, #393c41); }
.toggle-gate__fork { fill: light-dark(#cbced3, #393c41); }
.toggle-gate__hub { fill: light-dark(#5f626a, #a7abb2); }
.toggle-gate__weight { fill: light-dark(#6d7179, #7d8087); }
.toggle-gate__boom { fill: light-dark(#373a40, #f1f2f3); }
.toggle-gate__stripe { fill: light-dark(#6d7179, #7d8087); }
.toggle-gate__lamp { fill: light-dark(#cbced3, #393c41); transition: fill 0.25s, filter 0.25s; }
/* Down, the arm drops onto its rest and gives a little; up, it swings past and settles back. */
.toggle-gate__arm { transform-box: view-box; transform-origin: 15px 26px; transition: transform 0.6s cubic-bezier(0.55, 0, 0.6, 1.25); }
.toggle-gate__input:checked + .toggle-gate__art .toggle-gate__arm { transform: rotate(-80deg); transition: transform 0.9s cubic-bezier(0.3, 1.35, 0.55, 1); }
.toggle-gate__input:checked + .toggle-gate__art .toggle-gate__lamp { fill: light-dark(#373a40, #f1f2f3); filter: drop-shadow(0 0 3px light-dark(#373a40, #f1f2f3)); transition-delay: 0.35s; }
@media (prefers-reduced-motion: reduce) {
.toggle-gate__arm, .toggle-gate__input:checked + .toggle-gate__art .toggle-gate__arm { transition: none; }
}
`;
export function GateToggle() {
return (
<>
<style>
{css}
</style>
<label className="toggle-gate">
<input className="toggle-gate__input" type="checkbox" role="switch" />
<svg className="toggle-gate__art" viewBox="0 0 136 66" aria-hidden="true">
<rect className="toggle-gate__road" x="0" y="58" width="136" height="4" rx="2" />
{[36, 60, 84, 108].map((x) => (
<rect key={x} className="toggle-gate__line" x={x} y="59.5" width="12" height="1" />
))}
<path className="toggle-gate__fork" d="M124 58V35H122V32H129V35H127V58Z" />
<rect className="toggle-gate__post" x="8" y="26" width="14" height="32" rx="2" />
<g className="toggle-gate__arm">
<rect className="toggle-gate__weight" x="1" y="22.5" width="11" height="7" rx="1.5" />
<rect className="toggle-gate__boom" x="15" y="23.5" width="113" height="5" rx="2.5" />
{[24, 48, 72, 96, 120].map((x) => (
<rect key={x} className="toggle-gate__stripe" x={x} y="23.5" width="12" height="5" />
))}
</g>
<rect className="toggle-gate__box" x="6" y="20" width="18" height="12" rx="3" />
<circle className="toggle-gate__hub" cx="15" cy="26" r="3" />
<rect className="toggle-gate__box" x="12.5" y="14" width="5" height="6" />
<circle className="toggle-gate__lamp" cx="15" cy="11" r="4" />
</svg>
<span>Guest access</span>
</label>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
<label class="toggle-gate">
<input class="toggle-gate__input" type="checkbox" role="switch"/>
<svg class="toggle-gate__art" viewBox="0 0 136 66" aria-hidden="true">
<rect class="toggle-gate__road" x="0" y="58" width="136" height="4" rx="2"></rect>
<rect class="toggle-gate__line" x="36" y="59.5" width="12" height="1"></rect>
<rect class="toggle-gate__line" x="60" y="59.5" width="12" height="1"></rect>
<rect class="toggle-gate__line" x="84" y="59.5" width="12" height="1"></rect>
<rect class="toggle-gate__line" x="108" y="59.5" width="12" height="1"></rect>
<path class="toggle-gate__fork" d="M124 58V35H122V32H129V35H127V58Z"></path>
<rect class="toggle-gate__post" x="8" y="26" width="14" height="32" rx="2"></rect>
<g class="toggle-gate__arm">
<rect class="toggle-gate__weight" x="1" y="22.5" width="11" height="7" rx="1.5"></rect>
<rect class="toggle-gate__boom" x="15" y="23.5" width="113" height="5" rx="2.5"></rect>
<rect class="toggle-gate__stripe" x="24" y="23.5" width="12" height="5"></rect>
<rect class="toggle-gate__stripe" x="48" y="23.5" width="12" height="5"></rect>
<rect class="toggle-gate__stripe" x="72" y="23.5" width="12" height="5"></rect>
<rect class="toggle-gate__stripe" x="96" y="23.5" width="12" height="5"></rect>
<rect class="toggle-gate__stripe" x="120" y="23.5" width="12" height="5"></rect>
</g>
<rect class="toggle-gate__box" x="6" y="20" width="18" height="12" rx="3"></rect>
<circle class="toggle-gate__hub" cx="15" cy="26" r="3"></circle>
<rect class="toggle-gate__box" x="12.5" y="14" width="5" height="6"></rect>
<circle class="toggle-gate__lamp" cx="15" cy="11" r="4"></circle>
</svg>
<span>Guest access</span>
</label>.toggle-gate { display: inline-flex; align-items: center; gap: 14px; color: light-dark(#42454c, #e5e6e9); font-size: 15px; cursor: pointer; user-select: none; }
.toggle-gate__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-gate__art { display: block; width: 136px; height: 66px; overflow: visible; border-radius: 8px; }
.toggle-gate__input:focus-visible + .toggle-gate__art { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 4px; }
.toggle-gate__road { fill: light-dark(#dbdde1, #2e3035); }
.toggle-gate__line { fill: light-dark(#ebedf0, #232529); }
.toggle-gate__post { fill: light-dark(#dbdde1, #2e3035); }
.toggle-gate__box { fill: light-dark(#cbced3, #393c41); }
.toggle-gate__fork { fill: light-dark(#cbced3, #393c41); }
.toggle-gate__hub { fill: light-dark(#5f626a, #a7abb2); }
.toggle-gate__weight { fill: light-dark(#6d7179, #7d8087); }
.toggle-gate__boom { fill: light-dark(#373a40, #f1f2f3); }
.toggle-gate__stripe { fill: light-dark(#6d7179, #7d8087); }
.toggle-gate__lamp { fill: light-dark(#cbced3, #393c41); transition: fill 0.25s, filter 0.25s; }
/* Down, the arm drops onto its rest and gives a little; up, it swings past and settles back. */
.toggle-gate__arm { transform-box: view-box; transform-origin: 15px 26px; transition: transform 0.6s cubic-bezier(0.55, 0, 0.6, 1.25); }
.toggle-gate__input:checked + .toggle-gate__art .toggle-gate__arm { transform: rotate(-80deg); transition: transform 0.9s cubic-bezier(0.3, 1.35, 0.55, 1); }
.toggle-gate__input:checked + .toggle-gate__art .toggle-gate__lamp { fill: light-dark(#373a40, #f1f2f3); filter: drop-shadow(0 0 3px light-dark(#373a40, #f1f2f3)); transition-delay: 0.35s; }
@media (prefers-reduced-motion: reduce) {
.toggle-gate__arm, .toggle-gate__input:checked + .toggle-gate__art .toggle-gate__arm { transition: none; }
}Goal: Create a "Gate" toggle switch.
Visual behavior: A guest access switch drawn as a barrier gate: the striped arm swings up past upright and settles back as the lamp on the post lights; off drops it onto its rest with a little give.
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 arm is an SVG group turned about its pivot, with one overshooting curve for lifting and another for dropping, and the lamp fades on with it. 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 arm 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 "Gate" toggle switch.
Visual behavior: A guest access switch drawn as a barrier gate: the striped arm swings up past upright and settles back as the lamp on the post lights; off drops it onto its rest with a little give.
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 arm is an SVG group turned about its pivot, with one overshooting curve for lifting and another for dropping, and the lamp fades on with it. 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 arm 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 guest access switch drawn as a barrier gate: the striped arm swings up past upright and settles back as the lamp on the post lights; off drops it onto its rest with a little give.
- Dependencies
- None
- Difficulty
- Beginner
- Triggers
- click, state change, spring
- 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 the arm moves at once.