[Bug/Regression] Dynamic panel colors not automatically injected in custom theme CSS with Tailwind 4

In Filament v3 with Tailwind v3, dynamic panel colors (defined via ->colors() or tenant-specific colors via middleware) were automatically available in custom theme CSS files without any additional setup. With Filament v4 + Tailwind v4, these dynamic colors are no longer automatically available when using utility classes like text-primary-500, bg-primary-50, etc. in custom theme CSS files. Expected Behavior (Filament v3) Setup:
// AppPanelProvider.php
$panel->tenantMiddleware([TenantStyling::class])
->viteTheme('resources/css/filament/app/theme.css')
// AppPanelProvider.php
$panel->tenantMiddleware([TenantStyling::class])
->viteTheme('resources/css/filament/app/theme.css')
// TenantStyling.php
public function handle(Request $request, Closure $next): Response
{
$tenant = Filament::getTenant();
$panel = Filament::getCurrentPanel();

$colors = $panel->getColors();

$colors['primary'] = $tenant->primaryColor(); // i.e: #FF0000

$panel->colors($colors);

return $next($request);
}
// TenantStyling.php
public function handle(Request $request, Closure $next): Response
{
$tenant = Filament::getTenant();
$panel = Filament::getCurrentPanel();

$colors = $panel->getColors();

$colors['primary'] = $tenant->primaryColor(); // i.e: #FF0000

$panel->colors($colors);

return $next($request);
}
/* theme.css */
.fi-sidebar-item.fi-active .fi-sidebar-item-icon {
@apply text-primary-500; /* :white_check_mark: Works automatically */
}
/* theme.css */
.fi-sidebar-item.fi-active .fi-sidebar-item-icon {
@apply text-primary-500; /* :white_check_mark: Works automatically */
}
The classes would automatically use the dynamic colors defined in the panel provider. Actual Behavior (Filament v4) The same code doesn't work. The utility classes text-primary-500, bg-primary-50, etc. don't reflect the dynamic colors. Root Cause Tailwind v4 requires explicit color declarations via @theme inline directive Filament v4 doesn't automatically inject CSS variables (--primary-500, --primary-600, etc.) into the DOM when colors are set via ->colors() or middleware Developers must manually create a render hook to inject these variables Current Workaround Developers must add a HEAD_END render hook and a Blade view to inject CSS variables: 1. Add render hook:
// AppPanelProvider.php
->renderHook(
PanelsRenderHook::HEAD_END,
function () {
$colors = Filament::getCurrentPanel()->getColors();
return view('filament.tenant-colors', ['colors' => $colors]);
}
)
// AppPanelProvider.php
->renderHook(
PanelsRenderHook::HEAD_END,
function () {
$colors = Filament::getCurrentPanel()->getColors();
return view('filament.tenant-colors', ['colors' => $colors]);
}
)
2 Replies
Jonathan SARDO
Jonathan SARDOOP3d ago
2. Create Blade view:
{{-- resources/views/filament/tenant-colors.blade.php --}}
@php
use Filament\Support\Colors\Color;

function generateColorVariables($color, $colorName) {
if (empty($color)) return '';

$shades = is_string($color) && str_starts_with($color, '#')
? Color::hex($color)
: $color;

$css = '';
foreach ([50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as $shade) {
if (isset($shades[$shade])) {
$rgb = is_array($shades[$shade])
? implode(' ', array_map('intval', $shades[$shade]))
: $shades[$shade];
$css .= " --{$colorName}-{$shade}: {$rgb};\n";
}
}
return $css;
}
@endphp

@if(!empty($colors))
<style id="panel-colors">
:root {
@foreach($colors as $colorName => $colorValue)
{!! generateColorVariables($colorValue, $colorName) !!}
@endforeach
}
</style>
@endif
{{-- resources/views/filament/tenant-colors.blade.php --}}
@php
use Filament\Support\Colors\Color;

function generateColorVariables($color, $colorName) {
if (empty($color)) return '';

$shades = is_string($color) && str_starts_with($color, '#')
? Color::hex($color)
: $color;

$css = '';
foreach ([50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as $shade) {
if (isset($shades[$shade])) {
$rgb = is_array($shades[$shade])
? implode(' ', array_map('intval', $shades[$shade]))
: $shades[$shade];
$css .= " --{$colorName}-{$shade}: {$rgb};\n";
}
}
return $css;
}
@endphp

@if(!empty($colors))
<style id="panel-colors">
:root {
@foreach($colors as $colorName => $colorValue)
{!! generateColorVariables($colorValue, $colorName) !!}
@endforeach
}
</style>
@endif
3. Add Tailwind v4 theme declaration:
/* theme.css */
@theme inline {
--color-primary-50: var(--primary-50);
--color-primary-100: var(--primary-100);
/* ... all shades ... */
}
/* theme.css */
@theme inline {
--color-primary-50: var(--primary-50);
--color-primary-100: var(--primary-100);
/* ... all shades ... */
}
Proposed Solution Filament v4 should automatically inject CSS variables for panel colors into the <head> when a custom theme is used, similar to how it worked in v3. Suggested implementation: Automatically inject color CSS variables when viteTheme() is used Include a <style> tag in the panel layout that generates CSS variables from $panel->getColors() Provide a config option to disable this behavior for advanced use cases Example implementation in PanelManager or similar:
// Automatically add this render hook when viteTheme() is configured
if ($panel->hasViteTheme()) {
$panel->renderHook(
PanelsRenderHook::HEAD_END,
fn () => view('filament::components.panel-colors', [
'colors' => $panel->getColors()
])
);
}
// Automatically add this render hook when viteTheme() is configured
if ($panel->hasViteTheme()) {
$panel->renderHook(
PanelsRenderHook::HEAD_END,
fn () => view('filament::components.panel-colors', [
'colors' => $panel->getColors()
])
);
}
Benefits ✅ Backward compatibility - Existing v3 theme CSS would work with minimal changes ✅ Better DX - No need for manual boilerplate code ✅ Consistency - Works the same way across Filament installations ✅ Multi-tenancy friendly - Works automatically with dynamic tenant colors Additional Context This issue particularly affects: Multi-tenant applications with per-tenant branding Applications migrating from Filament v3 to v4 Custom admin themes that override default Filament styles The workaround is functional but requires significant boilerplate that should ideally be handled by the framework itself.
Dennis Koch
Dennis Koch3d ago
I get the colors injected without additional setup:
No description

Did you find this page helpful?