ColorField

Color input field with labels, descriptions, and validation

Usage

<script setup lang="ts">
import { ColorField, parseColor } from '@hareui/vue'
</script>
<script setup lang="ts">import { ColorField, ColorSwatch, parseColor } from '@hareui/vue'import { ref } from 'vue'const color = ref(parseColor('#0485F7'))</script><template>  <ColorField v-model="color" class="w-[280px]" name="color" label="Color">    <template #prefix>      <ColorSwatch :color="color" size="xs" />    </template>  </ColorField></template>

Anatomy

ColorField renders a Label, a group holding an optional prefix (e.g. a ColorSwatch), the input and an optional suffix, then a Description and a FieldError. The text parts come from props; each can be replaced with a slot.

<template>
  <ColorField label="…" description="…" error-message="…" :invalid="…">
    <template #label />        <!-- Label content -->
    <template #prefix />       <!-- content rendered before the input, e.g. a ColorSwatch -->
    <template #suffix />       <!-- content rendered after the input -->
    <template #description />  <!-- Description content -->
    <template #error />        <!-- FieldError content, rendered when invalid -->
  </ColorField>
</template>
ui keydata-slotElement
basecolor-field<div> root
—labelLabel (iff label / #label)
groupcolor-input-group<div role="group"> holding the prefix, input and suffix
prefixcolor-input-group-prefix<span> (iff #prefix)
inputcolor-input-group-input<input>
suffixcolor-input-group-suffix<span> (iff #suffix)
—descriptionDescription (iff description / #description)
—field-errorFieldError (while invalid, iff error-message / #error / validation errors)

ColorField combines a label, color input, description, and error into a single accessible component. It accepts a hex string or a Color object (from parseColor) and always commits a Color object.

Examples

Variants

The ColorField component supports two visual variants:

  • primary (default) - Standard styling with shadow, suitable for most use cases
  • secondary - Lower emphasis variant without shadow, suitable for use in surfaces
<script setup lang="ts">import { ColorField } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-4">    <ColorField class="w-[280px]" default-value="#0485F7" name="primary-color" label="Primary variant" variant="primary" />    <ColorField class="w-[280px]" default-value="#F43F5E" name="secondary-color" label="Secondary variant" variant="secondary" />  </div></template>

On Surface

When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.

Select your theme color
<script setup lang="ts">import { ColorField, Surface } from '@hareui/vue'</script><template>  <Surface class="w-[320px] p-4">    <ColorField      default-value="#3B82F6"      name="color"      label="Theme Color"      description="Select your theme color"      variant="secondary"    />  </Surface></template>

With Description

Enter your brand's primary color
Used for highlights and CTAs
<script setup lang="ts">import { ColorField } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-4">    <ColorField class="w-[280px]" default-value="#3B82F6" name="color" label="Primary Color" description="Enter your brand's primary color" />    <ColorField class="w-[280px]" default-value="#F59E0B" name="accent-color" label="Accent Color" description="Used for highlights and CTAs" />  </div></template>

Required Field

Required field
<script setup lang="ts">import { ColorField } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-4">    <ColorField required class="w-[280px]" name="color" label="Brand Color" placeholder="#000000" />    <ColorField required class="w-[280px]" name="theme-color" label="Theme Color" placeholder="#000000" description="Required field" />  </div></template>

Empty value and forms

Without v-model/default-value, or with a null model, the input is empty and shows its placeholder, as in HeroUI. Reka's ColorField always holds a colour internally, so an empty field still submits #000000 as its form value, and required can't block submitting an empty field. Validate with validate or check for a null model if you need to tell them apart.

Disabled State

This color field is disabled
This color field is disabled
<script setup lang="ts">import { ColorField } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-4">    <ColorField disabled class="w-[280px]" default-value="#0485F7" name="color" label="Color" description="This color field is disabled" />    <ColorField disabled class="w-[280px]" name="color-empty" label="Color" placeholder="#000000" description="This color field is disabled" />  </div></template>

Full Width

<script setup lang="ts">import { ColorField } from '@hareui/vue'</script><template>  <div class="w-[400px] space-y-4">    <ColorField full-width default-value="#10B981" name="color" label="Brand Color" />    <ColorField full-width default-value="#8B5CF6" name="color-with-suffix" label="Theme Color" />  </div></template>

Validation

Set invalid with error-message to control the state yourself, or validate with required, a validate function, or a surrounding Form.

<script setup lang="ts">import { ColorField } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-4">    <ColorField      invalid      required      class="w-[280px]"      name="color"      label="Color"      placeholder="#000000"      error-message="Please enter a valid hex color"    />    <ColorField      invalid      class="w-[280px]"      default-value="#000000"      name="invalid-color"      label="Background Color"      error-message="Invalid color format. Use hex (e.g., #FF5733)"    />  </div></template>

Channel Editing

ColorField supports editing individual color channels (hue, saturation, lightness, red, green, blue, alpha) by setting the colorSpace and channel props.

Edit individual HSL channels:

%
%
Current: #7f007f
<script setup lang="ts">import { ColorField, ColorSwatch, colorToString, parseColor } from '@hareui/vue'import { ref } from 'vue'const color = ref(parseColor('#7F007F'))</script><template>  <div class="flex flex-col gap-4">    <p class="text-sm text-muted">      Edit individual HSL channels:    </p>    <div class="flex gap-4">      <ColorField v-model="color" channel="hue" color-space="hsl" class="w-[100px]" name="hue" label="Hue" />      <ColorField v-model="color" channel="saturation" color-space="hsl" class="w-[100px]" name="saturation" label="Saturation">        <template #suffix>          <span class="text-sm text-muted">%</span>        </template>      </ColorField>      <ColorField v-model="color" channel="lightness" color-space="hsl" class="w-[100px]" name="lightness" label="Lightness">        <template #suffix>          <span class="text-sm text-muted">%</span>        </template>      </ColorField>    </div>    <div class="flex items-center gap-2">      <ColorSwatch :color="color" size="md" />      <span class="text-sm">Current: {{ colorToString(color, 'hex') }}</span>    </div>  </div></template>

Controlled

Bind the value with v-model to synchronize with other components or state management. The committed value is a Color object, or null when the input is cleared (set the model to null to clear it). Use colorToString(color, 'hex') to read it as a string.

Current value: #0485f7
<script setup lang="ts">import type { Color } from '@hareui/vue'import { Button, ColorField, ColorSwatch, colorToString, parseColor } from '@hareui/vue'import { ref } from 'vue'const value = ref<Color | null>(parseColor('#0485F7'))</script><template>  <div class="flex flex-col gap-4">    <ColorField      v-model="value"      class="w-[280px]"      name="color"      label="Color"      :description="`Current value: ${value ? colorToString(value, 'hex') : '(empty)'}`"    >      <template #prefix>        <ColorSwatch :color="value ?? undefined" size="xs" />      </template>    </ColorField>    <div class="flex gap-2">      <Button variant="tertiary" @click="value = parseColor('#EF4444')">        Set Red      </Button>      <Button variant="tertiary" @click="value = parseColor('#10B981')">        Set Green      </Button>      <Button variant="tertiary" @click="value = null">        Clear      </Button>    </div>  </div></template>

Form Example

Complete form example with validation and submission handling.

Choose your brand's primary color
<script setup lang="ts">import type { Color } from '@hareui/vue'import { Button, ColorField, ColorSwatch, Form, colorToString } from '@hareui/vue'import { ref } from 'vue'const value = ref<Color>()const isSubmitting = ref(false)function handleSubmit(e: Event) {  e.preventDefault()  if (!value.value)    return  isSubmitting.value = true  // Simulate API call  setTimeout(() => {    console.log('Color submitted:', { color: colorToString(value.value!, 'hex') })    value.value = undefined    isSubmitting.value = false  }, 1500)}</script><template>  <Form class="flex w-[280px] flex-col gap-4" @submit="handleSubmit">    <ColorField      v-model="value"      full-width      required      class="w-full"      name="brand-color"      label="Brand Color"      placeholder="#000000"      description="Choose your brand's primary color"    >      <template #prefix>        <ColorSwatch :color="value" size="xs" />      </template>    </ColorField>    <Button      class="w-full"      :disabled="!value"      :pending="isSubmitting"      type="submit"      variant="primary"    >      {{ isSubmitting ? 'Saving...' : 'Save Color' }}    </Button>  </Form></template>

Render Function

HeroUI's render prop replaces the root element. In Vue, attributes you pass to ColorField (other than aria-label / aria-labelledby, which go to the input) fall through to the root div.

<script setup lang="ts">import { ColorField, ColorSwatch, parseColor } from '@hareui/vue'import { ref } from 'vue'const color = ref(parseColor('#0485F7'))</script><template>  <!-- Attributes other than aria-label/aria-labelledby fall through to the root element (HeroUI's `render` prop) -->  <ColorField    v-model="color"    class="w-[280px]"    data-custom="foo"    name="color"    label="Color"  >    <template #prefix>      <ColorSwatch :color="color" size="xs" />    </template>  </ColorField></template>

Customization

Tailwind CSS

Applied to buttons, links, and focus rings.
<script setup lang="ts">import { ColorField, ColorSwatch, parseColor } from '@hareui/vue'import { ref } from 'vue'const color = ref(parseColor('#6366F1'))</script><template>  <ColorField    v-model="color"    class="w-full max-w-xs gap-1.5"    name="accent-color"    label="Accent color"    description="Applied to buttons, links, and focus rings."    :ui="{      base: '[&_[data-slot=label]]:font-medium [&_[data-slot=label]]:text-foreground',      group: 'rounded-xl bg-default shadow-none focus-within:ring-2 focus-within:ring-accent/15',      input: 'font-mono text-sm text-foreground placeholder:text-muted',    }"    variant="secondary"  >    <template #prefix>      <ColorSwatch class="rounded-md" :color="color" size="xs" />    </template>  </ColorField></template>

Global Configuration

app.use(createHareUI({
  ui: { colorField: { slots: { group: 'rounded-xl', input: 'font-mono' } } },
}))

Styling Reference

Slots

  • base → [data-slot="color-field"] – root container (flex flex-col gap-1)
  • group → [data-slot="color-input-group"] – container for the prefix, input and suffix, with border and background styling
  • prefix → [data-slot="color-input-group-prefix"] – content before the input, e.g. a ColorSwatch
  • input → [data-slot="color-input-group-input"] – the text input
  • suffix → [data-slot="color-input-group-suffix"] – content after the input

Note: The child components (Label, Description, FieldError) have their own themes. See their respective pages for customization options.

Interactive States

ColorField sets these data attributes:

  • Invalid: [data-invalid="true"] on the root and the group - Automatically hides the description when invalid
  • Disabled: [data-disabled="true"] on the root, [data-disabled] on the group and input
  • Required: [data-required="true"] on the root - Shows the label's required asterisk
  • Read Only: [data-readonly="true"] on the root, [data-readonly] on the group and input
  • Focus Within: [data-focus-within="true"] on the group - Applied when the input is focused
  • Focus Visible: [data-focus-visible="true"] on the group - Applied when focus is visible (keyboard navigation)

API Reference

Props

PropTypeDefaultDescription
labelstring-Label text rendered above the field.
descriptionstring-Helper text rendered below the field. Hidden while the field is invalid.
errorMessagestring-Error message rendered below the field while invalid. Defaults to the validation errors.
idstringuseId()The id of the input; label, description and error ids derive from it.
namestring-The name of the field, used when submitting a form.
placeholderstring-Temporary text shown when the input is empty.
modelValuestring | Color | null-The committed color (controlled). A hex string or a Reka Color object; null shows an empty input. Falls back to the enclosing ColorPicker's color when neither this nor v-model is set.
defaultValuestring | Color-The initial value when uncontrolled (no v-model) and outside a ColorPicker. When omitted the input starts empty (the submitted form value is still #000000).
colorSpaceColorSpace-The color space to operate in when channel is set.
channelColorChannel-The color channel to edit (e.g. 'hue', 'red', 'alpha'). If unset, edits the hex value.
localestring-Locale used to format and parse channel numbers. Defaults to the ConfigProvider locale.
stepnumber-Custom step for increment/decrement. Defaults to the channel's natural step, or 1 for hex.
disableWheelChangebooleanfalseWhether changing the value with the mouse wheel is disabled.
variant"primary" | "secondary"'primary'Visual variant of the group.
fullWidthbooleanfalseWhether the field takes the full width of its container.
disabledbooleanfalseWhether the field is disabled.
invalidbooleanundefinedWhether the field is invalid. Overrides validation when set.
requiredbooleanfalseWhether the field is required.
readonlybooleanfalseWhether the value can be selected but not changed.
validateValidateFn<Color>-Validates the committed color. Return an error message (or several) when invalid.
validationBehaviorValidationBehavior'native'native blocks form submission and shows errors on commit or submit; aria shows errors in realtime. Defaults to the surrounding Form.
uiComponentSlots<{ slots: { base: string; group: string[]; input: string[]; prefix: string; suffix: string; }; variants: { variant: { primary: { base: string; }; secondary: { group: string[]; input: string; }; }; fullWidth: { false: { base: string; group: string; }; true: { base: string; group: string; }; }; }; defaultVariants: { fullWidth: boolean; variant: string; }; }>-Per-slot class overrides.

Slots

SlotPropsDescription
labelanyLabel content. Replaces the label prop.
prefixanyContent rendered before the input, e.g. a ColorSwatch.
suffixanyContent rendered after the input, e.g. a unit label.
descriptionanyDescription content. Replaces the description prop.
errorValidationResultError content, rendered while invalid. Replaces the errorMessage prop.

Emits

EventPayloadDescription
update:modelValue[value: string | Color | null]-

Accessibility

  • The label's for points to the input id (auto-generated with useId() unless id is set)
  • The input's aria-describedby lists the ids of the rendered description (<id>-description) and error (<id>-error)
  • invalid sets aria-invalid="true" on the input; the error is announced via role="alert"
  • Keyboard: ↑/↓ step the hex value or the active channel, Enter commits typed text, mouse wheel steps the value unless disableWheelChange is set

On this page

No Headings