UI Components Easy
iOS-style Toggle Switch
A smooth iOS-inspired toggle switch built with CSS. Supports checked state, disabled state, label association, and a custom color accent. Zero JavaScript.
Open in Lab
MCP
css checkbox css-variables
Targets: HTML React Native
Expo Snack
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0f172a;
color: #f1f5f9;
min-height: 100vh;
display: grid;
place-items: center;
padding: 2rem;
}
.demo {
width: min(420px, 100%);
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 1.25rem;
padding: 2rem;
}
.demo-title {
font-size: 1rem;
font-weight: 600;
color: #64748b;
margin-bottom: 1.5rem;
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.75rem;
}
.toggle-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 0;
}
.toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1rem 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.toggle-row:last-child {
border-bottom: none;
}
.toggle-info {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.toggle-label {
font-size: 0.9rem;
font-weight: 500;
color: #e2e8f0;
}
.toggle-desc {
font-size: 0.8rem;
color: #475569;
}
/* โโ Toggle component โโ */
.toggle {
--toggle-color: #38bdf8;
--toggle-w: 2.75rem;
--toggle-h: 1.5rem;
--thumb-size: 1.125rem;
--thumb-offset: calc((var(--toggle-h) - var(--thumb-size)) / 2);
position: relative;
display: inline-block;
flex-shrink: 0;
cursor: pointer;
}
/* Hide native checkbox */
.toggle input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
/* Track */
.toggle__track {
display: block;
width: var(--toggle-w);
height: var(--toggle-h);
background: rgba(255, 255, 255, 0.1);
border-radius: 999px;
transition: background 0.25s ease;
position: relative;
}
/* Thumb */
.toggle__thumb {
position: absolute;
top: var(--thumb-offset);
left: var(--thumb-offset);
width: var(--thumb-size);
height: var(--thumb-size);
background: #fff;
border-radius: 50%;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
transition: transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.25s ease;
}
/* Checked state */
.toggle input:checked + .toggle__track {
background: var(--toggle-color);
}
.toggle input:checked + .toggle__track .toggle__thumb {
transform: translateX(calc(var(--toggle-w) - var(--thumb-size) - var(--thumb-offset) * 2));
}
/* Focus ring */
.toggle input:focus-visible + .toggle__track {
outline: 2px solid var(--toggle-color);
outline-offset: 2px;
}
/* Disabled state */
.toggle input:disabled + .toggle__track {
opacity: 0.35;
cursor: not-allowed;
}
.toggle input:disabled ~ * {
cursor: not-allowed;
}
.toggle:has(input:disabled) {
cursor: not-allowed;
}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iOS-style Toggle Switch</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h2 class="demo-title">Toggle Switches</h2>
<ul class="toggle-list">
<!-- Default off -->
<li class="toggle-row">
<div class="toggle-info">
<span class="toggle-label">Dark Mode</span>
<span class="toggle-desc">Switch to dark color scheme</span>
</div>
<label class="toggle">
<input type="checkbox" />
<span class="toggle__track" aria-hidden="true"><span class="toggle__thumb"></span></span>
</label>
</li>
<!-- Default on -->
<li class="toggle-row">
<div class="toggle-info">
<span class="toggle-label">Notifications</span>
<span class="toggle-desc">Receive push notifications</span>
</div>
<label class="toggle">
<input type="checkbox" checked />
<span class="toggle__track" aria-hidden="true"><span class="toggle__thumb"></span></span>
</label>
</li>
<!-- Custom color -->
<li class="toggle-row">
<div class="toggle-info">
<span class="toggle-label">Auto-save</span>
<span class="toggle-desc">Save changes automatically</span>
</div>
<label class="toggle" style="--toggle-color: #a855f7">
<input type="checkbox" checked />
<span class="toggle__track" aria-hidden="true"><span class="toggle__thumb"></span></span>
</label>
</li>
<!-- Disabled -->
<li class="toggle-row">
<div class="toggle-info">
<span class="toggle-label">Beta Features</span>
<span class="toggle-desc">Requires Pro plan</span>
</div>
<label class="toggle">
<input type="checkbox" disabled />
<span class="toggle__track" aria-hidden="true"><span class="toggle__thumb"></span></span>
</label>
</li>
</ul>
</div>
</body>
</html>import React, { useRef, useCallback } from "react";
import { View, TouchableOpacity, Animated, StyleSheet, Text } from "react-native";
const TRACK_WIDTH = 52;
const TRACK_HEIGHT = 32;
const THUMB_SIZE = 28;
const THUMB_OFFSET = 2;
const TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2;
interface ToggleSwitchProps {
value: boolean;
onValueChange: (val: boolean) => void;
disabled?: boolean;
color?: string;
}
function ToggleSwitch({
value,
onValueChange,
disabled = false,
color = "#34C759",
}: ToggleSwitchProps) {
const anim = useRef(new Animated.Value(value ? 1 : 0)).current;
const toggle = useCallback(() => {
if (disabled) return;
const next = !value;
Animated.spring(anim, {
toValue: next ? 1 : 0,
useNativeDriver: false,
friction: 8,
tension: 60,
}).start();
onValueChange(next);
}, [value, disabled, anim, onValueChange]);
const translateX = anim.interpolate({
inputRange: [0, 1],
outputRange: [THUMB_OFFSET, THUMB_OFFSET + TRAVEL],
});
const trackColor = anim.interpolate({
inputRange: [0, 1],
outputRange: ["#767577", color],
});
return (
<TouchableOpacity
activeOpacity={0.8}
onPress={toggle}
disabled={disabled}
style={{ opacity: disabled ? 0.4 : 1 }}
>
<Animated.View style={[styles.track, { backgroundColor: trackColor }]}>
<Animated.View style={[styles.thumb, { transform: [{ translateX }] }]} />
</Animated.View>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
track: {
width: TRACK_WIDTH,
height: TRACK_HEIGHT,
borderRadius: TRACK_HEIGHT / 2,
justifyContent: "center",
},
thumb: {
width: THUMB_SIZE,
height: THUMB_SIZE,
borderRadius: THUMB_SIZE / 2,
backgroundColor: "#fff",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 2.5,
elevation: 4,
},
});
/* โโ Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
export default function App() {
const [v1, setV1] = React.useState(false);
const [v2, setV2] = React.useState(true);
const [v3] = React.useState(false);
return (
<View style={demo.container}>
<Text style={demo.heading}>Toggle Switch</Text>
<View style={demo.row}>
<Text style={demo.label}>Default</Text>
<ToggleSwitch value={v1} onValueChange={setV1} />
</View>
<View style={demo.row}>
<Text style={demo.label}>Custom color</Text>
<ToggleSwitch value={v2} onValueChange={setV2} color="#6366F1" />
</View>
<View style={demo.row}>
<Text style={demo.label}>Disabled</Text>
<ToggleSwitch value={v3} onValueChange={() => {}} disabled />
</View>
</View>
);
}
const demo = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0f172a",
justifyContent: "center",
alignItems: "center",
padding: 24,
},
heading: {
color: "#fff",
fontSize: 22,
fontWeight: "700",
marginBottom: 32,
},
row: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
width: 220,
marginBottom: 24,
},
label: {
color: "#94a3b8",
fontSize: 16,
},
});iOS-style Toggle Switch
A polished toggle switch that mirrors the iOS UISwitch aesthetic, built entirely with CSS โ no JavaScript required. Uses a hidden <input type="checkbox"> and a styled <label> for full native semantics and accessibility.
How it works
The actual <input type="checkbox"> is visually hidden but focusable. The <label> renders the track and thumb using ::before and ::after pseudo-elements. The :checked state drives color and thumb position via CSS transitions.
States
- Off โ grey track, thumb on the left
- On โ accent-colored track, thumb slides right with
cubic-bezierspring feel - Disabled โ reduced opacity,
cursor: not-allowed - Focus-visible โ accessible focus ring
Customization
Change --toggle-color CSS variable to recolor the active state instantly.
When to use it
- Settings panels
- Feature flags and preferences
- Dark mode toggles