OpenSourceUIOpenSourceUIOpensource UI
GitHub—
  1. Home
  2. Components
  3. Buttons
  4. Slate Chip Switch
Back to ButtonsComponents / Buttons

Slate Chip Switch

Free Buttons React component — SlateChipSwitch. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.

Smooth pill chip switch with a white thumb and sky on-state fill — a cleaner alternative to textured toggles.

Preview

Setup

How to use

Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.

  1. 1

    Run in your terminal:

    $npm install clsx tailwind-merge lucide-react
  2. 2

    Copy lib/cn.ts below. Skip if you already have cn().

  3. 3

    Copy the code below and create components/buttons/slate-chip-switch.tsx in your project.

  4. 4

    Import and render:

    Example

    import { SlateChipSwitch } from "@/components/buttons/slate-chip-switch";

    <SlateChipSwitch defaultChecked onCheckedChange={setMode} label="Enable mode" />

lib/cn.ts

1
2
3
4
5
6
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

components/buttons/slate-chip-switch.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
"use client";
 
import {
forwardRef,
useState,
type ChangeEvent,
type ComponentPropsWithoutRef,
} from "react";
 
import { cn } from "@/lib/cn";
 
export type SlateChipSwitchProps = Readonly<
{
checked?: boolean;
defaultChecked?: boolean;
onCheckedChange?: (checked: boolean) => void;
label?: string;
} & Omit<
ComponentPropsWithoutRef<"input">,
"type" | "checked" | "defaultChecked" | "onChange"
>
>;
 
const CHIP_FRAME =
"relative inline-flex items-center rounded-full p-1 text-xl bg-linear-to-b from-[#eceff3] to-[#d9dee5] shadow-[0_1px_1px_rgba(255,255,255,0.75)]";
 
const CHIP_TRACK =
"relative h-[1.45em] w-[2.85em] rounded-full bg-[#c7ced8] shadow-[inset_0_1px_3px_rgba(0,0,0,0.18)] transition-[background-color,box-shadow] duration-300 ease-out motion-reduce:transition-none peer-checked:bg-sky-500 peer-checked:shadow-[inset_0_1px_3px_rgba(0,0,0,0.12),0_0_0_1px_rgba(14,116,144,0.25)] peer-checked:[&>span]:translate-x-[1.35em]";
 
const CHIP_THUMB =
"absolute left-[0.08em] top-1/2 size-[1.22em] -translate-y-1/2 rounded-full bg-white shadow-[0_2px_5px_rgba(0,0,0,0.22),inset_0_1px_0_rgba(255,255,255,0.95)] transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none";
 
export const SlateChipSwitch = forwardRef<
HTMLInputElement,
SlateChipSwitchProps
>(
(
{
className,
checked,
defaultChecked = false,
onCheckedChange,
label = "Enable mode",
disabled,
id,
...props
},
ref,
) => {
const [internalChecked, setInternalChecked] = useState(defaultChecked);
const isControlled = checked !== undefined;
const isOn = isControlled ? checked : internalChecked;
 
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const next = event.target.checked;
if (!isControlled) {
setInternalChecked(next);
}
onCheckedChange?.(next);
};
 
return (
<label
data-slot="slate-chip-switch"
data-state={isOn ? "on" : "off"}
className={cn(
CHIP_FRAME,
"has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-sky-400",
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
className,
)}
>
<input
ref={ref}
id={id}
type="checkbox"
checked={isControlled ? checked : undefined}
defaultChecked={isControlled ? undefined : defaultChecked}
disabled={disabled}
aria-label={label}
onChange={handleChange}
className="peer absolute inset-0 z-10 h-full w-full cursor-pointer appearance-none opacity-0 ring-0 outline-none focus:ring-0 disabled:cursor-not-allowed"
{...props}
/>
 
<span aria-hidden="true" data-layer="chip-track" className={CHIP_TRACK}>
<span data-layer="chip-thumb" className={CHIP_THUMB} />
</span>
</label>
);
},
);
 
SlateChipSwitch.displayName = "SlateChipSwitch";
PreviousNext

On this

page

Jump to a section on this page.

  • lib/cn.ts
  • components/buttons/slate-chip-switch.tsx
Platinum slotAvailable

Feature your product here.

Sticky card next to every component — highest-intent placement.

Claim this card