Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • SensioLabs Professional services to help you with Symfony
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by SensioLabs
  1. Home
  2. Documentation
  3. Bundles
  4. Symfony UX Twig Components
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud

Table of Contents

  • Installation
  • Creating a Basic Component
  • Naming Your Component
  • Passing Data into your Component
    • Customize the Twig Template
    • Twig Template Namespaces
    • The mount() Method
    • PreMount Hook
    • PostMount Hook
    • ExposeInTemplate Attribute
  • Fetching Services
    • Computed Properties
  • Component Attributes
    • Defaults & Merging
    • Only
    • Without
  • PreRenderEvent
  • PostRenderEvent
  • PreCreateForRenderEvent
  • PreMountEvent and PostMountEvent
  • Nested Components
  • Passing Blocks to Components
    • Inheritance & Forwarding "Outer Blocks"
    • Context / Variables Inside of Blocks
  • Component HTML Syntax
    • Passing Props as HTML Attributes
    • Passing Content (Blocks) to Components
  • Anonymous Components
  • Test Helpers
  • Contributing
  • Backward Compatibility promise

Twig Components

Edit this page

Twig Components

Twig components give you the power to bind an object to a template, making it easier to render and re-use small template "units" - like an "alert", markup for a modal, or a category sidebar:

Every component consists of (1) a class:

1
2
3
4
5
6
7
8
9
10
11
// src/Components/Alert.php
namespace App\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
class Alert
{
    public string $type = 'success';
    public string $message;
}

And (2) a corresponding template:

1
2
3
4
{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ type }}">
    {{ message }}
</div>

Done! Now render it wherever you want:

1
{{ component('Alert', { message: 'Hello Twig Components!' }) }}

Enjoy your new component!

Example of the Alert Component Example of the Alert Component

This brings the familiar "component" system from client-side frameworks into Symfony. Combine this with Live Components, to create an interactive frontend with automatic, Ajax-powered rendering.

Want a demo? Check out https://ux.symfony.com/twig-component#demo

Installation

Let's get this thing installed! Run:

1
$ composer require symfony/ux-twig-component

That's it! We're ready to go!

Creating a Basic Component

Let's create a reusable "alert" element that we can use to show success or error messages across our site. Step 1 is always to create a component that has an AsTwigComponent class attribute. Let's start as simple as possible:

1
2
3
4
5
6
7
8
9
// src/Components/Alert.php
namespace App\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
class Alert
{
}

Step 2 is to create a template for this component. By default, templates live in templates/components/{component_name}.html.twig, where {component_name} matches the class name of the component:

1
2
3
4
{# templates/components/Alert.html.twig #}
<div class="alert alert-success">
    Success! You've created a Twig component!
</div>

This isn't very interesting yet… since the message is hardcoded into the template. But it's enough! Celebrate by rendering your component from any other Twig template:

1
{{ component('Alert') }}

Done! You've just rendered your first Twig Component! Take a moment to fist pump - then come back!

Naming Your Component

2.8

Before 2.8, passing a name to AsTwigComponent was required. Now, the name is optional and defaults to the class name.

The name of your component is the class name by default. But you can customize it by passing an argument to AsTwigComponent:

1
2
3
4
#[AsTwigComponent('alert')]
class Alert
{
}

Passing Data into your Component

Good start: but this isn't very interesting yet! To make our Alert component reusable, we need to make the message and type (e.g. success, danger, etc) configurable. To do that, create a public property for each:

1
2
3
4
5
6
7
8
9
10
11
12
// src/Components/Alert.php
  // ...

  #[AsTwigComponent]
  class Alert
  {
+     public string $message;

+     public string $type = 'success';

      // ...
  }

In the template, the Alert instance is available via the this variable and public properties are available directly. Use them to render the two new properties:

2.1

The ability to reference local variables in the template (e.g. message) was added in TwigComponents 2.1. Previously, all data needed to be referenced through this (e.g. this.message).

1
2
3
4
5
6
<div class="alert alert-{{ type }}">
    {{ message }}

    {# Same as above, but using "this", which is the component object #}
    {{ this.message }}
</div>

How can we populate the message and type properties? By passing them as a 2nd argument to the component() function when rendering:

1
2
3
4
5
6
{{ component('Alert', { message: 'Successfully created!' }) }}

{{ component('Alert', {
    type: 'danger',
    message: 'Danger Will Robinson!'
}) }}

Behind the scenes, a new Alert will be instantiated and the message key (and type if passed) will be set onto the $message property of the object. Then, the component is rendered! If a property has a setter method (e.g. setMessage()), that will be called instead of setting the property directly.

Note

You can disable exposing public properties for a component. When disabled, this.property must be used:

1
2
3
4
5
#[AsTwigComponent(exposePublicProps: false)]
class Alert
{
    // ...
}

Customize the Twig Template

You can customize the template used to render the component by passing it as the second argument to the AsTwigComponent attribute:

1
2
3
4
5
6
7
8
9
// src/Components/Alert.php
  // ...

- #[AsTwigComponent]
+ #[AsTwigComponent(template: 'my/custom/template.html.twig')]
  class Alert
  {
      // ...
  }

Twig Template Namespaces

You can use a : in your component's name to indicate a namespace. The default template will replace the : with /. For example, a component with the name form:input will look for a template in templates/components/form/input.html.twig.

The mount() Method

If, for some reason, you don't want an option to the component() function to be set directly onto a property, you can, instead, create a mount() method in your component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Components/Alert.php
// ...

#[AsTwigComponent]
class Alert
{
    public string $message;
    public string $type = 'success';

    public function mount(bool $isSuccess = true)
    {
        $this->type = $isSuccess ? 'success' : 'danger';
    }

    // ...
}

The mount() method is called just one time immediately after your component is instantiated. Because the method has an $isSuccess argument, we can pass an isSuccess option when rendering the component:

1
2
3
4
{{ component('alert', {
    isSuccess: false,
    message: 'Danger Will Robinson!'
}) }}

If an option name matches an argument name in mount(), the option is passed as that argument and the component system will not try to set it directly on a property.

PreMount Hook

If you need to modify/validate data before it's mounted on the component use a PreMount hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/Components/Alert.php
use Symfony\UX\TwigComponent\Attribute\PreMount;
// ...

#[AsTwigComponent]
class Alert
{
    public string $message;
    public string $type = 'success';

    #[PreMount]
    public function preMount(array $data): array
    {
        // validate data
        $resolver = new OptionsResolver();
        $resolver->setDefaults(['type' => 'success']);
        $resolver->setAllowedValues('type', ['success', 'danger']);
        $resolver->setRequired('message');
        $resolver->setAllowedTypes('message', 'string');

        return $resolver->resolve($data);
    }

    // ...
}

Note

If your component has multiple PreMount hooks, and you'd like to control the order in which they're called, use the priority attribute parameter: PreMount(priority: 10) (higher called earlier).

PostMount Hook

2.1

The PostMount hook was added in TwigComponents 2.1.

After a component is instantiated and its data mounted, you can run extra code via the PostMount hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Components/Alert.php
use Symfony\UX\TwigComponent\Attribute\PostMount;
// ...

#[AsTwigComponent]
class Alert
{
    #[PostMount]
    public function postMount(): array
    {
        if (str_contains($this->message, 'danger')) {
            $this->type = 'danger';
        }
    }
    // ...
}

A PostMount method can also receive an array $data argument, which will contain any props passed to the component that have not yet been processed, i.e. because they don't correspond to any property. You can handle these props, remove them from the $data and return the array:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Components/Alert.php
#[AsTwigComponent]
class Alert
{
    public string $message;
    public string $type = 'success';

    #[PostMount]
    public function processAutoChooseType(array $data): array
    {
        if ($data['autoChooseType'] ?? false) {
            if (str_contains($this->message, 'danger')) {
                $this->type = 'danger';
            }

            // remove the autoChooseType prop from the data array
            unset($data['autoChooseType']);
        }

        // any remaining data will become attributes on the component
        return $data;
    }
    // ...
}

Note

If your component has multiple PostMount hooks, and you'd like to control the order in which they're called, use the priority attribute parameter: PostMount(priority: 10) (higher called earlier).

ExposeInTemplate Attribute

2.1

The ExposeInTemplate attribute was added in TwigComponents 2.1.

2.3

The ExposeInTemplate attribute can now be used on public methods.

All public component properties are available directly in your component template. You can use the ExposeInTemplate attribute to expose private/protected properties and public methods directly in a component template (someProp vs this.someProp, someMethod vs this.someMethod). Properties must be accessible (have a getter). Methods cannot have required parameters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// ...
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

#[AsTwigComponent]
class Alert
{
    #[ExposeInTemplate]
    private string $message; // available as `{{ message }}` in the template

    #[ExposeInTemplate('alert_type')]
    private string $type = 'success'; // available as `{{ alert_type }}` in the template

    #[ExposeInTemplate(name: 'ico', getter: 'fetchIcon')]
    private string $icon = 'ico-warning'; // available as `{{ ico }}` in the template using `fetchIcon()` as the getter

    /**
     * Required to access $this->message
     */
    public function getMessage(): string
    {
        return $this->message;
    }

    /**
     * Required to access $this->type
     */
    public function getType(): string
    {
        return $this->type;
    }

    /**
     * Required to access $this->icon
     */
    public function fetchIcon(): string
    {
        return $this->icon;
    }

    #[ExposeInTemplate]
    public function getActions(): array // available as `{{ actions }}` in the template
    {
        // ...
    }

    #[ExposeInTemplate('dismissable')]
    public function canBeDismissed(): bool // available as `{{ dismissable }}` in the template
    {
        // ...
    }

    // ...
}

Note

When using ExposeInTemplate on a method the value is fetched eagerly before rendering.

Fetching Services

Let's create a more complex example: a "featured products" component. You could choose to pass an array of Product objects into the component() function and set those on a $products property. But instead, let's allow the component to do the work of executing the query.

How? Components are services, which means autowiring works like normal. This example assumes you have a Product Doctrine entity and ProductRepository:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Components/FeaturedProducts.php
namespace App\Components;

use App\Repository\ProductRepository;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
class FeaturedProducts
{
    private ProductRepository $productRepository;

    public function __construct(ProductRepository $productRepository)
    {
        $this->productRepository = $productRepository;
    }

    public function getProducts(): array
    {
        // an example method that returns an array of Products
        return $this->productRepository->findFeatured();
    }
}

In the template, the getProducts() method can be accessed via this.products:

1
2
3
4
5
6
7
8
{# templates/components/FeaturedProducts.html.twig #}
<div>
    <h3>Featured Products</h3>

    {% for product in this.products %}
        ...
    {% endfor %}
</div>

And because this component doesn't have any public properties that we need to populate, you can render it with:

1
{{ component('FeaturedProducts') }}

Note

Because components are services, normal dependency injection can be used. However, each component service is registered with shared: false. That means that you can safely render the same component multiple times with different data because each component will be an independent instance.

Computed Properties

2.1

Computed Properties were added in TwigComponents 2.1.

In the previous example, instead of querying for the featured products immediately (e.g. in __construct()), we created a getProducts() method and called that from the template via this.products.

This was done because, as a general rule, you should make your components as lazy as possible and store only the information you need on its properties (this also helps if you convert your component to a live component later). With this setup, the query is only executed if and when the getProducts() method is actually called. This is very similar to the idea of "computed properties" in frameworks like Vue.

But there's no magic with the getProducts() method: if you call this.products multiple times in your template, the query would be executed multiple times.

To make your getProducts() method act like a true computed property, call computed.products in your template. computed is a proxy that wraps your component and caches the return of methods. If they are called additional times, the cached value is used.

1
2
3
4
5
6
7
8
9
10
11
12
13
{# templates/components/FeaturedProducts.html.twig #}
<div>
    <h3>Featured Products</h3>

    {% for product in computed.products %}
        ...
    {% endfor %}

    ...
    {% for product in computed.products %} {# use cache, does not result in a second query #}
        ...
    {% endfor %}
</div>

Note

Computed methods only work for component methods with no required arguments.

Component Attributes

2.1

Component attributes were added in TwigComponents 2.1.

A common need for components is to configure/render attributes for the root node. Attributes are any data passed to component() that cannot be mounted on the component itself. This extra data is added to a ComponentAttributes that is available as attributes in your component's template.

To use, in your component's template, render the attributes variable in the root element:

1
2
3
4
{# templates/components/MyComponent.html.twig #}
<div{{ attributes }}>
  My Component!
</div>

When rendering the component, you can pass an array of html attributes to add:

1
2
3
4
5
6
{{ component('MyComponent', { class: 'foo', style: 'color:red' }) }}

{# renders as: #}
<div class="foo" style="color:red">
  My Component!
</div>

Set an attribute's value to true to render just the attribute name:

1
2
3
4
5
6
7
8
{# templates/components/MyComponent.html.twig #}
<input{{ attributes }}/>

{# render component #}
{{ component('MyComponent', { type: 'text', value: '', autofocus: true }) }}

{# renders as: #}
<input type="text" value="" autofocus/>

Set an attribute's value to false to exclude the attribute:

1
2
3
4
5
6
7
8
{# templates/components/MyComponent.html.twig #}
<input{{ attributes }}/>

{# render component #}
{{ component('MyComponent', { type: 'text', value: '', autofocus: false }) }}

{# renders as: #}
<input type="text" value=""/>

To add a custom Stimulus controller to your root component element:

2.9

The ability to use stimulus_controller() with attributes.defaults() was added in TwigComponents 2.9 and requires symfony/stimulus-bundle. Previously, stimulus_controller() was passed to an attributes.add() method.

1
<div {{ attributes.defaults(stimulus_controller('my-controller', { someValue: 'foo' })) }}>

Note

You can adjust the attributes variable exposed in your template:

1
2
3
4
5
#[AsTwigComponent(attributesVar: '_attributes')]
class Alert
{
    // ...
}

Defaults & Merging

In your component template, you can set defaults that are merged with passed attributes. The passed attributes override the default with the exception of class. For class, the defaults are prepended:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{# templates/components/MyComponent.html.twig #}
<button{{ attributes.defaults({ class: 'bar', type: 'button' }) }}>Save</button>

{# render component #}
{{ component('MyComponent', { style: 'color:red' }) }}

{# renders as: #}
<button class="bar" type="button" style="color:red">Save</button>

{# render component #}
{{ component('MyComponent', { class: 'foo', type: 'submit' }) }}

{# renders as: #}
<button class="bar foo" type="submit">Save</button>

Only

Extract specific attributes and discard the rest:

1
2
3
4
5
6
7
8
9
10
11
12
{# templates/components/MyComponent.html.twig #}
<div{{ attributes.only('class') }}>
  My Component!
</div>

{# render component #}
{{ component('MyComponent', { class: 'foo', style: 'color:red' }) }}

{# renders as: #}
<div class="foo">
  My Component!
</div>

Without

Exclude specific attributes:

1
2
3
4
5
6
7
8
9
10
11
12
{# templates/components/MyComponent.html.twig #}
<div{{ attributes.without('class') }}>
  My Component!
</div>

{# render component #}
{{ component('MyComponent', { class: 'foo', style: 'color:red' }) }}

{# renders as: #}
<div style="color:red">
  My Component!
</div>

PreRenderEvent

2.1

The PreRenderEvent was added in TwigComponents 2.1.

Subscribing to the PreRenderEvent gives the ability to modify the twig template and twig variables before components are rendered:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\UX\TwigComponent\Event\PreRenderEvent;

class HookIntoTwigPreRenderSubscriber implements EventSubscriberInterface
{
    public function onPreRender(PreRenderEvent $event): void
    {
        $event->getComponent(); // the component object
        $event->getTemplate(); // the twig template name that will be rendered
        $event->getVariables(); // the variables that will be available in the template

        $event->setTemplate('some_other_template.html.twig'); // change the template used

        // manipulate the variables:
        $variables = $event->getVariables();
        $variables['custom'] = 'value';

        $event->setVariables($variables); // {{ custom }} will be available in your template
    }

    public static function getSubscribedEvents(): array
    {
        return [PreRenderEvent::class => 'onPreRender'];
    }
}

PostRenderEvent

2.5

The PostRenderEvent was added in TwigComponents 2.5.

The PostRenderEvent is called after a component has finished rendering and contains the MountedComponent that was just rendered.

PreCreateForRenderEvent

2.5

The PreCreateForRenderEvent was added in TwigComponents 2.5.

Subscribing to the PreCreateForRenderEvent gives the ability to be notified before a component object is created or hydrated, at the very start of the rendering process. You have access to the component name, input props and can interrupt the process by setting HTML. This event is not triggered during a re-render.

PreMountEvent and PostMountEvent

2.1

The PreMountEvent and PostMountEvent ere added in TwigComponents 2.5.

To run code just before or after a component's data is mounted, you can listen to PreMountEvent or PostMountEvent.

Nested Components

It's totally possible to nest one component into another. When you do this, there's nothing special to know: both components render independently. If you're using Live Components, then there are some guidelines related to how the re-rendering of parent and child components works. Read Live Nested Components.

Passing Blocks to Components

Tip

The Component HTML Syntax allows you to pass blocks to components in an even more readable way.

You can write your component's Twig template with blocks that can be overridden when rendering using the {% component %} syntax. These blocks can be thought of as slots which you may be familiar with from Vue. The component tag is very similar to Twig's native embed tag.

Consider a data table component. You pass it headers and rows but can expose blocks for the cells and an optional footer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{# templates/components/DataTable.html.twig #}
<div{{ attributes.defaults({class: 'data-table'}) }}>
    <table>
        <thead>
            <tr>
                {% for header in this.headers %}
                    <th class="{% block th_class %}data-table-header{% endblock %}">
                        {{ header }}
                    </th>
                {% endfor %}
            </tr>
        </thead>
        <tbody>
            {% for row in this.data %}
                <tr>
                    {% for cell in row %}
                        <td class="{% block td_class %}data-table-cell{% endblock %}">
                            {{ cell }}
                        </td>
                    {% endfor %}
                </tr>
            {% endfor %}
        </tbody>
    </table>
    {% block footer %}{% endblock %}
</div>

When rendering, you can override the th_class, td_class, and footer blocks. The with data is what's mounted on the component object.

1
2
3
4
5
6
7
8
9
10
11
12
{# templates/some_page.html.twig #}
{% component DataTable with {headers: ['key', 'value'], data: [[1, 2], [3, 4]]} %}
    {% block th_class %}{{ parent() }} text-bold{% endblock %}

    {% block td_class %}{{ parent() }} text-italic{% endblock %}

    {% block footer %}
        <div class="data-table-footer">
            My footer
        </div>
    {% endblock %}
{% endcomponent %}

2.11

The {% component %} syntax can also be used with LiveComponents since 2.11. However, there are some caveats related to the context between parent and child components during re-rending. Read Passing Blocks to Live Components.

Inheritance & Forwarding "Outer Blocks"

2.10

The outerBlocks variable was added in 2.10.

The content inside a {% component ... %} tag should be viewed as living in its own, independent template, which extends the component's template. This means that any blocks that live in the "outer" template are not available inside the {% component %} tag. However, a special outerBlocks variable is added as a way to refer to those blocks:

1
2
3
4
5
6
7
8
9
{% extends 'base.html.twig' %}

{% block call_to_action %}<strong>Attention! Free Puppies!</strong>{% endblock %}

{% block body %}
  {% component Alert %}
    {% block content %}{{ block(outerBlocks.call_to_action) }}{% endblock %}
  {% endcomponent %}
{% endblock %}

The outerBlocks variable becomes specially useful with nested components. For example, imagine we want to create a SuccessAlert component that's usable like this:

1
2
3
4
{# templates/some_page.html.twig #}
{% component SuccessAlert %}
    {% block content %}We will successfully <em>forward</em> this block content!{% endblock %}
{% endcomponent %}

But we already have a generic Alert component, and we want to re-use it:

1
2
3
4
{# templates/Alert.html.twig #}
<div class="alert alert-{{ type }}">
    {% block content %}{% endblock %}
</div>

To do this, the SuccessAlert component can grab the content block that's passed to it via the outerBlocks variable and forward it into Alert:

1
2
3
4
{# templates/SuccessAlert.html.twig #}
{% component Alert with { type: 'success' } %}
    {% block content %}{{ block(outerBlocks.content) }}{% endblock %}
{% endcomponent %}

Note that to pass a block multiple components down, each component needs to pass it.

Context / Variables Inside of Blocks

The content inside of the {% component ... %} should be viewed as living in its own, independent template, which extends the component's template. This has a few interesting consequences.

First, once you're inside of {% component ... %}, the this variable represents the component you're now rendering and you have access to all of that component's variables:

1
2
3
4
5
6
7
8
{# templates/SuccessAlert.html.twig #}
{{ this.someFunction }} {# this refers to SuccessAlert #}

{% component Alert with { type: 'success' } %}
    {{ this.someFunction }} {# this refers to Alert! #}

    {{ type }} {# references a "type" prop from Alert #}
{% endcomponent %}

Conveniently, in addition to the variables from the Alert component, you still have access to whatever variables are available in the original template:

1
2
3
4
5
{# templates/SuccessAlert.html.twig #}
{% set name = 'Fabien' %}
{% component Alert with { type: 'success' } %}
    Hello {{ name }}
{% endcomponent %}

Note that ALL variables from upper components (e.g. SuccessAlert) are available to lower components (e.g. Alert). However, because variables are merged, variables with the same name are overridden by lower components (that's also why this refers to the embedded, or "current" component).

The most interesting thing is that the content inside of {% component ... %} is executed as if it is "copy-and-pasted" into the block of the target template. This means you can access variables from the block you're overriding! For example:

1
2
3
4
5
6
{# templates/SuccessAlert.html.twig #}
{% for message in messages %}
    {% block alert_message %}
        A default {{ message }}
    {% endblock %}
{% endfor %}

When overriding the alert_message block, you have access to the message variable:

1
2
3
4
5
6
{# templates/some_page.html.twig #}
{% component SuccessAlert %}
    {% block alert_message %}
        I can override the alert_message block and access the {{ message }} too!
    {% endblock %}
{% endcomponent %}

Component HTML Syntax

2.8

This syntax was been introduced in 2.8 and is still experimental: it may change in the future.

Twig Components come with an HTML-like syntax to ease the readability of your template:

1
2
3
<twig:Alert></twig:Alert>
// or use a self-closing tag
<twig:Alert />

Passing Props as HTML Attributes

Passing props is done with HTML attributes. For example if you have this component:

1
2
3
4
5
6
7
#[AsTwigComponent]
class Alert
{
    public string $message = '';
    public bool $withActions = false;
    public string $type = 'success';
}

You can pass the message, withActions or type props as attributes:

1
2
// withActions will be set to true
<twig:Alert type="info" message="hello!" withActions />

To pass in a dynamic value, prefix the attribute with : or use the normal {{ }} syntax:

1
2
3
4
5
6
7
<twig:Alert message="hello!" :user="user.id" />

// equal to
<twig:Alert message="hello!" user="{{ user.id }}" />

// and pass object, or table, or anything you imagine
<twig:Alert :foo="['col' => ['foo', 'oof']]" />

To forward attributes to another component, use `{{...}}` spread operator syntax. This requires Twig 3.7.0 or higher:

1
<twig:Alert{{ ...myAttributes }} />

Passing Content (Blocks) to Components

You can also pass content directly to your component:

1
2
3
<twig:Alert type="success">
    <div>Congratulations! You've won a free puppy!</div>
</twig:Alert>

In your component template, this becomes a block named content:

1
2
3
4
5
<div class="alert alert-{{ type }}">
    {% block content %}
        // the content will appear in here
    {% endblock %}
 </div>

In addition to the default block, you can also add named blocks:

1
2
3
4
5
6
7
<twig:Alert type="success">
    <div>Congrats on winning a free puppy!</div>

    <twig:block name="footer">
        <button class="btn btn-primary">Claim your prize</button>
    </twig:block>
</twig:Alert>

And in your component template you can access your embedded block

1
2
3
4
<div class="alert alert-{{ type }}">
    {% block content %}{% endblock %}
    {% block footer %}{% endblock %}
 </div>

Anonymous Components

Sometimes a component is simple enough that it doesn't have any complex logic or injected services. In this case, you can skip the class and only create the template. The component name is determined by the location of the template (see Twig Template Namespaces):

1
2
3
4
{# templates/components/Button/Primary.html.twig #}
<button {{ attributes.defaults({ class: 'primary' }) }}>
    {% block content %}{% endblock %}
</button>

Then use your component with : to navigate through sub-directories (if there are any):

1
2
3
4
5
6
7
8
{# index.html.twig #}
...
<div>
   <twig:Button:Primary>Click Me!</twig:Button:Primary>
</div>

{# renders as: #}
<button class="primary">Click Me!</button>

Like normal, you can pass extra attributes that will be rendered on the element:

1
2
3
4
5
6
7
8
{# index.html.twig #}
...
<div>
   <twig:Button:Primary type="button" name="foo">Click Me!</twig:Button:Primary>
</div>

{# renders as: #}
<button class="primary" type="button" name="foo">Click Me!</button>

You can also pass a variable (prop) into your template:

1
2
3
4
5
{# index.html.twig #}
...
<div>
    <twig:Button icon="fa-plus" type="primary" role="button">Click Me!</twig:Button>
</div>

To tell the system that icon and type are props and not attributes, use the {% props %} tag at the top of your template.

1
2
3
4
5
6
7
8
9
{# templates/components/Button.html.twig #}
{% props icon = null, type = 'primary' %}

<button {{ attributes.defaults({ class: 'btn btn-'~type }) }}>
    {% block content %}{% endblock %}
    {% if icon %}
        <span class="fa-solid fa-{{ icon }}"></span>
    {% endif %}
</button>

Test Helpers

You can test how your component is mounted and rendered using the InteractsWithTwigComponents trait:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\UX\TwigComponent\Test\InteractsWithTwigComponents;

class MyComponentTest extends KernelTestCase
{
    use InteractsWithTwigComponents;

    public function testComponentMount(): void
    {
        $component = $this->mountTwigComponent(
            name: 'MyComponent', // can also use FQCN (MyComponent::class)
            data: ['foo' => 'bar'],
        );

        $this->assertInstanceOf(MyComponent::class, $component);
        $this->assertSame('bar', $component->foo);
    }

    public function testComponentRenders(): void
    {
        $rendered = $this->renderTwigComponent(
            name: 'MyComponent', // can also use FQCN (MyComponent::class)
            data: ['foo' => 'bar'],
        );

        $this->assertStringContainsString('bar', $rendered);

        // use the crawler
        $this->assertCount(5, $rendered->crawler('ul li'));
    }

    public function testEmbeddedComponentRenders(): void
    {
        $rendered = $this->renderTwigComponent(
            name: 'MyComponent', // can also use FQCN (MyComponent::class)
            data: ['foo' => 'bar'],
            content: '<div>My content</div>', // "content" (default) block
            blocks: [
                'header' => '<div>My header</div>',
                'menu' => $this->renderTwigComponent('Menu'), // can embed other components
            ],
        );

        $this->assertStringContainsString('bar', $rendered);
    }
}

Note

The InteractsWithTwigComponents trait can only be used in tests that extend Symfony\Bundle\FrameworkBundle\Test\KernelTestCase.

Contributing

Interested in contributing? Visit the main source for this repository: https://github.com/symfony/ux/tree/main/src/TwigComponent.

Backward Compatibility promise

This bundle aims at following the same Backward Compatibility promise as the Symfony framework: https://symfony.com/doc/current/contributing/code/bc.html

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version
    We stand with Ukraine.
    Version:

    Symfony UX is backed by

    The life jacket for your team and your project

    The life jacket for your team and your project

    Symfony Code Performance Profiling

    Symfony Code Performance Profiling

    Symfony footer

    ↓ Our footer now uses the colors of the Ukrainian flag because Symfony stands with the people of Ukraine.

    Avatar of chance garcia, a Symfony contributor

    Thanks chance garcia for being a Symfony contributor

    1 commit • 7 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • Symfony at a Glance
      • Symfony Components
      • Case Studies
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • SymfonyConnect
      • Support
      • How to be Involved
      • Code of Conduct
      • Events & Meetups
      • Projects using Symfony
      • Downloads Stats
      • Contributors
      • Backers
    • Blog

      • Events & Meetups
      • A week of symfony
      • Case studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Documentation
      • Living on the edge
      • Releases
      • Security Advisories
      • SymfonyInsight
      • Twig
      • SensioLabs
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Deployed on

    Follow Symfony

    Search by Meilisearch