Slider
Pick a value or a range by dragging. Array value: 1 = single, 2 = range, 3+ = multiple thumbs.
import { Slider } from "@/components/ui/slider";
export default function Demo() { return ( <div className="flex w-72 flex-col gap-6"> <Slider defaultValue={[33]} max={100} step={1} /> <Slider defaultValue={[25, 75]} /> <Slider defaultValue={[20, 50, 80]} /> </div> );}<script setup lang="ts">import { Slider } from "@/components/ui/slider";</script>
<template> <div class="flex w-72 flex-col gap-6"> <Slider :default-value="[33]" :max="100" :step="1" /> <Slider :default-value="[25, 75]" /> <Slider :default-value="[20, 50, 80]" /> </div></template><script lang="ts"> import { Slider } from "$lib/components/ui/slider";</script>
<div class="flex w-72 flex-col gap-6"> <Slider type="multiple" value={[33]} max={100} step={1} /> <Slider type="multiple" value={[25, 75]} /> <Slider type="multiple" value={[20, 50, 80]} /></div>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/sliderpnpm dlx shadcn@latest add @edmi-ui/slideryarn dlx shadcn@latest add @edmi-ui/sliderbunx shadcn@latest add @edmi-ui/sliderPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @base-ui/reactTerminal window pnpm add @base-ui/reactTerminal window yarn add @base-ui/reactTerminal window bun add @base-ui/reactAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/sliderpnpm dlx shadcn-vue@latest add @edmi-ui/slideryarn dlx shadcn-vue@latest add @edmi-ui/sliderbunx shadcn-vue@latest add @edmi-ui/sliderPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install reka-ui @vueuse/coreTerminal window pnpm add reka-ui @vueuse/coreTerminal window yarn add reka-ui @vueuse/coreTerminal window bun add reka-ui @vueuse/coreAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/slider.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/slider.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/slider.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/slider.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { Slider } from "@/components/ui/slider";
<div className="flex w-72 flex-col gap-6"> <Slider defaultValue={[33]} max={100} step={1} /> <Slider defaultValue={[25, 75]} /> <Slider defaultValue={[20, 50, 80]} /></div><script setup lang="ts">import { Slider } from "@/components/ui/slider";</script>
<template> <div class="flex w-72 flex-col gap-6"> <Slider :default-value="[33]" :max="100" :step="1" /> <Slider :default-value="[25, 75]" /> <Slider :default-value="[20, 50, 80]" /> </div></template><script lang="ts"> import { Slider } from "$lib/components/ui/slider";</script>
<div class="flex w-72 flex-col gap-6"> <Slider type="multiple" value={[33]} max={100} step={1} /> <Slider type="multiple" value={[25, 75]} /> <Slider type="multiple" value={[20, 50, 80]} /></div>Component source
Section titled “Component source”"use client";
import { Slider as SliderPrimitive } from "@base-ui/react/slider";import { cn } from "cn";
import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
// Array value: 1 = single, 2 = range, 3+ = multiple thumbs.function Slider({ className, defaultValue, value, min = 0, max = 100, elevation, ...props}: SliderPrimitive.Root.Props & { /** ✦ depth: raised +1 / floating +2 make the thumbs rise (never the track). */ elevation?: Elevation;}) { const level = useElevation(elevation, "handle"); const raised = level === "raised" || level === "floating"; const _values = Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min];
return ( <SliderPrimitive.Root className={cn( "data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full", className, )} data-slot="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-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col"> <SliderPrimitive.Track data-slot="slider-track" className="relative grow overflow-hidden rounded-full border border-border bg-muted select-none data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5" > <SliderPrimitive.Indicator data-slot="slider-range" className="rounded-full bg-brand select-none data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full" /> </SliderPrimitive.Track> {Array.from({ length: _values.length }, (_, index) => ( <SliderPrimitive.Thumb // biome-ignore lint/suspicious/noArrayIndexKey: thumbs are positional key={index} index={index} data-slot="slider-thumb" className={cn( "relative block size-[18px] shrink-0 rounded-full border border-brand-edge bg-white transition-shadow outline-none select-none after:absolute after:-inset-2 focus-visible:shadow-[0_0_0_4px_var(--ring-soft)] data-[dragging]:shadow-[0_0_0_4px_var(--ring-soft)]", raised && "border-transparent bg-linear-to-b from-white to-[#eeede9] shadow-thumb focus-visible:shadow-[0_0_0_4px_var(--ring-soft),0_0_1.5px_rgb(0_0_0/0.45)] data-[dragging]:shadow-[0_0_0_4px_var(--ring-soft),0_0_1.5px_rgb(0_0_0/0.45)]", )} /> ))} </SliderPrimitive.Control> </SliderPrimitive.Root> );}
export { Slider };<script setup lang="ts">import type { SliderRootEmits, SliderRootProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { SliderRange, SliderRoot, SliderThumb, SliderTrack, useForwardPropsEmits } from "reka-ui"import { computed } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation"
// Array value: 1 = single, 2 = range, 3+ = multiple thumbs.const props = withDefaults(defineProps<SliderRootProps & { class?: HTMLAttributes["class"], elevation?: Elevation }>(), { elevation: undefined })const emits = defineEmits<SliderRootEmits>()
const delegatedProps = reactiveOmit(props, "class", "elevation")
const level = useElevation(() => props.elevation, "handle")const raised = computed(() => level.value === "raised" || level.value === "floating")
const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>
<template> <SliderRoot v-slot="{ modelValue }" data-slot="slider" :class=" cn( 'relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col', props.class, ) " v-bind="forwarded" > <SliderTrack data-slot="slider-track" class="relative grow overflow-hidden rounded-full border border-border bg-muted select-none data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5" > <SliderRange data-slot="slider-range" class="absolute rounded-full bg-brand select-none data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full" /> </SliderTrack>
<SliderThumb v-for="(_, key) in modelValue" :key="key" data-slot="slider-thumb" :class="cn( 'relative block size-[18px] shrink-0 rounded-full border border-brand-edge bg-white transition-shadow outline-none select-none after:absolute after:-inset-2 focus-visible:shadow-[0_0_0_4px_var(--ring-soft)] data-[dragging]:shadow-[0_0_0_4px_var(--ring-soft)] active:shadow-[0_0_0_4px_var(--ring-soft)] disabled:pointer-events-none', raised && 'border-transparent bg-linear-to-b from-white to-[#eeede9] shadow-thumb focus-visible:shadow-[0_0_0_4px_var(--ring-soft),0_0_1.5px_rgb(0_0_0/0.45)] data-[dragging]:shadow-[0_0_0_4px_var(--ring-soft),0_0_1.5px_rgb(0_0_0/0.45)] active:shadow-[0_0_0_4px_var(--ring-soft),0_0_1.5px_rgb(0_0_0/0.45)]', )" /> </SliderRoot></template>export { default as Slider } from "./Slider.vue";<script lang="ts"> import { Slider as SliderPrimitive } from "bits-ui"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js";
// Array value: 1 = single, 2 = range, 3+ = multiple thumbs. let { ref = $bindable(null), value = $bindable(), orientation = "horizontal", class: className, elevation = "auto", ...restProps }: WithoutChildrenOrChild<SliderPrimitive.RootProps> & { /** ✦ depth: raised +1 / floating +2 make the thumbs rise (never the track). */ elevation?: Elevation; } = $props();
const level = useElevation(() => elevation, "handle"); const raised = $derived(level.current === "raised" || level.current === "floating");</script>
<!--Discriminated Unions + Destructing (required for bindable) do notget along, so we shut typescript up by casting `value` to `never`.--><SliderPrimitive.Root bind:ref bind:value={value as never} data-slot="slider" {orientation} class={cn( "relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col", className )} {...restProps}> {#snippet children({ thumbItems })} <span data-slot="slider-track" data-orientation={orientation} class="relative grow overflow-hidden rounded-full border border-border bg-muted select-none data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5" > <SliderPrimitive.Range data-slot="slider-range" class="absolute rounded-full bg-brand select-none data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full" /> </span> {#each thumbItems as thumb (thumb.index)} <SliderPrimitive.Thumb data-slot="slider-thumb" index={thumb.index} class={cn( "relative block size-[18px] shrink-0 rounded-full border border-brand-edge bg-white transition-shadow outline-none select-none after:absolute after:-inset-2 focus-visible:shadow-[0_0_0_4px_var(--ring-soft)] data-[active]:shadow-[0_0_0_4px_var(--ring-soft)] disabled:pointer-events-none", raised && "border-transparent bg-linear-to-b from-white to-[#eeede9] shadow-thumb focus-visible:shadow-[0_0_0_4px_var(--ring-soft),0_0_1.5px_rgb(0_0_0/0.45)] data-[active]:shadow-[0_0_0_4px_var(--ring-soft),0_0_1.5px_rgb(0_0_0/0.45)]" )} /> {/each} {/snippet}</SliderPrimitive.Root>import Root from "./slider.svelte";
export { Root, // Root as Slider,};Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Only the thumbs rise (raised +1 or floating +2); the track and range never get a bevel.
Flat (0)
Raised (+1)
Flat (0)
Raised (+1)
Flat (0)
Raised (+1)
import { Slider } from "@/components/ui/slider";
const levels = [ { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" },] as const;
export default function Demo() { return ( <div className="flex flex-col gap-5"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <div className="flex w-72 flex-col gap-5"> <Slider elevation={value} defaultValue={[33]} max={100} step={1} /> <Slider elevation={value} defaultValue={[25, 75]} /> </div> </div> ))} </div> );}<script setup lang="ts">import { Slider } from "@/components/ui/slider";
const levels = [{ value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }];</script>
<template> <div class="flex flex-col gap-5"> <div v-for="level in levels" :key="level.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ level.label }}</p> <div class="flex w-72 flex-col gap-5"> <Slider :elevation="level.value" :default-value="[40]" :max="100" /> <Slider :elevation="level.value" :default-value="[20, 70]" :max="100" /> </div> </div> </div></template><script lang="ts"> import { Slider } from "$lib/components/ui/slider";
const levels = [{ value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }] as const;</script>
<div class="flex flex-col gap-5"> {#each levels as level (level.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{level.label}</p> <div class="flex w-72 flex-col gap-5"> <Slider type="multiple" elevation={level.value} value={[33]} max={100} step={1} /> <Slider type="multiple" elevation={level.value} value={[25, 75]} /> </div> </div> {/each}</div>Built on Base UI Slider.
| Prop | Type | Default |
|---|---|---|
value / defaultValue |
number[] |
- |
min / max / step |
number |
0 / 100 / 1 |
orientation |
"horizontal" | "vertical" |
"horizontal" |
onValueChange |
(value: number[], event) => void |
- |
disabled |
boolean |
false |
elevation |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
✦ Edmi additions
Section titled “✦ Edmi additions”- Brand-colored range and a round thumb (
elevationraises the thumbs); focus shows a soft ring.