Color input
A swatch that opens the system color picker, with a hex box beside it. Both halves write the same value.
<script setup lang="ts">
import SaColorInput from '@shared/components/common/SaColorInput.vue'
import { Label } from '@shared/components/ui/label'
import { ref } from 'vue'
const brand = ref('#1DB9C3')
</script>
<template>
<div class="space-y-2">
<Label for="brand-color">Brand color</Label>
<SaColorInput id="brand-color" v-model="brand" aria-label="Brand color" />
<p class="text-sm text-muted-foreground">
Model value: {{ brand }}
</p>
</div>
</template>Usage
v-model is a hex string. <input type="color"> renders as pure operating system and ignores the radius and the focus ring, so the native input is kept screen-reader-only behind a swatch the template draws. Nothing else can open the system picker, so it has to stay there, and it stays focusable rather than hidden so the swatch is a real control rather than a picture of one.
The hex box only commits a value that parses as #abc or #aabbcc. A half-typed #0f7 never reaches the model, which is what keeps it from reaching the theme engine mid-keystroke. A value that does not parse leaves the swatch black while you keep typing.
Examples
Swatch only
:hex="false" drops the hex box, for a dense grid where the swatch alone is enough.
<script setup lang="ts">
import SaColorInput from '@shared/components/common/SaColorInput.vue'
import { ref } from 'vue'
const palette = ref(['#1DB9C3', '#0F6E56', '#0ea5e9', '#f59e0b', '#ef4444'])
</script>
<template>
<div class="flex flex-wrap items-center gap-2">
<SaColorInput
v-for="(color, i) in palette"
:key="i"
v-model="palette[i]"
:hex="false"
:aria-label="`Palette color ${i + 1}`"
/>
</div>
</template>Disabled
<script setup lang="ts">
import SaColorInput from '@shared/components/common/SaColorInput.vue'
import { ref } from 'vue'
const locked = ref('#0F6E56')
</script>
<template>
<SaColorInput v-model="locked" disabled aria-label="Locked color" />
</template>API
Source: src/shared/components/common/SaColorInput.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | required | The color, as #abc or #aabbcc. Anything else renders the swatch black. |
id | string | — | Put on the native color input, so a <Label for> points at the swatch. |
hex | boolean | true | Shows the hex box beside the swatch. |
disabled | boolean | false | Dims both halves and blocks them. |
ariaLabel | string | — | Names the swatch, and names the hex box as "<label> hex value". |
Emits
| Event | Payload | Description |
|---|---|---|
update:modelValue | value: string | Fires on every change from the system picker, and from the hex box only when the text parses as a color. |
Slots
None.
Accessibility
- The native color input stays in the page as screen-reader-only rather than hidden, so the swatch takes focus and opens from the keyboard.
- The swatch's wrapper shows the focus ring through
focus-within, so keyboard focus is visible on the control you can see. - The hex box is named "Hex value", or "<label> hex value" when you pass
ariaLabel, and isdir="ltr"in every language, because a hex code is read left to right.