Build a Custom Color Input Type in CRUDBooster

Build a Custom Color Input Type in CRUDBooster
By Ferry Oct 21, 2025 11 views
Build a Custom Color Input Type in CRUDBooster

Sometimes you need a field that feels tailor‑made — like a color picker for brand guidelines, themes, or status indicators. CRUDBooster supports custom input types with a clean, predictable structure. In this article, we’ll build `custom-color` that’s consistent, maintainable, and production‑ready.

The goal isn’t just “it works”, but “it’s worth using”: consistent code, easy maintenance, and aligned with CRUDBooster patterns.

Prerequisites
- PHP `8.2+`
- Laravel `11.x` or `12.x`
- CRUDBooster installed and running
- Familiarity with Livewire, Blade, and Service Providers

Why this matters:
- PHP/Laravel versions affect syntax, lifecycle, and package compatibility.
- CRUDBooster must be active so the registrar and theme can discover your type.
- Livewire and Blade are the foundation for rendering and data binding in CRUDBooster forms.

Practical tip:
- Store custom types under `app/Cb/Types`. It cleanly separates types from modules, makes discovery easier, and keeps a tidy architecture.

What We’ll Build
We’ll create a color input type `custom-color` that provides:
- A form template rendering a native, lightweight color picker.
- A view template for detail/read‑only pages with a small swatch.
- An option class to manage defaults and small reusable behaviors.
- A service provider to register the type with an explicit alias.

Why `custom-color`?
- Native `<input type="color">` is lightweight and sufficient for many needs.
- Easy to upgrade to a richer picker (palettes, alpha) via JS/CSS assets.
- Ideal for UI/theme configuration, plain text storage, and fast rendering in views.

Folder & File Structure
This structure ensures CRUDBooster can discover and render the type.

app/Cb/Types/Color
|
├── Function
│   └── Color.php
├── views
│   ├── form.blade.php
│   └── view.blade.php
└── ColorServiceProvider.php


File roles:
- `Function/Color.php`: option class for reusable settings (e.g., default color, preview toggle).
- `views/form.blade.php`: the form template (with Livewire binding and minimal a11y).
- `views/view.blade.php`: the read‑only/detail template so users see a swatch and the value.
- `ColorServiceProvider.php`: registers the view alias and the type with the CRUDBooster registrar.

Best practices:
- Use a clear, consistent alias (`custom-color`) so it’s easy to reference.
- Keep options in `Function/Color.php` so Form builder usage stays clean.

Register the Type (Service Provider)
The provider ties everything together: it loads views, registers the type, and (optionally) adds assets.


<?php
// File: app/Cb/Types/Color/ColorServiceProvider.php

namespace App\Cb\Types\Color;

use Illuminate\Support\ServiceProvider;
use CrudBooster\Components\Type\CBTypeRegistrar;

class ColorServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Register Blade views under the alias `custom-color`
        $this->loadViewsFrom(__DIR__.'/views', 'custom-color');

        // Register the type with CRUDBooster (color fits the text group)
        CBTypeRegistrar::addText([
            'type'          => 'custom-color',        // type name
            'form'          => 'custom-color::form',  // form view alias
            'view'          => 'custom-color::view',  // read-only view alias
            'clazz'         => Function\Color::class, // option class
            'generalOption' => true,                  // enable built-in general options
        ]);
    }
}

Then register the provider so Laravel includes it:


<?php
// File: app/Providers/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Cb\Types\Color\ColorServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->register(ColorServiceProvider::class);
    }
}

Key notes:
- `loadViewsFrom` exposes a namespace alias used by the registrar in `form` and `view`.
- `CBTypeRegistrar::addText` places the type in the correct group (text‑like), which matters for UI integration and default behavior.
- `generalOption => true` unlocks CRUDBooster’s built‑in input transformations from the module builder.

Type Groups You Can Use
Choose the right registrar to match UI/behavior expectations:
- `addText` — text‑like inputs (we use this for color)
- `addDateTime` — date & time inputs
- `addWysiwyg` — rich text editor
- `addNumeric` — numeric (number, money, decimal) with formatting
- `addUpload` — file/image upload
- `addPassword` — password fields with safe behavior
- `addJson` — structured JSON input
- `addSelect` — choices (select, radio, checkbox)
- `addMap` — maps and coordinates

How to choose:
- Consider how the value is stored (string vs numeric vs JSON) and how built‑in UI behaves.
- Pick the group that best matches the base behavior of your type so defaults stay relevant.

General Options (Built‑ins)
With `'generalOption' => true`, you can use helpful transformations from the module builder:
- `uppercase`, `lowercase`, `noSpace`, `noSpecialChar`
- `numeric`, `nonNumeric`, `numberFormat`, `phoneFormat`

Context for color:
- Colors are typically hex strings (`#RRGGBB`). Avoid `numeric/nonNumeric` for this use case.
- `noSpace` or `noSpecialChar` is irrelevant with native `<input type="color">`, but useful if you fall back to a manual text input.

Form Template (Blade)
The form template defines how the input is rendered and bound to Livewire. Keep accessibility (labels, focus) and responsiveness in mind.

{{-- File: app/Cb/Types/Color/views/form.blade.php --}}
{{-- The $column variable contains key, placeholder, label, helpText, etc. --}}
<input type="color"
       id="{{$column['key']}}"
       {{ $focus ? 'autofocus' : '' }}
       placeholder="{{$column['placeholder'] ?? ''}}"
       @readonly($column['readonly'] ?? false)
       wire:loading.attr="readonly"
       wire:target="formSave"
       @if(isset($column['live']))
           wire:model.live.debounce.{{$column['live']}}ms="formData.{{$column['key']}}"
       @else
           wire:model="formData.{{$column['key']}}"
       @endif
       class="form-control">

Binding details:
- `wire:model` binds the value to `formData[key]` so CRUDBooster manages form state.
- `wire:loading.attr="readonly"` prevents changes while submitting.
- `autofocus` improves UX when the field is important.

Optional extras:
- Render the label and helpText from `$column` outside the input to improve accessibility.
- Use `debounce` if color changes trigger heavy preview updates.

View Template (Blade)
The view template shows the saved value on detail/read‑only pages. A small swatch helps with quick visual verification.

{{-- File: app/Cb/Types/Color/views/view.blade.php --}}
{{-- Available: $column, $value, $formData --}}
@php
    $color = $value ?? ($column['option']['default'] ?? '#000000');
@endphp
<span style="display:inline-block;width:16px;height:16px;background-color:{{ $color }};border:1px solid #ddd;border-radius:3px;margin-right:8px;"></span>
<code>{{ $color }}</code>

Display tips:
- Keep the swatch small so it doesn’t disrupt layout.
- Show the color code for easy copying.
- Provide a fallback like `#000000` when the value is missing.

Type Option Class
Options make configuration reusable and clean when using the Form builder.

<?php
// File: app/Cb/Types/Color/Function/Color.php

namespace App\Cb\Types\Color\Function;

use CrudBooster\Components\Type\TypeOptionAbstract;

class Color extends TypeOptionAbstract
{
    /**
     * Set a default color (hex), e.g., #FF6B6B
     */
    public function default(string $hex): static
    {
        $this->option['default'] = $hex;
        return $this;
    }

    /**
     * Show or hide the preview swatch in the form
     */
    public function showPreview(bool $show): static
    {
        $this->option['showPreview'] = $show;
        return $this;
    }
}


Option best practices:
- Validate hex values at the form layer if needed (e.g., Laravel rules) to keep data safe.
- Keep options chainable: `Color::option()->default('#FF6B6B')->showPreview(true)`.

Adding CSS & JS Assets (Optional)
Need richer features (palettes, sampling, alpha)? Register CSS/JS assets — they’re automatically injected into the CRUDBooster theme header.


<?php
// Inside: app/Cb/Types/Color/ColorServiceProvider.php

use CrudBooster\Themes\CbThemeAssetRegistrar;

CbThemeAssetRegistrar::addCss('https://example.com/color-picker.css');
CbThemeAssetRegistrar::addJs('https://example.com/color-picker.js');


Notes:
- Ensure assets aren’t too heavy to keep the admin panel snappy.
- Consider self‑hosting assets if CDN access is unreliable in your environment.

Use It in a Form
Here’s a realistic usage in a Form Component — perfect for theme or branding settings.


<?php
use CrudBooster\Livewire\FormBuilder\Form;
use App\Cb\Types\Color\Function\Color;

class ThemeForm extends \CrudBooster\Livewire\BaseFormComponent
{
    public function init(): void
    {
        $this->makeForm([
            Form::add(label: 'Brand Color', key: 'brand_color', type: 'custom-color')
                ->option(Color::option()->default('#FF6B6B')->showPreview(true)),
        ]);
    }
}


Database suggestions:
- Store as `VARCHAR(7)` for hex `#RRGGBB`, or `VARCHAR(9)` if you support alpha `#RRGGBBAA`.
- Use a migration default if you want an initial value when records are created.

Workflow at a Glance
- Create the folder structure and files.
- Write lightweight, accessible form and view templates.
- Add an option class under `Function` for reusable configuration.
- Register the service provider and call `CBTypeRegistrar` for the right group.
- (Optional) Add CSS/JS assets for advanced pickers.
- Use `Form::add` with your type name and chain options.
- Test in a real module and watch UX and data consistency.

Tips That Save Time
- Keep `type` names, view aliases, and class names consistent.
- Use a short, clear alias with `loadViewsFrom` (e.g., `custom-color`).
- Enable `generalOption` only if it’s relevant to your input behavior.
- Keep types under `app/Cb/Types` for a clean separation from modules.

Troubleshooting
- Type not recognized: ensure the service provider is registered and `CBTypeRegistrar` is called.
- View not found: check your `loadViewsFrom` alias matches the registrar config.
- Options not applied: make sure your option class extends `TypeOptionAbstract` and the template uses `$column['option']`.
- Livewire not updating: verify `wire:model` targets `formData[key]` and there are no binding errors.
- CSS/JS not loading: verify URLs/access and ensure `CbThemeAssetRegistrar::addCss/addJs` is called.