jbafford/css-color-validator-bundle
A Symfony bundle that validates CSS color values, checking strings against supported color formats and specifications. Useful for form constraints and sanitizing user-supplied color input before it reaches your stylesheets.
Reality check before you type a single line: this repository is a Symfony bundle, not a Laravel package. It ships a Constraint + ConstraintValidator pair wired through Symfony's DI extension and Kernel::registerBundles(). Laravel does not consume bundles. There is no ServiceProvider, no config/css_color_validator.php, no facade, and no auto-discovery. Running composer require jbafford/css-color-validator-bundle in a Laravel app will at best pull in symfony/validator as dead weight and at worst fail resolution outright against PHP 8.x / Symfony 6–7 constraints.
So what do you actually do?
vendor/jbafford/css-color-validator-bundle/Validator/Constraints/CssColorValidator.php). Open that file. That is what you are effectively adopting.in_array() against a known set is the correct, cheaper, safer answer. Do not use a grammar parser for this.composer require jbafford/css-color-validator-bundle. The resolution failure is the proof you need that the bundle path is dead. Record the result.app/Rules/Support/CssColorValidator.php, retain the MIT header, and wrap it in an Illuminate\Contracts\Validation\Rule. This is the realistic first use case: a white-label branding settings form where users type a hex or rgb() value and you must reject garbage before it reaches your CSS generator.First use case to build: a theming endpoint (PUT /settings/theme) with a color field on the request. Rule: new ValidCssColor(). This is a two-hour task and gives you a working integration to test the grammar against real user input.
Pattern 1 — Laravel ValidationRule wrapper (the primary pattern).
// app/Rules/ValidCssColor.php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class ValidCssColor implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (!is_string($value) || !$this->isValidColor($value)) {
$fail('validation.css_color')->translate();
}
}
private function isValidColor(string $value): bool
{
// Delegate to vendored logic or spatie/color + explicit allowlist
return CssColorGrammar::matches(trim($value));
}
}
Pattern 2 — Form request integration. Define a ThemeSettingsRequest with rules(): array returning 'color' => ['required', 'string', new ValidCssColor()]. This is where the rule lives in most Laravel apps; you rarely call Validator::make() manually for this.
Pattern 3 — Legacy string-rule call sites. If you have existing 'color' => 'regex:...' rules scattered around, use Validator::extend('css_color', fn ($attr, $value) => ...) to register a string alias, then migrate call sites to the Rule object over time. Do not add new string-rule call sites.
Pattern 4 — Import / bulk validation. For CSV/JSON imports carrying color config, run the rule per row before persistence. Batch a validation pass over the input array and collect all failures, rather than failing on the first bad color — a half-imported theme config is worse than a rejected one.
Pattern 5 — Pair with a normalization layer. The bundle (and any grammar validator) answers yes/no only. In a theming pipeline you almost always follow with normalization: #FFF → #ffffff, rgb(255,0,0) → hex. Do this in a separate service (ColorNormalizer) backed by spatie/color. Keeping "is valid" and "is canonical" separate means you can swap either independently.
Pattern 6 — Testing. Maintain a fixture array of [input, expected] pairs covering: 3- and 6-digit hex, 8-digit hex with alpha, legacy rgb() comma syntax, modern space/slash rgb(), hsl(), hwb(), transparent, currentColor, named colors, and — critically — modern CSS Color 4/5 (lab(), lch(), oklch(), color(display-p3 …)). Assert accept/reject on each. Every false-rejection bug your users report gets added as a fixture.
The false-negative trap (highest pain). A 2010s-era validator will reject oklch(70% 0.15 250) or color(display-p3 1 0 0) even though browsers handle them fine. Rejecting valid input is the worst failure mode for a validation rule — it surfaces as a bug report against your product, and it is nearly impossible to distinguish from a user typing genuine garbage. Test your actual expected input syntaxes against the vendored validator before shipping. If your users only ever get colors from a picker emitting #rrggbb, use an allowlist regex instead and skip the grammar entirely.
Don't confuse validation with safety. If user-supplied color strings are interpolated into a <style> block or a generated .css file, a grammar-valid string is not automatically injection-safe (var(--x), url(...) inside certain functions, attr() in modern specs). If that is your real threat model, use a strict allowlist (# + hex, plus an enumerated set of function forms) and reject everything else. Grammar validation answers a correctness question, not a security one.
Configuration quirks. If you attempt the bundle path, expect Composer to fight you on Symfony version constraints. The *Bundle naming and single-maintainer history strongly suggest Symfony 3/4-era code. Even if resolution succeeds, you have adopted an unmaintained transitive dependency (symfony/validator) for one string check. Note this in the pull request so reviewers understand the trade.
Empty and null input. Confirm the vendored validator fails closed on "", " ", and null. Some grammar validators happily return true for an empty string because "zero tokens = vacuously valid." Your Rule wrapper should short-circuit these to false before delegating.
Maintenance posture — assume you are the fork. 1 star, 0 dependents, single maintainer, no issue triage, no release cadence. Plan for zero upstream fixes. Keep a NOTICE file crediting the original MIT license if you vendor the code, and pin the commit hash in a comment so future maintainers can find the origin.
Extension point is the wrapper, not the package. Do not patch the vendored class. Add behavior — allowlist overrides, normalization, logging of rejections — in your ValidCssColor rule or a small CssColorGrammar service that the rule calls. That keeps the vendored code frozen and diffable against upstream if you ever need to diff it.
Instrument rejections for the first two weeks. Log every color value your rule rejects (with a correlation ID, not the raw string if PII concerns apply) and review the log weekly. This is the only practical way to catch false negatives on real input before users escalate them as bugs. Roll the rule out behind a feature flag if the theming feature is customer-facing.
Migration guidance. There is no incremental migration path — 0 dependents, no evidence of prior internal use. Treat this as greenfield: write the rule, add fixtures, ship. If you later find the vendored behavior insufficient, swapping in spatie/color + an allowlist is a single-class change because the wrapper isolates the dependency.
How can I help you explore Laravel packages today?