redant/twig-components
Redant/Twig Components brings Laravel-style component architecture to Twig, making it easy to build reusable, self-contained UI components with slots, attributes, and view models. Keep templates organized and markup DRY across your PHP app.
Laravel ships with Blade, not Twig, so the first decision is whether Twig is already in the stack. This package is a framework-agnostic Twig extension; in Laravel you wire it through rcrowe/twigbridge. If the project is Blade-only, this package is not a drop-in — budget for a Twig view layer or use it only for isolated fragments rendered from PHP.
Minimal setup:
composer require redant/twig-components
composer require rcrowe/twigbridge # only if Twig is not already installed
php artisan vendor:publish --provider="TwigBridge\ServiceProvider"
Register the extension in config/twigbridge.php:
'extensions' => [
'enabled' => [
// ...
RedAnt\TwigComponents\Twig\ComponentExtension::class,
],
],
For a plain Twig environment (scripts, tests, non-Laravel apps), add it directly:
$twig->addExtension(new RedAnt\TwigComponents\Twig\ComponentExtension(/* renderer deps */));
First component: create a class under app/Twig/Components, extend Component, point it at a template, and declare props as public typed properties with defaults.
namespace App\Twig\Components;
use RedAnt\TwigComponents\Component;
class Alert extends Component
{
protected string $template = 'components/alert.html.twig';
public string $type = 'info';
public string $message = '';
public bool $dismissible = false;
}
Template in resources/views/components/alert.html.twig:
<div class="alert alert-{{ type }}" role="alert">
{{ message }}
{% if dismissible %}<button type="button" class="close">×</button>{% endif %}
{{ slot }}
</div>
Use it from a Twig view:
{% component 'alert' with { type: 'warning', message: 'Disk almost full' } %}{% endcomponent %}
First use case to target: a small design-system set — button, alert, card, badge — rendered from both Twig templates and PHP (mailables, PDFs, JSON fragments). That exercises props, slots, and the renderer early.
Where to look first in the repo: the Component base Class, ComponentRenderer, the Twig extension/token parser, and the README's exact tag/function syntax. Confirm names against the version you install; the last release is 2024-03-18 and the surface is small enough to read in one sitting.
Treat components as typed view models. Public typed props are the contract. Give every prop a safe default (or make it nullable) and keep validation/normalization in the constructor or getter methods so templates stay declarative.
public function __construct()
{
$this->type = in_array($this->type, ['info', 'warning', 'danger'], true)
? $this->type
: 'info';
}
Variants over class explosion. One Button with variant, size, and disabled props beats PrimaryButton, LargeButton, etc. Reserve subclasses for structural differences (e.g. Modal vs Drawer).
Slots for composition. The tag body is the natural default slot; named slots handle headers/footers. Decide deliberately whether slot content is escaped or trusted markup, and document it on the component class. A card that accepts arbitrary HTML needs a different escaping policy than a button label.
Compose inside Twig. Let page templates assemble components, and let higher-level components render lower-level ones. Keep leaf components dumb (no DB/service calls). If a component needs data, pass it in as props from the controller or a view composer.
Render from PHP when Twig is not the whole app. Inject ComponentRenderer (or resolve it from Laravel's container) to render the same component for mailables, queued jobs, PDF generation, or API error fragments. This is the main day-to-day leverage in a Laravel codebase that is otherwise Blade/JSON.
$html = app(ComponentRenderer::class)->render('alert', [
'type' => 'danger',
'message' => 'Payment failed',
]);
Mirror namespaces and template paths. Keep app/Twig/Components aligned with resources/views/components; if the package resolves templates by convention, a mismatched class/file name is the most common failure. Override the template property explicitly when in doubt.
Test at the renderer boundary. Boot a Twig environment with the extension in a PHPUnit test, render each component with representative props, and assert on HTML. Snapshot tests catch accidental markup changes across a design system.
Keep a component gallery route. A single /dev/components route that renders every component with variants is cheap and pays for itself immediately — there is no Storybook-style tooling in this ecosystem.
twig.extensions after boot; a silent config typo means the component tag is simply unknown.public string $message; with no default raises a PHP Error when the component is rendered without that prop. Default everything, or use nullable types and guard in the template.php artisan view:clear is the first debugging step when a new component "doesn't exist."|raw too eagerly, XSS. Pick a policy per component and enforce it in review.content, attributes, loop, and names of registered functions/filters unless you intend the shadowing..html.twig suffix must line up. Set $template explicitly for anything non-trivial.composer.json, read the source before relying on undocumented behavior, and check PHP/Twig version constraints against your app. Do not assume Symfony-specific docs apply unchanged to TwigBridge.Component, swap or decorate ComponentRenderer, and add your own Twig functions via the extension. Prefer these over forking; the codebase is small enough that a fork will drift.dump(). Wrap renderer calls in try/catch during development so missing-prop errors surface with the component name rather than a generic Twig runtime error.How can I help you explore Laravel packages today?