symfony/ux-twig-component
Twig Components let you bind a PHP object to a Twig template, turning small reusable units like alerts, modals, or sidebars into clean, self-contained components you can render anywhere in your Symfony app.
Install the package:
composer require symfony/ux-twig-component
Ensure your composer.json meets the requirements: Symfony 7.4+ and PHP 8.4+.
Enable the Twig bundle in config/bundles.php:
return [
// ...
Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true],
];
Create your first component:
// src/Component/AlertComponent.php
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent('alert')]
class AlertComponent {
public string $message = 'Default message';
public string $type = 'info';
}
Create the template at templates/components/alert.html.twig:
{# templates/components/alert.html.twig #}
<div class="alert alert-{{ type }}">
{{ message }}
</div>
Use the component in a template:
{% component('alert', {
message: 'Hello, Twig Components!',
type: 'success'
}) %}
Or with the HTML syntax:
<twig:alert message="Hello, Twig Components!" type="success" />
Replace hardcoded alerts in your app with a reusable AlertComponent. Define variants (e.g., success, error) via the $type prop and dynamically pass messages. This reduces template duplication and centralizes styling/logic.
#[AsTwigComponent] attribute. No manual service registration is needed (Symfony 5.3+ autoconfiguration).name argument:
#[AsTwigComponent(name: 'custom_alert')]
class AlertComponent { ... }
Public Properties as Props: Expose public properties directly in templates (e.g., {{ message }} instead of {{ this.message }}).
Computed Properties: Use the #[ExposeInTemplate] attribute to expose private methods/properties:
#[ExposeInTemplate]
public function getFormattedMessage(): string {
return strtoupper($this->message);
}
Access in Twig: {{ formattedMessage }}.
Default Values: Set defaults in the component class:
public string $message = 'No message provided';
Default Location: Templates are resolved from templates/components/{ComponentName}.html.twig.
Custom Paths: Override the template path via the template argument in #[AsTwigComponent]:
#[AsTwigComponent(template: 'path/to/custom_alert.html.twig')]
Anonymous Components: Use for one-off components without a class:
{% component('alert', { message: 'Dynamic alert' }) %}
Or with HTML syntax:
<twig:alert message="Dynamic alert" />
attributes variable in templates to render dynamic HTML attributes:
<div {{ attributes.addClass('custom-class') }}>
{{ message }}
</div>
attributes:
<twig:alert attributes.defaults(stimulus_controller('alert-controller')) />
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\Hook;
#[AsTwigComponent]
class AlertComponent {
#[Hook('preMount')]
public function preMount(): void {
$this->message = 'Modified at preMount!';
}
}
#[Hook('preRender')]
public function preRender(): void {
$this->template = 'path/to/override.html.twig';
}
provide/injectprovide in a parent component to share state with deeply nested children:
{% component('parent-component') %}
#[AsTwigComponent]
class ParentComponent {
public function provide(): array {
return ['theme' => 'dark'];
}
}
Inject in a child:
#[AsTwigComponent]
class ChildComponent {
#[ExposeInTemplate]
public function getTheme(): string {
return $this->inject('theme');
}
}
RenderedComponent class to test rendered output:
use Symfony\UX\TwigComponent\Test\RenderedComponent;
public function testAlertComponent(): void {
$component = new AlertComponent();
$component->message = 'Test message';
$rendered = RenderedComponent::createFromComponent($component);
$this->assertStringContainsString('Test message', $rendered->getHtml());
}
php bin/console debug:twig-component
PHP 8.4+ Requirement: Forgetting to upgrade PHP/PHPUnit (e.g., phpunit/dist.xml) will cause failures. Use:
composer require --dev phpunit/phpunit:^11.0
And update phpunit.xml.dist to use PHP 8.4+.
Template Path Resolution: If templates aren’t found, verify:
templates/components/{ComponentName}.html.twig.template argument in #[AsTwigComponent] is correct.Null Attributes: Passing null to ComponentAttributes throws an exception. Use remove() instead:
{{ attributes.remove('data-old-attr') }}
Stimulus Controllers: If Stimulus features (e.g., stimulus_controller) don’t work, ensure:
@symfony/stimulus-bridge is installed.assets/controllers.json.Debug Mode Overhead: Component data is collected in the profiler by default in debug mode. Disable with:
# config/packages/dev/twig_component.yaml
twig_component:
profiler:
collect_components: false
Debug Command: List all registered components and their metadata:
php bin/console debug:twig-component
Search for a specific component:
php bin/console debug:twig-component search alert
Profiler Integration: Use the Symfony profiler to inspect component rendering:
Template Fallback: If a template is missing, Twig falls back to index.html.twig in the same directory. Use this for shared layouts.
php bin/console cache:clear
preMount/postMount hooks. Move complex logic to services injected via constructor.Custom Component Factories: Implement ComponentFactoryInterface to create components dynamically:
use Symfony\UX\TwigComponent\ComponentFactoryInterface;
class CustomComponentFactory implements ComponentFactoryInterface {
public function create(string $name, array $props): object {
return new CustomComponent($props);
}
}
Register as a service with the twig.component.factory tag.
Template Finders: Extend ComponentTemplateFinder to support custom template locations (e.g., database-driven templates).
Attribute Escaping: Customize HTML attribute escaping by implementing HtmlAttributeEscaperInterface and passing it to ComponentAttributes.
ComponentInterface to #[AsTwigComponent]: Update legacy components:
// Before (Symfony UX 2.x)
class AlertComponent implements ComponentInterface {
public static function getComponentName(): string { return 'alert'; }
}
// After (Symfony UX 3.x)
#[AsTwigComponent('alert')]
class AlertComponent { ... }
How can I help you explore Laravel packages today?