Skip to content

Custom Theme โ€‹

PowerGrid 7 separates HTML from CSS. Blade views read classes through theme(). A theme is a PHP class that returns a nested token map.

ThemeRole
TailwindRoot theme. Full section methods. parentTheme is null.
DaisyUIToken-only subclass of Tailwind. Ships zero Blade files.
FluxSubclass of Tailwind. Keeps <flux:*> blades only where HTML differs.

Cheapest first:

  1. theme_overrides in config
  2. A Theme class with section methods (inherit Tailwind, override what differs)
  3. ArrayTheme โ€” the same tokens as a plain array

There is no Bootstrap theme class. Do not branch on isBootstrap5() or theme_style() โ€” both are gone. Use theme() and the theme class.

Selecting a Theme โ€‹

config/livewire-powergrid.php accepts a registered name or an FQCN. See UI Theme.

phpapp/Providers/AppServiceProvider.php
use PowerComponents\LivewirePowerGrid\PowerGridManager;
use App\PowerGridThemes\MyTheme;

public function boot(): void
{
    PowerGridManager::registerTheme('my-theme', MyTheme::class);
}
phpconfig/livewire-powergrid.php
'theme' => 'my-theme',

No-Code Overrides โ€‹

theme_overrides merge last. Use them for a handful of class changes on every table. See Theme Overrides.

Architecture โ€‹

A theme extends PowerComponents\LivewirePowerGrid\Themes\Theme.

  • struct() returns the base token tree (ThemeBuilder or array). Built-in themes use it only to set baseView; section tokens come from the section methods.
  • Token groups are public methods: layout(), header(), table(), footer(), cols(), tabs(), plus filter(), editable(), toggleable().
  • Theme::name() is the kebab-cased class basename. Do not declare public string $name.
  • Set protected ?string $parentTheme = Tailwind::class so undeclared sections inherit from Tailwind.
  • Prefer tokens over new Blade files. Add a view only when the markup is genuinely different.

Resolution order:

  1. Parent theme tokens
  2. struct() (merged over the parent)
  3. This theme's section methods
  4. Plugin themeTokens()
  5. config('livewire-powergrid.theme_overrides') โ€” last, wins

Creating a Child Theme โ€‹

Usual path: branded Tailwind variant, same HTML, different classes.

phpapp/PowerGridThemes/MyTheme.php
<?php

namespace App\PowerGridThemes;

use PowerComponents\LivewirePowerGrid\Themes\Theme;
use PowerComponents\LivewirePowerGrid\Themes\Components;
use PowerComponents\LivewirePowerGrid\Themes\Tailwind;

class MyTheme extends Theme
{
    protected ?string $parentTheme = Tailwind::class;

    protected function baseView(): string
    {
        return 'livewire-powergrid::components.themes.tailwind';
    }

    public function struct(): Components\ThemeBuilder
    {
        return Components\ThemeBuilder::make($this->name())
            ->baseView($this->baseView());
    }

    public function table(): array
    {
        return $this->section('table', fn (Components\Table $table) => $table
            ->layout(fn (Components\Layout $layout) => $layout
                ->th('font-semibold px-3 py-3 text-left text-xs text-white whitespace-nowrap')
                ->td('px-3 py-2 whitespace-nowrap')
            )
        );
    }
}

Everything you do not override comes from Tailwind. DaisyUI is this pattern taken all the way: it ships no blades.

A section method can also return a plain nested array:

phpapp/PowerGridThemes/MyTheme.php
public function footer(): array
{
    return [
        'footer' => [
            'pagination' => [
                'item' => 'btn btn-sm',
            ],
        ],
    ];
}

Register with PowerGridManager::registerTheme() or set 'theme' => \App\PowerGridThemes\MyTheme::class.

Scan the class in Tailwind CSS:

cssresources/css/app.css
@source "../../app/PowerGridThemes/MyTheme.php";

Then rebuild with npm run build or npm run dev.

Section Methods โ€‹

Only override sections that differ from the parent theme.

MethodBuilderTypical tokens
layout()Components\Layoutwrapper, card
header()Components\Headerlayout, searchBox, toggleColumns, filters, export
table()Components\Tablelayout, body/tr, checkbox, radio
footer()Components\Footerlayout, pagination (string alias)
cols()Components\Colsdiv
tabs()Components\Tabslist, tab, tabActive, badge
filter()array or Components\Filterper-type classes, dropdown.*, flyout.*
editable()Components\Componentclickable, input, error โ€” no view
toggleable()Components\Componentcolor fill() tokens โ€” no view

Search lives under header(), not as a top-level method.

phpapp/PowerGridThemes/MyTheme.php
public function header(): array
{
    return $this->section('header', fn (Components\Header $header) => $header
        ->view('header')
        ->layout(fn (Components\Layout $layout) => $layout
            ->container('md:flex md:flex-row w-full justify-between items-center gap-3 p-4')
            ->actions('px-3 py-2 rounded-md')
        )
        ->searchBox(fn (Components\SearchBox $searchBox) => $searchBox
            ->view('header.search')
            ->input('w-full rounded-md py-1.5 pl-8 text-sm')
            ->iconSearch('w-5 h-5 text-zinc-400')
        )
    );
}

Closure type-hints matter. Hint searchBox as Components\SearchBox, checkbox as Components\Checkbox, radio as Components\Radio, body as Components\Body, tabs as Components\Tabs. Hinting them as Components\Component throws a TypeError.

Editable and toggleable โ€‹

Do not set editable.view. The plugin blade is powergrid-plugins::Editable.index. Keep classes only.

phpapp/PowerGridThemes/MyTheme.php
public function editable(): array
{
    return [
        'editable' => (new Components\Component())
            ->clickable('cursor-pointer py-2')
            ->input('rounded-md text-sm w-full')
            ->error('text-sm text-red-600 p-1')
            ->toArray(),
    ];
}

public function toggleable(): array
{
    return [
        'toggleable' => (new Components\Component())
            ->fill([
                'colorOn' => 'var(--color-accent, #16a34a)',
                'colorOff' => 'var(--color-zinc-200, #e4e4e7)',
                'colorOnDark' => 'var(--color-accent, #16a34a)',
                'colorOffDark' => 'var(--color-zinc-600, #52525b)',
                'knobOn' => 'var(--color-accent-foreground, #ffffff)',
            ])
            ->toArray(),
    ];
}

Filters โ€‹

filter.multi_select has no view token. Markup is <x-livewire-powergrid::inputs.select>.

filter.dropdown.* and filter.flyout.* style the panel placements. Leave view alone unless you replace the markup. The flyout panel token must keep positioning (fixed inset-y-0) and stay above overlay.

Tokens without a dedicated builder (for example table.body.td.actions_wrapper) can be merged in resolveTokens():

phpapp/PowerGridThemes/MyTheme.php
public function resolveTokens(): array
{
    if (empty($this->tokens)) {
        $tokens = parent::resolveTokens();

        $this->tokens = array_replace_recursive($tokens, [
            'table' => [
                'body' => [
                    'td' => [
                        'actions_wrapper' => 'flex gap-2',
                    ],
                ],
            ],
        ]);
    }

    return $this->tokens;
}

ArrayTheme โ€‹

Author the same tokens as a plain array. Everything not defined falls back to the parent (Tailwind by default).

phpapp/PowerGridThemes/MyArrayTheme.php
use PowerComponents\LivewirePowerGrid\Themes\ArrayTheme;
use PowerComponents\LivewirePowerGrid\Themes\Tailwind;

class MyArrayTheme extends ArrayTheme
{
    protected ?string $parentTheme = Tailwind::class;

    public function struct(): array
    {
        return [
            'footer' => [
                'pagination' => [
                    'item' => 'btn btn-sm',
                ],
            ],
        ];
    }
}

Ad-hoc, without a subclass:

phpapp/Livewire/ProductTable.php
use PowerComponents\LivewirePowerGrid\Themes\ArrayTheme;
use PowerComponents\LivewirePowerGrid\Themes\Tailwind;
use PowerComponents\LivewirePowerGrid\Themes\Theme;

public function template(): ?Theme
{
    return ArrayTheme::fromArray(
        ['footer' => ['pagination' => ['item' => 'btn btn-sm']]],
        parentTheme: Tailwind::class,
        name: 'compact',
    );
}

ArrayTheme::fromFile(base_path('themes/my-theme.php')) loads a PHP file that returns the token array.

Per-Component Theme โ€‹

Prefer template(). customThemeClass() still works but is deprecated; template() wins if both are set.

phpapp/Livewire/ProductTable.php
use PowerComponents\LivewirePowerGrid\Themes\Theme;
use App\PowerGridThemes\MyTheme;

public function template(): ?Theme
{
    return new MyTheme();
}

To patch a few tokens on one table with merge(), see UI Theme Override.

Views โ€‹

  • theme('table.layout.td') โ€” CSS classes for a token
  • theme_view('header') โ€” Blade path for an alias

View resolution: theme views() map โ†’ token view โ†’ baseView + alias โ†’ parent theme โ†’ components.structure.*.

DaisyUI inherits Tailwind blades this way. Flux ships blades only where markup differs (<flux:*>).

Do not put @if($theme === 'bootstrap') in Blade. Structure is theme-agnostic; classes come from tokens.

Feature views (export, toggle-columns, soft-deletes) resolve automatically. Do not declare them in struct().

Migrating a v6 Theme โ€‹

The Upgrade Guide has the v6 โ†’ v7 key map. Short version:

  1. Extend Theme, set parentTheme = Tailwind::class
  2. Shrink struct() to baseView only
  3. Move each old method into a section method
  4. Nest search under header(), checkbox/radio under table()
  5. Drop editable.view, toggleable.view, filter.multi_select.view
  6. Delete public string $name

Laravel Boost includes the upgrade-theme-v6-to-v7 skill for this conversion. See AI Tools and Skills.

Created By Luan Freitas and @DanSysAnalyst