Build Color Channel Slider
Let's build a 1D color slider step by step.
Here's what we'll end up with:
Click to view the full code
<script setup lang="ts">
import "internationalized-color/css";
import {
useColor,
ColorSliderRoot,
ColorSliderTrack,
ColorSliderGradient,
ColorSliderThumb,
} from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
</script>
<template>
<ColorSliderRoot
v-model="color"
color-space="hsl"
channel="h"
as="div"
class="w-full"
>
<ColorSliderTrack
as="div"
class="relative h-5 overflow-hidden rounded-xl"
>
<ColorSliderGradient
as="div"
class="absolute inset-0 rounded-xl"
:colors="['red', 'yellow', 'lime', 'cyan', 'blue', 'magenta', 'red']"
/>
<ColorSliderThumb
class="
block size-5 rounded-full border-[2.5px] border-white bg-white
shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
"
aria-label="Hue"
/>
</ColorSliderTrack>
</ColorSliderRoot>
</template>Step 1: Set up state
Start by importing the color model and creating a reactive color value.
<script setup lang="ts">
import { useColor } from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
</script>useColor() creates a reactive color ref from any CSS color string. It returns a { color } object where color is a shallow ref holding the parsed Color instance.
Step 2: Add the root
ColorSliderRoot manages all the state and interactions. Tell it which color space and channel to control.
<script setup lang="ts">
import { useColor, ColorSliderRoot } from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
</script>
<template>
<ColorSliderRoot
v-model="color"
color-space="hsl"
channel="h"
>
<!-- children go here -->
</ColorSliderRoot>
</template>color-space— the color space to work in (hsl,oklch,hsb, etc.)channel— the channel this slider controls (h,s,l,hue,chroma, etc.)
Step 3: Add the track and gradient
ColorSliderTrack is the interactive area that handles pointer events. ColorSliderGradient renders the 1D gradient on a canvas.
<script setup lang="ts">
import {
useColor,
ColorSliderRoot,
ColorSliderTrack,
ColorSliderGradient,
} from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
</script>
<template>
<ColorSliderRoot
v-model="color"
color-space="hsl"
channel="h"
>
<ColorSliderTrack
class="relative h-5 overflow-hidden rounded-xl"
>
<ColorSliderGradient
class="absolute inset-0 rounded-xl"
:colors="['red', 'yellow', 'lime', 'cyan', 'blue', 'magenta', 'red']"
/>
</ColorSliderTrack>
</ColorSliderRoot>
</template>The colors prop defines the gradient stops. For a hue slider, use the full spectrum. For other channels, you can use fewer stops — the gradient will interpolate between them.
Step 4: Add the thumb
ColorSliderThumb is the draggable handle. It's positioned automatically by the component.
<script setup lang="ts">
import {
useColor,
ColorSliderRoot,
ColorSliderTrack,
ColorSliderGradient,
ColorSliderThumb,
} from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
</script>
<template>
<ColorSliderRoot
v-model="color"
color-space="hsl"
channel="h"
>
<ColorSliderTrack
class="relative h-5 overflow-hidden rounded-xl"
>
<ColorSliderGradient
class="absolute inset-0 rounded-xl"
:colors="['red', 'yellow', 'lime', 'cyan', 'blue', 'magenta', 'red']"
/>
<ColorSliderThumb
class="
block size-5 rounded-full border-[2.5px] border-white bg-white
shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
"
aria-label="Hue"
/>
</ColorSliderTrack>
</ColorSliderRoot>
</template>TIP
All components are completely unstyled — the classes above are just an example using Tailwind CSS. Use any styling approach you prefer.
Vertical orientation
Set orientation="vertical" to render a vertical slider:
<template>
<ColorSliderRoot
v-model="color"
color-space="hsl"
orientation="vertical"
channel="h"
>
<!-- ... -->
</ColorSliderRoot>
</template>Inverting direction
Use inverted to reverse the slider direction:
<template>
<ColorSliderRoot
v-model="color"
color-space="hsl"
:inverted="true"
channel="h"
>
<!-- ... -->
</ColorSliderRoot>
</template>Different channels
Switch the channel prop to control different color properties. For example, a lightness slider:
<template>
<ColorSliderRoot
v-model="color"
color-space="hsl"
channel="l"
>
<ColorSliderTrack
class="relative h-5 overflow-hidden rounded-xl"
>
<ColorSliderGradient
class="absolute inset-0 rounded-xl"
:colors="['black', 'hsl(210, 80%, 50%)', 'white']"
/>
<ColorSliderThumb
class="..."
aria-label="Lightness"
/>
</ColorSliderTrack>
</ColorSliderRoot>
</template>Listening to changes
Use @update:model-value for real-time updates (while dragging) and @value-commit for the final value (on release):
<script setup lang="ts">
// ...
const onColorChange = (color: Color) => {
console.log("dragging", color.toString("css"));
};
const onColorCommit = (color: Color) => {
console.log("committed", color.toString("css"));
};
</script>
<template>
<ColorSliderRoot
v-model="color"
color-space="hsl"
@update:model-value="onColorChange"
@value-commit="onColorCommit"
channel="h"
>
<!-- ... -->
</ColorSliderRoot>
</template>