Build Color Triangle
Let's build a triangular color picker 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,
ColorTriangleRoot,
ColorTriangleGradient,
ColorTriangleThumb,
} from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
</script>
<template>
<ColorTriangleRoot
v-model="color"
color-space="hsv"
channel-x="s"
channel-y="v"
as="div"
class="relative block size-64"
>
<ColorTriangleGradient
as="div"
class="absolute inset-0 block"
/>
<ColorTriangleThumb
class="
size-4 rounded-full border-2 border-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="Color"
/>
</ColorTriangleRoot>
</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
ColorTriangleRoot manages all the state and interactions. Tell it which color space and channels to map to the triangle axes.
<script setup lang="ts">
import { useColor, ColorTriangleRoot } from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
</script>
<template>
<ColorTriangleRoot
v-model="color"
color-space="hsv"
channel-x="s"
channel-y="v"
>
<!-- children go here -->
</ColorTriangleRoot>
</template>color-space— the color space to work in (hsv,hsl,rgb, etc.)channel-x— the channel mapped to the horizontal axischannel-y— the channel mapped to the vertical axis
Step 3: Add the gradient
ColorTriangleGradient renders the 2D gradient inside the triangular shape.
<script setup lang="ts">
import {
useColor,
ColorTriangleRoot,
ColorTriangleGradient,
} from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
</script>
<template>
<ColorTriangleRoot
v-model="color"
color-space="hsv"
channel-x="s"
channel-y="v"
class="relative block size-64"
>
<ColorTriangleGradient class="absolute inset-0 block" />
</ColorTriangleRoot>
</template>Step 4: Add the thumb
ColorTriangleThumb is the draggable handle. It's positioned automatically within the triangle.
<script setup lang="ts">
import {
useColor,
ColorTriangleRoot,
ColorTriangleGradient,
ColorTriangleThumb,
} from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
</script>
<template>
<ColorTriangleRoot
v-model="color"
color-space="hsv"
channel-x="s"
channel-y="v"
class="relative block size-64"
>
<ColorTriangleGradient class="absolute inset-0 block" />
<ColorTriangleThumb
class="
size-4 rounded-full border-2 border-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="Color"
/>
</ColorTriangleRoot>
</template>TIP
All components are completely unstyled — the classes above are just an example using Tailwind CSS. Use any styling approach you prefer.
Rotation
Use the rotation prop to rotate the triangle (in degrees):
<template>
<ColorTriangleRoot
v-model="color"
color-space="hsv"
:rotation="180"
channel-x="s"
channel-y="v"
>
<!-- ... -->
</ColorTriangleRoot>
</template>Three-channel mode
Add channel-z to enable barycentric three-channel mode. This maps all three channels to the triangle's vertices — useful for RGB color mixing:
<template>
<ColorTriangleRoot
v-model="color"
color-space="rgb"
channel-x="r"
channel-y="g"
channel-z="b"
>
<!-- ... -->
</ColorTriangleRoot>
</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>
<ColorTriangleRoot
v-model="color"
color-space="hsv"
@update:model-value="onColorChange"
@value-commit="onColorCommit"
channel-x="s"
channel-y="v"
>
<!-- ... -->
</ColorTriangleRoot>
</template>