Create or update a PowerGrid v7 theme using the Theme abstract class, per-section token methods (layout/header/table/footer/cols/tabs plus filter/editable/toggleable), config theme_overrides, or ArrayTheme
php artisan boost:add-skill power-components/livewire-powergrid
Save this content to: .claude/skills/powergrid-theme/SKILL.md
## What I do
- Restyle a token with **zero code** via `config('livewire-powergrid.theme_overrides')`
- Create a new theme class extending `Theme` with per-section token methods (`layout()`, `header()`, `table()`, `footer()`, `cols()`, `tabs()`, plus `filter()`, `editable()`, `toggleable()`)
- Author a data-first theme as a plain array with `ArrayTheme` (`fromArray()` / `fromFile()` or a subclass)
- Update an existing theme by adding or overriding tokens in the relevant section method
- Wire per-component overrides via `template()` + `merge()` (patch tokens)
- Register a theme by name (`PowerGridManager::registerTheme()`) and select it by name in config
- Run the theme test suite after changes
## When to use me
Use this when:
- A new UI theme needs to be added (e.g. Bootstrap, Flowbite, ShadCN)
- An existing theme's tokens need to be changed or extended
- A few token classes need to change with no theme class at all (config `theme_overrides`)
- A single PowerGrid component needs a one-off theme override
- Token keys are missing or misnamed and produce empty class output
This skill is only about **creating and updating v7 themes**. It is self-contained — everything you need is here and in `REFERENCE.md` (same folder).
## How to use me
### Example 1: Create a new theme class
```
Use the 'powergrid-theme' skill to create a new theme for Bootstrap 5.
Theme class: src/Themes/Bootstrap.php
```
### Example 2: Update tokens in an existing theme
```
Use the 'powergrid-theme' skill to update the DaisyUI theme:
- Change table.layout.thead to use 'bg-neutral text-neutral-content'
- Add a filter.boolean.select override for DaisyUI input sizing
```
### Example 3: No-code override via config
```
Use the 'powergrid-theme' skill to make table headers bolder for every table
via config('livewire-powergrid.theme_overrides'), without a Theme class.
```
### Example 4: Per-component override
```
Use the 'powergrid-theme' skill to apply a partial theme override to
MyTableComponent so that table.layout.tr gets an extra 'stripe' class.
```
---
## How it fits together (the short version)
A theme is a PHP class in `src/Themes/`. It extends `Theme` (`src/Themes/Theme.php`) and returns a token map. Blade views read tokens with the `theme()` / `theme_view()` helpers, e.g. `{{ theme('table.layout.td') }}` and `{{ theme_view('pagination') }}`.
There are **three ways to change theming**, cheapest first:
1. **No-code overrides (`config('livewire-powergrid.theme_overrides')`).** A nested token array in `resources/config/livewire-powergrid.php`, merged **last** (highest precedence) in `resolveTokens()`. Restyle any token without touching a Theme class:
```php
'theme_overrides' => [
'table' => ['layout' => ['th' => 'font-bold px-4 py-3']],
],
```
2. **Section methods on a Theme class.** Each token group is its own **public method** returning that group's slice — `layout()`, `header()`, `table()`, `footer()`, `cols()`, `tabs()`, plus `filter()`, `editable()`, `toggleable()`. `Theme::themeTokenMethods()` lists them and `resolveTokens()` auto-merges each.
3. **`ArrayTheme`** — a data-first theme authored as a plain nested array (no builder). See below.
**`struct()` is now tiny.** It only sets the base view; it does **not** carry the whole token tree any more:
```php
public function struct(): Components\ThemeBuilder
{
return Components\ThemeBuilder::make($this->name())->baseView($this->baseView());
}
protected function baseView(): string
{
return 'livewire-powergrid::components.themes.tailwind';
}
```
Everything else lives in the section methods. A **section method** can be written two equivalent ways:
- **Plain nested array** (6.x-familiar):
```php
public function footer(): array
{
return ['footer' => ['pagination' => ['item' => 'btn ...']]];
}
```
- **Fluent + type-safe via the `section()` helper** on `Theme` (it returns `['footer' => [...]]` with `view*` tokens baseView-prefixed):
```php
public function footer(): array
{
return $this->section('footer', fn (Components\Footer $f) => $f
->layout(fn (Components\Layout $l) => $l->container('...')->select('...'))
->pagination('pagination'));
}
```
The three canonical themes are the patterns to copy:
- `src/Themes/Tailwind.php` — the base theme; full section methods; `parentTheme = null`
- `src/Themes/DaisyUI.php` — a token-only subclass (`parentTheme = Tailwind::class`) that ships **zero blades** — imitate this
- `src/Themes/Flux.php` — a subclass that keeps its own `<flux:*>` blades only where the HTML genuinely differs
Inheritance is explicit: `Theme::$parentTheme` defaults to `null`. Set `protected ?string $parentTheme = Tailwind::class;` and a child overrides **only the sections it changes** — every other token, view, filter, editable, or toggleable value falls through to Tailwind. (DaisyUI and Flux both do this.)
**Prefer tokens over new blades.** DaisyUI ships no Blade files at all: `resources/views/components/themes/daisyui/` no longer exists. When a token names a view the theme does not ship, `Theme::doResolveView()` inherits the parent's blade. Only add a Blade file when the markup is genuinely different (as Flux does for its `<flux:*>` components).
**Selecting a theme.** `config('livewire-powergrid.theme')` accepts a registered **name** (`'tailwind'`, `'daisyui'`, `'flux'`) or an FQCN. Names live in `PowerGridManager::$themes` (default `DEFAULT_THEMES`); register your own with `PowerGridManager::registerTheme('bootstrap', BootstrapTheme::class)` and `PowerGridManager::resolveThemeClass()` resolves it.
Full token maps, the builder API, and the resolution order are in **`REFERENCE.md`**.
---
## Workflow
1. **Identify the goal.** A few token classes (no-code), a new/updated theme class, an array theme, or a per-component override?
2. **Read the closest real theme** — `Tailwind.php` (the full, authoritative section methods and token surface), `DaisyUI.php` (a token-only subclass), `Flux.php` (a subclass with a few of its own blades) — and, if updating, the target file. **Do not hand-write a token list from memory; diff against `Tailwind.php`.**
3. **Make the change:**
- *A few classes only* → add them to `config('livewire-powergrid.theme_overrides')` (no class needed).
- *New theme* → create `src/Themes/MyTheme.php` from the template below (tiny `struct()` + section methods).
- *Token change* → edit the relevant section method (`layout()`, `header()`, `table()`, `footer()`, `cols()`, `tabs()`, `filter()`, `editable()`, `toggleable()`).
- *Data-first theme* → use `ArrayTheme` (`fromArray()` / `fromFile()` or a subclass).
- *Per-component* → use `template()` + `merge()` (see below).
4. **Register it** (new default theme only) in `resources/config/livewire-powergrid.php`, by name or FQCN:
```php
'theme' => 'my-theme', // registered via PowerGridManager::registerTheme('my-theme', MyTheme::class)
// 'theme' => \PowerComponents\LivewirePowerGrid\Themes\MyTheme::class, // FQCN also works
```
5. **Test:**
```bash
composer test -- --filter="ThemeTest|ThemeBuilderTest|PowerGridComponentThemeTest"
composer test
```
---
## Minimal new-theme template
Copy this, rename the class, point `baseView()` at your own view folder, and fill the classes in the section methods. It inherits Tailwind's every undeclared token, view, `filter()`, `editable()`, and `toggleable()` — override a section only where your framework differs. Ship a Blade file only for markup that genuinely differs; otherwise omit `->view()` and inherit Tailwind's blade (DaisyUI ships none).
```php
<?php
namespace PowerComponents\LivewirePowerGrid\Themes;
class MyTheme extends Theme
{
// Inherit Tailwind for everything not declared below.
protected ?string $parentTheme = Tailwind::class;
// struct() only carries the base view. Every token group is its own method.
public function struct(): Components\ThemeBuilder
{
return Components\ThemeBuilder::make($this->name())->baseView($this->baseView());
}
protected function baseView(): string
{
return 'livewire-powergrid::components.themes.my-theme';
}
/** @return array<string, mixed> */
public function layout(): array
{
return $this->section('layout', fn (Components\Layout $layout) => $layout
->wrapper('space-y-4')
->card('rounded-xl border')
);
}
/** @return array<string, mixed> */
public function table(): array
{
return $this->section('table', fn (Components\Table $table) => $table
->layout(fn (Components\Layout $layout) => $layout
->container('overflow-x-auto relative border-t')
->table('min-w-full')
->thead('bg-zinc-100')
->tr('border-b')
->th('px-3 py-3 text-left text-xs')
->thActions('px-3 py-3 text-end text-xs')
->tbody('')
->td('px-3 py-2')
->tdActions('px-3 py-2 text-end')
)
->checkbox(fn (Components\Checkbox $checkbox) => $checkbox
->input('h-4 w-4')
)
->radio(fn (Components\Radio $radio) => $radio
->input('rounded-full')
)
);
}
/** @return array<string, mixed> */
public function tabs(): array
{
return $this->section('tabs', fn (Components\Tabs $tabs) => $tabs
->list('inline-flex items-center gap-1 rounded-xl border p-1')
->tab('rounded-lg px-3 py-1.5 text-sm font-medium')
->tabActive('bg-zinc-100 text-zinc-900')
->tabInactive('text-zinc-500')
->badge('rounded-full px-2 py-0.5 text-xs font-semibold')
->badgeActive('bg-blue-100 text-blue-700')
->badgeInactive('bg-zinc-100 text-zinc-600')
);
}
// header(), footer(), cols(), filter(), editable(), toggleable() are optional here —
// declare only the sections that differ from Tailwind. Read Tailwind.php for the full surface.
}
```
You can equally return plain nested arrays from any of these methods instead of using `section()`, e.g. `public function tabs(): array { return ['tabs' => ['list' => '...', 'tab' => '...']]; }`.
---
## `ArrayTheme` (data-first)
`src/Themes/ArrayTheme.php` lets you author a theme as a plain nested token array — no builder. Everything not declared falls back to the parent theme (Tailwind by default).
```php
// Ad-hoc, from an array:
$theme = \PowerComponents\LivewirePowerGrid\Themes\ArrayTheme::fromArray(
['footer' => ['pagination' => ['item' => 'btn ...']]],
parentTheme: \PowerComponents\LivewirePowerGrid\Themes\Tailwind::class,
name: 'my-theme',
);
// Or from a PHP file that `return`s the token array:
$theme = \PowerComponents\LivewirePowerGrid\Themes\ArrayTheme::fromFile(base_path('themes/my-theme.php'));
// Or as a subclass:
class MyArrayTheme extends ArrayTheme
{
protected ?string $parentTheme = Tailwind::class;
public function struct(): array
{
return ['footer' => ['pagination' => ['item' => 'btn ...']]];
}
}
```
---
## Per-component overrides
Both hooks live on your Livewire PowerGrid component (see `src/Concerns/Base.php` and `src/PowerGridComponent.php`), and are applied in `boot()` — never bind `powergrid.theme` yourself.
**Swap the whole theme class for one component** by returning it from `template()`. `customThemeClass()` is deprecated.
```php
public function template(): ?Theme
{
return new MyTheme();
}
```
**Patch a few tokens for one component** (deep merge, so pass only the keys you change):
```php
public function template(): ?Theme
{
return Tailwind::make()->merge([
'table' => [
'layout' => [
'tr' => 'stripe hover:bg-yellow-50',
],
],
]);
}
```
---
## Rules
- **`struct()` only sets `baseView`.** It returns `Components\ThemeBuilder::make($this->name())->baseView($this->baseView())` (or a plain array for `ArrayTheme`). It no longer carries the whole token tree — every token group lives in its own section method.
- **Token groups are public methods.** `layout()`, `header()`, `table()`, `footer()`, `cols()`, `tabs()`, `filter()`, `editable()`, `toggleable()` — the list in `Theme::themeTokenMethods()`. Each returns its slice (`['<group>' => [...]]`), built either with `$this->section('<group>', fn (...) => ...)` or as a plain nested array. `resolveTokens()` merges them automatically.
- **Declare `parentTheme` explicitly.** `Theme::$parentTheme` defaults to `null`; set it to `Tailwind::class` so undeclared sections/tokens inherit from the base theme. A child overrides only the sections it changes.
- **No-code overrides win.** `config('livewire-powergrid.theme_overrides')` is merged **last** in `resolveTokens()` (after `struct()`, the parent theme, the section methods, and plugin tokens), so it overrides everything.
- **Correct closure type-hints.** Inside `section()` the sub-builders are distinct classes: `->searchBox(fn (Components\SearchBox $s) => ...)`, `->checkbox(fn (Components\Checkbox $c) => ...)`, `->radio(fn (Components\Radio $r) => ...)`, `->body(fn (Components\Body $b) => $b->tr(fn (Components\Tr $tr) => ...))`, `->cols(fn (Components\Cols $c) => ...)`, `->tabs(fn (Components\Tabs $t) => ...)`. Hinting these as `Components\Component` throws a `TypeError`.
- **Pagination is a string.** `->pagination('pagination')` — `Footer::pagination()` accepts `Closure|array|string`; all three shipped themes use the string alias.
- **CSS classes live in `->layout(Closure)`** for `header`/`table`/`footer`; `tabs` sets its classes directly on `Components\Tabs`.
- **Prefer tokens over new blades.** DaisyUI ships zero blades and inherits Tailwind's markup through `parentTheme` + the view-resolution fallback in `Theme::doResolveView()`. Add a Blade file only for genuinely different HTML (as Flux does).
- **The filter drawer is styled, not rebuilt.** `filter.flyout.*` (set with `->flyout(fn (Components\Flyout $f) => ...)`) drives the drawer used when `config('livewire-powergrid.filter')` is `flyout`. Its blade is shared by every theme through `parentTheme`, so override the classes and leave `view` alone. `panel` must carry the positioning (`fixed inset-y-0`) and stay above `overlay`; `panel_left`/`panel_right` add only the edge anchoring. The `dropdown` variant (`config('...filter')` = `dropdown`) works the same way.
- **`tabs` is a theme-aware token group.** The `tabs()` section sets `list`/`tab`/`tabActive`/`tabInactive`/`badge`/`badgeActive`/`badgeInactive` and (optionally) `view`. The shared base blade `resources/views/components/themes/tailwind/tabs.blade.php` reads these tokens and renders tab icons via `IconRenderer`. Point `tabs.view` at your own blade only when the markup differs (Flux points it at `powergrid-plugins::Tabs.themes.flux`).
- **`toggleable()` fills color tokens, not a view.** It sets `colorOn`/`colorOff`/`colorOnDark`/`colorOffDark`/`knobOn`; the shipped Toggleable blade reads them via `theme('toggleable.color_on')` etc. Do not give it a `->view()`.
- **`editable()` is classes only.** Do not set `editable.view`. The plugin blade is `powergrid-plugins::Editable.index`; keep `clickable` / `input` / `error`.
- **`filter.multi_select` has no view token.** Markup is `<x-livewire-powergrid::inputs.select>`; keep `base` / `select` classes.
- **Feature views are auto-resolved.** export, toggle-columns, soft-deletes, etc. resolve via `baseView + alias` (then the `parentTheme` chain, then `components.structure.*`). Do not declare them.
- **View aliases**: a value without `::` is prefixed with `baseView + '.'`. Use a fully-qualified `livewire-powergrid::...` path only to point at another theme's view.
---
## Completion checklist
- [ ] `struct()` returns `ThemeBuilder::make(...)->baseView(...)` only (or an array for `ArrayTheme`)
- [ ] `protected ?string $parentTheme = Tailwind::class;` is declared
- [ ] `baseView()` points at a real folder under `resources/views/components/themes/`
- [ ] Only the sections that differ from Tailwind are overridden, each as its own method (`layout`/`header`/`table`/`footer`/`cols`/`tabs`/`filter`/`editable`/`toggleable`)
- [ ] Sub-builder closures use the correct classes (`SearchBox`/`Checkbox`/`Radio`/`Body`/`Tr`/`Cols`/`Tabs`), never `Components\Component`
- [ ] `->pagination('pagination')` (string form)
- [ ] `editable()` has no `->view()`; `toggleable()` is color `fill()` only; `filter.multi_select` has no view token
- [ ] A Blade file is added only where the HTML genuinely differs; otherwise the parent's view is inherited
- [ ] Theme registered/selected in config by name or FQCN (new default only)
- [ ] `composer test` passes
See **`REFERENCE.md`** for the full section-method token map, the `filter()`/`editable()`/`toggleable()`/`tabs()` shapes, the complete builder API, dot-notation token keys, config `theme_overrides`, the theme registry, and the token/view resolution order.
config('livewire-powergrid.theme_overrides')Theme with per-section token methods (layout(), header(), table(), footer(), cols(), tabs(), plus filter(), editable(), toggleable())ArrayTheme (fromArray() / fromFile() or a subclass)template() + merge() (patch tokens)PowerGridManager::registerTheme()) and select it by name in configUse this when:
theme_overrides)This skill is only about creating and updating v7 themes. It is self-contained — everything you need is here and in REFERENCE.md (same folder).
Use the 'powergrid-theme' skill to create a new theme for Bootstrap 5.
Theme class: src/Themes/Bootstrap.php
Use the 'powergrid-theme' skill to update the DaisyUI theme:
- Change table.layout.thead to use 'bg-neutral text-neutral-content'
- Add a filter.boolean.select override for DaisyUI input sizing
Use the 'powergrid-theme' skill to make table headers bolder for every table
via config('livewire-powergrid.theme_overrides'), without a Theme class.
Use the 'powergrid-theme' skill to apply a partial theme override to
MyTableComponent so that table.layout.tr gets an extra 'stripe' class.
A theme is a PHP class in src/Themes/. It extends Theme (src/Themes/Theme.php) and returns a token map. Blade views read tokens with the theme() / theme_view() helpers, e.g. {{ theme('table.layout.td') }} and {{ theme_view('pagination') }}.
There are three ways to change theming, cheapest first:
config('livewire-powergrid.theme_overrides')). A nested token array in resources/config/livewire-powergrid.php, merged last (highest precedence) in resolveTokens(). Restyle any token without touching a Theme class:
'theme_overrides' => [
'table' => ['layout' => ['th' => 'font-bold px-4 py-3']],
],
layout(), header(), table(), footer(), cols(), tabs(), plus filter(), editable(), toggleable(). Theme::themeTokenMethods() lists them and resolveTokens() auto-merges each.ArrayTheme — a data-first theme authored as a plain nested array (no builder). See below.struct() is now tiny. It only sets the base view; it does not carry the whole token tree any more:
public function struct(): Components\ThemeBuilder
{
return Components\ThemeBuilder::make($this->name())->baseView($this->baseView());
}
protected function baseView(): string
{
return 'livewire-powergrid::components.themes.tailwind';
}
Everything else lives in the section methods. A section method can be written two equivalent ways:
public function footer(): array
{
return ['footer' => ['pagination' => ['item' => 'btn ...']]];
}
section() helper on Theme (it returns ['footer' => [...]] with view* tokens baseView-prefixed):
public function footer(): array
{
return $this->section('footer', fn (Components\Footer $f) => $f
->layout(fn (Components\Layout $l) => $l->container('...')->select('...'))
->pagination('pagination'));
}
The three canonical themes are the patterns to copy:
src/Themes/Tailwind.php — the base theme; full section methods; parentTheme = nullsrc/Themes/DaisyUI.php — a token-only subclass (parentTheme = Tailwind::class) that ships zero blades — imitate thissrc/Themes/Flux.php — a subclass that keeps its own <flux:*> blades only where the HTML genuinely differsInheritance is explicit: Theme::$parentTheme defaults to null. Set protected ?string $parentTheme = Tailwind::class; and a child overrides only the sections it changes — every other token, view, filter, editable, or toggleable value falls through to Tailwind. (DaisyUI and Flux both do this.)
Prefer tokens over new blades. DaisyUI ships no Blade files at all: resources/views/components/themes/daisyui/ no longer exists. When a token names a view the theme does not ship, Theme::doResolveView() inherits the parent's blade. Only add a Blade file when the markup is genuinely different (as Flux does for its <flux:*> components).
Selecting a theme. config('livewire-powergrid.theme') accepts a registered name ('tailwind', 'daisyui', 'flux') or an FQCN. Names live in PowerGridManager::$themes (default DEFAULT_THEMES); register your own with PowerGridManager::registerTheme('bootstrap', BootstrapTheme::class) and PowerGridManager::resolveThemeClass() resolves it.
Full token maps, the builder API, and the resolution order are in REFERENCE.md.
Tailwind.php (the full, authoritative section methods and token surface), DaisyUI.php (a token-only subclass), Flux.php (a subclass with a few of its own blades) — and, if updating, the target file. Do not hand-write a token list from memory; diff against Tailwind.php.config('livewire-powergrid.theme_overrides') (no class needed).src/Themes/MyTheme.php from the template below (tiny struct() + section methods).layout(), header(), table(), footer(), cols(), tabs(), filter(), editable(), toggleable()).ArrayTheme (fromArray() / fromFile() or a subclass).template() + merge() (see below).resources/config/livewire-powergrid.php, by name or FQCN:
'theme' => 'my-theme', // registered via PowerGridManager::registerTheme('my-theme', MyTheme::class)
// 'theme' => \PowerComponents\LivewirePowerGrid\Themes\MyTheme::class, // FQCN also works
composer test -- --filter="ThemeTest|ThemeBuilderTest|PowerGridComponentThemeTest"
composer test
Copy this, rename the class, point baseView() at your own view folder, and fill the classes in the section methods. It inherits Tailwind's every undeclared token, view, filter(), editable(), and toggleable() — override a section only where your framework differs. Ship a Blade file only for markup that genuinely differs; otherwise omit ->view() and inherit Tailwind's blade (DaisyUI ships none).
<?php
namespace PowerComponents\LivewirePowerGrid\Themes;
class MyTheme extends Theme
{
// Inherit Tailwind for everything not declared below.
protected ?string $parentTheme = Tailwind::class;
// struct() only carries the base view. Every token group is its own method.
public function struct(): Components\ThemeBuilder
{
return Components\ThemeBuilder::make($this->name())->baseView($this->baseView());
}
protected function baseView(): string
{
return 'livewire-powergrid::components.themes.my-theme';
}
/** @return array<string, mixed> */
public function layout(): array
{
return $this->section('layout', fn (Components\Layout $layout) => $layout
->wrapper('space-y-4')
->card('rounded-xl border')
);
}
/** @return array<string, mixed> */
public function table(): array
{
return $this->section('table', fn (Components\Table $table) => $table
->layout(fn (Components\Layout $layout) => $layout
->container('overflow-x-auto relative border-t')
->table('min-w-full')
->thead('bg-zinc-100')
->tr('border-b')
->th('px-3 py-3 text-left text-xs')
->thActions('px-3 py-3 text-end text-xs')
->tbody('')
->td('px-3 py-2')
->tdActions('px-3 py-2 text-end')
)
->checkbox(fn (Components\Checkbox $checkbox) => $checkbox
->input('h-4 w-4')
)
->radio(fn (Components\Radio $radio) => $radio
->input('rounded-full')
)
);
}
/** @return array<string, mixed> */
public function tabs(): array
{
return $this->section('tabs', fn (Components\Tabs $tabs) => $tabs
->list('inline-flex items-center gap-1 rounded-xl border p-1')
->tab('rounded-lg px-3 py-1.5 text-sm font-medium')
->tabActive('bg-zinc-100 text-zinc-900')
->tabInactive('text-zinc-500')
->badge('rounded-full px-2 py-0.5 text-xs font-semibold')
->badgeActive('bg-blue-100 text-blue-700')
->badgeInactive('bg-zinc-100 text-zinc-600')
);
}
// header(), footer(), cols(), filter(), editable(), toggleable() are optional here —
// declare only the sections that differ from Tailwind. Read Tailwind.php for the full surface.
}
You can equally return plain nested arrays from any of these methods instead of using section(), e.g. public function tabs(): array { return ['tabs' => ['list' => '...', 'tab' => '...']]; }.
ArrayTheme (data-first)src/Themes/ArrayTheme.php lets you author a theme as a plain nested token array — no builder. Everything not declared falls back to the parent theme (Tailwind by default).
// Ad-hoc, from an array:
$theme = \PowerComponents\LivewirePowerGrid\Themes\ArrayTheme::fromArray(
['footer' => ['pagination' => ['item' => 'btn ...']]],
parentTheme: \PowerComponents\LivewirePowerGrid\Themes\Tailwind::class,
name: 'my-theme',
);
// Or from a PHP file that `return`s the token array:
$theme = \PowerComponents\LivewirePowerGrid\Themes\ArrayTheme::fromFile(base_path('themes/my-theme.php'));
// Or as a subclass:
class MyArrayTheme extends ArrayTheme
{
protected ?string $parentTheme = Tailwind::class;
public function struct(): array
{
return ['footer' => ['pagination' => ['item' => 'btn ...']]];
}
}
Both hooks live on your Livewire PowerGrid component (see src/Concerns/Base.php and src/PowerGridComponent.php), and are applied in boot() — never bind powergrid.theme yourself.
Swap the whole theme class for one component by returning it from template(). customThemeClass() is deprecated.
public function template(): ?Theme
{
return new MyTheme();
}
Patch a few tokens for one component (deep merge, so pass only the keys you change):
public function template(): ?Theme
{
return Tailwind::make()->merge([
'table' => [
'layout' => [
'tr' => 'stripe hover:bg-yellow-50',
],
],
]);
}
struct() only sets baseView. It returns Components\ThemeBuilder::make($this->name())->baseView($this->baseView()) (or a plain array for ArrayTheme). It no longer carries the whole token tree — every token group lives in its own section method.layout(), header(), table(), footer(), cols(), tabs(), filter(), editable(), toggleable() — the list in Theme::themeTokenMethods(). Each returns its slice (['<group>' => [...]]), built either with $this->section('<group>', fn (...) => ...) or as a plain nested array. resolveTokens() merges them automatically.parentTheme explicitly. Theme::$parentTheme defaults to null; set it to Tailwind::class so undeclared sections/tokens inherit from the base theme. A child overrides only the sections it changes.config('livewire-powergrid.theme_overrides') is merged last in resolveTokens() (after struct(), the parent theme, the section methods, and plugin tokens), so it overrides everything.section() the sub-builders are distinct classes: ->searchBox(fn (Components\SearchBox $s) => ...), ->checkbox(fn (Components\Checkbox $c) => ...), ->radio(fn (Components\Radio $r) => ...), ->body(fn (Components\Body $b) => $b->tr(fn (Components\Tr $tr) => ...)), ->cols(fn (Components\Cols $c) => ...), ->tabs(fn (Components\Tabs $t) => ...). Hinting these as Components\Component throws a TypeError.->pagination('pagination') — Footer::pagination() accepts Closure|array|string; all three shipped themes use the string alias.->layout(Closure) for header/table/footer; tabs sets its classes directly on Components\Tabs.parentTheme + the view-resolution fallback in Theme::doResolveView(). Add a Blade file only for genuinely different HTML (as Flux does).filter.flyout.* (set with ->flyout(fn (Components\Flyout $f) => ...)) drives the drawer used when config('livewire-powergrid.filter') is flyout. Its blade is shared by every theme through parentTheme, so override the classes and leave view alone. panel must carry the positioning (fixed inset-y-0) and stay above overlay; panel_left/panel_right add only the edge anchoring. The dropdown variant (config('...filter') = dropdown) works the same way.tabs is a theme-aware token group. The tabs() section sets list/tab/tabActive/tabInactive/badge/badgeActive/badgeInactive and (optionally) view. The shared base blade resources/views/components/themes/tailwind/tabs.blade.php reads these tokens and renders tab icons via IconRenderer. Point tabs.view at your own blade only when the markup differs (Flux points it at powergrid-plugins::Tabs.themes.flux).toggleable() fills color tokens, not a view. It sets colorOn/colorOff/colorOnDark/colorOffDark/knobOn; the shipped Toggleable blade reads them via theme('toggleable.color_on') etc. Do not give it a ->view().editable() is classes only. Do not set editable.view. The plugin blade is powergrid-plugins::Editable.index; keep clickable / input / error.filter.multi_select has no view token. Markup is <x-livewire-powergrid::inputs.select>; keep base / select classes.baseView + alias (then the parentTheme chain, then components.structure.*). Do not declare them.:: is prefixed with baseView + '.'. Use a fully-qualified livewire-powergrid::... path only to point at another theme's view.struct() returns ThemeBuilder::make(...)->baseView(...) only (or an array for ArrayTheme)protected ?string $parentTheme = Tailwind::class; is declaredbaseView() points at a real folder under resources/views/components/themes/layout/header/table/footer/cols/tabs/filter/editable/toggleable)SearchBox/Checkbox/Radio/Body/Tr/Cols/Tabs), never Components\Component->pagination('pagination') (string form)editable() has no ->view(); toggleable() is color fill() only; filter.multi_select has no view tokencomposer test passesSee REFERENCE.md for the full section-method token map, the filter()/editable()/toggleable()/tabs() shapes, the complete builder API, dot-notation token keys, config theme_overrides, the theme registry, and the token/view resolution order.
How can I help you explore Laravel packages today?