In previous topics, we explored JavaScript Stimulus components. Now we'll learn server-side Twig Components.
Twig Components let you create reusable UI pieces by pairing a PHP class with a Twig template. Think of them as custom HTML tags for your application.
Why Use Twig Components?
- Reusability — Define once, use everywhere. No more copying the same HTML structure across templates.
- Encapsulation — Logic lives in the PHP class, presentation in the template. Each component is self-contained.
- Type safety — Public properties can be typed, giving you IDE autocompletion and catching errors early.
- Cleaner templates —
{{ component('Alert', { type: 'error', message: 'Failed' }) }}is more readable. - Refactoring confidence — Change a component's internals without hunting through every template that uses it.
Installation
Open your project, click Tools -> Composer packages, add the package:
symfony/ux-twig-component.
Generate scripts, then install the recipe manually by Command Prompt or PowerShell:
php bin/console app:recipes:install symfony/ux-twig-component
The recipe does minimal setup:
- Registers the bundle in
config/bundles.php - Creates the configuration file
config/packages/twig_component.yamlwhich typically maps a namespace to your components directory, i.e.templates/components/by default.
Your First Component
Every component has two parts: a PHP class that holds the data, and a Twig template that renders it.
The PHP class:
Create a Custom File with:
- File Name -
Alert.php - Include Common Files - DISABLE IT. It is not applicable.
- Caption - NOT applicable.
- Path - Set the path as
src/Twig/Components. - Content - Enter your whole class, note that you should use the special placeholder
{ProjectNamespace}as your namespace.
<?php
// src/Twig/Components/Alert.php
namespace {ProjectNamespace}\Twig\Components;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent]
class Alert
{
public string $type = 'info';
public string $message;
}
The template:
Create a Custom File with:
- File Name -
Alert.html.twig - Include Common Files - DISABLE IT. It is not applicable.
- Caption - NOT applicable.
- Path - Set the path as
templates/components. - Content - Enter your template for the component, e.g.
{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ type }}">
{{ message }}
</div>
Using it:
{{ component('Alert', { type: 'success', message: 'Saved!' }) }}
{{ component('Alert', { type: 'danger', message: 'Something went wrong.' }) }}
Public properties on the class become attributes you pass when using the component.
Adding Content
To include HTML content in a component, pass it as a property:
<?php
// src/Twig/Components/Card.php
namespace {ProjectNamespace}\Twig\Components;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent]
class Card
{
public string $title;
public string $content = '';
}
{# templates/components/Card.html.twig #}
<div class="card">
<div class="card-header">{{ title }}</div>
<div class="card-body">
{{ content|raw }}
</div>
</div>
Note: |raw tells Twig to output the content without escaping.
Using it:
{{ component('Card', { title: 'Welcome', content: '<p>Any HTML goes here.</p>' }) }}
Computed Properties
Add methods to your class and call them from the template:
<?php
namespace {ProjectNamespace}\Twig\Components;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent]
class UserBadge
{
public string $firstName;
public string $lastName;
public function getFullName(): string
{
return $this->firstName . ' ' . $this->lastName;
}
}
<span class="badge">{{ this.fullName }}</span>
Access computed properties through this.fullName, which calls getFullName().
Syntax Limitations
PHPMaker supports the function syntax only:
{{ component('Name', { prop: 'value' }) }}
The <twig:Name ...> and {% component 'Name' ... %}{% endcomponent %} syntaxes are not supported:
Learn More
That covers the basics. Once you're comfortable, you can explore passing extra HTML attributes, dependency injection, PreMount hooks, and anonymous components.