Back to React Spectrum

ColorPicker

packages/react-aria-components/docs/ColorPicker.mdx

2022-12-1616.3 KB
Original Source

{/* Copyright 2024 Adobe. All rights reserved. This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs'; export default Layout;

import docs from 'docs:react-aria-components'; import statelyDocs from 'docs:@react-stately/color'; import {PropTable, HeaderInfo, TypeLink, PageDescription, StateTable, ContextTable} from '@react-spectrum/docs'; import styles from '@react-spectrum/docs/src/docs.css'; import packageData from 'react-aria-components/package.json'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; import {Divider} from '@react-spectrum/divider'; import {ExampleList} from '@react-spectrum/docs/src/ExampleList'; import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; import {Keyboard} from '@react-spectrum/text'; import {StarterKits} from '@react-spectrum/docs/src/StarterKits'; import ColorSlider from '@react-spectrum/docs/pages/assets//component-illustrations/ColorSlider.svg'; import ColorWheel from '@react-spectrum/docs/pages/assets//component-illustrations/ColorWheel.svg'; import ColorArea from '@react-spectrum/docs/pages/assets//component-illustrations/ColorArea.svg'; import ColorField from '@react-spectrum/docs/pages/assets//component-illustrations/ColorField.svg'; import ColorSwatch from '@react-spectrum/docs/pages/assets//component-illustrations/ColorSwatch.svg'; import ColorSwatchPicker from '@react-spectrum/docs/pages/assets//component-illustrations/ColorSwatchPicker.svg';


category: Color keywords: [color picker, aria] type: component

ColorPicker

<PageDescription>{docs.exports.ColorPicker.description}</PageDescription>

<HeaderInfo packageData={packageData} componentNames={['ColorPicker']} />

Example

tsx
import {ColorPicker, Button, Popover, Dialog, DialogTrigger} from 'react-aria-components';
import {MyColorSwatch} from './ColorSwatch';
import {MyColorSlider} from './ColorSlider';
import {MyColorArea} from './ColorArea';
import {MyColorField} from './ColorField';

<ColorPicker defaultValue="#5100FF">
  <DialogTrigger>
    <Button className="color-picker">
      <MyColorSwatch />
      <span>Fill color</span>
    </Button>
    <Popover placement="bottom start">
      <Dialog className="color-picker-dialog">
        <MyColorArea colorSpace="hsb" xChannel="saturation" yChannel="brightness" />
        <MyColorSlider colorSpace="hsb" channel="hue" />
        <MyColorField label="Hex" />
      </Dialog>
    </Popover>
  </DialogTrigger>
</ColorPicker>
<details> <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
css
@import './Button.mdx' layer(button);
@import "./ColorArea.mdx" layer(colorarea);
@import "./ColorSlider.mdx" layer(colorslider);
@import "./ColorSwatch.mdx" layer(colorswatch);
@import "./ColorSwatchPicker.mdx" layer(colorswatchpicker);
@import "./ColorField.mdx" layer(colorfield);
@import "./Popover.mdx" layer(popover);
@import './Dialog.mdx' layer(dialog);
@import './ListBox.mdx' layer(listbox);
@import './Select.mdx' layer(select);
css
@import "@react-aria/example-theme";

.color-picker {
  background: none;
  border: none;
  padding: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  outline: none;
  border-radius: 4px;
  appearance: none;
  vertical-align: middle;
  font-size: 1rem;
  color: var(--text-color);

  &[data-focus-visible] {
    outline: 2px solid var(--focus-ring-color);
    outline-offset: 2px;
  }
}

.color-picker-dialog {
  outline: none;
  padding: 15px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 192px;
  max-height: inherit;
  box-sizing: border-box;
  overflow: auto;
}
</details>

Features

The <input type="color"> HTML element can be used to build a color picker, however it is very inconsistent across browsers and operating systems and does not allow customization. ColorPicker helps achieve accessible color pickers that can be styled as needed.

  • Customizable – Support for rendering any color picker UI by placing components like ColorArea, ColorSlider, ColorWheel, ColorField, and ColorSwatchPicker inside. These can be arranged in any desired combination or layout, with or without a popover.
  • Accessible – All color components announce localized color descriptions for screen reader users (e.g. "dark vibrant blue").

Anatomy

A color picker does not render any UI by itself. You can render any combination of color components as children of a color picker, including ColorArea, ColorSlider, ColorWheel, ColorField, ColorSwatch, and ColorSwatchPicker. ColorPicker manages the state of the selected color, and coordinates the value between all of the components inside it.

tsx
import {ColorPicker} from 'react-aria-components';

<ColorPicker>
</ColorPicker>

Composed components

A ColorPicker uses the following components, which may also be used standalone or reused in other components.

<section className={styles.cardGroup} data-size="small">

<ExampleCard url="ColorArea.html" title="ColorArea" description="A color area allows users to adjust two channels of a color value."> <ColorArea /> </ExampleCard>

<ExampleCard url="ColorSlider.html" title="ColorSlider" description="A color slider allows users to adjust an individual channel of a color value."> <ColorSlider /> </ExampleCard>

<ExampleCard url="ColorWheel.html" title="ColorWheel" description="A color wheel allows users to adjust the hue of a color value on a circular track."> <ColorWheel /> </ExampleCard>

<ExampleCard url="ColorField.html" title="ColorField" description="A color field allows users to edit a hex color or individual color channel value."> <ColorField /> </ExampleCard>

<ExampleCard url="ColorSwatch.html" title="ColorSwatch" description="A ColorSwatch displays a preview of a selected color."> <ColorSwatch /> </ExampleCard>

<ExampleCard url="ColorSwatchPicker.html" title="ColorSwatchPicker" description="A ColorSwatchPicker displays a list of color swatches and allows a user to select one of them."> <ColorSwatchPicker /> </ExampleCard>

</section>

Starter kits

To help kick-start your project, we offer starter kits that include example implementations of all React Aria components with various styling solutions. All components are fully styled, including support for dark mode, high contrast mode, and all UI states. Each starter comes with a pre-configured Storybook that you can experiment with, or use as a starting point for your own component library.

<StarterKits component="colorpicker" />

Reusable wrappers

If you will use a ColorPicker in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency.

This example wraps ColorPicker and all of its children together into a single component. It uses the reusable wrappers for ColorSwatch, ColorArea, ColorSlider, and ColorField created on their corresponding documentation pages. Custom children can also be provided to customize the layout within the popover.

tsx
import type {ColorPickerProps} from 'react-aria-components';
import {Button, ColorPicker, Dialog, DialogTrigger, Popover} from 'react-aria-components';
import {MyColorSwatch} from './ColorSwatch';
import {MyColorArea} from './ColorArea';
import {MyColorSlider} from './ColorSlider';
import {MyColorField} from './ColorField';

interface MyColorPickerProps extends Omit<ColorPickerProps, 'children'> {
  label?: string,
  children?: React.ReactNode
}

function MyColorPicker({label, children, ...props}: MyColorPickerProps) {
  return (
    <ColorPicker {...props}>
      <DialogTrigger>
        <Button className="color-picker">
          <MyColorSwatch />
          <span>{label}</span>
        </Button>
        <Popover placement="bottom start">
          <Dialog className="color-picker-dialog">
            {children || <>
              <MyColorArea colorSpace="hsb" xChannel="saturation" yChannel="brightness" />
              <MyColorSlider colorSpace="hsb" channel="hue" />
              <MyColorField label="Hex" />
            </>}
          </Dialog>
        </Popover>
      </DialogTrigger>
    </ColorPicker>
  );
}

<MyColorPicker label="Fill color" defaultValue="#f00" />

Value

A ColorPicker requires either an uncontrolled default value or a controlled value, provided using the defaultValue or value props respectively. The value provided to either of these props should be a color string or <TypeLink links={docs.links} type={docs.exports.Color} /> object.

Uncontrolled

By default, ColorPicker is uncontrolled. You can set a default value using the defaultValue prop.

tsx
<MyColorPicker
  label="Color"
  defaultValue="hsl(25, 100%, 50%)" />

Controlled

In the example below, the <TypeLink links={docs.links} type={docs.exports.parseColor} /> function is used to parse the initial color from a HSL string so that value's type remains consistent.

tsx
import {parseColor} from 'react-aria-components';

function Example() {
  let [value, setValue] = React.useState(parseColor('hsl(25, 100%, 50%)'));
  return (
    <MyColorPicker
      label="Color"
      value={value}
      onChange={setValue} />
  );
}

Events

ColorPicker accepts an onChange prop which is triggered whenever the value is edited by the user. It receives a <TypeLink links={docs.links} type={docs.exports.Color} /> object as a parameter.

The example below uses onChange to update a separate element with the color value as a string.

tsx
function Example() {
  let [value, setValue] = React.useState(parseColor('hsl(50, 100%, 50%)'));

  return (
    <div>
      <MyColorPicker
        label="Color"
        value={value}
        onChange={setValue} />
      <p>Selected color: {value.toString('hsl')}</p>
    </div>
  );
}

Examples

Channel sliders

This example uses ColorSlider to allow a user to adjust each channel of a color value, with a Select to switch between color spaces.

tsx
import type {ColorSpace} from 'react-aria-components';
import {getColorChannels} from 'react-aria-components';
import {MyColorSlider} from './ColorSlider';
import {MySelect, MyItem} from './Select';

function Example() {
  let [space, setSpace] = React.useState<ColorSpace>('rgb');

  return (
    <MyColorPicker label="Fill color" defaultValue="#184">
      <MySelect aria-label="Color space" selectedKey={space} onSelectionChange={s => setSpace(s as ColorSpace)}>
        <MyItem id="rgb">RGB</MyItem>
        <MyItem id="hsl">HSL</MyItem>
        <MyItem id="hsb">HSB</MyItem>
      </MySelect>
      {getColorChannels(space).map(channel => (
        <MyColorSlider key={channel} colorSpace={space} channel={channel} />
      ))}
      <MyColorSlider channel="alpha" />
    </MyColorPicker>
  );
}

Color wheel

This example combines a ColorWheel and ColorArea to build an HSB color picker.

tsx
import {MyColorWheel} from './ColorWheel';
import {MyColorArea} from './ColorArea';

<MyColorPicker label="Stroke color" defaultValue="#345">
  <MyColorWheel />
  <MyColorArea 
    colorSpace="hsb"
    xChannel="saturation"
    yChannel="brightness"
    style={{width: '100px', height: '100px', position: 'absolute', top: 'calc(50% - 50px)', left: 'calc(50% - 50px)'}} />
</MyColorPicker>

Channel fields

This example uses ColorField to allow a user to edit the value of each color channel as a number, along with a Select to switch between color spaces.

tsx
import type {ColorSpace} from 'react-aria-components';
import {getColorChannels} from 'react-aria-components';
import {MyColorArea} from './ColorArea';
import {MyColorSlider} from './ColorSlider';
import {MySelect, MyItem} from './Select';
import {MyColorField} from './ColorField';

function Example() {
  let [space, setSpace] = React.useState<ColorSpace>('rgb');

  return (
    <MyColorPicker label="Color" defaultValue="#f80">
      <MyColorArea colorSpace="hsb" xChannel="saturation" yChannel="brightness" />
      <MyColorSlider colorSpace="hsb" channel="hue" />
      <MySelect aria-label="Color space" selectedKey={space} onSelectionChange={s => setSpace(s as ColorSpace)}>
        <MyItem id="rgb">RGB</MyItem>
        <MyItem id="hsl">HSL</MyItem>
        <MyItem id="hsb">HSB</MyItem>
      </MySelect>
      <div style={{display: 'flex', gap: 4, width: 192}}>
        {getColorChannels(space).map(channel => (
          <MyColorField key={channel} colorSpace={space} channel={channel} style={{flex: 1}} />
        ))}
      </div>
    </MyColorPicker>
  );
}

Swatches

This example uses a ColorSwatchPicker to provide color presets for a color picker.

tsx
import {MyColorSwatchPicker, MyColorSwatchPickerItem} from './ColorSwatchPicker';

<MyColorPicker label="Color" defaultValue="#A00">
  <MyColorArea colorSpace="hsb" xChannel="saturation" yChannel="brightness" />
  <MyColorSlider colorSpace="hsb" channel="hue" />
  <MyColorSwatchPicker>
    <MyColorSwatchPickerItem color="#A00" />
    <MyColorSwatchPickerItem color="#f80" />
    <MyColorSwatchPickerItem color="#080" />
    <MyColorSwatchPickerItem color="#08f" />
    <MyColorSwatchPickerItem color="#008" />
  </MyColorSwatchPicker>
</MyColorPicker>

Props

<PropTable component={docs.exports.ColorPicker} links={docs.links} />

Advanced customization

Contexts

All React Aria Components export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in React Aria Components itself. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in mergeProps).

<ContextTable components={['ColorPicker']} docs={docs} />

State

ColorPicker provides a <TypeLink links={statelyDocs.links} type={statelyDocs.exports.ColorPickerState} /> object to its children via ColorPickerStateContext. This can be used to access and manipulate the color area's state.

This example uses the browser EyeDropper API (currently available in Chromium-based browsers) to allow users to sample on-screen colors. The ColorPicker is updated when the user chooses a color via the ColorPickerStateContext.

tsx
import {ColorPickerStateContext, parseColor} from 'react-aria-components';
import SamplerIcon from '@spectrum-icons/workflow/Sampler';

function EyeDropperButton() {
  let state = React.useContext(ColorPickerStateContext)!;

  // Check browser support.
  // @ts-ignore
  if (typeof EyeDropper === 'undefined') {
    return 'EyeDropper is not supported in your browser.';
  }

  return (
    <Button
      aria-label="Eye dropper"
      style={{alignSelf: 'start'}}
      onPress={() => {
        // @ts-ignore
        new EyeDropper().open().then(result => state.setColor(parseColor(result.sRGBHex)));
      }}>
      <SamplerIcon size="S" />
    </Button>
  );
}

<MyColorPicker label="Color" defaultValue="#345">
  <MyColorArea colorSpace="hsb" xChannel="saturation" yChannel="brightness" />
  <MyColorSlider colorSpace="hsb" channel="hue" />
  <EyeDropperButton />
</MyColorPicker>