Skip to content

ColorWheel

A circular 2D area component for adjusting two color channels mapped to angle and radius.

Preview

#1980e6
Source code
vue
<script setup lang="ts">
import "internationalized-color/css";
import {
  ColorWheelRoot,
  ColorWheelGradient,
  ColorWheelThumb,
  useColor,
} from "@urcolor/vue";

const { color, hex } = useColor("hsl(210, 80%, 50%)");
</script>

<template>
  <code>{{ hex }}</code>
  <ColorWheelRoot
    v-model="color"
    color-space="hsl"
    channel-angle="h"
    channel-radius="s"
    class="relative block size-64 overflow-hidden rounded-full"
    style="container-type: inline-size"
  >
    <ColorWheelGradient class="absolute inset-0 block" />
    <ColorWheelThumb
      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)]
      "
    />
  </ColorWheelRoot>
</template>

Anatomy

vue
<template>
  <ColorWheelRoot>
    <ColorWheelCheckerboard />
    <ColorWheelGradient />
    <ColorWheelThumb>
      <ColorWheelThumbX />
      <ColorWheelThumbY />
    </ColorWheelThumb>
  </ColorWheelRoot>
</template>

Examples

HSL / Hue x Saturation

HSL color wheel with Hue mapped to angle and Saturation to radius.

#1980e6
Source code
vue
<script setup lang="ts">
import "internationalized-color/css";
import {
  ColorWheelRoot,
  ColorWheelGradient,
  ColorWheelThumb,
  useColor,
} from "@urcolor/vue";

const { color, hex } = useColor("hsl(210, 80%, 50%)");
</script>

<template>
  <code>{{ hex }}</code>
  <ColorWheelRoot
    v-model="color"
    color-space="hsl"
    channel-angle="h"
    channel-radius="s"
    class="relative block size-64 overflow-hidden rounded-full"
    style="container-type: inline-size"
  >
    <ColorWheelGradient class="absolute inset-0 block" />
    <ColorWheelThumb
      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)]
      "
    />
  </ColorWheelRoot>
</template>

HSL / Hue x Lightness

HSL color wheel with Hue mapped to angle and Lightness to radius.

#1980e6
Source code
vue
<script setup lang="ts">
import "internationalized-color/css";
import {
  ColorWheelRoot,
  ColorWheelGradient,
  ColorWheelThumb,
  useColor,
} from "@urcolor/vue";

const { color, hex } = useColor("hsl(210, 80%, 50%)");
</script>

<template>
  <code>{{ hex }}</code>
  <ColorWheelRoot
    v-model="color"
    color-space="hsl"
    channel-angle="h"
    channel-radius="l"
    class="relative block size-64 overflow-hidden rounded-full"
    style="container-type: inline-size"
  >
    <ColorWheelGradient class="absolute inset-0 block" />
    <ColorWheelThumb
      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)]
      "
    />
  </ColorWheelRoot>
</template>

OKLCh / Hue x Chroma

OKLCh color wheel with Hue mapped to angle and Chroma to radius.

#0093a9
Source code
vue
<script setup lang="ts">
import "internationalized-color/css";
import {
  ColorWheelRoot,
  ColorWheelGradient,
  ColorWheelThumb,
  useColor,
} from "@urcolor/vue";

const { color, hex } = useColor("oklch(0.6 0.15 210)");
</script>

<template>
  <code>{{ hex }}</code>
  <ColorWheelRoot
    v-model="color"
    color-space="oklch"
    channel-angle="h"
    channel-radius="c"
    class="relative block size-64 overflow-hidden rounded-full"
    style="container-type: inline-size"
  >
    <ColorWheelGradient class="absolute inset-0 block" />
    <ColorWheelThumb
      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)]
      "
    />
  </ColorWheelRoot>
</template>

API Reference

ColorWheelRoot

The root container that manages wheel state and color channel binding.

PropTypeDefaultDescription
modelValueColor | string | nullControlled color value (v-model).
defaultValueColor | string'hsl(0, 100%, 50%)'Initial color when uncontrolled.
colorSpacestring'hsl'Color space mode (e.g. 'hsl', 'oklch').
channelAnglestringAutoChannel mapped to the angle axis (e.g. 'h'). Auto-derived from color space.
channelRadiusstringAutoChannel mapped to the radius axis (e.g. 's'). Auto-derived from color space.
startAnglenumber0Starting angle offset in degrees.
disabledbooleanfalseDisables interaction.
dir'ltr' | 'rtl'Reading direction.
namestringHidden input name for form submission.
requiredbooleanfalseMarks as required for form submission.
EventPayloadDescription
update:modelValueColor | undefinedEmitted when color changes.
valueCommitColorEmitted when interaction ends.

ColorWheelGradient

Renders a polar gradient canvas for the wheel. Automatically samples the gradient from the root's color space and channel configuration.

PropTypeDefaultDescription
channelOverridesRecord<string, number> | false{ alpha: 1 }Lock specific channels to fixed values in the gradient. Set to false to reflect all channels from current color including alpha.

ColorWheelCheckerboard

Renders a checkerboard pattern behind the gradient to visualize alpha transparency. Place it inside ColorWheelRoot before ColorWheelGradient.

ColorWheelThumb

Wrapper for the thumb indicator. Position is set automatically via CSS custom properties using polar coordinates.

ColorWheelThumbX / ColorWheelThumbY

Individual axis thumb elements for angle and radius. Both are required inside ColorWheelThumb for keyboard navigation to work on both axes.

Accessibility

ColorWheel provides a circular 2D interface with two independently focusable thumb elements for keyboard access to the angle and radius axes.

ARIA Labels

AttributeDescription
aria-labelLabels the root element with the color wheel's purpose.
role="slider"Applied to each thumb element (ThumbX, ThumbY) for screen reader recognition.
aria-valuemin / aria-valuemaxDefines the range for the angle and radius channels.
aria-valuenowCurrent value of the focused channel.

Keyboard Navigation

KeyAction
Arrow RightIncrease angle by one step
Arrow LeftDecrease angle by one step
Arrow UpIncrease radius by one step
Arrow DownDecrease radius by one step
Shift + ArrowMove by 10 steps
Page Up / Page DownIncrease/decrease radius by 10 steps
HomeMove to minimum
EndMove to maximum