Skip to content

ColorTriangle

A triangular 2D area component for adjusting two (or three) color channels simultaneously.

Preview

Source code
tsx
import "internationalized-color/css";
import { ColorTriangle, useColor } from "@urcolor/react";

export default function ColorTriangleSV() {
  const { color, setColor, hex } = useColor("hsl(210, 80%, 50%)");

  return (
    <>
      <code>{hex}</code>
      <ColorTriangle.Root
        value={color}
        onValueChange={setColor}
        colorSpace="hsv"
        channelX="s"
        channelY="v"
        className="relative block size-64"
      >
        <ColorTriangle.Gradient className="absolute inset-0 block" />
        <ColorTriangle.Thumb
          className="
            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)]
          "
        />
      </ColorTriangle.Root>
    </>
  );
}

Anatomy

tsx
<ColorTriangle.Root>
  <ColorTriangle.Checkerboard />
  <ColorTriangle.Gradient />
  <ColorTriangle.Thumb />
</ColorTriangle.Root>

Examples

HSV / Saturation x Value

HSV color triangle with Saturation and Value mapped to the triangle axes.

Source code
tsx
import "internationalized-color/css";
import { ColorTriangle, useColor } from "@urcolor/react";

export default function ColorTriangleSV() {
  const { color, setColor, hex } = useColor("hsl(210, 80%, 50%)");

  return (
    <>
      <code>{hex}</code>
      <ColorTriangle.Root
        value={color}
        onValueChange={setColor}
        colorSpace="hsv"
        channelX="s"
        channelY="v"
        className="relative block size-64"
      >
        <ColorTriangle.Gradient className="absolute inset-0 block" />
        <ColorTriangle.Thumb
          className="
            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)]
          "
        />
      </ColorTriangle.Root>
    </>
  );
}

HSL / Saturation x Lightness

HSL color triangle with Saturation and Lightness mapped to the triangle axes.

Source code
tsx
import "internationalized-color/css";
import { ColorTriangle, useColor } from "@urcolor/react";

export default function ColorTriangleSL() {
  const { color, setColor, hex } = useColor("hsl(210, 80%, 50%)");

  return (
    <>
      <code>{hex}</code>
      <ColorTriangle.Root
        value={color}
        onValueChange={setColor}
        colorSpace="hsl"
        channelX="s"
        channelY="l"
        className="relative block size-64"
      >
        <ColorTriangle.Gradient className="absolute inset-0 block" />
        <ColorTriangle.Thumb
          className="
            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)]
          "
        />
      </ColorTriangle.Root>
    </>
  );
}

Maxwell's RGB Triangle

Three-channel RGB triangle using barycentric coordinates.

Source code
tsx
import "internationalized-color/css";
import { ColorTriangle, useColor } from "@urcolor/react";

export default function ColorTriangleRGB() {
  const { color, setColor, hex } = useColor("hsl(210, 80%, 50%)");

  return (
    <>
      <code>{hex}</code>
      <ColorTriangle.Root
        value={color}
        onValueChange={setColor}
        colorSpace="rgb"
        channelX="r"
        channelY="g"
        channelZ="b"
        className="relative block size-64"
      >
        <ColorTriangle.Gradient className="absolute inset-0 block" />
        <ColorTriangle.Thumb
          className="
            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)]
          "
        />
      </ColorTriangle.Root>
    </>
  );
}

API Reference

ColorTriangle.Root

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

PropTypeDefaultDescription
valueColor | string | nullControlled color value.
defaultValueColor | string'hsl(0, 100%, 50%)'Initial color when uncontrolled.
colorSpacestring'hsv'Color space mode (e.g. 'hsv', 'hsl', 'rgb').
channelXstringAutoChannel for the X axis. Auto-derived from color space.
channelYstringAutoChannel for the Y axis. Auto-derived from color space.
channelZstringOptional third channel for barycentric three-channel mode.
rotationnumber0Triangle rotation in degrees.
invertedbooleanfalseInvert the triangle.
thumbAlignment'contain' | 'overflow''overflow'How thumb is positioned relative to bounds.
disabledbooleanfalseDisables interaction.
onValueChange(color: Color) => voidCalled when color changes.
onValueCommit(color: Color) => voidCalled when interaction ends.

ColorTriangle.Gradient

Renders a triangular gradient canvas.

PropTypeDefaultDescription
channelOverridesRecord<string, number> | false{ alpha: 1 }Lock specific channels to fixed values in the gradient.

ColorTriangle.Checkerboard

Renders a checkerboard pattern behind the gradient to visualize alpha transparency.

ColorTriangle.Thumb

Wrapper for the thumb indicator. Position is set automatically.

ColorTriangle.ThumbX / ColorTriangle.ThumbY

Individual axis thumb elements for keyboard navigation.

ColorTriangle.ThumbZ

Optional third axis thumb for three-channel barycentric mode.

Accessibility

ColorTriangle provides a triangular 2D interface with independently focusable thumb elements.

Keyboard Navigation

Two-Channel Mode

KeyAction
Arrow RightIncrease Y channel
Arrow LeftDecrease Y channel
Arrow UpIncrease X channel
Arrow DownDecrease X channel
Shift + ArrowMove by 10 steps
Home / Page UpJump to max
End / Page DownJump to min

Three-Channel Mode

KeyAction
Arrow Up / Arrow RightIncrease focused channel by 5%
Arrow Down / Arrow LeftDecrease focused channel by 5%
Shift + ArrowMove by 20%
Page UpIncrease by 20%
Page DownDecrease by 20%
HomeJump to focused channel vertex
EndJump to center (equal distribution)