DEV Community

Cover image for Understanding Filament themes in v4/v5: from Colors to custom CSS
yebor974 for Filament Mastery

Posted on • Originally published at filamentmastery.com on

Understanding Filament themes in v4/v5: from Colors to custom CSS

Filament provides a flexible theming system that lets you customize the look and feel of your admin panel. This guide covers how to create and apply custom themes for Filament v4/v5 with Tailwind CSS v4.

This is an updated version of an earlier guide written for Filament v3 and Tailwind v3. The overall approach is similar, but what the theme command generates and the CSS syntax have changed with Tailwind v4's CSS-first configuration. If you're still on Filament v3, the original guide is available here.

Setting primary colors for panels

Before touching the theme files, the simplest customization is setting your panel's color palette. This part hasn't changed: Filament still uses the Filament\Support\Colors\Color class.

Option 1: define colors per panel

In your panel provider:

use Filament\Support\Colors\Color;

public function panel(Panel $panel): Panel
{
    return $panel
        ->colors([
            'danger' => Color::Rose,
            'gray' => Color::Gray,
            'info' => Color::Blue,
            'primary' => Color::Indigo,
            'success' => Color::Emerald,
            'warning' => Color::Orange,
        ]);
}

Enter fullscreen mode Exit fullscreen mode

Option 2: define colors globally

In AppServiceProvider, applied across all panels:

use Filament\Facades\Filament;
use Filament\Support\Colors\Color;

public function boot(): void
{
    Filament::defaultColors([
        'danger' => Color::Rose,
        'gray' => Color::Gray,
        'info' => Color::Blue,
        'primary' => Color::Indigo,
        'success' => Color::Emerald,
        'warning' => Color::Orange,
    ]);
}

Enter fullscreen mode Exit fullscreen mode

When to stop here: if your goal is only to change branding colors, ->colors() is all you need. Create a custom theme only when you need structural UI changes, like repositioning elements, changing spacing, or styling parts of the interface that color tokens don't reach.

Generating the theme

The command itself hasn't changed:

php artisan make:filament-theme

Enter fullscreen mode Exit fullscreen mode

You'll be prompted for a panel name (default: admin). What changed is what this command generates and does for you.

On a Tailwind v4 setup, running it for a member panel generates a theme.css scoped to that panel:

@import '../../../../vendor/filament/filament/resources/css/theme.css';

@source '../../../../app/Filament/Member/**/*';
@source '../../../../resources/views/filament/member/**/*';

Enter fullscreen mode Exit fullscreen mode

Notice the @source paths point specifically to Member, not a generic Filament/**/*. Each panel gets its own scoped theme out of the box.

That's it: the generated file is intentionally minimal. If you want a dedicated place for CSS variables or light/dark overrides, you can add your own @layer base block:

@layer base {

    :root {

    }

    :root[class~="dark"] {

    }

}

Enter fullscreen mode Exit fullscreen mode

The other change worth noting: in Filament v3, you had to manually add the theme to vite.config.js and register it with ->viteTheme() in your panel provider. In v4/v5, the command does both for you automatically. No manual wiring required.

A few things changed compared to Tailwind v3:

No more tailwind.config.js. Tailwind v4 no longer requires a tailwind.config.js file for basic theme customization. It moved to CSS-first configuration. There's nothing left to generate or maintain in a separate JS config file for the theme.

@source directives replace the old content array. Tailwind v4 scans these paths directly from CSS to know which files to look at for class usage, instead of declaring them in a JS config.

The generated file is intentionally minimal. Just the import and the @source paths for that panel. No boilerplate @layer base block is added automatically; that's something you add yourself if and when you need it, as shown above.

Adding your own directories to @source

The generated @source directives only cover Filament's own files. Your resources, components, or anything outside app/Filament won't be scanned by default. If you use Tailwind classes in Blade components, Livewire components, or custom views, you need to add those directories yourself:

@source '../../../../app/Filament/**/*';
@source '../../../../resources/views/filament/**/*';
@source '../../../../resources/views/components/**/*';
@source '../../../../resources/views/livewire/**/*';
@source '../../../../app/Livewire/**/*';

Enter fullscreen mode Exit fullscreen mode

Forget to add a directory here, and Tailwind will purge classes it never saw being used, even if they're correctly written in your code. Rebuild with npm run build after adding new paths.

What gets registered automatically

Since the command handles registration for you, vite.config.js already includes the new theme after running make:filament-theme:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/css/filament/admin/theme.css',
                'resources/js/app.js',
            ],
            refresh: true,
        }),
        tailwindcss(),
    ],
});

Enter fullscreen mode Exit fullscreen mode

And your panel provider already has the theme registered too:

->viteTheme('resources/css/filament/admin/theme.css')

Enter fullscreen mode Exit fullscreen mode

Nothing left to wire up manually. Just run the command, and both files are updated for you.

Multiple panels, multiple themes

If your application has more than one panel, an admin panel and a member panel for example, each one gets its own theme file and is registered independently.

Run make:filament-theme once per panel:

php artisan make:filament-theme backend
php artisan make:filament-theme member

Enter fullscreen mode Exit fullscreen mode

Each run automatically adds its theme to vite.config.js and registers it in the matching panel provider. You'll end up with both entries already in place:

laravel({
    input: [
        'resources/css/app.css',
        'resources/css/filament/backend/theme.css',
        'resources/css/filament/member/theme.css',
        'resources/js/app.js',
    ],
    refresh: true,
}),


// BackendPanelProvider
->id('backend')
->viteTheme('resources/css/filament/backend/theme.css')

// MemberPanelProvider
->id('member')
->viteTheme('resources/css/filament/member/theme.css')

Enter fullscreen mode Exit fullscreen mode

Each theme.css is scoped to its own panel by default. The @source directives only point to that panel's files, and customizing one theme doesn't affect the other. This is how the Multipanel and Multi-Tenant starters are set up: two themes, generated empty and scoped, ready for each panel to be styled differently if needed.

Sharing one theme across panels: you don't have to generate a separate file per panel. If two panels should look identical, you can point both panel providers at the same theme file:

// BackendPanelProvider
->viteTheme('resources/css/filament/shared/theme.css')

// MemberPanelProvider
->viteTheme('resources/css/filament/shared/theme.css')
Enter fullscreen mode Exit fullscreen mode

Just make sure the @source paths in that shared file cover both panels directories, since a single generated theme only scopes to the panel it was created for.

Reading Filament's hook classes

Once you start customizing beyond colors, you'll be targeting Filament's own CSS classes, the ones used in the sidebar example below and throughout the interface. They follow a consistent naming convention worth knowing:

  • fi is short for "Filament"
  • fi-ac is used for classes from the Actions package
  • fi-fo is used for classes from the Forms package
  • fi-in is used for classes from the Infolists package
  • fi-no is used for classes from the Notifications package
  • fi-sc is used for classes from the Schema package
  • fi-ta is used for classes from the Tables package
  • fi-wi is used for classes from the Widgets package
  • btn is short for "button"
  • col is short for "column"
  • ctn is short for "container"
  • wrp is short for "wrapper"

Once you know this pattern, hook class names become much easier to read without digging through Filament's source every time.

Finding hook classes in practice: the naming convention tells you what a class means once you've found it, but you'll still need to find it first. The fastest way:

  1. Open your browser's dev tools on the panel you want to customize
  2. Inspect the element you want to target
  3. Look for classes starting with fi- in the inspector
  4. Use that class in your theme's CSS

This is the most reliable way to target the exact element you're looking at, rather than guessing from the naming convention alone.

Customizing your theme

With the theme registered, you can now add your own styles directly in theme.css. Filament's hook classes, documented in the Filament docs, let you target specific parts of the interface.

Here's an example customizing the sidebar, the same idea as in the original v3 guide, adapted to the new file structure:

@import '../../../../vendor/filament/filament/resources/css/theme.css';

@source '../../../../app/Filament/**/*';
@source '../../../../resources/views/filament/**/*';

.fi-sidebar {
    @apply !bg-gray-500;

    .fi-sidebar-header {
        .fi-icon-btn-icon {
            @apply text-primary-500;
        }
    }

    .fi-sidebar-group-label, .fi-icon-btn-icon, .fi-sidebar-group-icon, .fi-sidebar-item-icon, .fi-sidebar-item-button, .fi-sidebar-item-label {
        @apply text-white;
    }

    .fi-sidebar-item-active {
        .fi-sidebar-item-button {
            @apply bg-primary-500;
        }
    }

    .fi-sidebar-item, .fi-sidebar-item-button {
        :hover {
            @apply hover:bg-primary-500;
        }
    }

    .fi-icon-btn {
        :hover {
            @apply hover:text-primary-500;
        }
    }

    .fi-sidebar-nav-groups {
        @apply gap-y-0;
    }
}

Enter fullscreen mode Exit fullscreen mode

The @apply syntax and the hook classes themselves haven't changed. What changed is everything around them: how the theme is generated, how Tailwind scans your files, and how the build is configured.

Using CSS variables for reusable values

If you find yourself repeating the same color or value across multiple rules, defining a CSS variable in @layer base keeps things consistent and easier to update later:

@layer base {
    :root {
        --sidebar-bg: #1f2937;
    }

    :root[class~="dark"] {
        --sidebar-bg: #111827;
    }
}
Enter fullscreen mode Exit fullscreen mode

Then reference it directly in your styles:

.fi-sidebar {
    background: var(--sidebar-bg);
}
Enter fullscreen mode Exit fullscreen mode

This is especially useful for values you'll reuse in several places, or want to swap between light and dark mode without duplicating rules.

Run npm run build once you're done, and your custom theme is live.

Common mistakes when customizing Filament themes

A few things come up repeatedly when developers start working with Filament themes for the first time.

Creating a theme when ->colors() is enough. If the only goal is changing the primary color, buttons, and sidebar accents, ->colors() in the panel provider covers that without generating any files. A custom theme is only necessary for structural changes, targeting specific interface elements via hook classes, or using CSS variables for fine-grained control. Generating a theme file when it isn't needed just adds a build step and a file to maintain.

Forgetting to add custom Blade directories to @source. The generated theme only scans Filament's own directories by default. Any Tailwind classes used in custom Blade components, Livewire components, or views outside app/Filament won't be detected unless you explicitly add those paths. Tailwind silently purges the undetected classes, and the issue only shows up at build time when styles randomly stop working in production.

Styling internal Filament classes instead of hook classes. Filament's hook classes, prefixed with fi-, are the intended surface for CSS customization. Styling classes without the fi- prefix means targeting internal implementation details that can change between releases without notice. Filament's own documentation explicitly warns against this: if a hook class you need doesn't exist yet, the recommended path is to open a pull request to add it, not to target internal classes and accept the maintenance risk.

Overriding too much. The more CSS overrides added to a theme, the harder upgrades become. When Filament ships a new version, any structural change to the interface can break rules that were written against the old markup. Prefer color tokens, CSS variables, and targeted hook class overrides before rewriting large sections of the interface. A theme should adapt Filament to a brand, not turn Filament into a completely different application.

And of course, editing vendor files directly. Any change made inside vendor/filament is overwritten the next time composer update runs. If something in Filament's default theme needs to be overridden, the right place is the custom theme.css file, using hook classes to target the element. The vendor directory is never the right place.

Don't want to build a theme from scratch?

If building a custom Filament theme isn't where you want to spend your time, Filafly offers premium themes and plugins ready to drop into your panel.

Use code MASTERY15 for 15% off your purchase.

Discover Filafly themes →

Originally published on Filament Mastery

Top comments (0)