symfony-ux

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Symfony UX

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.
Symfony 专属的现代前端栈,借助服务端渲染的HTML,用最少的JavaScript构建响应式UI。
Symfony UX 遵循渐进增强理念:从纯HTML开始,仅在需要的地方添加交互能力,优先选择服务端渲染而非客户端JavaScript。每个工具都解决特定问题——选择最适配需求的最简方案即可。

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)
|
+-- 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.
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)
|
+-- Real-time (WebSocket/SSE)?
    -> Turbo Stream + Mercure
这些工具可以自然组合使用。一个典型页面会用Turbo Drive处理导航,Turbo Frames处理局部区块更新,TwigComponents实现可复用UI元素,LiveComponents实现响应式表单/搜索,Stimulus处理不需要服务端往返的客户端行为。

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)
功能StimulusTurboTwigComponentLiveComponent
需引入JavaScript是(极少)
服务端重渲染是(页面/Frame级别)是(AJAX)
状态管理仅JSURL/服务端Props(不可变)LiveProp(可变)
双向绑定手动实现data-model
支持实时能力手动实现是(Streams+Mercure)是(轮询/事件触发)
懒加载支持(stimulusFetch)支持(lazy frames)支持(defer/lazy)

Installation

安装

bash
undefined
bash
undefined

All core packages

All core packages

composer require symfony/ux-turbo symfony/stimulus-bundle
symfony/ux-twig-component symfony/ux-live-component
composer require symfony/ux-turbo symfony/stimulus-bundle
symfony/ux-twig-component symfony/ux-live-component

Individual

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)
undefined
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)
undefined

Common Patterns

常用模式

Pattern 1: Static Component (TwigComponent)

模式1:静态组件(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!" />
无交互的可复用UI,适用于按钮、卡片、提示框、徽章等场景。
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)

模式2:组件+JS行为(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>
服务端渲染的组件附加客户端交互能力,适用于纯展示类交互(切换、动画、第三方JS库集成),且不需要服务端数据的场景。
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)

模式3:服务端响应式组件(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>
用户输入时通过AJAX重渲染的组件,适用于搜索框、筛选器、带实时校验的表单,以及所有每次交互都需要服务端数据的场景。
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)

模式4:基于Frame的导航(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>
无需全页重载的局部页面更新,适用于分页、内联编辑、标签页内容、服务端加载模态框等场景。
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)

模式5:多区块更新(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>
通过单次服务端响应更新多个页面区域,适用于表单提交后会影响页面多个部分的场景。
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>
你也可以使用Twig组件语法:
twig
<twig:Turbo:Stream:Append target="comments">
    {{ include('comment/_comment.html.twig') }}
</twig:Turbo:Stream:Append>

Pattern 6: LiveComponent Inside Turbo Frame

模式6:Turbo Frame中嵌套LiveComponent

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>
结合使用实现复杂UI——Frame限定导航作用域,LiveComponent处理该作用域内的响应式逻辑。
twig
<turbo-frame id="search-section">
    <twig:ProductSearch />
</turbo-frame>

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

模式7:实时更新(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>
通过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.
Stimulus —— 为现有HTML添加JS行为、集成第三方库(图表、日期选择器、地图)、纯客户端交互(切换、标签页、剪贴板操作),以及所有需要完全控制JavaScript执行的场景。
Turbo Drive —— 类SPA的导航体验,自动生效零配置。安装后所有链接和表单默认走AJAX请求,可通过
data-turbo="false"
选择性关闭。
Turbo Frames —— 加载或更新单个页面区块:内联编辑、区块内分页、模态框内容加载、懒加载侧边栏。
Turbo Streams —— 同时更新多个页面区块、结合Mercure实现实时广播、表单提交后的消息提示、删除操作后同时更新列表和计数。
TwigComponent —— 可复用UI元素(按钮、卡片、提示框、表单控件)、统一样式和标记、初始渲染后不需要服务端交互、组件组合和嵌套场景。
LiveComponent —— 带实时校验的表单、带实时结果的搜索、数据绑定(类似Vue/React但基于服务端渲染)、所有状态随用户交互变化的组件,以及不想编写JavaScript的场景。

Combining Tools

工具组合

+-----------------------------------------------------+
|                     Page                             |
|  +------------------------------------------------+  |
|  | Turbo Drive (automatic full-page AJAX)          |  |
|  |  +------------------------------------------+  |  |
|  |  | Turbo Frame (partial section)             |  |  |
|  |  |  +------------------------------------+  |  |  |
|  |  |  | LiveComponent (reactive)            |  |  |  |
|  |  |  |  +------------------------------+  |  |  |  |
|  |  |  |  | TwigComponent (static)        |  |  |  |  |
|  |  |  |  |  + Stimulus (JS behavior)     |  |  |  |  |
|  |  |  |  +------------------------------+  |  |  |  |
|  |  |  +------------------------------------+  |  |  |
|  |  +------------------------------------------+  |  |
|  +------------------------------------------------+  |
+-----------------------------------------------------+
+-----------------------------------------------------+
|                     页面                             |
|  +------------------------------------------------+  |
|  | Turbo Drive (自动全页AJAX)                     |  |
|  |  +------------------------------------------+  |  |
|  |  | Turbo Frame (局部区块)                    |  |  |
|  |  |  +------------------------------------+  |  |  |
|  |  |  | LiveComponent (响应式)               |  |  |  |
|  |  |  |  +------------------------------+  |  |  |  |
|  |  |  |  | TwigComponent (静态)          |  |  |  |  |
|  |  |  |  |  + Stimulus (JS行为)          |  |  |  |  |
|  |  |  |  +------------------------------+  |  |  |  |
|  |  |  +------------------------------------+  |  |  |
|  |  +------------------------------------------+  |  |
|  +------------------------------------------------+  |
+-----------------------------------------------------+

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

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.
不要对静态内容使用LiveComponent。如果组件在初始加载后永远不会重渲染,改用TwigComponent即可——LiveComponent会带来不必要的开销(AJAX请求、状态序列化)。
能用Frame实现就不要用Turbo Streams。如果你只需要更新页面的一个区块,Turbo Frame更简单,也不需要特殊的响应格式。
Turbo能实现的逻辑不要用Stimulus。在为链接或表单交互编写Stimulus控制器之前,先确认Turbo Drive/Frames是否已经支持该能力。
不要和Turbo Drive对抗。如果某个链接或表单在Turbo下表现异常,通常的修复方案是确保服务端返回完整的合法HTML页面,而不是直接禁用Turbo。

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
如需了解每个工具的详细文档,请查阅对应专属技能:
  • Stimulus:控制器、目标元素、值、动作、出口、懒加载
  • Turbo:Drive、Frames、Streams、Mercure集成、
    <twig:Turbo:Stream:*>
    组件
  • TwigComponent:Props、区块、计算属性、匿名组件、属性
  • LiveComponent:LiveProp、LiveAction、data-model、表单、事件触发/监听、轮询、defer/lazy