Symfony Ux logo

Symfony Ux

Community
smnandre
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 question in Symfony. Also trigger when the user asks "which UX package should I use", "how to make this interactive", "should I use Stimulus or LiveComponent", "how to structure my Symfony frontend", "what is the difference between Turbo and LiveComponent", "should this be a Frame or a LiveComponent", "how do these UX packages work together", "what is the Symfony way to do frontend". Do NOT trigger when the user clearly names a specific tool (stimulus, turbo, twig-component, live-component, ux-icons, ux-map) -- defer to the specialized skill instead.

Overview

Publishersmnandre
Repositorysymfony-ux-skills
Skill namesymfony-ux
Stars
173
Forks
14
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by smnandre on GitHub. Read the source before you install it.

Installation

Install the Symfony Ux AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/smnandre/symfony-ux-skills.git /tmp/symfony-ux-skills
mkdir -p .claude/skills
cp -r /tmp/symfony-ux-skills/skills/symfony-ux .claude/skills/symfony-ux
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Symfony Ux in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Symfony Ux on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Symfony Ux is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Symfony UX

Modern frontend stack for Symfony. Build reactive UIs with minimal JavaScript using server-rendered HTML.

Symfony UX follows a progressive enhancement philosophy: start with plain HTML, add interactivity only where needed, and prefer server-side rendering over client-side JavaScript. Each tool solves a specific problem -- pick the simplest one that fits.

Decision Tree: Which Tool?

Need frontend interactivity?
|
+-- Pure JavaScript behavior (no server)?
|   -> Stimulus
|      (DOM manipulation, event handling, third-party libs)
|
+-- Navigation / partial page updates?
|   -> Turbo
|      +-- Full page AJAX        -> Turbo Drive (automatic, zero config)
|      +-- Single section update  -> Turbo Frame
|      +-- Multiple sections      -> Turbo Stream
|
+-- Reusable UI component?
|   |
|   +-- Static (no live updates)?
|   |   -> TwigComponent
|   |      (props, blocks, computed properties)
|   |
|   +-- Dynamic (re-renders on interaction)?
|       -> LiveComponent
|          (data binding, actions, forms, real-time validation)
|
+-- Need icons?
|   -> UX Icons
|      (inline SVG from 200+ Iconify sets or local files)
|
+-- Need an interactive map?
|   -> UX Map
|      (Leaflet or Google Maps, markers, polygons, circles)
|
+-- Real-time (WebSocket/SSE)?
    -> Turbo Stream + Mercure

The tools compose naturally. A typical page uses Turbo Drive for navigation, Turbo Frames for partial sections, TwigComponents for reusable UI elements, LiveComponents for reactive forms/search, and Stimulus for client-side behavior that doesn't need a server round-trip.

Quick Comparison

FeatureStimulusTurboTwigComponentLiveComponent
JavaScript requiredYes (minimal)NoNoNo
Server re-renderNoYes (page/frame)NoYes (AJAX)
State managementJS onlyURL/ServerProps (immutable)LiveProp (mutable)
Two-way bindingManualNoNodata-model
Real-time capableManualYes (Streams+Mercure)NoYes (polling/emit)
Lazy loadingYes (stimulusFetch)Yes (lazy frames)NoYes (defer/lazy)

UX Icons and UX Map are utility packages that complement the tools above. Icons provides inline SVG rendering (local files + 200,000+ Iconify icons). Map provides interactive maps (Leaflet or Google Maps) with PHP-first configuration. Both work inside TwigComponents, LiveComponents, and Turbo Frames. Map also has a dedicated ComponentWithMapTrait for reactive maps in LiveComponents.

Installation

bash
# All core packages
composer require symfony/ux-turbo symfony/stimulus-bundle \
    symfony/ux-twig-component symfony/ux-live-component

# Individual
composer require symfony/stimulus-bundle      # Stimulus
composer require symfony/ux-turbo             # Turbo
composer require symfony/ux-twig-component    # TwigComponent
composer require symfony/ux-live-component    # LiveComponent (includes TwigComponent)
composer require symfony/ux-icons             # UX Icons
composer require symfony/ux-map               # UX Map (then add a renderer below)
composer require symfony/ux-leaflet-map       # Leaflet renderer (free)
composer require symfony/ux-google-map        # Google Maps renderer (requires API key)

Common Patterns

Pattern 1: Static Component (TwigComponent)

Reusable UI with no interactivity. Use for buttons, cards, alerts, badges.

php
#[AsTwigComponent]
final class Alert
{
    public string $type = 'info';
    public string $message;
}
twig
{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ type }}" {{ attributes }}>
    {{ message }}
</div>
twig
<twig:Alert type="success" message="Saved!" />

Pattern 2: Component + JS Behavior (TwigComponent + Stimulus)

Server-rendered component with client-side interactivity. Use when the interaction is purely cosmetic (toggling, animations, third-party JS libs) and doesn't need server data.

php
#[AsTwigComponent]
final class Dropdown
{
    public string $label;
}
twig
{# templates/components/Dropdown.html.twig #}
<div data-controller="dropdown" {{ attributes }}>
    <button data-action="click->dropdown#toggle">{{ label }}</button>
    <div data-dropdown-target="menu" hidden>
        {% block content %}{% endblock %}
    </div>
</div>

Pattern 3: Server-Reactive Component (LiveComponent)

Component that re-renders via AJAX on user input. Use for search boxes, filters, forms with real-time validation, anything that needs server data on every interaction.

php
#[AsLiveComponent]
final class SearchBox
{
    use DefaultActionTrait;

    #[LiveProp(writable: true, url: true)]
    public string $query = '';

    public function __construct(
        private readonly ProductRepository $products,
    ) {}

    public function getResults(): array
    {
        return $this->products->search($this->query);
    }
}
twig
<div {{ attributes }}>
    <input data-model="debounce(300)|query" placeholder="Search...">
    <div data-loading="addClass(opacity-50)">
        {% for item in this.results %}
            <div>{{ item.name }}</div>
        {% endfor %}
    </div>
</div>

Pattern 4: Frame-Based Navigation (Turbo Frame)

Partial page updates without full reload. Use for pagination, inline editing, tabbed content, modals loaded from server.

twig
<turbo-frame id="product-list">
    {% for product in products %}
        <a href="{{ path('product_show', {id: product.id}) }}">
            {{ product.name }}
        </a>
    {% endfor %}
</turbo-frame>

Pattern 5: Multi-Section Update (Turbo Stream)

Update multiple page areas from a single server response. Use after form submissions that affect several parts of the page.

php
#[Route('/comments', methods: ['POST'])]
public function create(Request $request): Response
{
    // ... save comment

    $request->setRequestFormat(TurboBundle::STREAM_FORMAT);
    return $this->render('comment/create.stream.html.twig', [
        'comment' => $comment,
        'count' => $count,
    ]);
}
twig
{# create.stream.html.twig #}
<turbo-stream action="append" target="comments">
    <template>{{ include('comment/_comment.html.twig') }}</template>
</turbo-stream>
<turbo-stream action="update" target="comment-count">
    <template>{{ count }}</template>
</turbo-stream>

You can also use the Twig component syntax:

twig
<twig:Turbo:Stream:Append target="comments">
    {{ include('comment/_comment.html.twig') }}
</twig:Turbo:Stream:Append>

Pattern 6: LiveComponent Inside Turbo Frame

Combine for complex UIs -- the frame scopes navigation, the LiveComponent handles reactivity within that scope.

twig
<turbo-frame id="search-section">
    <twig:ProductSearch />
</turbo-frame>

Pattern 7: Real-Time Updates (Mercure + Turbo Stream)

Broadcast server-side events to all connected browsers via SSE.

php
use Symfony\UX\Turbo\Attribute\Broadcast;

#[Broadcast]
class Message
{
    // Entity changes broadcast automatically
}
twig
<turbo-stream-source src="{{ mercure('chat')|escape('html_attr') }}">
</turbo-stream-source>
<div id="messages">...</div>

When to Use What

Stimulus -- Adding JS behavior to existing HTML, integrating third-party libraries (charts, datepickers, maps), client-only interactions (toggles, tabs, clipboard), anything where you need full control over JavaScript execution.

Turbo Drive -- SPA-like navigation. Automatic, zero config. Just install and all links/forms become AJAX. Opt out selectively with data-turbo="false".

Turbo Frames -- Loading or updating a single page section: inline editing, pagination within a section, modal content loading, lazy-loaded sidebar.

Turbo Streams -- Updating multiple page sections at once, real-time broadcasts (with Mercure), flash messages after form submit, delete confirmations that update a list and a counter.

TwigComponent -- Reusable UI elements (buttons, cards, alerts, form widgets), consistent styling and markup, no server interaction needed after initial render, component composition and nesting.

LiveComponent -- Forms with real-time validation, search with live results, data binding (like Vue/React but server-rendered), any component whose state changes based on user interaction, when you want to avoid writing JavaScript entirely.

UX Icons -- Rendering SVG icons in templates. Supports 200+ Iconify icon sets (Lucide, Tabler, Heroicons, MDI...) and local SVG files. Icons are inlined as <svg> -- no icon fonts, no runtime HTTP requests. Use <twig:ux:icon name="lucide:check" />.

UX Map -- Displaying interactive maps with markers, polygons, polylines, circles, and info windows. Build the map in PHP (new Map()), render in Twig (ux_map(map)). Supports Leaflet (free) and Google Maps. Works inside LiveComponents via ComponentWithMapTrait for reactive maps.

Combining Tools

+-----------------------------------------------------+
|                     Page                            |
|  +------------------------------------------------+ |
|  | Turbo Drive (automatic full-page AJAX)         | |
|  |  +------------------------------------------+  | |
|  |  | Turbo Frame (partial section)            |  | |
|  |  |  +------------------------------------+  |  | |
|  |  |  | LiveComponent (reactive)           |  |  | |
|  |  |  |  +------------------------------+  |  |  | |
|  |  |  |  | TwigComponent (static)       |  |  |  | |
|  |  |  |  |  + Stimulus (JS behavior)    |  |  |  | |
|  |  |  |  +------------------------------+  |  |  | |
|  |  |  +------------------------------------+  |  | |
|  |  +------------------------------------------+  | |
|  +------------------------------------------------+ |
+-----------------------------------------------------+

File Structure

src/
  Twig/
    Components/
      Alert.php              # TwigComponent
      Button.php             # TwigComponent
      SearchBox.php          # LiveComponent
      ProductForm.php        # LiveComponent

templates/
  components/
    Alert.html.twig
    Button.html.twig
    SearchBox.html.twig
    ProductForm.html.twig

assets/
  controllers/
    dropdown_controller.js   # Stimulus
    modal_controller.js      # Stimulus
    chart_controller.js      # Stimulus
  icons/
    close.svg                # UX Icons (local)
    header/
      logo.svg               # UX Icons (namespaced: header:logo)

Anti-Patterns to Avoid

Don't use LiveComponent for static content. If a component never re-renders after initial load, use TwigComponent instead -- LiveComponent adds unnecessary overhead (AJAX requests, state serialization).

Don't use Turbo Streams when a Frame is enough. If you're only updating one section of the page, a Turbo Frame is simpler and requires no special response format.

Don't reach for Stimulus when Turbo handles it. Before writing a Stimulus controller for a link or form interaction, check if Turbo Drive/Frames already handle it.

Don't fight Turbo Drive. If a link or form behaves oddly with Turbo, the fix is usually to ensure the server returns a proper full HTML page, not to disable Turbo.

Anti-Patterns for Icons and Map

Don't use icon fonts when UX Icons is available. Inline SVG is more accessible, stylable, and doesn't require extra HTTP requests.

Don't hardcode map center/zoom when you have markers. Use fitBoundsToMarkers() to auto-fit the viewport.

Don't forget explicit height on map containers. Without it, the <div> collapses to 0px and the map is invisible.

Don't deploy with on-demand icons enabled. Run php bin/console ux:icons:lock before deploying to avoid runtime HTTP requests to the Iconify API.

Related Skills

For detailed documentation on each tool, read the dedicated skill:

  • Stimulus: Controllers, targets, values, actions, outlets, lazy loading
  • Turbo: Drive, Frames, Streams, Mercure integration, <twig:Turbo:Stream:*> components
  • TwigComponent: Props, blocks, computed properties, anonymous components, attributes
  • LiveComponent: LiveProp, LiveAction, data-model, forms, emit/listen, polling, defer/lazy
  • UX Icons: Iconify on-demand, local SVG, icon sets, aliases, ux:icons:lock CLI
  • UX Map: Leaflet/Google Maps, markers, polygons, polylines, circles, ComponentWithMapTrait

Frequently asked questions

What does the Symfony Ux AI skill do?

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 question in Symfony. Also trigger when the user asks "which UX package should I use", "how to make this interactive", "should I use Stimulus or LiveComponent", "how to structure my Symfony frontend", "what is the difference between Turbo and LiveComponent", "should this be a Frame or a LiveComponent", "how do these UX pac...

Why use Symfony Ux on TypingMind?

Because you install it once and use it with any model. Symfony Ux is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Symfony Ux in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/smnandre/symfony-ux-skills/tree/main/skills/symfony-ux. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Symfony Ux?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Symfony Ux?

As many as you like. As long as a model supports skills, you can use Symfony Ux with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Symfony Ux AI skill free?

Yes. It is published on GitHub by smnandre under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇