Skip to content
ShineUI
Install

ColorPicker

Brand colors only.

<sui-color-picker>Advanced inputsStable

About

Brand-constrained color picker: a trigger showing the current swatch and its hex, and a panel of the six palette colors plus navy and orange tints. There is no free spectrum. Reach for it when a color must stay on brand, such as a category or tag color; use sui-color-editor where the color is user content.

Usage

<sui-color-picker label="Category color" name="category" value="#FF9422" help="Brand palette only."></sui-color-picker> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-color-picker"); el.addEventListener("suiChange", (e) => console.log(e.detail)); </script>
import { SuiColorPicker } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiColorPicker label="Category color" name="category" value="#FF9422" help="Brand palette only." onSuiChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiColorPicker } from "@shineui/vue"; import "@shineui/core/shineui.css"; const onChange = (e) => console.log(e.detail); </script> <template> <SuiColorPicker label="Category color" name="category" value="#FF9422" help="Brand palette only." @sui-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiColorPicker } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiColorPicker], template: `<sui-color-picker label="Category color" name="category" value="#FF9422" help="Brand palette only." [(open)]="open" (suiChange)="onChange($event)"></sui-color-picker>`, }) export class Example { open = false; onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

The trigger is aria-haspopup="listbox" with aria-expanded. Off-palette colors are not offered; use sui-color-editor where the color is user content.

<div style="max-width:220px"> <sui-color-picker label="Category color" name="category" value="#FF9422" help="Brand palette only."></sui-color-picker> </div>

Open palette

The panel holds one role="listbox" of swatch options, each wrapped in a tooltip that names its hex. The chosen swatch carries a check, white on a dark swatch and navy on a pale one, so the one ring on a swatch is focus. Picking leaves the panel open, so suiClose never reports "pick".

<div style="max-width:220px"> <sui-color-picker id="cp-open" label="Category color" value="#121C4D" open></sui-color-picker> </div>

Inline

inline shows the palette in the page with no trigger, so open, suiOpen and suiClose do nothing. The swatches keep the same arrow keys, and the current swatch is the one tab stop.

<div style="max-width:220px"> <sui-color-picker id="cp-inline" label="Category color" name="category" value="#8E9CAF" inline help="Brand palette only."></sui-color-picker> </div>

States

A picker the user cannot open cannot be filled in, so a disabled picker never holds the form on valueMissing. An author-set error still paints the trigger in the danger tone.

<div style="display:grid;gap:14px;max-width:220px"> <sui-color-picker id="cp-req" label="Tag color" value="" required help="Pick a swatch to continue."></sui-color-picker> <sui-color-picker id="cp-err" label="Ledger color" value="#8E9CAF" error="This color is already used by Payroll."></sui-color-picker> <sui-color-picker id="cp-invalid" label="Chart series" value="#D86F00" invalid></sui-color-picker> <sui-color-picker id="cp-off" label="Managed by your administrator" value="#121C4D" disabled></sui-color-picker> </div>

Read-only

readonly keeps the trigger named and in the tab order, opens nothing, and the value still posts. An inline palette shows its swatches and picks none.

<div style="display:grid;gap:14px;max-width:320px"> <sui-color-picker id="ro-sui-color-picker" label="Brand" name="sui-color-picker" value="#FF9422" readonly required></sui-color-picker> </div>

In a form

The picker posts name=hex. An empty picker posts an empty string, and a disabled one is barred from submission and posts nothing at all.

Submit to see what posts.
<form id="cp-form" style="display:grid;gap:14px;max-width:220px"> <sui-color-picker label="Category color" name="category" value="#FF9422"></sui-color-picker> <sui-color-picker label="Tag color" name="tag" value=""></sui-color-picker> <sui-button type="submit" variant="brand" label="Submit"></sui-button> <output id="cp-form-out" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Submit to see what posts.</output> </form>
// setup(root): properties and events setup(root) { const form = root.querySelector('#cp-form'); const out = root.querySelector('#cp-form-out'); form.addEventListener('submit', (e) => { e.preventDefault(); out.textContent = [...new FormData(form)].map(([k, v]) => `${k}=${v || '(empty)'}`).join(' '); }); }

Events

suiOpen fires after the element opens itself, and the cancelable suiClose fires before it closes and says why: escape, outside or trigger. suiChange carries the picked hex.

Idle.
<div style="display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap"> <div style="width:220px"><sui-color-picker id="cp-events" label="Category color" name="category" value="#FF9422"></sui-color-picker></div> <span id="cp-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Idle.</span> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#cp-events'); const log = root.querySelector('#cp-log'); el.addEventListener('suiOpen', () => { log.textContent = 'suiOpen. Palette open.'; }); el.addEventListener('suiClose', (e) => { log.textContent = `suiClose (${e.detail}).`; }); el.addEventListener('suiChange', (e) => { log.textContent = `suiChange ${e.detail}. The panel stays open.`; }); }

Keyboard

KeyAction
ArrowDown ArrowUpOn the trigger, opens the palette and focuses the selected swatch. In the panel, moves between rows.
ArrowRight ArrowLeftMoves along a row and on into the next or previous row.
Home EndJumps to the first palette swatch or the last tint.
Enter SpacePicks the focused swatch. The panel stays open.
EscapeCloses the palette and returns focus to the trigger. An inline palette has nothing to close.
TabCloses the palette and moves on.

API

<sui-color-picker>

Properties

PropertyAttributeTypeDefaultDescription
disableddisabledbooleanfalseDisables the trigger. A disabled control posts nothing and never holds the form.
errorerrorstringstring | undefinedError message; paints the trigger in the danger tone.
helphelpstringstring | undefinedHelp text under the trigger. Replaced by error when set.
inlineinlinebooleanfalseShows the palette in the page with no trigger, always open. The swatches keep the same keyboard model; open, suiOpen and suiClose do nothing here.
invalidinvalidbooleanfalseDanger border without a message.
labellabelstringstring | undefinedLabel above the control.
namenamestringstring | undefinedForm control name. Posts the hex, or an empty string while nothing is picked; a disabled picker posts nothing. Reflected: entry construction reads the content attribute, and the three framework bindings set props, so an unreflected name would post nothing.
openopenbooleanfalseOpens the palette. Mutable; the element closes itself on outside click and Escape. Picking a swatch leaves it open.
readOnlyreadonlybooleanfalseDisplay only: the trigger keeps its name and its tab stop and opens nothing (an inline palette shows its swatches and picks none), and the value still posts. Neither a read-only nor a disabled picker holds the form on valueMissing.
requiredrequiredbooleanfalseA swatch must be picked for the surrounding form to be valid (start from value="").
valuevaluestring'#121C4D'Current hex (#RRGGBB), or "" for no choice yet. Mutable; updated before suiChange fires.

Events

EventDetailReactVueDescription
suiChangestringonSuiChange@sui-changeFires when a swatch is picked; detail is the hex string.
suiCloseSuiCloseReason"cancel" | "close-button" | "escape" | "outside" | "pick" | "scrim" | "trigger"onSuiClose@sui-closeFires before the element closes the palette itself; detail is 'escape' | 'outside' | 'trigger' (Tab reports 'outside', as the select family does, because focus left the control). Picking deliberately leaves the panel open, so there is no 'pick' reason here. Cancelable: preventDefault() keeps it open. Does not bubble: a preventDefault() in a dialog's own suiClose listener cannot veto this one.
suiOpenvoidonSuiOpen@sui-openFires after the element opens the palette itself. Does not bubble, so a dialog around the picker never takes it for its own.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves keyboard focus to the trigger, or to the current swatch when inline.

Types

SuiCloseReason typeWhy a dismissible surface is closing. Detail of the cancelable suiClose. A consumer expresses "a stray backdrop click must not close this" by calling preventDefault() on the reason it does not accept, which is why none of these needs a prop of its own.
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also