Easy to Use
Simple v-model API with rounded corners, smooth transitions, and a refined UI
A lightweight, accessible color picker component for Vue 3
Selected color: #ff0000
English locale: #3b82f6
Japanese locale: #10b981
<template>
<colorPicker
v-model="color"
locale="en-US"
:messages="{ moreColors: 'Custom Label...' }"
/>
</template>The panel auto-detects available viewport space by default. You can lock it to any side with the placement prop.
Pinned to top-end: #f59e0b
<colorPicker v-model="color" placement="top-end" />
<colorPicker v-model="color" placement="bottom" /> <!-- vertical locked, horizontal auto -->When the picker sits inside a container with overflow: hidden (tables, dialogs, cards), the panel can get clipped. Set teleport to render the panel into document.body (or any CSS selector) with fixed positioning that tracks the trigger on scroll and resize.
<colorPicker v-model="color" teleport />
<colorPicker v-model="color" teleport="#popup-root" />npm install vcolorpicker -SRegister the plugin in main.js:
import { createApp } from 'vue'
import vcolorpicker from 'vcolorpicker'
import App from './App.vue'
const app = createApp(App)
app.use(vcolorpicker)
app.mount('#app')Use in your component:
<template>
<colorPicker v-model="color" />
</template>
<script setup>
import { ref } from 'vue'
const color = ref('#ff0000')
</script>| Prop | Type | Default | Description |
|---|---|---|---|
v-model / modelValue | string | — | Current color value |
defaultColor | string | #000000 | Color the "Default" button resets to |
disabled | boolean | false | Disabled state |
locale | 'zh-CN' | 'en-US' | 'ja-JP' | Auto | Built-in panel labels; follows <html lang> / navigator.language when omitted |
messages | Partial<ColorPickerMessages> | — | Override built-in labels with custom text |
placement | ColorPickerPlacement | 'auto' | Panel placement: 'auto', 'top', 'bottom', 'top-start', 'top-end', 'bottom-start', 'bottom-end'. 'auto' picks based on available viewport space; directional values lock that axis |
teleport | boolean | string | false | Render the panel into body (true) or a CSS selector target, escaping overflow: hidden ancestors |
| Event | Payload | Description |
|---|---|---|
change | (color: string) | Color value changed |
update:modelValue | (color: string) | v-model sync event |
open | — | Panel opened |
close | — | Panel closed (outside click, Esc, or after selection) |
hover | (color: string) | Swatch hover; emits empty string on leave |
<colorPicker v-model="color" @change="onColorChange" />Methods exposed via template ref:
<script setup lang="ts">
import { ref } from 'vue'
import type { ColorPickerInstance } from 'vcolorpicker'
const pickerRef = ref<ColorPickerInstance>()
const color = ref('#ff0000')
const openProgrammatically = () => pickerRef.value?.open()
</script>
<template>
<colorPicker ref="pickerRef" v-model="color" />
<button @click="openProgrammatically">Open Panel</button>
</template>| Method | Description |
|---|---|
open() | Open the panel (no-op when disabled) |
close() | Close the panel |
focus() | Move focus back to the trigger button |
Tab; Enter / Space activates; ↑ / ↓ on a focused trigger opens the panel and focuses the first swatchTab enters the grid once, then ← → ↑ ↓ move between swatches, Home / End jump within a row, Enter / Space selectsEsc closes the panel and returns focus to the triggerrole="button", aria-haspopup="dialog", aria-expanded; the panel uses role="dialog"; every swatch has aria-label="#RRGGBB":focus-visible, so mouse users are not distracteddocument / navigator are unavailable (Node SSR), the initial locale falls back to zh-CN (or the explicit locale prop) so server and client render identically — no hydration mismatchonMounted. To avoid a post-mount flicker from Chinese to another language, pass an explicit locale prop in SSR projectsMutationObserver watching <html lang> is a module-level singleton — multiple component instances share a single observerOverride these custom properties to theme the component without piercing selectors:
.m-colorPicker {
--vcp-swatch-size: 15px;
--vcp-panel-width: 190px;
--vcp-panel-bg: #fff;
--vcp-panel-border: 1px solid #ddd;
--vcp-panel-radius: 2px;
--vcp-panel-shadow: 0 8px 24px rgba(0, 0, 0, .18);
--vcp-panel-padding: 10px;
--vcp-text-color: #333;
--vcp-focus-color: #4e81bb;
--vcp-transition: .3s ease;
--vcp-z-index: 10000;
}Example — dark theme:
.dark .m-colorPicker {
--vcp-panel-bg: #1f1f1f;
--vcp-panel-border: 1px solid #333;
--vcp-text-color: #eee;
}With
teleportenabled the panel is no longer a descendant of.m-colorPicker, so also override.m-colorPicker-box(the panel root) — or define the variables on:rootto cover both.
Types exported from the package:
import type {
ColorPickerProps,
ColorPickerEmits,
ColorPickerExposed,
ColorPickerInstance,
ColorPickerLocale,
ColorPickerMessages,
ColorPickerPlacement
} from 'vcolorpicker'Yes. vColorPicker is built for Vue 3 and can be used with a standard v-model API.
Yes. Set locale="zh-CN", locale="en-US" or locale="ja-JP" to use built-in translations, or pass messages to override any label.
Yes. The package ships with full type declarations — props, emits, instance methods, and helper types.
Yes. Clicking More Colors... opens the native HTML5 color input in supported browsers.
Yes. Tab to focus, Enter / Space to activate, Esc to close, and ARIA roles / labels are all wired up.