Base UI slider dressed as the Cloud Dancer pill: a teal-shaded off-white strip with a dark filled range and a dark orb thumb. Takes one value or a range, with a label per thumb.
"use client"
import { Slider as SliderPrimitive } from "@base-ui/react/slider"
import { cn } from "cn"
// Cloud Dancer slider, inverted: the stock Slider with the Cloud Dancer pill
// as the strip, teal-shaded, a void bar for the filled range and a void orb.
// --s-main and --s-accent are the strip'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] 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_-1.5px_-2px_4px_rgb(var(--s-main)/0.28),inset_-1px_-1px_2px_rgb(var(--s-main)/0.15),inset_1px_1.5px_2.5px_rgb(255_255_255/0.85),inset_0.5px_1px_1.5px_rgb(255_255_255/0.60),inset_-2px_-2.5px_4px_rgb(var(--s-accent)/0.10),0_1px_3px_rgb(var(--s-main)/0.18),0_1px_1.5px_rgb(0_0_0/0.06)]"
const RANGE =
"bg-[radial-gradient(circle_at_40%_38%,oklch(0.28_0.008_60),oklch(0.16_0.006_60))] shadow-[inset_1px_1.5px_3px_rgb(255_255_255/0.07),inset_-1px_-1px_3px_rgb(0_0_0/0.45)]"
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-[box-shadow]"
/**
* A Base UI slider styled as the Cloud Dancer pill: an off-white,
* teal-shaded strip with a dark filled range and a dark orb thumb. Give
* `value` or `defaultValue` a number for one thumb or an array for a range,
* and name each thumb with `thumbLabels`. The strip, range and thumb keep
* their colors in light and dark themes.
*/
function CloudDancerSlider({
className,
defaultValue,
value,
min = 0,
max = 100,
thumbLabels,
...props
}: SliderPrimitive.Root.Props & {
/** One name per thumb for a range, e.g. ["Minimum price", "Maximum price"]. */
thumbLabels?: string[]
}) {
// A number or no value is one thumb; an array is one thumb per entry.
const initial = value ?? defaultValue
const thumbs = Array.isArray(initial) ? initial.length : 1
return (
<SliderPrimitive.Root
className={(state) =>
cn(
"data-horizontal:w-full data-vertical:h-full",
typeof className === "function" ? className(state) : className
)
}
data-slot="cloud-dancer-slider"
defaultValue={defaultValue}
value={value}
min={min}
max={max}
thumbAlignment="edge"
{...props}
>
<SliderPrimitive.Control className="relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col">
<SliderPrimitive.Track
data-slot="cloud-dancer-slider-track"
className={cn(
"relative grow overflow-hidden rounded-full select-none data-horizontal:h-2.5 data-horizontal:w-full data-vertical:h-full data-vertical:w-2.5",
TRACK
)}
>
<SliderPrimitive.Indicator
data-slot="cloud-dancer-slider-range"
className={cn(
"rounded-full select-none data-horizontal:h-full data-vertical:w-full",
RANGE
)}
/>
</SliderPrimitive.Track>
{Array.from({ length: thumbs }, (_, index) => (
<SliderPrimitive.Thumb
data-slot="cloud-dancer-slider-thumb"
key={index}
index={index}
aria-label={thumbLabels?.[index]}
className={cn(
"block size-4.5 shrink-0 rounded-full ring-0 transition-shadow select-none hover:ring-4 hover:ring-ring/30 focus-visible:ring-4 focus-visible:ring-ring/30 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50",
THUMB
)}
/>
))}
</SliderPrimitive.Control>
</SliderPrimitive.Root>
)
}
export { CloudDancerSlider }
pnpm dlx shadcn add @01orbital/cloud-dancer-slidernpx shadcn add @01orbital/cloud-dancer-slideryarn shadcn add @01orbital/cloud-dancer-sliderbunx --bun shadcn add @01orbital/cloud-dancer-sliderFree under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.
It lands at components/slider/cloud-dancer-slider.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 { CloudDancerSlider } from "@/components/slider/cloud-dancer-slider"
import { Field, FieldTitle } from "@/components/ui/field"
export default function CloudDancerSliderDemo() {
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">
<Field className="w-64">
<FieldTitle id={id}>Volume</FieldTitle>
<CloudDancerSlider
aria-labelledby={id}
defaultValue={[35]}
max={100}
step={1}
/>
</Field>
</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 UI Elements 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.
Install Command
Copyable shell command in an InputGroup: a $ prompt, the command in Geist Mono and a copy button. If copying fails it selects the text and shows a hint; add the dark class over art.