Skip to content

/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

shell
$ npx -y skills add smnandre/symfony-ux-skills --skill twig-component --agent claude-code

How 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
How auto-invocation works

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.md
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">&times;</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 }}</sp
Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withsymfony-ux-skills

AI agent skills for the Symfony UX frontend stack -- Stimulus, Turbo, TwigComponent, LiveComponent, UX Icons and UX Map. By Simon Andre

Get the whole plugin, auto-invoked
Stats
165
Stars
0
Views
12
Forks
Maintained
Maintenance
MIT
License
1mo ago
Last commit
5mo ago
Created

Repo: smnandre/symfony-ux-skills

Other skills on symfony-ux-skills.