A Base UI switch with a cream pill groove shaded teal that warms to rose when checked, and a dark void orb for a thumb. Default and small sizes, client component.
"use client"
import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
import { cn } from "cn"
// Cloud Dancer switch, inverted: the stock Switch with the Cloud Dancer pill
// as the groove, teal-shaded, warming to rose when checked, and a void orb.
// --s-main and --s-accent are the pill's shade and rim, as rgb channels.
const TRACK =
"ring-1 ring-foreground/10 dark:ring-foreground/25 [--s-main:90_144_144] [--s-accent:184_92_76] data-checked:[--s-main:196_112_112] data-checked:[--s-accent:90_144_144] bg-[radial-gradient(circle_at_35%_35%,#ffffff_0%,oklch(0.995_0.002_90)_40%,oklch(0.915_0.006_85)_100%)] shadow-[inset_-2px_-3px_5px_rgb(var(--s-main)/0.28),inset_-1px_-1px_3px_rgb(var(--s-main)/0.15),inset_1.5px_2px_3px_rgb(255_255_255/0.85),inset_1px_1px_2px_rgb(255_255_255/0.60),inset_-3px_-3px_6px_rgb(var(--s-accent)/0.10),0_1.5px_4px_rgb(var(--s-main)/0.18),0_1px_2px_rgb(0_0_0/0.06)] transition-[box-shadow] duration-200 ease-out"
const THUMB =
"bg-[radial-gradient(circle_at_40%_38%,oklch(0.28_0.008_60),oklch(0.16_0.006_60))] shadow-[inset_1.5px_2px_4px_rgb(255_255_255/0.07),inset_-1px_-1.5px_4px_rgb(0_0_0/0.45),0_2px_5px_rgb(0_0_0/0.14),0_1px_2px_rgb(0_0_0/0.08)] transition-transform duration-200 ease-out"
/**
* A switch for a setting that turns on or off, built on Base UI's `Switch`:
* a pale pill track with teal inner shading that turns rose when checked,
* and a dark round thumb that slides across.
*
* Keyboard, focus and form behavior come from Base UI. An invisible area
* around the track enlarges the hit target. `className` lands on the track
* and, as in Base UI, can be a function of its state.
*/
function CloudDancerSwitch({
className,
size = "default",
...props
}: SwitchPrimitive.Root.Props & {
/** `default` is a 32px-wide track; `sm` is 24px wide. */
size?: "sm" | "default"
}) {
return (
<SwitchPrimitive.Root
data-slot="cloud-dancer-switch"
data-size={size}
className={(state) =>
cn(
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent outline-none group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-disabled:cursor-not-allowed data-disabled:opacity-50",
TRACK,
typeof className === "function" ? className(state) : className
)
}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="cloud-dancer-switch-thumb"
className={cn(
"pointer-events-none block rounded-full ring-0 group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-checked:translate-x-[calc(100%-2px)] data-unchecked:translate-x-0",
THUMB
)}
/>
</SwitchPrimitive.Root>
)
}
export { CloudDancerSwitch }
pnpm dlx shadcn add @01orbital/cloud-dancer-switchnpx shadcn add @01orbital/cloud-dancer-switchyarn shadcn add @01orbital/cloud-dancer-switchbunx --bun shadcn add @01orbital/cloud-dancer-switchFree under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.
It lands at components/button/cloud-dancer-switch.tsx, as source you own and edit.
The example also uses field. Install it too:
pnpm dlx shadcn add @01orbital/fieldnpx shadcn add @01orbital/fieldyarn shadcn add @01orbital/fieldbunx --bun shadcn add @01orbital/field"use client"
import { useId } from "react"
import { CloudDancerSwitch } from "@/components/button/cloud-dancer-switch"
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
const SETTINGS = [
{ name: "weekly", label: "Weekly summary", defaultChecked: true },
{ name: "mentions", label: "Mention alerts", defaultChecked: false },
] as const
// Both states at once: the void pill with its rose orb, and the cream groove.
export default function CloudDancerSwitchDemo() {
const id = useId()
return (
<div className="relative isolate grid min-h-full w-full flex-1 place-items-center overflow-hidden bg-background py-24 text-foreground">
<FieldGroup className="w-64 gap-4">
{SETTINGS.map((setting) => (
<Field key={setting.name} orientation="horizontal">
<CloudDancerSwitch
id={`${id}-${setting.name}`}
name={setting.name}
defaultChecked={setting.defaultChecked}
/>
<FieldLabel
htmlFor={`${id}-${setting.name}`}
className="font-normal"
>
{setting.label}
</FieldLabel>
</Field>
))}
</FieldGroup>
</div>
)
}Prop
Type
It also accepts the props of the Base UI part it wraps, and the HTML attributes of the element it renders.
npm: @base-ui/react, cn. The CLI installs them for you.
No other registry pieces.
Compare
Every Button component side by side: whether it follows the theme or stays dark, how many parts and custom props it has, and whether it is free. Open one for its preview, API and install.
Frosted Button
A translucent Button for use over art: a blurred, faint background-tinted fill with a border and inner glow that follow currentColor. Needs a backdrop behind it for the blur to read.