Skip to content
RTL Support: Full
Accessibility: Full
Translations: Work in progress

Color Picker

A versatile color picker component that supports swatches, custom colors, and various picker formats.

Basic Usage

The type prop chooses how the picker is presented:

  • swatch (default) — a small color trigger that opens a popover of preset swatches. Best when users pick from a curated palette (brand colors, a theme).
  • picker — a color trigger that opens the full custom picker (saturation/brightness area, hue and alpha sliders, and input fields). Best when users need to choose any color.
  • inline-picker — the full picker rendered directly in the page with no trigger or popover. Best for embedding inside a panel or form where it should always be visible.

Bind the selected color with value and listen to @update:picker (custom colors) or @select (swatch/final selection). The three examples below show each type.

#
%
selected color from inline-picker:
vue
<template>
  <HLColorPicker id="basic-color-picker" :swatches="swatches" />
</template>

<script setup lang="ts">
import { HLColorPicker } from '@platform-ui/highrise'
import { swatches } from './options'
</script>
ts
import type { HLColorPickerSections } from '@platform-ui/highrise'

export const swatches: HLColorPickerSections[] = [
  {
    key: 'brand',
    label: 'Brand Colors',
    swatches: ['var(--primary-200)', 'var(--primary-500)', 'var(--primary-800)'],
    showAddButton: false,
    showTooltip: 'value',
  },
]
vue
<template>
  <HLColorPicker id="basic-color-picker-2" type="picker" @select="handleOnSelect" />
</template>

<script setup lang="ts">
import { HLColorPicker } from '@platform-ui/highrise'

const handleOnSelect = (...args: unknown[]) => {
  console.log('select: ', ...args)
}
</script>
vue
<template>
  <div class="w-80">
    <HLColorPicker id="basic-color-picker-3" type="inline-picker" @select="handleOnSelect" />
  </div>
</template>

<script setup lang="ts">
import { HLColorPicker } from '@platform-ui/highrise'

const handleOnSelect = (...args: unknown[]) => {
  console.log('select: ', ...args)
}
</script>

Configuring the Picker

Use pickerOptions to control which parts of the picker UI appear and which color formats are available. Every field is optional and falls back to its default (see Types):

  • showHue — the hue slider (the rainbow bar) for choosing the base color. Hiding it locks the picker to shades of the current hue.
  • showAlpha — the opacity slider and its alpha input. Hide it when you only want fully-opaque colors.
  • showInputFields — the numeric/hex input fields (with the format dropdown) for typing exact values, instead of only dragging in the color area.
  • showEyeDropper — the eyedropper button that lets users sample any color on screen. (Browser support varies; it only appears where the EyeDropper API is available.)
  • showCTA — the Save / Cancel action buttons. Only applies to the swatch type's add/edit flow; picker and inline-picker commit changes live and don't show these.
  • pickerFormatOptions — which formats appear in the format dropdown (any of HEX, RGB, HSL, HSB, Custom).
  • defaultPickerFormat — the format selected initially. Must be one of pickerFormatOptions, otherwise it falls back to the first entry.
 
 
 
vue
<template>
  <HLColorPicker
    id="picker-options-demo"
    type="inline-picker"
    :value="color"
    :picker-options="{
      showAlpha: false, // hide the opacity slider
      showEyeDropper: false, // hide the eyedropper tool
      showCTA: false, // hide the save/cancel buttons
      pickerFormatOptions: ['HEX', 'RGB'], // only offer HEX and RGB
      defaultPickerFormat: 'RGB', // start in RGB
    }"
    @update:picker="(value, value8) => (color = value8)"
  />
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import { HLColorPicker } from '@platform-ui/highrise'

  const color = ref('#26a6a6')
</script>

Empty States

Use the #empty slot to render custom content for sections with no swatches.

vue
<template>
  <HLColorPicker id="basic-color-picker" :swatches="emptySwatches">
    <template #empty="{ key }"> No {{ key.toLowerCase() }} colors found </template>
  </HLColorPicker>
</template>

<script setup lang="ts">
import { HLColorPicker } from '@platform-ui/highrise'
import { emptySwatches } from './options'
</script>
ts
import type { HLColorPickerSections } from '@platform-ui/highrise'

export const emptySwatches: HLColorPickerSections[] = [
  {
    key: 'brand',
    label: 'Brand Colors',
    swatches: [],
    showAddButton: false,
    showTooltip: 'value',
  },
  {
    key: 'default',
    label: 'Default Colors',
    swatches: [],
    showAddButton: true,
    showTransparentSwatch: true,
    showEyeDropper: true,
    showTooltip: 'both',
  },
  {
    key: 'custom',
    label: 'Custom Colors',
    swatches: [],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
]

More Swatches

Open dev tools console and play with the picker to see how the emits work!

vue
<template>
  <HLColorPicker
    id="basic-color-picker"
    :swatches="moreSwatches"
    :forward-picker-updates="true"
    @select="(...args) => handleCustomUpdate('select', args)"
    @swatch:add="(...args) => handleCustomUpdate('swatch:add', args)"
    @swatch:delete="(...args) => handleCustomUpdate('swatch:delete', args)"
    @swatch:edit="(...args) => handleCustomUpdate('swatch:edit', args)"
    @swatch:reset="(...args) => handleCustomUpdate('swatch:reset', args)"
    @update:picker="(...args) => handleCustomUpdate('update:picker', args)"
    @update:picker:back="(...args) => handleCustomUpdate('update:picker:back', args)"
    @update:picker:close="(...args) => handleCustomUpdate('update:picker:close', args)"
    @update:show="(...args) => handleCustomUpdate('update:show', args)"
    @update:swatches="(...args) => handleCustomUpdate('update:swatches', args)"
  />
</template>

<script setup lang="ts">
import { HLColorPicker } from '@platform-ui/highrise'
import { moreSwatches } from './options'

const handleCustomUpdate = (type: string, args: unknown[]) => {
  console.log(`${type}: `, ...args)
}
</script>
ts
import type { HLColorPickerSections } from '@platform-ui/highrise'

export const moreSwatches: HLColorPickerSections[] = [
  {
    key: 'brand',
    label: 'Brand Colors',
    swatches: ['var(--primary-500)', 'var(--primary-600)', 'var(--primary-700)', 'var(--primary-800)', 'var(--primary-900)'],
    showAddButton: false,
    showTooltip: 'value',
  },
  {
    key: 'default',
    label: 'Default Colors',
    swatches: ['var(--gray-100)', 'var(--gray-200)', 'var(--gray-300)', 'var(--gray-400)', 'var(--gray-500)'],
    showAddButton: true,
    showTransparentSwatch: true,
    showEyeDropper: true,
    showTooltip: 'both',
  },
  {
    key: 'custom',
    label: 'Custom Colors',
    swatches: ['var(--green-100)', 'var(--green-200)', 'var(--green-300)', 'var(--green-400)', 'var(--green-500)'],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
  {
    key: 'blues',
    label: 'Blues',
    swatches: ['var(--indigo-100)', 'var(--indigo-200)', 'var(--indigo-300)', 'var(--indigo-400)', 'var(--indigo-500)'],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
  {
    key: 'pinks',
    label: 'Pinks',
    swatches: ['var(--pink-100)', 'var(--pink-200)', 'var(--pink-300)', 'var(--pink-400)', 'var(--pink-500)'],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
]

CRUD Operations on Swatches

Look out for notifications based on CRUD operations performed on the swatches!

vue
<template>
  <HLColorPicker
    id="basic-color-picker"
    :swatches="colorpickerSections"
    @swatch:add="handleSwatchAdd"
    @swatch:edit="handleSwatchEdit"
    @swatch:delete="handleSwatchDelete"
  />
</template>

<script setup lang="ts">
import { HLColorPicker } from '@platform-ui/highrise'
import { colorpickerSections, handleSwatchAdd, handleSwatchEdit, handleSwatchDelete } from './options'
</script>
ts
import { h } from 'vue'
import type { HLColorPickerSections, HLColorSwatch } from '@platform-ui/highrise'
import { HLAlert, HLSwatchTile, HLText, useHLNotification } from '@platform-ui/highrise'

export const colorpickerSections: HLColorPickerSections[] = [
  {
    key: 'brand',
    label: 'Brand Colors',
    swatches: ['var(--primary-500)', 'var(--primary-600)', 'var(--primary-700)', 'var(--primary-800)', 'var(--primary-900)'],
    showAddButton: false,
    showTooltip: 'value',
  },
  {
    key: 'default',
    label: 'Default Colors',
    swatches: ['var(--gray-100)', 'var(--gray-200)', 'var(--gray-300)', 'var(--gray-400)', 'var(--gray-500)'],
    showAddButton: true,
    showTransparentSwatch: true,
    showEyeDropper: true,
    showTooltip: 'both',
  },
  {
    key: 'custom',
    label: 'Custom Colors',
    swatches: ['var(--green-100)', 'var(--green-200)', 'var(--green-300)', 'var(--green-400)', 'var(--green-500)'],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
  {
    key: 'blues',
    label: 'Blues',
    swatches: ['var(--indigo-100)', 'var(--indigo-200)', 'var(--indigo-300)', 'var(--indigo-400)', 'var(--indigo-500)'],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
  {
    key: 'pinks',
    label: 'Pinks',
    swatches: ['var(--pink-100)', 'var(--pink-200)', 'var(--pink-300)', 'var(--pink-400)', 'var(--pink-500)'],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
]

const notification = useHLNotification()
function openNotification(
  action: 'add' | 'edit' | 'delete',
  sectionKey: string,
  sectionIndex: number,
  value: string | HLColorSwatch,
  editorDeleteIndex?: number
) {
  const mergedValue = typeof value === 'string' ? value : JSON.stringify(value)
  const message =
    action === 'add' ? 'A new swatch has been added' : action === 'edit' ? 'A swatch has been edited' : 'A swatch has been deleted'
  const color = action === 'add' ? 'green' : action === 'edit' ? 'blue' : 'red'
  const n = notification.create({
    content: () =>
      h(
        HLAlert,
        {
          closable: true,
          color,
          id: 'test-alert',
          width: '720px',
          onClose: () => {
            n.destroy()
          },
        },
        {
          default: () =>
            h(
              'div',
              {
                class: 'flex flex-col gap-2',
              },
              [
                h(
                  HLText,
                  {
                    size: 'xl',
                    weight: 'bold',
                  },
                  () => message
                ),
                h(
                  HLText,
                  {
                    size: 'md',
                    weight: 'medium',
                  },
                  () => `Section: ${sectionKey}`
                ),
                h(
                  HLText,
                  {
                    size: 'md',
                    weight: 'medium',
                  },
                  () => `Section Index: ${sectionIndex}`
                ),
                h(
                  HLText,
                  {
                    size: 'md',
                    weight: 'medium',
                  },
                  () => `Value: ${mergedValue}`
                ),
                h(
                  HLText,
                  {
                    size: 'md',
                    weight: 'medium',
                  },
                  () => `Swatch Index: ${editorDeleteIndex ?? 'N/A'}`
                ),
                h(HLSwatchTile, {
                  value: typeof value === 'string' ? value : value.value8,
                  shape: 'rectangle',
                  size: 'lg',
                }),
              ]
            ),
        }
      ),
    duration: 10000,
  })
}
export const handleSwatchAdd = (sectionKey: string, sectionIndex: number, value: string | HLColorSwatch) => {
  console.log('handleSwatchAdd: ', sectionKey, sectionIndex, value)
  openNotification('add', sectionKey, sectionIndex, value)
}
export const handleSwatchEdit = (sectionKey: string, sectionIndex: number, editIndex: number, value: string | HLColorSwatch) => {
  console.log('handleSwatchEdit: ', sectionKey, sectionIndex, editIndex, value)
  openNotification('edit', sectionKey, sectionIndex, value, editIndex)
}
export const handleSwatchDelete = (sectionKey: string, sectionIndex: number, deleteIndex: number, value: string | HLColorSwatch) => {
  console.log('handleSwatchDelete: ', sectionKey, sectionIndex, deleteIndex, value)
  openNotification('delete', sectionKey, sectionIndex, value, deleteIndex)
}

Default Swatches

Spread defaultColorSections to include the built-in default palette alongside your own sections.

vue
<template>
  <HLColorPicker id="basic-color-picker" :swatches="colorpickerSections" :width="394" />
</template>

<script setup lang="ts">
import { HLColorPicker } from '@platform-ui/highrise'
import { colorpickerSections } from './options'
</script>
ts
import type { HLColorPickerSections } from '@platform-ui/highrise'
import { defaultColorSections } from '@platform-ui/highrise'

export const colorpickerSections: HLColorPickerSections[] = [
  {
    key: 'brand',
    label: 'Brand Colors',
    swatches: ['var(--primary-500)', 'var(--primary-600)', 'var(--primary-700)', 'var(--primary-800)', 'var(--primary-900)'],
    showAddButton: false,
    showTooltip: 'value',
  },
  ...defaultColorSections,
  {
    key: 'custom',
    label: 'Custom Colors',
    swatches: ['var(--green-100)', 'var(--green-200)', 'var(--green-300)', 'var(--green-400)', 'var(--green-500)'],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
]

Tooltip Types

The showTooltip property controls what information is displayed when hovering over a color swatch. It has four possible values:

  • name: Shows only the color's label in the tooltip
  • value: Shows only the color value and alpha percentage in the tooltip
  • both: Shows both the name and value information If the color has a label: Shows the label in the header and the color value with alpha below If the color has no label: Shows just the color value with alpha
  • false: No tooltip will appear when hovering over the color swatch
vue
<template>
  <HLColorPicker id="basic-color-picker" :swatches="tooltipSections" :width="392" />
</template>

<script setup lang="ts">
import { HLColorPicker } from '@platform-ui/highrise'
import { tooltipSections } from './options'
</script>
ts
import type { HLColorPickerSections } from '@platform-ui/highrise'

export const tooltipSections: HLColorPickerSections[] = [
  {
    key: 'custom1',
    label: 'Show Name',
    swatches: [
      {
        value: '#22C55E',
        value8: '#22C55EFF',
        alpha: 100,
        label: 'Success Green',
      },
      {
        value: '#16A34A',
        value8: '#16A34AFF',
        alpha: 100,
        label: 'Forest Green',
      },
      {
        value: '#15803D',
        value8: '#15803DFF',
        alpha: 100,
        label: 'Deep Green',
      },
      {
        value: '#166534',
        value8: '#166534FF',
        alpha: 100,
        label: 'Dark Green',
      },
      {
        value: '#14532D',
        value8: '#14532DFF',
        alpha: 100,
        label: 'Pine Green',
      },
      {
        value: '#14532D',
        value8: '#14532DFE',
        alpha: 99,
        label: 'Pine Green v2',
        custom: '{{ customValues.pine.green.v2 }}',
      },
    ],
    showAddButton: true,
    showEyeDropper: true,
    showTransparentSwatch: true,
    showTooltip: 'name',
    editable: true,
  },
  {
    key: 'custom2',
    label: 'Show Value',
    swatches: [
      {
        value: '#3B82F6',
        value8: '#3B82F6FF',
        alpha: 100,
        label: 'Primary Blue',
      },
      {
        value: '#2563EB',
        value8: '#2563EBFF',
        alpha: 100,
        label: 'Royal Blue',
      },
      {
        value: '#1D4ED8',
        value8: '#1D4ED8FF',
        alpha: 100,
        label: 'Deep Blue',
      },
      {
        value: '#1E40AF',
        value8: '#1E40AFFF',
        alpha: 100,
        label: 'Navy Blue',
      },
      {
        value: '#1E3A8A',
        value8: '#1E3A8AFF',
        alpha: 100,
        label: 'Dark Blue',
      },
      {
        value: '#1E3A8A',
        value8: '#1E3A8AFE',
        alpha: 99,
        label: 'Dark Blue v2',
        custom:
          '{{ customValues.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2.darkBlue.v2 }}',
      },
    ],
    showAddButton: true,
    showEyeDropper: true,
    showTransparentSwatch: true,
    showTooltip: 'value',
    editable: true,
  },
  {
    key: 'custom3',
    label: 'Show Both',
    swatches: [
      {
        value: '#EF4444',
        value8: '#EF4444FF',
        alpha: 100,
        label: 'Bright Red',
      },
      {
        value: '#DC2626',
        value8: '#DC2626FF',
        alpha: 100,
        label: 'Fire Red',
      },
      {
        value: '#B91C1C',
        value8: '#B91C1CFF',
        alpha: 100,
        label: 'Ruby Red',
      },
      {
        value: '#991B1B',
        value8: '#991B1BFF',
        alpha: 100,
        label: 'Dark Red',
      },
      {
        value: '#7F1D1D',
        value8: '#7F1D1DFF',
        alpha: 100,
        label: 'Deep Red',
      },
      {
        value: '#7F1D1D',
        value8: '#7F1D1DFE',
        alpha: 99,
        label: 'Deep Red v2',
        custom:
          '{{ customValues.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2.deepRed.v2 }}',
      },
    ],
    showAddButton: true,
    showEyeDropper: true,
    showTransparentSwatch: true,
    showTooltip: 'both',
    editable: true,
  },
]

Different Swatch Shapes

Control swatch appearance with swatch-shape and swatch-size on the picker (they set the default square/circle/rectangle shape and size for all swatches), then override individual tiles with overrideTileShape per section or per swatch. The example below sets swatch-size="3xs" and uses per-swatch overrideTileShape.

vue
<template>
  <!-- swatch-shape sets the default for every tile; overrideTileShape (in options) overrides individual ones -->
  <HLColorPicker id="basic-color-picker" swatch-shape="circle" swatch-size="3xs" :width="392" :swatches="swatches" />
</template>

<script setup lang="ts">
import { HLColorPicker } from '@platform-ui/highrise'
import { swatches } from './options'
</script>
ts
import type { HLColorPickerSections } from '@platform-ui/highrise'

export const swatches: HLColorPickerSections[] = [
  {
    key: 'custom1',
    label: 'Show Name',
    swatches: [
      {
        value: '#22C55E',
        value8: '#22C55EFF',
        alpha: 100,
        label: 'Success Green',
        overrideTileShape: 'circle',
      },
      {
        value: '#16A34A',
        value8: '#16A34AFF',
        alpha: 100,
        label: 'Forest Green',
      },
      {
        value: '#15803D',
        value8: '#15803DFF',
        alpha: 100,
        label: 'Deep Green',
        overrideTileShape: 'rectangle',
      },
      {
        value: '#166534',
        value8: '#166534FF',
        alpha: 100,
        label: 'Dark Green',
      },
      {
        value: '#14532D',
        value8: '#14532DFF',
        alpha: 100,
        label: 'Pine Green',
      },
    ],
    showAddButton: true,
    showEyeDropper: true,
    showTransparentSwatch: true,
    showTooltip: 'name',
    editable: true,
    overrideTileShape: 'rectangle',
  },
  {
    key: 'custom2',
    label: 'Show Value',
    swatches: [
      {
        value: '#3B82F6',
        value8: '#3B82F6FF',
        alpha: 100,
        label: 'Primary Blue',
      },
      {
        value: '#2563EB',
        value8: '#2563EBFF',
        alpha: 100,
        label: 'Royal Blue',
      },
      {
        value: '#1D4ED8',
        value8: '#1D4ED8FF',
        alpha: 100,
        label: 'Deep Blue',
      },
      {
        value: '#1E40AF',
        value8: '#1E40AFFF',
        alpha: 100,
        label: 'Navy Blue',
      },
      {
        value: '#1E3A8A',
        value8: '#1E3A8AFF',
        alpha: 100,
        label: 'Dark Blue',
      },
    ],
    showAddButton: true,
    showEyeDropper: true,
    showTransparentSwatch: true,
    showTooltip: 'value',
    editable: true,
  },
]

Popover Behavior

For swatch and picker types the picker opens in a popover. Tune it with:

  • placement — where the popover opens relative to the trigger (any HLPopoverPlacement, default bottom-start).
  • autoCloseOnSelect — close the popover as soon as a color is selected (default false, so it stays open for multiple picks).
  • to — the teleport target for the popover. Pass a CSS selector or element to mount it inside a specific container, or false to render it in place (useful inside scrolling/overflow-hidden containers).
vue
<template>
  <HLColorPicker
    id="popover-behavior-demo"
    placement="right-start"
    :auto-close-on-select="true"
    :to="false"
    :swatches="swatches"
  />
</template>

<script setup lang="ts">
  import { HLColorPicker } from '@platform-ui/highrise'
  import { swatches } from './options'
</script>
ts
import type { HLColorPickerSections } from '@platform-ui/highrise'

export const swatches: HLColorPickerSections[] = [
  {
    key: 'brand',
    label: 'Brand Colors',
    swatches: ['var(--primary-500)', 'var(--success-500)', 'var(--warning-500)', 'var(--error-500)'],
    showAddButton: false,
  },
]

Custom Trigger

Use the #trigger slot to replace the default swatch trigger with your own element.

vue
<template>
  <HLColorPicker id="basic-color-picker" :width="394" :swatches="swatches">
    <template #trigger>
      <HLButton id="cp-trigger-1" size="sm" variant="secondary" color="blue"> Select Color </HLButton>
    </template>
  </HLColorPicker>
</template>

<script setup lang="ts">
import { HLColorPicker, HLButton } from '@platform-ui/highrise'
import { swatches } from './options'
</script>
ts
import type { HLColorPickerSections } from '@platform-ui/highrise'

export const swatches: HLColorPickerSections[] = [
  {
    key: 'custom',
    label: 'Custom Colors',
    swatches: ['var(--green-100)', 'var(--green-200)', 'var(--green-300)', 'var(--green-400)', 'var(--green-500)'],
  },
]

Custom Add Button Slot

Use the #swatchAddButton slot to customize the per-section Add button; it receives sectionKey and sectionIndex.

vue
<template>
  <HLColorPicker id="basic-color-picker" :width="394" :swatches="colorpickerSections">
    <template #swatchAddButton="{ sectionKey, sectionIndex }">
      <HLButton :id="`${sectionKey}-${sectionIndex}`" size="3xs" color="blue" variant="secondary">
        {{ sectionIndex }}. {{ sectionKey }}
      </HLButton>
    </template>
  </HLColorPicker>
</template>

<script setup lang="ts">
import { HLColorPicker, HLButton } from '@platform-ui/highrise'
import { colorpickerSections } from './options'
</script>
ts
import type { HLColorPickerSections } from '@platform-ui/highrise'

export const colorpickerSections: HLColorPickerSections[] = [
  {
    key: 'brand',
    label: 'Brand Colors',
    swatches: ['var(--primary-500)', 'var(--primary-600)', 'var(--primary-700)', 'var(--primary-800)', 'var(--primary-900)'],
    showAddButton: false,
    showTooltip: 'value',
  },
  {
    key: 'default',
    label: 'Default Colors',
    swatches: ['var(--gray-100)', 'var(--gray-200)', 'var(--gray-300)', 'var(--gray-400)', 'var(--gray-500)'],
    showAddButton: true,
    showTransparentSwatch: true,
    showEyeDropper: true,
    showTooltip: 'both',
  },
  {
    key: 'custom',
    label: 'Custom Colors',
    swatches: ['var(--green-100)', 'var(--green-200)', 'var(--green-300)', 'var(--green-400)', 'var(--green-500)'],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
  {
    key: 'blues',
    label: 'Blues',
    swatches: ['var(--indigo-100)', 'var(--indigo-200)', 'var(--indigo-300)', 'var(--indigo-400)', 'var(--indigo-500)'],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
  {
    key: 'pinks',
    label: 'Pinks',
    swatches: ['var(--pink-100)', 'var(--pink-200)', 'var(--pink-300)', 'var(--pink-400)', 'var(--pink-500)'],
    showAddButton: true,
    showEyeDropper: true,
    showTooltip: 'name',
    editable: true,
  },
]

Accessibility

  • Label the picker via aria-label / aria-labelledby and expose the chosen color with aria-valuetext.
  • Tie contrast guidance or validation hints through aria-describedby, and toggle aria-invalid when a color is rejected.
  • When a palette popover opens, keep the trigger’s aria-expanded / aria-controls in sync with the panel id.

Props

NameTypeDefaultDescription
id *string | undefinedundefinedThe unique identifier for the color picker
valuestring | HLColorSwatch'#000000ff'The current color value
autoCloseOnSelectbooleanfalseWhether to close the picker after color selection
forwardPickerUpdatesbooleanfalseWhen true, emit update:picker to consumers when the picker value changes in swatch mode (add/edit flow) as well
pickerOptionsHLColorPickerOptionsdefaultPickerOptsConfiguration options for the color picker
placement'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' as HLPopoverPlacement'bottom-start'Placement of the color picker
swatchesHLColorPickerSections[][]Array of color section configurations
swatchShape'square' | 'circle' | 'rectangle' as HLSwatchTileShape'square'The shape of color swatches
swatchSize'lg' | 'md' | 'sm' | 'xs' | '2xs' | '3xs' as HLSwatchTileSize'xs'The size of color swatches
trigger'click' | 'hover' | 'focus' | 'manual' as HLPopoverTrigger'click'The trigger action for the color-picker to open
type'swatch' | 'picker' | 'inline-picker' as HLColorPickerType'swatch'The type of color picker to display
widthnumber298Width of the color picker in pixels
tostring | HTMLElement | falseundefinedTeleport target for the color picker popover. Pass a CSS selector or HTMLElement to mount the picker inside a specific container. Pass false to disable teleporting.

Types

ts
export type HLColorPickerSections = {
  key: string
  label: string | false
  swatches: Array<HLColorSwatch | string>
  showAddButton?: boolean
  showTransparentSwatch?: boolean
  showEyeDropper?: boolean
  showTooltip?: 'name' | 'value' | 'both' | false
  editable?: boolean
  overrideTileShape?: 'square' | 'circle' | 'rectangle'
}
ts
export type HLColorPickerOptions = {
  // Format to show by default in the color picker
  defaultPickerFormat?: 'HEX' | 'RGB' | 'HSL' | 'HSB' | 'Custom'
  // Available format options in the dropdown
  pickerFormatOptions?: Array<'HEX' | 'RGB' | 'HSL' | 'HSB' | 'Custom'>
  // Show alpha/opacity control
  showAlpha?: boolean
  // Show save/cancel buttons
  showCTA?: boolean
  // Show eyedropper tool
  showEyeDropper?: boolean
  // Show hue slider
  showHue?: boolean
  // Show input fields for color values
  showInputFields?: boolean
}
ts
// Default values
const defaultPickerOpts = {
  defaultPickerFormat: 'HEX',
  pickerFormatOptions: ['HEX', 'RGB', 'HSL', 'HSB', 'Custom'],
  showAlpha: true,
  showCTA: true,
  showEyeDropper: true,
  showHue: true,
  showInputFields: true,
} as const
ts
export type HLColorSwatch = {
  alpha: number
  value: string
  value8: string
  label?: string
  oldValue?: string | HLColorSwatch
  overrideTileShape?: 'square' | 'circle' | 'rectangle'
  custom?: false | string
  inputType?: 'HEX' | 'RGB' | 'HSL' | 'HSB' | 'Custom'
}

Supported Color Formats

The color picker supports the following formats:

FormatExampleDescription
HEX#FF00006-digit (RGB) hexadecimal
HEXA#FF0000FF8-digit (RGBA) hexadecimal
RGBrgb(255, 0, 0)RGB values (0-255)
RGBArgba(255, 0, 0, 1)RGB with alpha channel (0-1)
HSLhsl(0, 100%, 50%)Hue (0-360), Saturation (0-100%), Lightness (0-100%)
HSLAhsla(0, 100%, 50%, 1)HSL with alpha channel (0-1)
HSBhsb(0, 100%, 100%)Hue (0-360), Saturation (0-100%), Brightness (0-100%)
Customvar(--primary-500) | {{ location.customValue }}Custom variable names

Emits

Note: When using @update:picker, use rest parameters or an optional 7th parameter so your handler works both when forwardPickerUpdates is true (7 arguments) and when it is false or when using non-swatch types (6 arguments).

NameParametersDescription
@select(selectedValue: string | HLColorSwatch, sectionKey?: string, sectionIndex?: number, tileIndex?: number) => voidEmitted when a color is selected. For type="picker" / type="inline-picker", selectedValue is the HEXA string. For type="swatch", selectedValue is the chosen swatch and the remaining arguments identify its section and position.
@swatch:add(sectionKey: string, sectionIndex: number, newValue: HLColorSwatch) => voidEmitted when a new swatch is added; index not necessary as new swatches are added to the start of the array; swatches array is not updated in case of custom value
@swatch:delete(sectionKey: string, sectionIndex: number, deleteIndex: number, deletedValue: string | HLColorSwatch) => voidEmitted when a swatch is deleted
@swatch:edit(sectionKey: string, sectionIndex: number, editIndex: number, updatedValue: string | HLColorSwatch) => voidEmitted when a swatch is edited
@swatch:reset(sectionKey: string, sectionIndex: number, resetIndex: number, revertedValue: string | HLColorSwatch) => voidEmitted when a swatch is deleted
@update:picker(value: string, value8: string, alpha: number, inputType?: PickerInputType, label?: string, custom?: string | false, isEditing?: boolean) => voidEmitted when picker value changes. Always emitted for type="picker" and type="inline-picker". For type="swatch", also emitted during add/edit flow when forwardPickerUpdates is true. The 7th argument isEditing is true when the update is from editing an existing swatch.
@update:picker:back(save?: boolean, deleteColor?: boolean, custom?: boolean) => voidEmitted when the user navigates back from the picker (swatch add/edit flow). Only when forwardPickerUpdates is true. save is true when saving, deleteColor when deleting the color being edited, custom for custom value.
@update:picker:close() => voidEmitted when the picker popover is closed. Only when forwardPickerUpdates is true.
@update:show(value: boolean) => voidEmitted when picker visibility changes
@update:swatches(value: HLColorPickerSections[]) => voidEmitted when swatches are updated

Slots

NameParametersDescription
trigger()Custom trigger element. If not provided, defaults to a color swatch with the current value
swatchAddButton(sectionKey: string, sectionIndex: number)Slot to replace the "Add" button in each section. Will be available only if showAddButton is enabled for the corresponding section