# Color Slider (/ui/components/react/color-slider)




## Usage [#usage]

```tsx
import { ColorSlider } from '@appica/ui-react/color-slider'
import { parseColor } from '@appica/ui-react/color'
```

```tsx
<ColorSlider channel="hue" defaultValue="hsb(217, 76%, 96%)" />
```

`ColorSlider` ramps a track through one `channel` of a color and puts a thumb on it. It's the one-dimensional counterpart to [Color Area](/ui/components/react/color-area): the area covers two channels, the slider covers the third, and together they make a picker.

`channel` is required - it's what the track is a ramp of. The rest of the color rides along unchanged, so a hue slider on `hsb(217, 76%, 96%)` keeps that saturation and brightness at every point of the track.

The value is a `Color`, the same type the area takes: a plain object from `parseColor`, or any CSS color string. Every handler is called with a `Color`, which you write out with `formatColor`.

```tsx
const [color, setColor] = useState(() => parseColor('#3b82f6'))

<ColorSlider channel="hue" value={color} onValueChange={setColor} />
```

`colorSpace` defaults to the space of the value **when that space carries the channel**. Pass a hex value to a hue slider and it reads hue from HSB, because RGB has none. Pass an HSL color and it stays HSL. That rule is what lets an area and a slider share one piece of state without either of them converting it out from under the other.

Inside a [Color Picker](/ui/components/react/color-picker) that piece of state is the picker's, so `value` and the handlers can be left off: `<ColorSlider channel="hue" />` is enough.

## Examples [#examples]

### Building a picker [#building-a-picker]

This is the shape most color pickers take: an area for saturation and brightness, a hue slider for the channel it can't reach, an alpha slider, and a [Color Swatch](/ui/components/react/color-swatch) for the result. All four read and write one `Color` in state, and none of them needs to know about the others.

```tsx
'use client'

import { useState } from 'react'
import { ColorArea } from '@appica/ui-react/color-area'
import { ColorSlider } from '@appica/ui-react/color-slider'
import { ColorSwatch } from '@appica/ui-react/color-swatch'
import { type Color, formatColor, parseColor } from '@appica/ui-react/color'

export default function ColorSliderPicker() {
  const [color, setColor] = useState<Color>(() => parseColor('hsb(217, 76%, 96%)'))

  return (
    <div className="flex w-full max-w-56 flex-col gap-3">
      <ColorArea
        value={color}
        onValueChange={setColor}
        xChannel="saturation"
        yChannel="brightness"
        aria-label="Saturation and brightness"
      />
      <ColorSlider channel="hue" value={color} onValueChange={setColor} />
      <ColorSlider channel="alpha" value={color} onValueChange={setColor} />
      <div className="flex items-center gap-3">
        <ColorSwatch color={color} />
        <span className="font-mono text-xs text-nowrap">
          {color.alpha < 1 ? formatColor(color, 'rgba') : formatColor(color, 'hex')}
        </span>
      </div>
    </div>
  )
}
```

### Any channel [#any-channel]

A slider works on any channel of its space. Three RGB sliders make a channel mixer, where each track shows what moving that one channel would do while the other two hold still.

```tsx
'use client'

import { useState } from 'react'
import { ColorSlider } from '@appica/ui-react/color-slider'
import { type Color, formatChannelValue, parseColor } from '@appica/ui-react/color'

const CHANNELS = ['red', 'green', 'blue'] as const

export default function ColorSliderChannels() {
  const [color, setColor] = useState<Color>(() => parseColor('rgb(59, 130, 246)'))

  return (
    <div className="flex w-full max-w-70 flex-col gap-3">
      {CHANNELS.map((channel) => (
        <div key={channel} className="flex items-center gap-3">
          <span className="text-foreground-muted w-10 text-xs capitalize">{channel}</span>
          <ColorSlider channel={channel} value={color} onValueChange={setColor} className="flex-1" />
          <span className="text-foreground-muted w-8 text-end text-xs tabular-nums">
            {formatChannelValue(color, channel)}
          </span>
        </div>
      ))}
    </div>
  )
}
```

### Alpha [#alpha]

`channel="alpha"` ramps from transparent to opaque and draws the track over a checkerboard, so the transparency is visible rather than implied. Alpha belongs to every space, so it pairs with any other slider.

```tsx
'use client'

import { useState } from 'react'
import { ColorSlider } from '@appica/ui-react/color-slider'
import { type Color, formatColor, parseColor } from '@appica/ui-react/color'

export default function ColorSliderAlpha() {
  const [color, setColor] = useState<Color>(() => parseColor('rgba(59, 130, 246, 0.6)'))

  return (
    <div className="flex w-full max-w-70 flex-col gap-3">
      <ColorSlider channel="alpha" value={color} onValueChange={setColor} />
      <span className="text-foreground-muted font-mono text-xs">{formatColor(color)}</span>
    </div>
  )
}
```

### Vertical [#vertical]

`orientation="vertical"&#x60; runs the track bottom to top, with the channel's minimum at the bottom. The arrow keys follow: &#x2A;*↑** increases.

```tsx
'use client'

import { useState } from 'react'
import { ColorSlider } from '@appica/ui-react/color-slider'
import { type Color, parseColor } from '@appica/ui-react/color'

export default function ColorSliderVertical() {
  const [color, setColor] = useState<Color>(() => parseColor('hsb(217, 76%, 96%)'))

  return (
    <div className="flex items-end gap-4">
      <ColorSlider channel="hue" orientation="vertical" value={color} onValueChange={setColor} />
      <ColorSlider channel="saturation" orientation="vertical" value={color} onValueChange={setColor} />
      <ColorSlider channel="brightness" orientation="vertical" value={color} onValueChange={setColor} />
    </div>
  )
}
```

### Sizing [#sizing]

The track is a full-width `h-5` pill by default and takes any size through `className`; `thumbProps` sizes the thumb to match. A vertical track uses `h-40 w-5`. The thumb's travel is measured from both, so resizing either keeps the thumb inside the track without any further setup.

```tsx
import { ColorSlider } from '@appica/ui-react/color-slider'

export default function ColorSliderSizing() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      <ColorSlider
        channel="hue"
        defaultValue="hsb(217, 76%, 96%)"
        className="h-2"
        thumbProps={{ className: 'size-4' }}
      />
      <ColorSlider channel="hue" defaultValue="hsb(140, 76%, 96%)" />
      <ColorSlider
        channel="hue"
        defaultValue="hsb(30, 76%, 96%)"
        className="h-10 rounded-xl"
        thumbProps={{ className: 'size-7' }}
      />
    </div>
  )
}
```

### Disabled [#disabled]

`disabled` blocks interaction and swaps the gradient for a flat muted fill inside a dashed outline, dimmed.

```tsx
import { ColorSlider } from '@appica/ui-react/color-slider'

export default function ColorSliderDisabled() {
  return <ColorSlider channel="hue" defaultValue="hsb(217, 76%, 96%)" disabled className="max-w-70" />
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior (roving focus, popup placement, and the like) follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

A horizontal track ramps from the right, and &#x2A;*←/→** swap so the arrow pointing at the maximum is still the one that reaches it. A vertical track is unaffected in either direction. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).


## API reference [#api-reference]

| Prop               | Type    | Default        | Description                                                                                                                                |
| ------------------ | ---------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel`          | `ColorChannel`                                 | -              | Channel the track ramps through, and the one the slider changes.                                                                                                                  |
| `value`            | `Color \| string`                   | -              | Selected color. Pass a `Color` or any CSS color string to control the component. Inside a `ColorPicker` it can be left off: the control then reads and writes the picker's color. |
| `defaultValue`     | `Color \| string`                   | `'#ffffff'`    | Color selected before any interaction, when the component is uncontrolled.                                                                                                        |
| `onValueChange`    | `(value: Color) => void`            | -              | Fires on every change, including each frame of a drag.                                                                                                                            |
| `onValueCommitted` | `(value: Color) => void`            | -              | Fires once a drag ends or a key press settles, with the color that was landed on.                                                                                                 |
| `colorSpace`       | `'rgb' \| 'hsl' \| 'hsb'`           | value's space  | Color space the channel is read from. Defaults to the space of the value when it carries `channel`, so a slider shares a color with an area without converting it.                |
| `orientation`      | `'horizontal' \| 'vertical'`        | `'horizontal'` | Axis the track runs along. A vertical track fills from the bottom.                                                                                                                |
| `disabled`         | `boolean`                                      | `false`        | Prevent interaction and dim the track.                                                                                                                                            |
| `name`             | `string`                                       | -              | Name of the hidden input, used when submitting an HTML form.                                                                                                                      |
| `form`             | `string`                                       | -              | `id` of the `<form>` the hidden input belongs to, when it sits outside it.                                                                                                        |
| `thumbProps`       | `ComponentPropsWithoutRef\<'span'>` | -              | Props for the thumb element, for styling or a test id.                                                                                                                            |
| `aria-label`       | `string`                                       | channel name   | Accessible name. Defaults to the channel, e.g. `Hue`.                                                                                                                             |
| `className`        | `string`                                       | -              | Extra classes on the root, merged via `tailwind-merge`.                                                                                                                           |

Every other `<div>` attribute is forwarded to the root. The root carries `data-orientation`, `data-channel`, `data-disabled` and `data-dragging`; the parts are addressable through `data-slot` (`color-slider`, `color-slider-surface`, `color-slider-thumb`).

The color model - `Color`, `parseColor`, `formatColor`, `convertColor` and the channel helpers - ships from `@appica/ui-react/color` and is documented on the [Color Area](/ui/components/react/color-area#api-reference) page.

## Accessibility [#accessibility]

* The track holds a visually hidden `<input type="range">`, so assistive technology gets a real `role="slider"` with the channel's own bounds and step rather than a generic 0-100.
* `aria-label` defaults to the channel name (`Hue`, `Saturation`, …), so a bare slider is still announced usefully. `aria-valuetext` pairs the channel value with the resulting color, e.g. `217°, #3b82f6`.
* Keyboard: &#x2A;*←/→*&#x2A; and &#x2A;*↑/↓** move by one step, **Shift** with an arrow and **Page Up·Down** move by the page step, and **Home/End** jump to the channel's minimum and maximum.
* The thumb stays inside the track at both ends rather than half-hanging off it, and the ramp is laid across that same travel, so the color under the thumb is always the color the slider reports. The track keeps its full width because CSS extends the end stops past the ramp, capping the pill in the channel's minimum and maximum.
* The thumb is a white rim with a faint dark ring on each side, so it keeps an edge over any point of the track in either theme, including where the track is white. `forced-color-adjust: none` keeps the gradient intact in forced-colors mode.
* The thumb's position is never transitioned, so it sits exactly under the pointer. The only animation is the scale while it is pressed, and that honors `prefers-reduced-motion`.
