Orbit
import { useState } from 'react';
const css = `
.toggle-orbit { position: relative; display: inline-flex; align-items: center; gap: 12px; color: light-dark(#42454c, #e5e6e9); font-size: 14px; cursor: pointer; user-select: none; }
.toggle-orbit__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-orbit__track { position: relative; width: 84px; height: 40px; border-radius: 999px; }
.toggle-orbit__input:focus-visible + .toggle-orbit__track { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 3px; }
.toggle-orbit__ring { position: absolute; left: 10px; right: 10px; top: 6px; bottom: 6px; border-radius: 50%; border: 1px dashed light-dark(#cbced3, #393c41); }
.toggle-orbit__star {
position: absolute;
left: 50%;
top: 50%;
width: 10px;
height: 10px;
margin: -5px 0 0 -5px;
border-radius: 50%;
background: light-dark(#6d7179, #7d8087);
transition: background-color 0.4s 0.6s, box-shadow 0.4s 0.6s;
}
/* The planet rides the orbit: each press sends it half a lap further, always forward. */
.toggle-orbit__planet {
position: absolute;
left: 0;
top: 0;
width: 14px;
height: 14px;
border-radius: 50%;
background: radial-gradient(circle at 35% 30%, light-dark(#373a40, #f1f2f3), light-dark(#5f626a, #a7abb2) 80%);
offset-path: path('M10 20 A32 14 0 1 1 74 20 A32 14 0 1 1 10 20 Z');
offset-rotate: 0deg;
transition: offset-distance 0.9s cubic-bezier(0.45, 0, 0.3, 1);
}
.toggle-orbit__input:checked + .toggle-orbit__track .toggle-orbit__star { background: light-dark(#373a40, #f1f2f3); box-shadow: 0 0 10px 2px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 55%, transparent); }
.toggle-orbit__input:checked + .toggle-orbit__track .toggle-orbit__ring { border-color: light-dark(#5f626a, #a7abb2); }
@media (prefers-reduced-motion: reduce) {
.toggle-orbit__track * { transition-duration: 0.01s !important; transition-delay: 0s !important; }
}
`;
export function OrbitToggle() {
const [laps, setLaps] = useState(0);
return (
<>
<style>
{css}
</style>
<label className="toggle-orbit">
<input className="toggle-orbit__input" type="checkbox" role="switch" onChange={() => setLaps((n) => n + 1)} />
<span className="toggle-orbit__track" aria-hidden="true">
<span className="toggle-orbit__ring" />
<span className="toggle-orbit__star" />
<span className="toggle-orbit__planet" style={{ offsetDistance: `${laps * 50}%` }} />
</span>
Orbit sync
</label>
</>
);
}Markup where the component goes, CSS in your stylesheet, script before </body>. In Vue, Svelte or Angular, call initToggleOrbit(el) on the mounted element instead.
<label class="toggle-orbit"><input class="toggle-orbit__input" type="checkbox" role="switch"/><span class="toggle-orbit__track" aria-hidden="true"><span class="toggle-orbit__ring"></span><span class="toggle-orbit__star"></span><span class="toggle-orbit__planet" style="offset-distance:0%"></span></span>Orbit sync</label>.toggle-orbit { position: relative; display: inline-flex; align-items: center; gap: 12px; color: light-dark(#42454c, #e5e6e9); font-size: 14px; cursor: pointer; user-select: none; }
.toggle-orbit__input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.toggle-orbit__track { position: relative; width: 84px; height: 40px; border-radius: 999px; }
.toggle-orbit__input:focus-visible + .toggle-orbit__track { outline: 2px solid light-dark(#5f626a, #a7abb2); outline-offset: 3px; }
.toggle-orbit__ring { position: absolute; left: 10px; right: 10px; top: 6px; bottom: 6px; border-radius: 50%; border: 1px dashed light-dark(#cbced3, #393c41); }
.toggle-orbit__star {
position: absolute;
left: 50%;
top: 50%;
width: 10px;
height: 10px;
margin: -5px 0 0 -5px;
border-radius: 50%;
background: light-dark(#6d7179, #7d8087);
transition: background-color 0.4s 0.6s, box-shadow 0.4s 0.6s;
}
/* The planet rides the orbit: each press sends it half a lap further, always forward. */
.toggle-orbit__planet {
position: absolute;
left: 0;
top: 0;
width: 14px;
height: 14px;
border-radius: 50%;
background: radial-gradient(circle at 35% 30%, light-dark(#373a40, #f1f2f3), light-dark(#5f626a, #a7abb2) 80%);
offset-path: path('M10 20 A32 14 0 1 1 74 20 A32 14 0 1 1 10 20 Z');
offset-rotate: 0deg;
transition: offset-distance 0.9s cubic-bezier(0.45, 0, 0.3, 1);
}
.toggle-orbit__input:checked + .toggle-orbit__track .toggle-orbit__star { background: light-dark(#373a40, #f1f2f3); box-shadow: 0 0 10px 2px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 55%, transparent); }
.toggle-orbit__input:checked + .toggle-orbit__track .toggle-orbit__ring { border-color: light-dark(#5f626a, #a7abb2); }
@media (prefers-reduced-motion: reduce) {
.toggle-orbit__track * { transition-duration: 0.01s !important; transition-delay: 0s !important; }
}function initToggleOrbit(root) {
var input = root.querySelector('input');
var planet = root.querySelector('.toggle-orbit__planet');
if (!input || !planet) return;
// Each press sends the planet half a lap further; a closed path wraps, so it always moves forward.
var laps = 0;
function change() {
laps += 1;
planet.style.offsetDistance = laps * 50 + '%';
}
input.addEventListener('change', change);
return function () { input.removeEventListener('change', change); };
}
document.querySelectorAll('.toggle-orbit').forEach(initToggleOrbit);Goal: Create a "Orbit" toggle.
Visual behavior: An orbit switch: the planet travels half an orbit round the star with every press, always moving forward, and the star lights up when the switch is on.
Technique:
- A real native checkbox with role="switch" (visually hidden: absolute, 1x1, opacity 0) inside a <label>; every visual is aria-hidden and styled from the input with :checked + sibling selectors.
- The planet uses offset-path on an elliptical closed path; offset-distance transitions with an ease.
- A press counter sets offset-distance to count x 50%. A closed path wraps distances past 100%, so the planet always moves forward, over the top on and under the bottom off.
Accessibility: A native checkbox with role="switch" inside a <label>, so Space toggles it and screen readers announce a switch; the visible label names it and :focus-visible on the input outlines the visual. Under prefers-reduced-motion it jumps.
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 "Orbit" toggle.
Visual behavior: An orbit switch: the planet travels half an orbit round the star with every press, always moving forward, and the star lights up when the switch is on.
Technique:
- A real native checkbox with role="switch" (visually hidden: absolute, 1x1, opacity 0) inside a <label>; every visual is aria-hidden and styled from the input with :checked + sibling selectors.
- The planet uses offset-path on an elliptical closed path; offset-distance transitions with an ease.
- A press counter sets offset-distance to count x 50%. A closed path wraps distances past 100%, so the planet always moves forward, over the top on and under the bottom off.
Accessibility: A native checkbox with role="switch" inside a <label>, so Space toggles it and screen readers announce a switch; the visible label names it and :focus-visible on the input outlines the visual. Under prefers-reduced-motion it jumps.
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
An orbit switch: the planet travels half an orbit round the star with every press, always moving forward, and the star lights up when the switch is on.
- Dependencies
- None
- Difficulty
- Intermediate
- Triggers
- click, state change, glow
- Techniques
- css transition, state machine
- 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 screen readers announce a switch; the visible label names it and :focus-visible on the input outlines the visual. Under prefers-reduced-motion it jumps.