--- name: twig-component description: Symfony UX TwigComponent for reusable UI building blocks -- server-rendered components with PHP classes and Twig templates. Use when creating buttons, cards, alerts, badges, navbars, or any reusable UI element with props, blocks/slots, computed properties, or anonymous (template-only) components. Code triggers: AsTwigComponent, #[AsTwigComponent], ExposeInTemplate, PreMount, PostMount, , , component(), computed properties, anonymous component, HTML syntax. Also trigger when the user asks "how to create a reusable component", "how to make a component library", "how to pass props to a component", "how to use slots/blocks in a component", "how to build a design system in Symfony", "what is the HTML syntax for components", "how to create a component without a PHP class". Do NOT trigger for components that re-render dynamically on user input (use live-component), for JS behavior (use stimulus), or for page navigation (use turbo). license: MIT metadata: author: Simon Andre email: smn.andre@gmail.com url: https://smnandre.dev version: "1.0" --- # TwigComponent Reusable UI components with PHP classes + Twig templates. Think React/Vue components, but server-rendered with zero JavaScript. Two flavors exist: **class components** (PHP class + Twig template) for components that need logic, services, or computed properties, and **anonymous components** (Twig-only, no PHP class) for simple presentational elements. ## When to Use TwigComponent Use TwigComponent when you need reusable markup with props but no server re-rendering after the initial render. If the component needs to react to user input (re-render via AJAX, data binding, actions), use LiveComponent instead. Good candidates: buttons, alerts, cards, badges, icons, form widgets, layout sections, navigation items, table rows, modals (structure only). ## Installation ```bash composer require symfony/ux-twig-component ``` ## Class Component A PHP class annotated with `#[AsTwigComponent]` paired with a Twig template. ```php // src/Twig/Components/Alert.php namespace App\Twig\Components; use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; #[AsTwigComponent] final class Alert { public string $type = 'info'; public string $message; public bool $dismissible = false; } ``` ```twig {# templates/components/Alert.html.twig #}
{{ message }} {% if dismissible %} {% endif %}
``` ```twig {# Usage #} {# With block content instead of message prop #} Warning: Check your input ``` ## Anonymous Component (Twig Only) No PHP class needed. Props are declared with `{% props %}` directly in the template. Use for simple presentational components with no logic. ```twig {# templates/components/Button.html.twig #} {% props variant = 'primary', size = 'md', disabled = false %} ``` ```twig Delete ``` ## Props ### Public Properties (Class Components) Public properties become props. Required props have no default value. ```php #[AsTwigComponent] final class Card { public string $title; // Required public ?string $subtitle = null; // Optional public bool $shadow = true; // Optional with default } ``` ### mount() for Derived State Use `mount()` to compute values from incoming props. The method runs once during component initialization. ```php #[AsTwigComponent] final class UserCard { public User $user; public string $displayName; public function mount(User $user): void { $this->user = $user; $this->displayName = $user->getFullName(); } } ``` ```twig ``` ### Dynamic Props (Colon Prefix) Prefix a prop with `:` to pass a Twig expression instead of a string literal. ```twig {# Pass a variable #} {# Pass an expression #} ``` ## Blocks (Slots) Blocks let parent templates inject content into specific areas of a component. ### Default Block Content between component tags goes to `{% block content %}`: ```twig {# Component template #}
{% block content %}{% endblock %}
{# Usage #}

This is the card content

``` ### Named Blocks ```twig {# templates/components/Modal.html.twig #}
{% block header %}Default Header{% endblock %}
{% block content %}{% endblock %}
{% block footer %}{% endblock %}
``` ```twig

Confirm Action

Are you sure?

``` ## Computed Properties Methods prefixed with `get` become accessible as `this.xxx` in templates. They are computed on each access (not cached across re-renders -- for caching, see LiveComponent's `computed`). ```php #[AsTwigComponent] final class ProductCard { public Product $product; public function getFormattedPrice(): string { return number_format($this->product->getPrice(), 2) . ' EUR'; } public function isOnSale(): bool { return $this->product->getDiscount() > 0; } } ``` ```twig
{{ this.formattedPrice }} {% if this.onSale %} Sale! {% endif %}
``` ## Attributes Extra HTML attributes passed to the component are available via `{{ attributes }}`. This is how you let consumers add custom classes, ids, data attributes, etc. ```twig {# Usage #} {# In component template -- renders class, id, data-controller #}
...
``` ### Attributes Methods ```twig {# Merge with defaults #}
{# Exclude specific #}
{# Only render specific #}
{# Check existence #} {% if attributes.has('disabled') %} ``` ## Components as Services Components are Symfony services -- autowiring works naturally. Use the constructor for dependencies, public properties for props. ```php #[AsTwigComponent] final class FeaturedProducts { public function __construct( private readonly ProductRepository $products, ) {} public function getProducts(): array { return $this->products->findFeatured(limit: 6); } } ``` ```twig {# templates/components/FeaturedProducts.html.twig #} ``` ```twig {# Usage -- no props needed, data comes from service #} ``` ## Lifecycle Hooks ```php use Symfony\UX\TwigComponent\Attribute\PreMount; use Symfony\UX\TwigComponent\Attribute\PostMount; #[AsTwigComponent] final class DataTable { public array $data; public string $sortBy = 'id'; #[PreMount] public function preMount(array $data): array { // Modify/validate incoming data before property assignment $data['sortBy'] ??= 'id'; return $data; } #[PostMount] public function postMount(): void { // Runs after all props are set $this->data = $this->sortData($this->data); } } ``` ## Nested Components Components compose naturally -- nest them like HTML elements: ```twig Featured ``` ## Configuration ```yaml # config/packages/twig_component.yaml twig_component: anonymous_template_directory: 'components/' defaults: App\Twig\Components\: 'components/' ``` ## HTML vs Twig Syntax ```twig {# HTML syntax (recommended -- better IDE support, more readable) #} {# Twig syntax (alternative -- useful in edge cases) #} {% component 'Alert' with {type: 'success', message: 'Done!'} %} {% endcomponent %} ``` Prefer HTML syntax (``) in all cases. The Twig syntax (`{% component %}`) is legacy and less readable. ## References - **Full API** (attribute options, hooks, configuration, all methods): [references/api.md](references/api.md) - **Patterns** (forms, tables, layouts, composition, real-world examples): [references/patterns.md](references/patterns.md) - **Gotchas** (attributes, naming, nested components, common pitfalls): [references/gotchas.md](references/gotchas.md) ## See Also - **UX Icons** integrates naturally in TwigComponent templates: `` inside your component markup. - **UX Map** can be rendered inside a TwigComponent template via `{{ ux_map(map, {style: 'height: 400px;'}) }}`.