Button

Cloud Dancer Switch

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.

Free, MIT licenseOpen in Browse
"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 }

Installation

pnpm dlx shadcn add @01orbital/cloud-dancer-switch
npx shadcn add @01orbital/cloud-dancer-switch
yarn shadcn add @01orbital/cloud-dancer-switch
bunx --bun shadcn add @01orbital/cloud-dancer-switch

Free 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/field
npx shadcn add @01orbital/field
yarn shadcn add @01orbital/field
bunx --bun shadcn add @01orbital/field

Usage

"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>
  )
}

API reference

Prop

Type

It also accepts the props of the Base UI part it wraps, and the HTML attributes of the element it renders.

Dependencies

npm: @base-ui/react, cn. The CLI installs them for you.

No other registry pieces.