oh/color-picker-type-bundle
A Symfony bundle that adds a ready-made color picker form type, letting you drop a color selection field into forms with minimal setup. Configurable options make it easy to control the picker's behavior and appearance.
oh/color-picker-type-bundle is a Symfony2 form type bundle, not a Laravel package. There is no service provider, no config/ to publish, no Blade views, and no facade. composer require oh/color-picker-type-bundle in a Laravel app gets you an autoloaded Symfony AbstractType you cannot render. The realistic day-to-day posture is: read it as a reference implementation, then reimplement the pattern in ~30 lines of Blade + a validation rule.Form/Type/ColorPickerType.php, extending AbstractType with buildForm() / configureOptions(); (2) the Twig template under Resources/views/Form/ that renders the input and bootstraps the JS widget; (3) Resources/public/js/ + Resources/public/css/ for the vendored picker library. If the type does nothing beyond ->add('color', TextType::class, [...]) plus a template block, it is a thin wrapper — confirmed by reading buildForm().// app/Rules/HexColor.php
public function passes($attribute, $value) {
return is_string($value) && preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value);
}
{{-- resources/views/components/color-picker.blade.php --}}
<div x-data="{ color: @js(old($name, $value ?? '#000000')) }" class="flex items-center gap-2">
<input type="color" x-model="color" :value="color" class="h-9 w-9 ...">
<input type="text" name="{{ $name }}" x-model="color" maxlength="7"
class="..." {{ $attributes->merge(['class' => '']) }}>
</div>
brand_color hex value on a settings/profile form. Picker opens, user selects, value posts as #RRGGBB, HexColor rule validates it, model stores the canonical lowercase string.new Oh\ColorPickerTypeBundle\OhColorPickerTypeBundle() to AppKernel.php, run bin/console assets:install --symlink, then ->add('color', ColorPickerType::class) in a form. If that produces no visible widget, the asset step is the culprit — see Gotchas.<x-color-picker> component plus the HexColor rule. Keep the pair in one place so every form (settings, brand themes, tag labels, category colors) uses the identical markup and the identical server-side check. Never let a form ship the input without the rule.#ABC, ABCDEF, or an empty string if untouched. Do it in a FormRequest:
protected function prepareForValidation(): void
{
$c = $this->input('brand_color');
$this->merge(['brand_color' => $c === '' || $c === null ? null : '#' . ltrim(strtolower($c), '#')]);
}
Then validate with ['nullable', 'string', new HexColor]. Store canonical #rrggbb — never 3-digit, never uppercase — so later comparisons, contrast checks, and CSS injection don't drift.$table->string('brand_color', 7)->nullable() (or a char(7)), holding #rrggbb. Keeping it a plain string with no alpha and no color-space metadata is the one design decision from this bundle worth copying verbatim: it keeps future HSL/OKLCH or theme-token work a read-time concern rather than a migration.setDefaultOptions/configureOptions; in Laravel those become props plus attribute merging: :readonly, :required, placeholder, size, :preview="false". Pass through unknown attributes with $attributes->merge() so the component composes like a native input.wire:model on the text input works because the picker dispatches input; pair it with wire:model.blur or a debounce so the picker doesn't fire a request on every drag. On the color swatch, wire:ignore the wrapper if you re-init a third-party library.<input type="color"> (native, zero JS, no CSP inline scripts). Layer a maintained picker (Pickr, Spectrum) on top via a Vite entry if you need presets/swatches/named palettes. The Symfony bundle's Twig template is exactly this layering, written pre-Vite — copy the layering idea, not the asset plumbing.resources/js/color-picker.js imported from app.js, CSS in resources/css/app.css, built with Vite. This replaces assets:install entirely; do not attempt to publish the bundle's Resources/public/ into public/.app/Resources/OhColorPickerTypeBundle/views/Form/...; Laravel users override by publishing the component view (php artisan vendor:publish style) or by wrapping it — same idea, so downstream apps can theme the swatch without editing your component.#fff, #FFFFFF, fff (normalized), red, javascript:alert(1), and '' pins the contract far better than clicking a picker in the browser; component tests assert the input name and the current value are rendered.assets:install. In the Symfony path, forgetting bin/console assets:install --symlink (or the wrong --symlink flag) leaves the JS/CSS unreferenced and you get a plain text input with no picker. Clear var/cache / app/cache afterwards — Symfony caches Twig hard, so an overridden template that "isn't taking effect" is usually a stale cache, not a path bug. Verify with bin/console debug:twig and debug:container --tag=form.type.Resources/views/Form/...). A silent miss falls back to the vendor template with no error."", which fails a strict regex rule and passes a nullable rule only if you convert it first. Normalize '' → null in prepareForValidation(), and decide explicitly whether the column allows null or enforces a default.#RGB and #RRGGBB are easy; #RRGGBBAA, rgb(), hsl(), CSS named colors, and transparent are not covered by the bundle or by the naive regex — reject them deliberately rather than accepting them into a string(7) column. Also anchor the pattern (^...$) or you'll accept #ff0000-onclick=.#-prefix, and 3→6 expansion are yours to write, server-side.DOMContentLoaded will silently stop working after a Livewire update or Turbo visit. Either wrap in wire:ignore and re-init on livewire:navigated, or use the native <input type="color"> — it never needs re-init and is the more robust baseline.<style>/<script> and eval-adjacent code; under a strict Content-Security-Policy they fail silently in production but work locally. Test under your real headers before assuming the widget works.aria-label. The bundle does not do this, and any picker you drop in probably won't either.name attribute and appear in the POST payload; (2) does prepareForValidation() see the normalized value; (3) does the rule reject the bad-input test cases; (4) only then look at the JS. Nearly every "the picker doesn't work" bug is a step-1 naming or a step-2 normalization problem, not a JavaScript problem.How can I help you explore Laravel packages today?