PHPackages                             danmartuszewski/ux-twig-component - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. danmartuszewski/ux-twig-component

ActiveSymfony-bundle[Templating &amp; Views](/categories/templating)

danmartuszewski/ux-twig-component
=================================

Twig components for Symfony

01.0kPHP

Since Jan 18Pushed 4y agoCompare

[ Source](https://github.com/danmartuszewski/ux-twig-component)[ Packagist](https://packagist.org/packages/danmartuszewski/ux-twig-component)[ RSS](/packages/danmartuszewski-ux-twig-component/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

Twig Components
===============

[](#twig-components)

**EXPERIMENTAL** This component is currently experimental and is likely to change, or even change drastically.

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:

```
// src/Components/AlertComponent.php
namespace App\Components;

use Symfony\UX\TwigComponent\AbstractComponent;

class AlertComponent extends AbstractComponent
{
    public string $type = 'success';
    public string $message;

    public static function getComponentName(): string
    {
        return 'alert';
    }

    // optional, specify template name
    public static function getTemplate() : ?string
    {
        return '@App/components/alert.html.twig';
    }
}
```

And (2) a corresponding template:

```
{# templates/components/alert.html.twig #}

    {{ this.message }}

```

Done! Now render it wherever you want:

```
{{ component('alert', { message: 'Hello Twig Components!' }) }}
```

Enjoy your new component!

[![Example of the AlertComponent](./alert-example.png)](./alert-example.png)

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

Installation
------------

[](#installation)

Let's get this thing installed! Run:

```
composer require symfony/ux-twig-component

```

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

Creating a Basic Component
--------------------------

[](#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 implements `ComponentInterface`. Let's start as simple as possible:

```
// src/Components/AlertComponent.php
namespace App\Components;

use Symfony\UX\TwigComponent\ComponentInterface;

class AlertComponent implements ComponentInterface
{
    public static function getComponentName(): string
    {
        return 'alert';
    }
}
```

Step 2 is to create a template for this component. Templates live in `templates/components/{Component Name}.html.twig`, where `{Component Name}` is whatever you return from the `getComponentName()`method:

```
{# templates/components/alert.html.twig #}

    Success! You've created a Twig component!

```

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:

```
{{ component('alert') }}
```

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

Passing Data into your Component
--------------------------------

[](#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:

```
// src/Components/AlertComponent.php
// ...

class AlertComponent implements ComponentInterface
{
+    public string $message;

+    public string $type = 'success';

    // ...
}
```

In the template, the `AlertComponent` instance is available via the `this` variable. Use it to render the two new properties:

```

    {{ this.message }}

```

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

```
{{ component('alert', { message: 'Successfully created!' }) }}

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

Behind the scenes, a new `AlertComponent` 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.

### The mount() Method

[](#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:

```
// src/Components/AlertComponent.php
// ...

class AlertComponent implements ComponentInterface
{
    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:

```
{{ 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.

Fetching Services
-----------------

[](#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`:

```
// src/Components/FeaturedProductsComponent.php
namespace App\Components;

use App\Repository\ProductRepository;
use Symfony\UX\TwigComponent\ComponentInterface;

class FeaturedProductsComponent implements ComponentInterface
{
    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();
    }

    public static function getComponentName() : string
    {
        return 'featured_products';
    }
}
```

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

```
{# templates/components/featured_products.html.twig #}

    Featured Products

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

```

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

```
{{ component('featured_products') }}
```

**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

[](#computed-properties)

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](../LiveComponent)) 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](https://v3.vuejs.org/guide/computed.html).

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 (where its value is only evaluated the first time you call the method), you can store its result on a private property:

```
// src/Components/FeaturedProductsComponent.php
namespace App\Components;
// ...

class FeaturedProductsComponent implements ComponentInterface
{
    private ProductRepository $productRepository;

+    private ?array $products = null;

    // ...

    public function getProducts(): array
    {
+        if ($this->products === null) {
+            $this->products = $this->productRepository->findFeatured();
+        }

-        return $this->productRepository->findFeatured();
+        return $this->products;
    }
}
```

Contributing
------------

[](#contributing)

Interested in contributing? Visit the main source for this repository: .

Have fun!

###  Health Score

19

—

LowBetter than 10% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity14

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity27

Early-stage or recently created project

 Bus Factor1

Top contributor holds 50% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/274274438c2a797388020659830553a6f40c2d8b6f8f4c56c8a748c5822d2a05?d=identicon)[dmartuszewski](/maintainers/dmartuszewski)

---

Top Contributors

[![weaverryan](https://avatars.githubusercontent.com/u/121003?v=4)](https://github.com/weaverryan "weaverryan (2 commits)")[![danmartuszewski](https://avatars.githubusercontent.com/u/323465?v=4)](https://github.com/danmartuszewski "danmartuszewski (1 commits)")[![kbond](https://avatars.githubusercontent.com/u/127811?v=4)](https://github.com/kbond "kbond (1 commits)")

### Embed Badge

![Health badge](/badges/danmartuszewski-ux-twig-component/health.svg)

```
[![Health](https://phpackages.com/badges/danmartuszewski-ux-twig-component/health.svg)](https://phpackages.com/packages/danmartuszewski-ux-twig-component)
```

###  Alternatives

[mustache/mustache

A Mustache implementation in PHP.

3.3k44.6M290](/packages/mustache-mustache)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[whitecube/nova-flexible-content

Flexible Content &amp; Repeater Fields for Laravel Nova.

8053.0M25](/packages/whitecube-nova-flexible-content)[mopa/bootstrap-bundle

Easy integration of twitters bootstrap into symfony2

7042.9M33](/packages/mopa-bootstrap-bundle)[limenius/react-bundle

Client and Server-side react rendering in a Symfony Bundle

3871.2M](/packages/limenius-react-bundle)[nicmart/string-template

StringTemplate is a very simple string template engine for php. I've written it to have a thing like sprintf, but with named and nested substutions.

2101.7M30](/packages/nicmart-string-template)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
