Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
powergrid-theme
power-components/livewire-powergrid
claude-code
powergrid
themes
theme-tokens
blade
config-overrides
per-component-overrides

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

Install
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.

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:
    '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:

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):
    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):
    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:
    'theme' => 'my-theme', // registered via PowerGridManager::registerTheme('my-theme', MyTheme::class)
    // 'theme' => \PowerComponents\LivewirePowerGrid\Themes\MyTheme::class, // FQCN also works
    
  5. Test:
    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

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 ...']]];
    }
}

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.

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',
            ],
        ],
    ]);
}

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.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json