Skip to content
Brok UI

Loading…

No results

Color Dropdown

Open source

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.

Version
v1.4.2
Stability
stable
License
MIT
Related
Editor Toolbar
Color Swatch Selector
Color Palette

Preview

previews.components.color-dropdown.default.blade.php Blade
<div class="w-full flex justify-center p-6">
    <x-ui.editor.color-dropdown />
</div>

Installation

terminal
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:

resources/js/ui/index.js JS
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.

  • blade resources/views/components/ui/editor/color-dropdown.blade.php
  • blade resources/views/components/ui/color-dropdown.blade.php
Registry dependencies
dropdown editor
Packages
composer: jml/brok:^0.2
npm: alpinejs

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.

color-dropdown.md
# 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

manifest knowledge + registry-derived coverage

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.

color-dropdown

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

Rich authoring and editing

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
root
Theming hooks
color-dropdown

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
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-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, 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 under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Needs wire:key

Add a stable wire:key when Livewire can reorder this interactive component.

livewire-component.blade.php Blade
<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.

livewire-form.blade.php Blade
<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.

resources/views/components/ui/editor/color-dropdown.blade.php Blade
@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>
resources/views/components/ui/color-dropdown.blade.php Blade
{{-- <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