
# Custom Plugins

A custom plugin is a PHP class implementing `TailwindPHP\Plugin\PluginInterface`. It receives a `PluginAPI` instance and uses it to register static and functional utilities, components, and variants. Once registered with `registerPlugin()`, the plugin resolves by name in a `@plugin` directive — the same mechanism the built-in typography and forms plugins use.

## `PluginInterface`

Implement three methods:

```php
namespace TailwindPHP\Plugin;

interface PluginInterface
{
    public function getName(): string;
    public function __invoke(PluginAPI $api, array $options = []): void;
    public function getThemeExtensions(array $options = []): array;
}
```

- `getName()` returns the identifier used in `@plugin "..."`.
- `__invoke()` runs the plugin, registering everything through `$api`. The `$options` array carries the values from the `@plugin` CSS block.
- `getThemeExtensions()` returns theme namespaces to merge before utilities are generated (return `[]` if none).

## Example

```php
use TailwindPHP\Plugin\PluginInterface;
use TailwindPHP\Plugin\PluginAPI;

class MyCustomPlugin implements PluginInterface
{
    public function getName(): string
    {
        return 'my-custom-plugin';
    }

    public function __invoke(PluginAPI $api, array $options = []): void
    {
        // Static utilities
        $api->addUtilities([
            '.btn' => [
                'padding' => '0.5rem 1rem',
                'border-radius' => '0.25rem',
                'font-weight' => '600',
            ],
            '.btn-primary' => [
                'background-color' => 'blue',
                'color' => 'white',
            ],
        ]);

        // Functional utilities (generates .gutter-sm, .gutter-md, .gutter-lg)
        $api->matchUtilities(
            [
                'gutter' => fn ($value) => ['padding-inline' => $value],
            ],
            ['values' => ['sm' => '1rem', 'md' => '2rem', 'lg' => '3rem']]
        );

        // Component classes (sorted into the components layer)
        $api->addComponents([
            '.card' => [
                'background-color' => 'white',
                'border-radius' => '0.5rem',
                'padding' => '1rem',
            ],
        ]);

        // Custom variant (see the note below the API reference)
        $api->addVariant('hocus', '&:hover, &:focus');

        // Read a theme value
        $primary = $api->theme('colors.blue.500', '#3b82f6');
    }

    public function getThemeExtensions(array $options = []): array
    {
        return []; // Return theme additions if needed
    }
}
```

## `PluginAPI` reference

The API mirrors the TailwindCSS plugin API:

| Method | Signature | Purpose |
|--------|-----------|---------|
| `addBase` | `addBase(array $css): void` | Add base-layer styles. |
| `addUtilities` | `addUtilities(array $utilities, array $options = []): void` | Register static utility classes keyed by selector. |
| `matchUtilities` | `matchUtilities(array $utilities, array $options = []): void` | Register functional utilities; `$options['values']` supplies the value map, `$options['supportsNegativeValues']` enables negatives. |
| `addComponents` | `addComponents(array $components, array $options = []): void` | Register component classes (components layer). |
| `matchComponents` | `matchComponents(array $components, array $options = []): void` | Register functional components with a value map. |
| `addVariant` | `addVariant(string $name, string\|array $variant): void` | Register a static variant from a selector. |
| `matchVariant` | `matchVariant(string $name, callable $callback, array $options = []): void` | Register a functional variant; `$options['values']` supplies the value map. |
| `theme` | `theme(string $path, mixed $defaultValue = null): mixed` | Read a theme value by dot path (e.g. `colors.blue.500`). |
| `config` | `config(?string $path = null, mixed $defaultValue = null): mixed` | Read a config value by dot path, or the whole config when `$path` is null. |
| `prefix` | `prefix(string $className): string` | Apply the configured prefix to a class name. |

The callback passed to `matchUtilities`, `matchComponents`, and `matchVariant` receives the value and a second argument array containing a `modifier` key.

Pick utility names that core Tailwind does not already use. When a plugin utility shares a name with a core one (for example `tab-*`, which core Tailwind uses for `tab-size` since v4.3), both declarations land in the same rule and the core one comes last, so it wins: a plugin `tab-4` mapped to `7` still produces `tab-size: 4`.

> **Known issue:** variants registered with `addVariant()`, and the per-value variants `matchVariant()` registers, are not applied in the current release. `hocus:flex` from the example above compiles to `.hocus\:flex { display: flex; }` with no `:hover`/`:focus` selector ([#10](https://github.com/inline0/tailwindphp/issues/10)). Until that is fixed, define the variant in CSS with [`@custom-variant`](/docs/usage/directives#custom-variant), which works: `@custom-variant hocus (&:hover, &:focus);`.

## Registering and using

Register the plugin instance, then reference it by the name from `getName()`:

```php
use TailwindPHP\tw;
use function TailwindPHP\registerPlugin;

registerPlugin(new MyCustomPlugin());

$css = tw::generate(
    '<div class="btn btn-primary card gutter-md">…</div>',
    '@plugin "my-custom-plugin"; @import "tailwindcss/utilities.css";'
);
```

See [Plugins](/docs/plugins) for the `@plugin` directive, and [Theme](/docs/usage/theme) for the values exposed through `$api->theme()`.
