47 CSS Toggles & Switches45 / 47
Outline
A pure-stroke toggle — track and thumb are outlines only, no fills. When toggled on, the thumb fills with brand color. Minimalist; pairs with the brutalist demo.
Published Updated
The code
<label class="tgl-45">
<input class="tgl-45__input" type="checkbox" checked>
<span class="tgl-45__track" aria-hidden="true">
<span class="tgl-45__thumb"></span>
</span>
<span class="tgl-45__label">Sync</span>
</label><label class="tgl-45">
<input class="tgl-45__input" type="checkbox" checked>
<span class="tgl-45__track" aria-hidden="true">
<span class="tgl-45__thumb"></span>
</span>
<span class="tgl-45__label">Sync</span>
</label>.tgl-45 {
font-family: "Inter", "Segoe UI", system-ui, sans-serif;
font-size: 14px;
color: #f0eeff;
min-height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 48px 20px;
background: #14141e;
box-sizing: border-box;
}
.tgl-45__input {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
}
.tgl-45__track {
position: relative;
width: 56px;
height: 28px;
background: transparent;
border: 1px solid #6a6a7a;
border-radius: 999px;
transition: border-color 0.2s ease;
box-sizing: border-box;
}
.tgl-45__thumb {
position: absolute;
top: 50%;
left: 3px;
width: 20px;
height: 20px;
border-radius: 50%;
background: transparent;
border: 1px solid #6a6a7a;
transform: translateY(-50%);
transition: left 0.2s cubic-bezier(0.4, 0, 0.2, 1), background 0.2s ease, border-color 0.2s ease;
}
.tgl-45__input:checked ~ .tgl-45__track {
border-color: #7c6cff;
}
.tgl-45__input:checked ~ .tgl-45__track .tgl-45__thumb {
/* Inner width: 56 - 2 = 54. Thumb 20. Max left: 54-20-3 = 31. */
left: 31px;
background: #7c6cff;
border-color: #7c6cff;
}
.tgl-45__input:focus-visible ~ .tgl-45__track {
outline: 2px solid #7c6cff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.tgl-45__track,
.tgl-45__thumb {
transition: none;
}
}.tgl-45 {
font-family: "Inter", "Segoe UI", system-ui, sans-serif;
font-size: 14px;
color: #f0eeff;
min-height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 48px 20px;
background: #14141e;
box-sizing: border-box;
}
.tgl-45__input {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
}
.tgl-45__track {
position: relative;
width: 56px;
height: 28px;
background: transparent;
border: 1px solid #6a6a7a;
border-radius: 999px;
transition: border-color 0.2s ease;
box-sizing: border-box;
}
.tgl-45__thumb {
position: absolute;
top: 50%;
left: 3px;
width: 20px;
height: 20px;
border-radius: 50%;
background: transparent;
border: 1px solid #6a6a7a;
transform: translateY(-50%);
transition: left 0.2s cubic-bezier(0.4, 0, 0.2, 1), background 0.2s ease, border-color 0.2s ease;
}
.tgl-45__input:checked ~ .tgl-45__track {
border-color: #7c6cff;
}
.tgl-45__input:checked ~ .tgl-45__track .tgl-45__thumb {
/* Inner width: 56 - 2 = 54. Thumb 20. Max left: 54-20-3 = 31. */
left: 31px;
background: #7c6cff;
border-color: #7c6cff;
}
.tgl-45__input:focus-visible ~ .tgl-45__track {
outline: 2px solid #7c6cff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.tgl-45__track,
.tgl-45__thumb {
transition: none;
}
}/* No JavaScript — hollow 1px-stroke track and thumb; :checked fills the thumb solid and recolors both strokes. */
/* No JavaScript — hollow 1px-stroke track and thumb; :checked fills the thumb solid and recolors both strokes. */Here's a working CSS Toggles & Switche from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Outline
Source: https://codefronts.com/components/css-toggles-switches/outline/
A pure-stroke toggle — track and thumb are outlines only, no fills. When toggled on, the thumb fills with brand color. Minimalist; pairs with the brutalist demo.
## HTML
```html
<label class="tgl-45">
<input class="tgl-45__input" type="checkbox" checked>
<span class="tgl-45__track" aria-hidden="true">
<span class="tgl-45__thumb"></span>
</span>
<span class="tgl-45__label">Sync</span>
</label>
```
## CSS
```css
.tgl-45 {
font-family: "Inter", "Segoe UI", system-ui, sans-serif;
font-size: 14px;
color: #f0eeff;
min-height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 48px 20px;
background: #14141e;
box-sizing: border-box;
}
.tgl-45__input {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
}
.tgl-45__track {
position: relative;
width: 56px;
height: 28px;
background: transparent;
border: 1px solid #6a6a7a;
border-radius: 999px;
transition: border-color 0.2s ease;
box-sizing: border-box;
}
.tgl-45__thumb {
position: absolute;
top: 50%;
left: 3px;
width: 20px;
height: 20px;
border-radius: 50%;
background: transparent;
border: 1px solid #6a6a7a;
transform: translateY(-50%);
transition: left 0.2s cubic-bezier(0.4, 0, 0.2, 1), background 0.2s ease, border-color 0.2s ease;
}
.tgl-45__input:checked ~ .tgl-45__track {
border-color: #7c6cff;
}
.tgl-45__input:checked ~ .tgl-45__track .tgl-45__thumb {
/* Inner width: 56 - 2 = 54. Thumb 20. Max left: 54-20-3 = 31. */
left: 31px;
background: #7c6cff;
border-color: #7c6cff;
}
.tgl-45__input:focus-visible ~ .tgl-45__track {
outline: 2px solid #7c6cff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.tgl-45__track,
.tgl-45__thumb {
transition: none;
}
}
```
## JavaScript
```js
/* No JavaScript — hollow 1px-stroke track and thumb; :checked fills the thumb solid and recolors both strokes. */
```Here's a working CSS Toggles & Switche from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Outline
Source: https://codefronts.com/components/css-toggles-switches/outline/
A pure-stroke toggle — track and thumb are outlines only, no fills. When toggled on, the thumb fills with brand color. Minimalist; pairs with the brutalist demo.
## HTML
```html
<label class="tgl-45">
<input class="tgl-45__input" type="checkbox" checked>
<span class="tgl-45__track" aria-hidden="true">
<span class="tgl-45__thumb"></span>
</span>
<span class="tgl-45__label">Sync</span>
</label>
```
## CSS
```css
.tgl-45 {
font-family: "Inter", "Segoe UI", system-ui, sans-serif;
font-size: 14px;
color: #f0eeff;
min-height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
padding: 48px 20px;
background: #14141e;
box-sizing: border-box;
}
.tgl-45__input {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
}
.tgl-45__track {
position: relative;
width: 56px;
height: 28px;
background: transparent;
border: 1px solid #6a6a7a;
border-radius: 999px;
transition: border-color 0.2s ease;
box-sizing: border-box;
}
.tgl-45__thumb {
position: absolute;
top: 50%;
left: 3px;
width: 20px;
height: 20px;
border-radius: 50%;
background: transparent;
border: 1px solid #6a6a7a;
transform: translateY(-50%);
transition: left 0.2s cubic-bezier(0.4, 0, 0.2, 1), background 0.2s ease, border-color 0.2s ease;
}
.tgl-45__input:checked ~ .tgl-45__track {
border-color: #7c6cff;
}
.tgl-45__input:checked ~ .tgl-45__track .tgl-45__thumb {
/* Inner width: 56 - 2 = 54. Thumb 20. Max left: 54-20-3 = 31. */
left: 31px;
background: #7c6cff;
border-color: #7c6cff;
}
.tgl-45__input:focus-visible ~ .tgl-45__track {
outline: 2px solid #7c6cff;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.tgl-45__track,
.tgl-45__thumb {
transition: none;
}
}
```
## JavaScript
```js
/* No JavaScript — hollow 1px-stroke track and thumb; :checked fills the thumb solid and recolors both strokes. */
```How this works
Stroke-only at rest: a 1px-bordered transparent 56×28 track and a 1px-bordered transparent 20px thumb, both in neutral gray. Checking fills the thumb solid brand-violet and recolors both borders — so state is double-encoded as position AND fill density, which keeps it legible in grayscale.
The slide math is annotated in-source (54px inner − 20px thumb − 3px inset = left:31px). At 1px strokes this is the lightest-touch toggle in the collection; it disappears into text-heavy UI and dense tables where a filled switch would shout.
Make it yours
- Thicken to 2px strokes for standalone use outside dense UI.
- Inherit theme color automatically by switching the grays to currentColor (see demo 24).
- Square the radii for a technical/wireframe look.
- Fill the track too on :checked if you need a louder on-state.
Gotchas — read before shipping
- 1px borders can shimmer at fractional zoom levels — 2px is safer if your users zoom.
- Keep the fill change alongside the position change; outline-only state fails color-independence.
- Transparent backgrounds mean the surface shows through — check contrast of the gray strokes on your actual background.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 1+ | 1+ | 1+ | 1+ |
Borders + transitions; works everywhere.