symfony/ux-twig-component
Symfony UX Twig Components lets you bind PHP objects to Twig templates to build reusable UI pieces like alerts, modals, and sidebars. Create small, composable components with clean rendering and better template organization for Symfony apps.
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?