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().
Learn More
That covers the basics. Once you're comfortable, you can explore passing extra HTML attributes, dependency injection, PreMount hooks, and anonymous components.