This is a single-purpose Nova field package: it ships one field class plus a compiled Vue component. Treat it as a snippet library rather than infrastructure — the whole package is probably ~5 files, so read it before you rely on it.
Install and verify compatibility first.
composer require juniora/nova-color-field
Before writing any code, open vendor/juniora/nova-color-field/composer.json and check the laravel/nova constraint. The last release is 2024-12-21, which predates Nova 5. If you're on Nova 5, expect to fork or patch the constraint — the field API is stable enough that this usually works, but the Vue component's imports (laravel-nova, laravel-mix vs Vite) are the part that breaks.
Check that dist/ is committed. Nova resolves a field's frontend from the field.js path declared in the field class. If the repo omits the compiled bundle, you get a silently blank/missing component and must build it yourself:
cd vendor/juniora/nova-color-field
npm install && npm run prod # or dev
That path is gitignored inside vendor/, so it disappears on every composer install — a hard blocker for CI. In that case copy the field into your app with php artisan nova:field acme color and port the Vue component.
First use case — a brand/accent color on a settings resource:
use Juniora\NovaColorField\ColorField; // confirm the exact namespace in src/
ColorField::make('Brand Color', 'brand_color')
->rules('required', 'regex:/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/')
->help('Hex value, e.g. #4F46E5'),
with a matching column:
$table->string('brand_color', 7)->nullable();
Where to look first, in order: src/*.php (the field class — this tells you what withMeta keys the component actually reads), resources/js/components/Field.vue (what props/options exist), dist/js/field.js (does it exist), and composer.json.
Normalize on the way in and out. Pickers are inconsistent about what they emit — #fff vs #ffffff, with or without the hash, sometimes '' for cleared. Pin it down at the field level so the rest of your app can assume one format:
ColorField::make('Brand Color')
->resolveUsing(fn ($value) => $value ? strtolower($value) : null)
->fillUsing(function ($request, $model, $attribute) {
$value = $request->input($attribute);
$model->{$attribute} = $value ? strtolower(trim($value)) : null;
})
->nullable(),
Do not store the color as-is and normalize at render time; you'll end up with three formats in the column within a month.
Index views need a swatch, not a hex string. Most color fields render as plain text on the index, which is useless at a glance. Options, roughly in order of effort:
->hideFromIndex() if the color is a detail-page concern only.Text::make('Color', fn () => $this->brand_color)->asHtml() with an inline <span style="..."> — sanitize if the value is user-writable.index component via ->withMeta() if the package supports a swatch mode (check the Vue file).Palettes enforce brand consistency. If the component accepts preset swatches (usually a withMeta(['palette' => [...]]) or a dedicated method), use it. Free-form color input on a marketing site produces #FF0000 and #f00 and red and everyone hates it six months later.
Pair it with a semantic field, don't replace one. A color column is a presentation detail. If the color means something — status, priority, category — store an enum/status string and map to colors in the Nova layer or a config file:
Badge::make('Status')->map([
'active' => 'green',
'archived' => 'gray',
])
Storing raw hex for semantic state means a redesign becomes a data migration. Reserve ColorField for genuinely presentational choices: brand accents, chart series colors, label backgrounds on a per-record basis.
JSON columns for multi-color sets. If you need two or three coordinated colors (background + foreground + accent), store a JSON object and validate keys individually with ->rules(['array', 'accent.regex:...']). Don't invent column-per-color schemas.
Multi-tenant / settings resources are the sweet spot: a singleton Setting model or a BrandTheme resource where an admin picks colors, and the frontend reads them via a cached config. Wrap reads in Cache::remember and bust on saved.
Testing. Nova's own Nova::assertField / field assertion helpers cover existence and metadata but not interaction. For anything beyond presence, use Laravel Dusk against a real Nova page, or skip the component entirely in tests and assert the normalized value written by fillUsing — that's the part that actually breaks.
app/Nova/Fields/ via php artisan nova:field and own it. That's an hour of work and removes the package from your composer.json risk surface entirely.laravel-nova import paths). A package last released in Dec 2024 is a coin flip on Nova 5. Pin to a working Nova minor or fork; don't discover this in a production deploy.varchar(7) holds #ffffff. It does not hold 8-digit hex with alpha (#ffffffcc), rgb(...), or hsl(...). If the component supports an alpha slider or format switch, use varchar(9) or varchar(32) from day one — widening a column later is easy, but the dirty data written in the meantime is not.'' when cleared. Without a fillUsing/mutator, you get '' in a nullable column and whereNull('brand_color') starts lying to you. Normalize '' → null explicitly.style attribute on the public site, validate against a strict hex regex server-side on write — never trust the picker's output. A value like red;background:url(...) is not a color.$component property matches the registered Vue component name, confirm dist/js/field.js is present and readable by the web server (ls -l, check permissions after composer install as a different user), then hard-refresh — Nova's field assets are cached aggressively by the browser.->withMeta([...]), ->resolveUsing(), ->fillUsing(), ->displayUsing(), and ->component('your-own') let you reskin it against your own Vue component while keeping the PHP-side ergonomics. If the bundled component disappoints, keep the field class and swap the component rather than abandoning the package.WHERE col LIKE ?, which is meaningless for colors; adding a color column to $search is noise. Sortable color columns are equally pointless — resist.How can I help you explore Laravel packages today?