Color Dropdown
Text and highlight colour picker for the rich-text editor. Swatches are token-pure in source: each carries a data-token attribute naming the CSS custom property; the colour is resolved at runtime via getComputedStyle so no hex/rgb literal ever appears in the template. Composed as <x-ui.editor.color-dropdown>; <x-ui.color-dropdown> stays as the compatibility tag.
Preview
<div class="w-full flex justify-center p-6">
<x-ui.editor.color-dropdown />
</div>
Installation
php artisan ui:add color-dropdown
Note
This component ships an Alpine behavior module at
resources/js/ui/color-dropdown.js. Import it once from your bundle so it registers on alpine:init:
import './color-dropdown.js';
Registry contract
php artisan ui:add color-dropdown
writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.
-
resources/views/components/ui/editor/color-dropdown.blade.php -
resources/views/components/ui/color-dropdown.blade.php
Use with AI
A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.
# Brok UI: Color Dropdown (`color-dropdown`)
Text and highlight colour picker for the rich-text editor. Swatches are token-pure in source: each carries a data-token attribute naming the CSS custom property; the colour is resolved at runtime via getComputedStyle so no hex/rgb literal ever appears in the template. Composed as <x-ui.editor.color-dropdown>; <x-ui.color-dropdown> stays as the compatibility tag.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add color-dropdown
```
## Usage
```blade
<div class="w-full flex justify-center p-6">
<x-ui.editor.color-dropdown />
</div>
```
## Use when
- Use when users need rich structured authoring, annotation, or collaborative editing controls.
- Letting a user set text or highlight color from a fixed set of semantic tokens inside <x-ui.editor>.
## Avoid when
- Do not use rich editor affordances for simple one-line or plain-text tasks.
- Every applied color must come from outside the token set as a first-class named swatch; only the custom color well covers ad-hoc picks, and every named swatch stays a token.
## Anti-patterns
- Rich editors for one-line or easily validated input
## Rules
- Use the `<brok:color-dropdown>` tag (or `<x-ui.color-dropdown>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.
## Links
- Docs: https://brokui.dev/docs/components/color-dropdown
- Registry JSON (files, props, contract): https://brokui.dev/r/open/color-dropdown.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
API
Props
No documented props. Pass normal Blade attributes through the component root.
Slots
default—x-ui.editor.color-dropdown— Installed subcomponent from the registry item.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- Clicking a swatch resolves the semantic token to a live CSS color at click time and dispatches editor-exec with foreColor or hiliteColor; Default/None clears the applied color with removeFormat.
- The trigger's colour bar reflects the last applied text colour; a native color input lets a caller pick a fully custom color outside the token set.
- Installs a JavaScript behavior module when the registry item includes resources/js/ui files.
- Declares registry capability flags: a11y, interactive, responsive, rtl, darkMode, localized.
Guidance
Create or revise structured rich content.
Use when
- Use when users need rich structured authoring, annotation, or collaborative editing controls.
- Letting a user set text or highlight color from a fixed set of semantic tokens inside <x-ui.editor>.
Avoid when
- Do not use rich editor affordances for simple one-line or plain-text tasks.
- Every applied color must come from outside the token set as a first-class named swatch; only the custom color well covers ad-hoc picks, and every named swatch stays a token.
Use instead
- Textarea or plain field for simple content
Anti-patterns
- Rich editors for one-line or easily validated input
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- native
- Focus
managed
- Each swatch row is a role="group" with an aria-label naming text or highlight colour, and every swatch button carries its own aria-label and title naming the token.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, so keyboard focus is always visible. - Disabled and invalid states are conveyed to assistive tech, not by color alone.
- Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
- Labels go through
__()and layout uses logical properties (ms-*,text-start), so it mirrors underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
Add a stable wire:key when Livewire can reorder this interactive component.
<div wire:key="color-dropdown-{{ $record->id }}">
<div class="w-full flex justify-center p-6">
<x-ui.editor.color-dropdown />
</div>
</div>
Validation
Validation support: native. Keep the error message connected with aria-describedby.
<form wire:submit="save" class="space-y-2">
<brok:color-dropdown
wire:model="value"
:aria-invalid="$errors->has('value') ? 'true' : 'false'"
aria-describedby="value-error"
/>
@error('value')
<p id="value-error" role="alert">{{ $message }}</p>
@enderror
<brok:button type="submit" wire:loading.attr="disabled">
<span wire:loading.remove>Save</span>
<span wire:loading>Saving…</span>
</brok:button>
</form>
Source
The exact, editable files ui:add writes
into your app. Previews render this same code; there are no preview-only components.
@props([])
{{--
Color Dropdown — text and highlight colour picker for <x-ui.editor>.
TOKEN-PURE DESIGN: No colour literal (hex/rgb/hsl) ever appears in this
template. Each swatch button exposes its colour solely as:
• a Tailwind token utility class for the visual swatch (bg-primary, …)
• x-bind:data-token holding the CSS custom-property NAME (--primary, …)
At click-time, Alpine resolves the live semantic token through window.UI.
The resolver prefers full-color aliases such as OKLCH and falls back to the
legacy HSL channels. This means:
• The raw-colour guard test never sees a literal colour in source.
• Colours automatically track the active theme/surface.
• Dark-mode and custom brand tokens are supported for free.
Two rows of swatches — the status tokens and the neutral chart hues — plus
a "Custom" well per row: a native colour input, so any colour can be picked
(its value comes from the user at runtime, never from this source). The
trigger's colour bar reflects the last applied text colour.
Dispatches (all bubbling, so parent <x-ui.editor> catches them):
editor-exec { command: 'foreColor', value: 'hsl(…)' } — text colour
editor-exec { command: 'hiliteColor', value: 'hsl(…)' } — highlight colour
editor-exec { command: 'removeFormat' } — clear all colour
@mousedown.prevent on every swatch preserves the editor selection.
Standalone (no editor ancestor) the events no-op harmlessly.
--}}
{{--
The foundation helper resolves each CSS custom-property name to a CSS color.
Named function so it can be called from two separate row templates without
repeating the getComputedStyle call inline in the click handler.
--}}
<div data-slot="color-dropdown" x-data="{ current: '', mark: '' }" {{ $attributes->merge(['class' => 'inline-flex']) }}>
<x-ui.dropdown>
{{-- ── Trigger: "A" glyph over a colour bar ────────────────────────────── --}}
<x-ui.dropdown.trigger
aria-label="{{ __('Text colour') }}"
@mousedown.prevent=""
class="inline-flex size-8 flex-col items-center justify-center gap-0.5 rounded-md text-sm font-bold text-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring"
>
<span aria-hidden="true" class="leading-none">A</span>
{{-- Colour bar: the last applied colour (a token class by default, the
resolved value once a swatch or custom colour was chosen). --}}
<span aria-hidden="true" class="h-1 w-4 rounded-full bg-primary" :style="current ? `background-color:${current}` : ''"></span>
</x-ui.dropdown.trigger>
{{-- ── Dropdown panel ───────────────────────────────────────────────────── --}}
<x-ui.dropdown.content align="start" class="w-56 p-2">
@php
$tokens = [
['--foreground', __('Foreground'), 'bg-foreground', 'bg-foreground/20'],
['--primary', __('Primary'), 'bg-primary', 'bg-primary/20'],
['--info', __('Info'), 'bg-info', 'bg-info/20'],
['--success', __('Success'), 'bg-success', 'bg-success/20'],
['--warning', __('Warning'), 'bg-warning', 'bg-warning/20'],
['--destructive', __('Destructive'), 'bg-destructive', 'bg-destructive/20'],
['--muted-foreground', __('Muted'), 'bg-muted-foreground', 'bg-muted-foreground/20'],
['--chart-1', __('Blue'), 'bg-chart-1', 'bg-chart-1/20'],
['--chart-2', __('Teal'), 'bg-chart-2', 'bg-chart-2/20'],
['--chart-3', __('Amber'), 'bg-chart-3', 'bg-chart-3/20'],
['--chart-4', __('Violet'), 'bg-chart-4', 'bg-chart-4/20'],
['--chart-5', __('Rose'), 'bg-chart-5', 'bg-chart-5/20'],
];
@endphp
{{-- ── Text colour ──────────────────────────────────────────────────── --}}
<x-ui.dropdown.label>{{ __('Text colour') }}</x-ui.dropdown.label>
<div class="grid grid-cols-7 gap-1 px-1 pb-2 pt-1" role="group" aria-label="{{ __('Text colour swatches') }}">
<button
type="button"
aria-label="{{ __('Default') }}"
title="{{ __('Default') }}"
@mousedown.prevent=""
@click="current = ''; $dispatch('editor-exec', { command: 'removeFormat' }); closeAndFocus()"
class="flex size-6 items-center justify-center rounded-full border border-border bg-background text-muted-foreground transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring motion-reduce:transition-none"
>
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 4l16 16M20 4 4 20"/></svg>
</button>
@foreach ($tokens as [$token, $name, $solid, $tint])
<button
type="button"
aria-label="{{ $name }}"
title="{{ $name }}"
data-token="{{ $token }}"
@mousedown.prevent=""
@click="current = window.UI.resolveColor($el.dataset.token, $el); $dispatch('editor-exec', { command: 'foreColor', value: current }); closeAndFocus()"
class="size-6 rounded-full {{ $solid }} ring-1 ring-inset ring-border/40 transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring motion-reduce:transition-none"
></button>
@endforeach
{{-- Custom: a native colour well. --}}
<label class="relative flex size-6 cursor-pointer items-center justify-center overflow-hidden rounded-full border border-dashed border-border bg-background text-muted-foreground transition-transform hover:scale-110 focus-within:ring-2 focus-within:ring-ring motion-reduce:transition-none" title="{{ __('Custom colour') }}">
<span class="sr-only">{{ __('Custom text colour') }}</span>
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
<input type="color" class="absolute inset-0 size-full cursor-pointer opacity-0" @mousedown.stop="" @input="current = $event.target.value; $dispatch('editor-exec', { command: 'foreColor', value: current })" />
</label>
</div>
<x-ui.dropdown.separator />
{{-- ── Highlight colour ─────────────────────────────────────────────── --}}
<x-ui.dropdown.label class="mt-1">{{ __('Highlight') }}</x-ui.dropdown.label>
<div class="grid grid-cols-7 gap-1 px-1 pb-1 pt-1" role="group" aria-label="{{ __('Highlight colour swatches') }}">
<button
type="button"
aria-label="{{ __('None') }}"
title="{{ __('None') }}"
@mousedown.prevent=""
@click="mark = ''; $dispatch('editor-exec', { command: 'hiliteColor', value: 'transparent' }); closeAndFocus()"
class="flex size-6 items-center justify-center rounded-sm border border-border bg-background text-muted-foreground transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring motion-reduce:transition-none"
>
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 4l16 16M20 4 4 20"/></svg>
</button>
@foreach ($tokens as [$token, $name, $solid, $tint])
<button
type="button"
aria-label="{{ $name }}"
title="{{ $name }}"
data-token="{{ $token }}"
@mousedown.prevent=""
@click="mark = window.UI.resolveColorAlpha($el.dataset.token, 0.25, $el); $dispatch('editor-exec', { command: 'hiliteColor', value: mark }); closeAndFocus()"
class="size-6 rounded-sm {{ $tint }} ring-1 ring-inset ring-border/40 transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring motion-reduce:transition-none"
></button>
@endforeach
<label class="relative flex size-6 cursor-pointer items-center justify-center overflow-hidden rounded-sm border border-dashed border-border bg-background text-muted-foreground transition-transform hover:scale-110 focus-within:ring-2 focus-within:ring-ring motion-reduce:transition-none" title="{{ __('Custom highlight') }}">
<span class="sr-only">{{ __('Custom highlight colour') }}</span>
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
<input type="color" class="absolute inset-0 size-full cursor-pointer opacity-0" @mousedown.stop="" @input="mark = $event.target.value; $dispatch('editor-exec', { command: 'hiliteColor', value: mark })" />
</label>
</div>
</x-ui.dropdown.content>
</x-ui.dropdown>
</div>
{{-- <x-ui.color-dropdown> is the install slug's compatibility tag: the part lives at
<x-ui.editor.color-dropdown> (ui/editor/color-dropdown) with every other editor part.
Compose the namespaced tag in new code; this file only forwards to it. --}}
@props([])
<x-ui.editor.color-dropdown {{ $attributes }} />
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.4.2 - Status
- Stable
- License
-
open - Accessibility reviewed
- No review date recorded
- Last breaking change
- No date recorded
- Deprecation
- Not deprecated
- Contract
-
v6 - Foundation
-
≥ 1.0.0