Skip to content

Theme Authoring

A FilamentCraft theme is one PHP class that declares the settings panel your end-users see in the editor — Colors, Typography, Buttons, Inputs, Boxes, and anything else you want to expose. The package ships one default theme (StudioTheme) with a 9-category panel. Custom themes coexist alongside it or replace it entirely.

The theme settings panel
A theme settings panel rendered from a single PHP class.

On this page vs. its siblings

PageCovers
Theme Authoring (here)Create a theme class, register it, and consume its CSS variables.
Color SchemesThe ColorSchemeGroup system — 14 built-in schemes, OkLch shades, per-site persistence.
Live PreviewHow edits patch the iframe, live-update modes, and where values are stored.
Setting TypesEvery input you can place in a theme schema.

1. Create the theme class

Place it in app/Themes/AcmeTheme.php and extend AbstractTheme. A theme's settingsSchema() returns top-level Category groups, each holding setting types:

php
<?php

namespace App\Themes;

use FilamentCraft\Settings\Types\Category;
use FilamentCraft\Settings\Types\Checkbox;
use FilamentCraft\Settings\Types\ColorSchemeGroup;
use FilamentCraft\Settings\Types\Font;
use FilamentCraft\Settings\Types\Range;
use FilamentCraft\Theming\AbstractTheme;

final class AcmeTheme extends AbstractTheme
{
    public function slug(): string
    {
        return 'acme';
    }

    public function name(): string
    {
        return 'Acme';
    }

    public function settingsSchema(): array
    {
        return [
            Category::make('colors')
                ->label('Colors')
                ->icon('heroicon-o-swatch')
                ->collapsed(false) // open by default; categories are collapsed otherwise
                ->settings([
                    ColorSchemeGroup::make('color_scheme')->default('light'),
                ]),

            Category::make('typography')
                ->label('Typography')
                ->icon('heroicon-o-language')
                ->settings([
                    Font::make('default_font')->cssVar('--font-default')->default('inter'),
                    Font::make('heading_font')
                        ->cssVar('--font-heading')
                        ->onlyCategory(['display', 'serif'])
                        ->default('playfair-display'),
                    Range::make('body_size')
                        ->cssVar('--font-body-size', 'px')
                        ->unit('px')->min(12)->max(20)->default(16),
                ]),

            Category::make('buttons')
                ->label('Buttons')
                ->icon('heroicon-o-cursor-arrow-rays')
                ->settings([
                    Range::make('button_radius')
                        ->cssVar('--button-radius', 'px')
                        ->unit('px')->min(0)->max(24)->default(8),
                    Checkbox::make('button_uppercase')
                        ->cssVar('--button-text-transform')
                        ->cssValues('uppercase', 'none')
                        ->default(false),
                ]),
        ];
    }
}

AbstractTheme also gives you optional overrides: description(), previewImage(), stylesheets(), and scripts().

2. Register it

Two equivalent ways — pick whichever fits your setup.

A. Via config/filamentcraft.php (recommended — works for panel routes and public preview/site routes):

php
'themes' => [
    'paths' => [],
    'builtin_enabled' => true,    // ship StudioTheme alongside; set false to replace it
    'register' => [
        \App\Themes\AcmeTheme::class,
    ],
],

B. Via the plugin's fluent API (panel-only — for themes that should render only inside Filament admin, not on public pages):

php
use FilamentCraft\FilamentCraftPlugin;

public function panel(Panel $panel): Panel
{
    return $panel->plugin(
        FilamentCraftPlugin::make()
            ->registerTheme(\App\Themes\AcmeTheme::class)
            ->withoutBuiltinThemes() // optional — drop StudioTheme so only Acme appears
    );
}

Then create a Theme Eloquent row whose slug matches ('acme') and assign your Site to it.

3. Consume the variables in your CSS

Every cssVar() declaration is emitted into <style id="fc-tokens"> on :root of the rendered iframe / public page:

css
.btn {
  border-radius: var(--button-radius);
  text-transform: var(--button-text-transform);
  font-size: var(--font-body-size);
  font-family: var(--font-default);
}

If you use Tailwind v4, map them into utility classes via @theme inline so bg-primary, text-on-primary, etc. work in your section blades:

css
@theme inline {
  --font-default: var(--font-default);
  --font-heading: var(--font-heading);
  --color-primary: var(--color-primary);
  --color-primary-50: var(--color-primary-50);
  --color-primary-500: var(--color-primary-500);
  --color-primary-900: var(--color-primary-900);
}

ColorSchemeGroup automatically emits Tailwind-style 11-step OkLch shades (--color-primary-50…950) for every non-on-* token of the active scheme — so bg-primary-600 and hover:bg-primary-700 work natively. The full color system is on the Color Schemes page.

4. Reading values programmatically

To read or write theme settings outside the editor (migrations, seeds, API endpoints):

php
use FilamentCraft\Support\SiteThemeSettings;

// Read merged values (overrides + schema defaults).
$values = SiteThemeSettings::values($site, $site->theme->templateSettings());

// Write a partial set (diffs against defaults; values matching default aren't stamped).
SiteThemeSettings::sync($site, $schema, ['button_radius' => 12]);

Where these land — and how authored color schemes persist separately — is covered in Live Preview → Persistence.

Reference

  • Canonical theme: src/Theming/Themes/StudioTheme.php — the 9-category default panel.
  • Theme contract: FilamentCraft\Theming\ThemeContract (extend AbstractTheme for sensible defaults: description, previewImage, stylesheets, scripts).
  • Setting base: FilamentCraft\Settings\SettingcssVar(), default(), label(), info(), required(), visibleIf().

Proprietary — distributed via Anystack.