/twig-component
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
$ npx -y skills add smnandre/symfony-ux-skills --skill twig-component --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/twig-component
Context preview
The summary Claude sees to decide when to auto-load this skill.
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
SKILL.md
twig-component.SKILL.mdname: 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, <twig:Alert />, <twig:Button>, 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.2.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
composer require symfony/ux-twig-component
Class Component
A PHP class annotated with `#[AsTwigComponent]` paired with a Twig template.
// 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;
}{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ type }}" {{ attributes }}>
{{ message }}
{% if dismissible %}
<button type="button" class="close">×</button>
{% endif %}
</div>{# Usage #}
<twig:Alert type="success" message="Saved!" />
<twig:Alert type="danger" message="Error occurred" dismissible />
{# With block content instead of message prop #}
<twig:Alert type="warning">
<strong>Warning:</strong> Check your input
</twig:Alert>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.
{# templates/components/Button.html.twig #}
{% props variant = 'primary', size = 'md', disabled = false %}
<button
class="btn btn-{{ variant }} btn-{{ size }}"
{{ disabled ? 'disabled' }}
{{ attributes }}
>
{% block content %}{% endblock %}
</button><twig:Button variant="danger" size="lg">Delete</twig:Button>
Props
Public Properties (Class Components)
Public properties become props. Required props have no default value.
#[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.
#[AsTwigComponent]
final class UserCard
{
public User $user;
public string $displayName;
public function mount(User $user): void
{
$this->user = $user;
$this->displayName = $user->getFullName();
}
}<twig:UserCard :user="currentUser" />
Dynamic Props (Colon Prefix)
Prefix a prop with `:` to pass a Twig expression instead of a string literal.
{# Pass a variable #}
<twig:Alert :type="alertType" :message="flashMessage" />
{# Pass an expression #}
<twig:UserList :users="users|filter(u => u.active)" />Blocks (Slots)
Blocks let parent templates inject content into specific areas of a component.
Default Block
Content between component tags goes to `{% block content %}`:
{# Component template #}
<div class="card">{% block content %}{% endblock %}</div>
{# Usage #}
<twig:Card><p>This is the card content</p></twig:Card>Named Blocks
{# templates/components/Modal.html.twig #}
<dialog class="modal" {{ attributes }}>
<header>{% block header %}Default Header{% endblock %}</header>
<main>{% block content %}{% endblock %}</main>
<footer>{% block footer %}{% endblock %}</footer>
</dialog><twig:Modal>
<twig:block name="header"><h2>Confirm Action</h2></twig:block>
<twig:block name="content"><p>Are you sure?</p></twig:block>
<twig:block name="footer">
<button>Cancel</button>
<button>Confirm</button>
</twig:block>
</twig:Modal>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`).
#[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;
}
}<div class="product">
<span class="price">{{ this.formattedPrice }}</spRead more
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, <twig:Alert />, <twig:Button>, 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.2.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
composer require symfony/ux-twig-component
Class Component
A PHP class annotated with `#[AsTwigComponent]` paired with a Twig template.
// 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;
}{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ type }}" {{ attributes }}>
{{ message }}
{% if dismissible %}
<button type="button" class="close">×</button>
{% endif %}
</div>{# Usage #}
<twig:Alert type="success" message="Saved!" />
<twig:Alert type="danger" message="Error occurred" dismissible />
{# With block content instead of message prop #}
<twig:Alert type="warning">
<strong>Warning:</strong> Check your input
</twig:Alert>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.
{# templates/components/Button.html.twig #}
{% props variant = 'primary', size = 'md', disabled = false %}
<button
class="btn btn-{{ variant }} btn-{{ size }}"
{{ disabled ? 'disabled' }}
{{ attributes }}
>
{% block content %}{% endblock %}
</button><twig:Button variant="danger" size="lg">Delete</twig:Button>
Props
Public Properties (Class Components)
Public properties become props. Required props have no default value.
#[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.
#[AsTwigComponent]
final class UserCard
{
public User $user;
public string $displayName;
public function mount(User $user): void
{
$this->user = $user;
$this->displayName = $user->getFullName();
}
}<twig:UserCard :user="currentUser" />
Dynamic Props (Colon Prefix)
Prefix a prop with `:` to pass a Twig expression instead of a string literal.
{# Pass a variable #}
<twig:Alert :type="alertType" :message="flashMessage" />
{# Pass an expression #}
<twig:UserList :users="users|filter(u => u.active)" />Blocks (Slots)
Blocks let parent templates inject content into specific areas of a component.
Default Block
Content between component tags goes to `{% block content %}`:
{# Component template #}
<div class="card">{% block content %}{% endblock %}</div>
{# Usage #}
<twig:Card><p>This is the card content</p></twig:Card>Named Blocks
{# templates/components/Modal.html.twig #}
<dialog class="modal" {{ attributes }}>
<header>{% block header %}Default Header{% endblock %}</header>
<main>{% block content %}{% endblock %}</main>
<footer>{% block footer %}{% endblock %}</footer>
</dialog><twig:Modal>
<twig:block name="header"><h2>Confirm Action</h2></twig:block>
<twig:block name="content"><p>Are you sure?</p></twig:block>
<twig:block name="footer">
<button>Cancel</button>
<button>Confirm</button>
</twig:block>
</twig:Modal>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`).
#[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;
}
}<div class="product">
<span class="price">{{ this.formattedPrice }}</spShowing the first part of this file.
AI agent skills for the Symfony UX frontend stack -- Stimulus, Turbo, TwigComponent, LiveComponent, UX Icons and UX Map. By Simon Andre
Repo: smnandre/symfony-ux-skills
Other skills on symfony-ux-skills.
- /live-component
Symfony UX LiveComponent for reactive server-rendered UI -- components that re-render via AJAX on user interaction, zero JavaScript required. Use when building live search, real-time filtering, dynamic forms, inline validation, dependent selects, auto-save, polling,
Open skill - /stimulus
Stimulus JS framework for Symfony UX -- client-side behavior via HTML data attributes, zero server round-trips. Use when creating controllers for DOM manipulation, handling click/input/submit events, managing targets and values, wiring outlets between controllers, wrapping
Open skill - /symfony-ux
Symfony UX frontend stack -- decision tree and orchestrator for choosing between Stimulus, Turbo, TwigComponent, LiveComponent, UX Icons, and UX Map. Use when the user is unsure which tool fits, wants to combine multiple UX packages, or asks a general frontend architecture
Open skill - /turbo
Hotwire Turbo for Symfony UX -- SPA-like speed with zero JavaScript. Covers Drive (navigation), Frames (partial page sections), and Streams (multi-target updates). Use when building ajax navigation, lazy-loaded sections, inline editing, pagination without reload, modals from the
Open skill - /ux-icons
Symfony UX Icons for rendering SVG icons in Twig templates. Supports 200,000+ Iconify icons (Lucide, Heroicons, Tabler, Material Design, etc.), local SVG files, and custom icon sets with aliases. Use when displaying icons, configuring icon defaults, importing or locking
Open skill - /ux-map
Symfony UX Map for interactive maps with Leaflet or Google Maps in Symfony. Covers markers, polygons, polylines, circles, info windows, and LiveComponent integration. Use when displaying maps, placing markers, drawing shapes or routes, handling map events, building store
Open skill

