LaravelPackages.net
Acme Inc.
Toggle sidebar
ttbooking/twig-component

Class components and slots for Twig: {% component %} and {% slot %} tags, auto-discovery registry with a manifest cache. Framework-free core + optional Laravel integration.

52
0
0.5.0
About ttbooking/twig-component

ttbooking/twig-component is a Laravel package for class components and slots for twig: {% component %} and {% slot %} tags, auto-discovery registry with a manifest cache. framework-free core + optional laravel integration.. It currently has 0 GitHub stars and 52 downloads on Packagist (latest version 0.5.0). Install it with composer require ttbooking/twig-component. Discover more Laravel packages by ttbooking or browse all Laravel packages to compare alternatives.

Last updated

TTBooking Twig Component

Tests Latest Stable Version Total Downloads PHP Version License

Class components and slots for Twig: the {% component %} tag, the component() function, Vue 3 style {% slot %} slots and an auto-discovery component registry with a manifest cache.

The core is framework-neutral — it runs on bare Twig in any PHP application. The Laravel integration (DI through the container, rendering via view(), artisan commands) is an optional layer; integrating with another framework means implementing two small interfaces.

Features

  • Components as classes. Widgets (your own class with logic + DI) and — in Laravel — presentational components on spatie/laravel-data (props only).
  • Tag and function. {% component 'name' with {...} %}…{% endcomponent %} and inline component('name', {...}).
  • Vue 3 style slots. A default slot (the tag body) + named {% slot 'name' %} with a <slot> fallback in the component template. The slot body executes in the caller's scope.
  • Auto-discovery. Components are found by namespace/path; the name is derived from the class by convention (App\View\TwigComponents\UI\Box → ui:box). The manifest is cached for production.
  • Integration points. ComponentFactory (how to instantiate a component) and TemplateRenderer (how to render a template) — nothing else touches the framework.

Requirements

  • PHP ≥ 8.3
  • Twig ≥ 3.21

Optional (Laravel integration): Laravel ^13, rcrowe/twigbridge ^0.14, spatie/laravel-data ^4 for Data components.

Installation

composer require ttbooking/twig-component

Without a framework (bare Twig)

use TTBooking\TwigComponent\{ComponentExtension, ComponentRegistry, NativeComponentFactory, TwigTemplateRenderer};
use Twig\Environment;
use Twig\Loader\FilesystemLoader;

$registry = new ComponentRegistry(
    namespace: 'App\\View\\TwigComponents\\',
    componentsPath: __DIR__.'/src/View/TwigComponents',
    manifestPath: __DIR__.'/var/cache/twig-component.php',
);

$twig = new Environment(new FilesystemLoader(__DIR__.'/templates'));
$twig->addExtension(new ComponentExtension(
    $registry,
    new NativeComponentFactory($psr11Container), // container is optional: without it — props-only constructors
    new TwigTemplateRenderer($twig),             // also accepts a closure fn (): Environment
));

NativeComponentFactory instantiates widgets via new with named arguments; if any PSR-11 container is provided, constructor parameters without a default are autowired from it by type. Warm the manifest during deployment: $registry->cache().

Laravel

The ServiceProvider is registered via package discovery. Publish the config and enable the Twig extension in config/twigbridge.php:

php artisan vendor:publish --tag=twig-component-config
// config/twigbridge.php
'extensions' => [
    'enabled' => [
        // …
        TTBooking\TwigComponent\ComponentExtension::class,
    ],
],

The provider binds the Laravel implementations itself: widgets are built by the container (app($class, $props)), templates render through view(), Data components are available.

A component can also be rendered directly from PHP — handy in tests of application components; no Twig environment is needed for that:

$html = app(ComponentExtension::class)->renderComponent('ui:box', ['title' => 'Orders']);

Another framework

Implement ComponentFactory (instantiating a component from props using your DI) and, if needed, TemplateRenderer (when rendering must not go directly through Twig) — then assemble ComponentExtension as in the example above.

Configuration (Laravel)

config/twig-component.php — where to look for components and where to write the manifest:

return [
    'namespace' => 'App\\View\\TwigComponents\\',
    'path'      => app_path('View/TwigComponents'),
    'manifest'  => base_path('bootstrap/cache/twig-component.php'),
];

Quick start

A widget component:

namespace App\View\TwigComponents\UI;

use TTBooking\TwigComponent\TwigComponent;

class Box implements TwigComponent
{
    public function __construct(
        public string $title = '',
        public bool $collapsible = false,
    ) {}

    public function template(): string
    {
        // a name the active renderer understands:
        // standalone — a Twig template path: return 'components/ui/box.html.twig';
        // Laravel — a view name (view()->name() enables IDE navigation):
        return view('components/ui/box')->name();
    }

    public function context(): array
    {
        return [];
    }
}

The components/ui/box.html.twig template (this is the component instance, slots are the passed slots):

<div class="box">
    <div class="box-header">
        {% slot 'header' %}<h3>{{ this.title }}</h3>{% endslot %}
    </div>
    <div class="box-body">{% slot %}{% endslot %}</div>
</div>

Usage at the call site:

{% component 'ui:box' with { title: 'Orders' } %}
    <p>The body content is the default slot.</p>
    {% slot 'header' %}<h3>A custom heading instead of the fallback</h3>{% endslot %}
{% endcomponent %}

A presentational component (data only, Laravel integration) extends Spatie\LaravelData\Data; props are available in the template directly. Outside Laravel this kind is unavailable — a limitation of laravel-data itself; use widgets instead.

Manifest cache

The component registry is cached into a flat manifest. In Laravel (wired into optimize):

php artisan twig-component:cache   # build (part of php artisan optimize)
php artisan twig-component:clear   # remove (part of optimize:clear)

Without a framework — $registry->cache() / $registry->clearCache() in your deploy script.

How slots work

A component renders in a separate Twig pass, so the slot body is captured into a string and injected into the component template rather than being bound with native {% embed %}. Consequences: slots render eagerly, there are no scoped slots.

Rules (modeled on Vue 3):

  • A top-level {% slot 'name' %} inside {% component %} passes a named slot (like <template #name>); all remaining body content is the default slot content.
  • The default slot can also be passed explicitly: {% slot %}…{% endslot %} without a name (like <template #default>). Combining an explicit default slot with loose content is a template compilation error; passing the same slot twice is one too.
  • A body of nothing but whitespace/newlines (formatting around {% slot %}) does not count as the default slot — the {% slot %} fallback in the component template is preserved.
  • A {% slot %} deeper than the top level (e.g. inside {% if %}) is not slot passing but a "hole with a fallback" in the current template's scope. In a component template this lets you forward your slots into a nested component; on a regular page there is no slots and such a tag simply renders its fallback in place.
  • The this and slots keys of the render context are reserved: a context() key (or a Data component prop — Data props become the context) with such a name raises an error instead of a silent override.

Tests

composer install
vendor/bin/phpunit                    # both suites
vendor/bin/phpunit --testsuite Core   # the core on bare Twig, no framework
vendor/bin/phpunit --testsuite Laravel # the integration layer on Orchestra Testbench

No database required.

Documentation

Detailed guides live in the docs/ folder (Russian translation: docs/ru/):

  • Getting started — installation, standalone bootstrap, your first component.
  • Components — widgets, props and this, context(), DI, the component() function, the naming convention.
  • Slots — default and named slots, passing rules, forwarding into a nested component.
  • Laravel — ServiceProvider, config, Data components, artisan commands, rendering in tests.
  • Recipes — ready-made examples: a box with a slot, a modal with named slots, a select with logic in context().

License

MIT.

Comments