Skip to main content

Documentation

No results found.
Features

Branding Color System

WebProCMS' branding system gives every install three named color families — primary, secondary, and tone (gray) — and turns each one into a full 19-shade palette. Rows on the public site reference brand-aware utilities (bg-primary, text-ton...

WebProCMS' branding system gives every install three named color families — primary, secondary, and tone (gray) — and turns each one into a full 19-shade palette. Rows on the public site reference brand-aware utilities (bg-primary, text-tone-700, bg-primary-50) that update site-wide the moment the brand color changes.


The problem

A typical site has dozens of color decisions baked into row templates: button fills, hover states, callout backgrounds, badge tints, link colors, headline text, muted body copy, borders, dividers. If those are written as raw Tailwind colors (bg-blue-600, text-zinc-400), changing the brand means hand-editing every row file. If they're written as a single --brand variable, the design loses tonal range — every "subtle" surface ends up the same heavy color as every CTA.

The middle ground — handing editors a Tailwind picker with thousands of color/shade combinations — also fails. Most edits come out off-brand because the editor has to manually colour-match a callout background to the family they picked for the button, and pick a readable foreground each time.

The fix

WebProCMS replaces brand color with a system of three families, each generated into a full 19-shade palette. Row templates reference semantic utilities like bg-primary and text-primary-100 — never raw color names. Changing the family color regenerates the entire palette and propagates through every row instantly.

Editors pick a base color per family (hex, OKLCH, or a matched Tailwind color name) from the Colors settings page, plus a "Smart Picker" that generates coordinated primary/secondary colors from a vibrancy + distribution preset.

The three families

Family Default Use
primary oklch(0.441 0.129 272) (purple) Brand color. CTAs, highlights, icons, links.
secondary oklch(0.725 0.132 124) (green) Supporting accent. Optional — can be toggled off in settings.
tone oklch(0.37 0.012 285.8) (zinc) Neutral gray scale. Body text, borders, muted backgrounds.

There is no brand accent family — it was removed; use secondary, or the per-section-preset text-row-accent / bg-row-accent utilities.

tone is special: instead of being generated from OKLCH math, it aliases one of five built-in Tailwind gray scales (slate, gray, zinc, neutral, stone) for the 11 standard stops, with computed half-steps filled in. Editors pick the gray feel of the site once and every text-tone-* / border-tone-* class on the site follows.

The 19-shade palette

Each color family generates a 19-shade palette. The standard 11 Tailwind stops (50/100/200/.../900/950) are joined by 8 half-steps (150/250/350/450/550/650/750/850) for finer tonal control:

50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950

Generation lives in BrandingStyleService::generateShadeOklch(). The base color's OKLCH lightness is matched to the closest shade in a fixed lightness/chroma curve (e.g. a base color at L=0.441 anchors at shade 700), and every other shade is computed by holding the hue constant and stepping lightness + chroma along that curve. The result is that the editor's chosen color always appears as one of the 19 shades — and the rest of the palette flows from it tonally rather than being arbitrarily generated.

Tone uses toneScaleShades() instead — the 11 standard stops come from Tailwind's built-in scale and the 8 half-steps are computed via OKLCH interpolation between adjacent stops.

The full palette is emitted as CSS custom properties at :root:

--color-primary: oklch(0.441 0.129 272);
--color-primary-50: oklch(0.972 0.012 272);
--color-primary-100: oklch(0.942 0.022 272);
...
--color-primary-950: oklch(0.282 0.072 272);

Tailwind v4 picks these up as utilities automatically (bg-primary-550, text-tone-150, border-primary-400, etc.). The shade-picker UI exposes only the 11 standard stops to keep the dropdown manageable; half-steps are available when hand-written as literal classes in row templates.

No dedicated foreground/surface variables

There is no --color-{family}-foreground or --color-{family}-surface CSS variable for the brand families — that system was removed. Row templates use shade stops directly instead:

For Use
Text/icon color placed on top of a solid fill (e.g. inside a primary button) text-primary-100
Subtle tinted background (callouts, badges) bg-primary-50 (or bg-primary-50 dark:bg-primary-950 for a dark-aware tint)

A row template that says <button class="bg-primary text-primary-100"> always renders a readable button, regardless of which color the editor picks for primary. A callout that says <div class="bg-primary-50 text-primary"> always renders a tinted highlight that's brand-colored without being loud.

(Flux UI's dashboard-only --color-accent-foreground / --color-accent-surface tokens are a separate, unrelated system that still exists for Flux's internal focus-ring/switch styling — it isn't part of the brand color families described here.)

Picking colors — the Colors settings page

Editors set each family's base color directly (hex, OKLCH, or by matching a Tailwind color name) on Dashboard → Settings → Design → Colors, or use the Smart Picker to generate a coordinated primary/secondary pair from a vibrancy level (Muted/Soft/Balanced/Rich/Vibrant) and a distribution preset (Uniform/Soft Primary/Bold Primary/Bold Secondary/Graduated/Vivid Supporting). Implementation lives in the brandingColors Alpine component in resources/js/manager.js.

Per-section overrides (Section settings page)

The section settings page (the paintbrush button on each row) offers its own context-aware text-color picker, scoped to one row only — it offers a different, larger set of shade options depending on whether the row's background is dark, a brand surface tint, or a light neutral. The section-level override writes an actual shade class name (e.g. text-primary-50) into that one row's formTextClasses. It does not change the global CSS variables — only the class stored for that specific preset.

Tone scale aliasing

For neutral grays, hand-rolled OKLCH interpolation produces visually wrong results compared to Tailwind's tuned defaults. Tone instead aliases the chosen Tailwind gray family for the 11 standard stops:

--color-tone-50: var(--color-zinc-50);
--color-tone-100: var(--color-zinc-100);
--color-tone-500: var(--color-zinc-500);
...

The 8 half-steps (150/250/.../850) are computed via OKLCH interpolation between adjacent standard stops in BrandingStyleService::toneScaleShades(). Editors swap the entire gray feel of the site by picking a different scale (slate / gray / zinc / neutral / stone) — one setting, every tone-* utility on the site updates.

Section spacing tokens

The Spacing settings page also bundles vertical rhythm. Three CSS variables drive every row's vertical spacing on the public site:

Token Default (Medium) Purpose
--section-pad-y 5rem Standard rows
--section-pad-y-banner 3rem (1rem less) Compact CTAs
--section-pad-y-hero 6rem (1rem more) Heroes

Picking Small / Medium / Large / X-Large / 2X-Large / 3X-Large from the Spacing settings page sets all three at once via BrandingStyleService::spacingValues(). Custom values (5.5rem, etc.) are accepted and the banner/hero variants offset from them automatically.

Other tokens controlled the same way: --spacing-section-x (side padding), --spacing-items (grid item gap), --width-container (container max-width), --radius-base (border radius), --shadow-base (box shadow).

Optional families

secondary can be toggled off in settings (branding.color_group_toggles). When off, neither the base color nor any of its 19 shades are emitted as CSS variables — keeping the final stylesheet payload minimal for sites that only need primary + tone. tone and primary are always emitted.

What lives where

Path Purpose
app/Services/BrandingStyleService.php generateShadeOklch() (19-shade OKLCH math), toneScaleShades() (gray scale aliasing + half-step interpolation), build() / buildDashboard() (emit :root { --color-* } blocks for public + dashboard). defaultColors() defines the three-family defaults. hexToOklch() converts hex picks to OKLCH for storage.
resources/js/manager.js brandingColors Alpine component (per-family color pickers, Smart Picker). sectionStylePicker Alpine component (per-row context-aware text-color options). generateShades() (JS port of the OKLCH curve for live previews).
resources/views/pages/dashboard/settings/⚡colors.blade.php The Colors page — color pickers, Smart Picker, tone scale selector.
resources/views/pages/dashboard/settings/⚡spacing.blade.php The Spacing page — section spacing, side padding, item gap, container width, radius, shadow.
resources/css/public.css Declares the static --color-* defaults in @theme so Tailwind v4 generates utility classes (bg-primary-550, text-tone-150, etc.) at build time. Runtime BrandingStyleService <style> block overrides the values.

Settings reference

Key Default Notes
branding.colors.primary oklch(0.441 0.129 272) Stored as OKLCH string.
branding.colors.secondary oklch(0.725 0.132 124) Optional family.
branding.colors.tone oklch(0.37 0.012 285.8) Display anchor for tone; actual scale comes from tone_scale.
branding.tone_scale 'zinc' One of slate, gray, zinc, neutral, stone.
branding.color_group_toggles.secondary true Set false to omit secondary family from CSS.
branding.section_spacing 'medium' Drives all three section spacing tokens.
branding.section_spacing_banner '' Custom override for --section-pad-y-banner.
branding.section_spacing_hero '' Custom override for --section-pad-y-hero.
branding.section_side_padding 'medium' --spacing-section-x.
branding.section_item_gap 'medium' --spacing-items.
branding.container_width 'medium' --width-container.
branding.radius 'medium' --radius-base.
branding.shadow 'medium' --shadow-base.