Skip to content

Color Schemes

A color scheme is a coordinated set of 22 semantic color tokens (Background, Surface, Primary, and their on-* foreground pairs, etc.). Sections reference these tokens semantically — bg-primary, text-on-surface — so changing the scheme re-skins the whole page without touching any section.

The system is driven by the ColorSchemeGroup theme setting and the ColorScheme per-section picker.

The token editor

ColorSchemeGroup::make('color_scheme') renders a grid of scheme tiles plus an "Add scheme" affordance. Click a tile to drill into the 22-token editor.

The color scheme grid in the theme panel
The scheme grid in the theme panel.
22semantic tokens
14built-in schemes
OkLchauto shade scale

The 22 tokens are paired foreground/background semantics:

Background tokenForeground token
BackgroundOn Background
SurfaceOn Surface
Surface AltOn Surface Alt
PrimaryOn Primary
SecondaryOn Secondary
AccentOn Accent
NeutralOn Neutral
InfoOn Info
SuccessOn Success
WarningOn Warning
DangerOn Danger

The 14 built-in schemes

These schemes are always available and can't be deleted:

light, dark, default, modern, velocity, neutral, accent, inverse, brand-primary, brand-secondary, cupcake, lofi, nord, silk.

The package default is modern. Built-ins are editable per site, not just clonable: token edits are stored as a site-level override, and an override identical to the package default is dropped — so untouched built-ins keep inheriting token upgrades from future package versions. End-users can also add their own schemes on top — see Persistence.

Auto-generated OkLch shades

For every non-on-* token of every scheme, ColorSchemeGroup emits a Tailwind-style 11-step shade ramp computed in OkLch: --color-primary-50 through --color-primary-950, each ramp scoped under its scheme's selector. That means utilities like bg-primary-600 and hover:bg-primary-700 work natively — and re-resolve on a scheme switch — with no extra configuration; map them once in your @theme inline block (see Theme Authoring → step 3).

Applying a scheme in a section

Every built-in section exposes a ColorScheme::make('scheme') control. In the Blade view, apply the selected scheme's data attributes and use the semantic utility classes:

blade
<div {!! $section->colorScheme()->attributes() !!} class="bg-background text-on-background">
  <h2 class="text-on-surface">…</h2>
  <a class="bg-primary text-on-primary hover:bg-primary-700">Get started</a>
</div>

Utilities like bg-background and text-on-surface only exist if your host CSS maps the variables through Tailwind's @theme inline block (see Theme Authoring → step 3). Without Tailwind, use the --fc-color-* custom properties directly — this is what the built-in sections do:

blade
<div {!! $section->colorScheme()->attributes() !!}
     style="background: var(--fc-color-background); color: var(--fc-color-on-background)">

The value object behind colorScheme() offers more than attributes(): classes() (returns bg-{surface} text-on-{surface}), token('primary'), background(), foreground(), and outputCssVars() (inline CSS-variable declarations for the scheme).

In the editor, the color_scheme setting is site-wide: it lives on Site.settings_json['color_scheme'], and every template, region, and host-rendered shell page picks it up — choosing it while editing one page restyles the whole site. Changing a section's scheme setting overrides it for that section alone. Both go through a server refresh like any other setting (see Live Preview).

Upgrading from a pre-1.31 install

The scheme used to persist onto the template draft, so each page carried its own. Upgrading publishes a migration that promotes the scheme a site already chose — its home page's, else the first page carrying one — onto the site row:

bash
composer update filamentcraft/filamentcraft
php artisan vendor:publish --tag=filamentcraft-migrations
php artisan migrate

Skip it and a site whose pages used a non-default scheme renders on the package default instead. Run php artisan filamentcraft:doctor to see whether any site is in that state — it names the site, the page, and the scheme the page asked for.

The visitor dark-mode toggle

The built-in Header and Footer sections ship an opt-in dark-mode switcher for site visitors. Enable it under the section's Dark mode settings group:

  • show_scheme_toggle (Toggle, off by default) — renders a sun/moon button.
  • scheme_toggle_placement (Select) — inline places the button next to the section's other actions; floating renders a fixed round button at the bottom corner of the viewport (RTL-aware, respects mobile safe-area insets).

How it works under the hood:

  1. Clicking the button sets data-fc-scheme-override="dark" on <html> and stores the choice in localStorage under a per-site key (fc-scheme-override-{siteId}); clicking again removes both. Inside the editor canvas the flip is preview-only and never persisted.
  2. The override layer is pre-rendered CSS for every scheme, so the flip needs no server round-trip and re-tokens sections even when they pin their own scheme.
  3. An inline script in <head> re-applies the persisted choice before first paint — no light-theme flash (FOUC) for returning visitors.

Floating toggles are reparented to <body> at runtime (a sticky header's backdrop-filter would otherwise pin the "fixed" button to the header), and when several floating toggles render (e.g. header and footer) only the first is kept — worth knowing if you build a custom header.

The toggle targets the site's dark scheme by default. Because the override layer is emitted for every scheme slug, a custom partial can point the button at any scheme via its data-fc-scheme-dark attribute — e.g. data-fc-scheme-dark="nord".

Custom sections can reuse the shipped partial directly:

blade
@include('filamentcraft::sections.partials.scheme-toggle', [
    'floating' => true,       // fixed round button; false renders inline
    'darkScheme' => 'dark',   // any scheme slug — e.g. 'nord'
])

Persistence

  • The active scheme slug is stored on the template draft, so different templates can use different schemes.
  • User-authored schemes and built-in token edits are persisted on Site.settings_json['schemes'] — each site keeps its own changes without mutating the shared Theme.

The effective scheme map merges three layers, later layers winning:

  1. ColorSchemes::defaults() — the 14 package-shipped schemes
  2. Theme.tokens_json['schemes'] — theme-author overrides
  3. Site.settings_json['schemes'] — end-user overrides

Programmatic helpers:

php
use FilamentCraft\Support\SiteColorSchemes;

$schemes = SiteColorSchemes::forSite($site);          // full merged map (all three layers)
$stored  = SiteColorSchemes::overrides($site);        // just this site's stored overrides
SiteColorSchemes::isBuiltIn('cupcake');               // true — clonable/editable, not deletable

// The site's active (global) scheme — what every page renders on.
$active = SiteColorSchemes::activeFor($site);         // falls back to 'modern'
SiteColorSchemes::setActive($site, 'noir');           // unknown slugs and the default stay unstamped

$newSlug = SiteColorSchemes::add($site, cloneFrom: 'cupcake');            // returns "scheme-N"
$slug    = SiteColorSchemes::add($site, cloneFrom: 'dark', slug: 'noir'); // explicit slug
SiteColorSchemes::remove($site, 'scheme-13');

// Replace the site's override map; entries equal to a package default are
// dropped so untouched built-ins keep inheriting package upgrades.
SiteColorSchemes::syncOverrides($site, $schemes);