{ CF }

20 Pure CSS Toggles & Switches

Timed Toggle

An SVG arc fills around a circle when the toggle activates, visualising session time. Temporal UI for progress-aware states — users see state over time, not just on/off. The ember glow signals an active countdown.

Pure CSS MIT licensed

Timed Toggle the 3rd of 20 designs in the 20 Pure CSS Toggles & Switches collection. The design is implemented in pure CSS — no JavaScript required. Copy the HTML and CSS panels below into your project. Because the demo is pure CSS, it works in any framework or templating engine you happen to use. The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.

Live preview

Open in playground

The code

<label class="tg-time">
  <input class="tg-time-input" type="checkbox" checked>
  <span class="tg-time-body" aria-hidden="true">
    <span class="tg-time-arc">
      <svg viewBox="0 0 28 28">
        <circle class="tg-time-bg" cx="14" cy="14" r="11"/>
        <circle class="tg-time-prog" cx="14" cy="14" r="11"/>
      </svg>
      <span class="tg-time-dot"></span>
    </span>
    <span class="tg-time-text">15 min · active</span>
  </span>
</label>
.tg-time {
  --tg-time-rim: #14141e;
  --tg-time-wire: #1e1e2e;
  --tg-time-fog: #3a3a52;
  --tg-time-ash: #7a7a98;
  --tg-time-ember: #ff6b35;
  display: inline-block;
  cursor: pointer;
  font-family: "Inter", "Segoe UI", system-ui, sans-serif;
  font-size: 14px;
  user-select: none;
}
.tg-time-input {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0);
  white-space: nowrap; border: 0;
}
.tg-time-body {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  padding: 10px 16px;
  border-radius: 100px;
  background: var(--tg-time-rim);
  border: 1px solid var(--tg-time-wire);
  transition: border-color 0.3s ease, background 0.3s ease;
}
.tg-time-arc {
  position: relative;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
}
.tg-time-arc svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}
.tg-time-bg {
  fill: none;
  stroke: var(--tg-time-wire);
  stroke-width: 3;
}
/* circumference at r=11 is 2*pi*11 ~= 69. We use 82 as the dasharray
   so the fill animates from "empty" (offset 82) to "nearly full" (offset
   20) — leaving a small gap signals it's still counting down, not done. */
.tg-time-prog {
  fill: none;
  stroke: var(--tg-time-ember);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 82;
  stroke-dashoffset: 82;
  transition: stroke-dashoffset 0.4s ease;
}
.tg-time-dot {
  position: absolute;
  top: 50%; left: 50%;
  width: 8px; height: 8px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--tg-time-fog);
  transition: background 0.3s ease, box-shadow 0.3s ease;
}
.tg-time-text {
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--tg-time-ash);
  transition: color 0.3s ease;
}
.tg-time-input:checked ~ .tg-time-body {
  border-color: rgba(255,107,53,0.4);
  background: rgba(255,107,53,0.05);
}
.tg-time-input:checked ~ .tg-time-body .tg-time-prog {
  stroke-dashoffset: 20;
}
.tg-time-input:checked ~ .tg-time-body .tg-time-dot {
  background: var(--tg-time-ember);
  box-shadow: 0 0 6px var(--tg-time-ember);
}
.tg-time-input:checked ~ .tg-time-body .tg-time-text {
  color: var(--tg-time-ember);
}
.tg-time-input:focus-visible ~ .tg-time-body {
  outline: 2px solid var(--tg-time-ember);
  outline-offset: 4px;
}
@media (prefers-reduced-motion: reduce) {
  .tg-time-body,
  .tg-time-prog,
  .tg-time-dot,
  .tg-time-text { transition: none; }
}

Search CodeFronts

Loading…