ColorPicker

A composable color picker that synchronizes color value between multiple color components

Usage

<script setup lang="ts">
import { ColorArea, ColorPicker, ColorSlider, ColorSwatch, Label } from '@hareui/vue'
</script>
<script setup lang="ts">import { ColorArea, ColorPicker, ColorSlider, ColorSwatch, Label } from '@hareui/vue'</script><template>  <ColorPicker default-value="#0485F7">    <template #trigger>      <ColorSwatch size="lg" />      <Label>Pick a color</Label>    </template>    <ColorArea aria-label="Color area" class="max-w-full" color-space="hsb" x-channel="saturation" y-channel="brightness" />    <ColorSlider channel="hue" class="gap-1 px-1" color-space="hsb" label="Hue" :ui="{ output: 'text-muted' }" />  </ColorPicker></template>

Anatomy

ColorPicker renders a trigger button and a popover. The trigger slot holds the button content, and the default slot holds the popover content. Color components placed in either slot without their own v-model read and write the picker's color.

<ColorPicker default-value="#0485F7">
  <template #trigger>
    <ColorSwatch />
    <Label>Pick a color</Label>
  </template>
  <ColorArea color-space="hsb" x-channel="saturation" y-channel="brightness" />
  <ColorSlider channel="hue" color-space="hsb" />
</ColorPicker>

Examples

Controlled

v-model accepts a hex string or a Color object and emits a Color. v-model:open controls the popover.

Selected: #325578

<script setup lang="ts">import type { ColorSwatchPickerItem } from '@hareui/vue'import { Button, ColorArea, ColorField, ColorPicker, ColorSlider, ColorSwatch, ColorSwatchPicker, Label, colorToString, parseColor } from '@hareui/vue'import { ref } from 'vue'const color = ref(parseColor('#325578'))const presets: ColorSwatchPickerItem[] = ['#ef4444', '#f97316', '#eab308', '#22c55e', '#06b6d4', '#3b82f6', '#8b5cf6', '#ec4899', '#f43f5e']function shuffleColor() {  const h = Math.floor(Math.random() * 360)  const s = 50 + Math.floor(Math.random() * 50)  const l = 40 + Math.floor(Math.random() * 30)  color.value = parseColor(`hsl(${h}, ${s}%, ${l}%)`)}</script><template>  <div class="flex flex-col gap-4">    <ColorPicker v-model="color">      <template #trigger>        <ColorSwatch size="lg" />        <Label>Pick a color</Label>      </template>      <ColorSwatchPicker :items="presets" class="justify-center pt-2" size="xs" />      <ColorArea aria-label="Color area" class="max-w-full" color-space="hsb" x-channel="saturation" y-channel="brightness" />      <div class="flex items-center gap-2 px-1">        <ColorSlider aria-label="Hue slider" channel="hue" class="flex-1" color-space="hsb" />        <Button icon-only aria-label="Shuffle color" size="sm" variant="tertiary" @click="shuffleColor">          <svg class="size-4" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 4h2.5c3 0 4 8 7 8H14M2 12h2.5c1 0 1.8-1.4 2.5-2.6M14 4h-2.5c-1 0-1.8 1-2.5 2.2M12 2l2 2-2 2M12 10l2 2-2 2" /></svg>        </Button>      </div>      <ColorField aria-label="Color field" variant="secondary">        <template #prefix>          <ColorSwatch size="xs" />        </template>      </ColorField>    </ColorPicker>    <p class="w-60 text-sm text-muted">      Selected: <span class="font-medium">{{ colorToString(color, 'hex') }}</span>    </p>  </div></template>

With Swatches

<script setup lang="ts">import type { ColorSwatchPickerItem } from '@hareui/vue'import { ColorArea, ColorPicker, ColorSlider, ColorSwatch, ColorSwatchPicker, Label } from '@hareui/vue'const presets: ColorSwatchPickerItem[] = ['#ef4444', '#f97316', '#eab308', '#22c55e', '#06b6d4', '#3b82f6', '#8b5cf6', '#ec4899', '#f43f5e']</script><template>  <ColorPicker default-value="#F43F5E">    <template #trigger>      <ColorSwatch size="lg" />      <Label>Brand Color</Label>    </template>    <ColorArea aria-label="Color area" class="max-w-full" color-space="hsb" x-channel="saturation" y-channel="brightness" />    <ColorSlider aria-label="Hue slider" channel="hue" class="gap-1 px-1" color-space="hsb" label="Hue" :ui="{ output: 'text-muted' }" />    <ColorSwatchPicker :items="presets" class="justify-center px-1" size="xs" />  </ColorPicker></template>

With Fields

Use ColorField to edit individual channels, with a Select to switch between color spaces.

<script setup lang="ts">import type { ListBoxItem } from '@hareui/vue'import type { ColorChannel, ColorSpace } from 'reka-ui'import { ColorArea, ColorField, ColorPicker, ColorSlider, ColorSwatch, Label, Select } from '@hareui/vue'import { ref } from 'vue'const colorSpace = ref<ColorSpace>('hsl')const channels: Record<ColorSpace, ColorChannel[]> = {  hsb: ['hue', 'saturation', 'brightness'],  hsl: ['hue', 'saturation', 'lightness'],  rgb: ['red', 'green', 'blue'],}const spaces: ListBoxItem[] = Object.keys(channels).map(value => ({ value, label: value }))</script><template>  <ColorPicker default-value="hsla(220, 90%, 50%, 0.8)" :ui="{ popover: 'max-w-62 gap-2' }">    <template #trigger>      <ColorSwatch size="lg" />      <Label>Pick a color</Label>    </template>    <ColorArea class="max-w-full" color-space="hsb" x-channel="saturation" y-channel="brightness" />    <ColorSlider channel="hue" class="gap-1 px-1" color-space="hsb" label="Hue" :ui="{ output: 'text-muted' }" />    <Select v-model="colorSpace" aria-label="Color space" variant="secondary" class="uppercase" :items="spaces" />    <div class="grid w-full grid-cols-3 items-center gap-2">      <ColorField v-for="channel in channels[colorSpace]" :key="channel" :aria-label="channel" :channel="channel" :color-space="colorSpace" variant="secondary" />    </div>  </ColorPicker></template>

With Sliders

Use several ColorSlider components to adjust each channel.

<script setup lang="ts">import type { ListBoxItem } from '@hareui/vue'import type { ColorChannel, ColorSpace } from 'reka-ui'import { ColorPicker, ColorSlider, ColorSwatch, Label, Select } from '@hareui/vue'import { ref } from 'vue'const colorSpace = ref<ColorSpace>('hsl')const channels: Record<ColorSpace, ColorChannel[]> = {  hsb: ['hue', 'saturation', 'brightness', 'alpha'],  hsl: ['hue', 'saturation', 'lightness', 'alpha'],  rgb: ['red', 'green', 'blue', 'alpha'],}const spaces: ListBoxItem[] = Object.keys(channels).map(value => ({ value, label: value }))const title = (s: string) => s[0].toUpperCase() + s.slice(1)</script><template>  <ColorPicker default-value="hsl(219, 58%, 93%)" :ui="{ popover: 'max-w-62 gap-2 px-2 py-3' }">    <template #trigger>      <ColorSwatch size="lg" />      <Label>Pick a color</Label>    </template>    <Select v-model="colorSpace" aria-label="Color space" variant="secondary" class="uppercase" :items="spaces" />    <div class="flex flex-col gap-2">      <ColorSlider        v-for="channel in channels[colorSpace]" :key="channel" :aria-label="channel" :channel="channel"        class="gap-1 px-1" :color-space="colorSpace" :label="title(channel)" :ui="{ output: 'text-muted' }"      />    </div>  </ColorPicker></template>

Customization

Tailwind CSS

<script setup lang="ts">import { ColorArea, ColorPicker, ColorSlider, ColorSwatch, Label } from '@hareui/vue'</script><template>  <ColorPicker default-value="#0485F7" :ui="{ trigger: 'rounded-xl bg-default-soft px-3 py-2', popover: 'bg-surface' }">    <template #trigger>      <ColorSwatch size="lg" />      <Label class="font-medium text-foreground">Theme color</Label>    </template>    <ColorArea aria-label="Color area" class="max-w-full" color-space="hsb" x-channel="saturation" y-channel="brightness" />    <ColorSlider channel="hue" class="gap-1 px-1" color-space="hsb" label="Hue" :ui="{ output: 'text-muted' }" />  </ColorPicker></template>

Global CSS

app.use(createHareUI({
  ui: {
    colorPicker: {
      slots: {
        trigger: 'gap-4 rounded-lg',
        popover: 'rounded-xl p-4',
      },
    },
  },
}))

Styling Reference

ui keydata-slotDescription
basecolor-pickerWrapper (inline-flex)
triggercolor-picker-triggerTrigger button
popovercolor-picker-popoverPopover container (portalled to body)

Interactive States

  • Focus: :focus-visible or data-focus-visible="true"
  • Disabled: :disabled or data-disabled="true"

API Reference

Props

PropTypeDefaultDescription
modelValuestring | Color-The current colour (controlled). Use with v-model.
defaultValuestring | Color-The initial colour when uncontrolled.
openbooleanundefinedWhether the popover is open (controlled). Use with v-model:open.
defaultOpenboolean-Whether the popover is open initially when uncontrolled.
side"top" | "bottom" | "right" | "left""bottom"Side of the trigger the popover opens on.
align"center" | "start" | "end""start"Alignment of the popover against the trigger. HeroUI's default placement is bottom left, i.e. start in LTR.
sideOffsetnumber8Distance in px between the trigger and the popover.
portalstring | boolean | HTMLElementtrueRender the popover into body (or a given target) instead of in place.
disabledboolean-Whether the trigger is disabled.
uiComponentSlots<{ slots: { base: string; trigger: string[]; popover: string[]; }; }>-Per-slot class overrides.

Slots

SlotPropsDescription
trigger{ color: Color; open: boolean; }Content of the trigger button, typically a ColorSwatch and a Label. Receives the current colour and open state.
default{ color: Color; close: () => void; }Popover content (ColorArea, ColorSlider, ColorField, ColorSwatchPicker, …). Colour components rendered here follow the picker's colour unless they have their own v-model.

Emits

EventPayloadDescription
update:modelValue[value: Color]-
update:open[value: boolean]-

On this page

No Headings