本文へ移動
cccskills
無料GitHub で公開

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 server, flash messages via streams, or real-time updates via Mercure/SSE. Code triggers: turbo-frame, turbo-stream, data-turbo-frame, data-turbo, data-turbo-action, turbo-stream-source, TurboStreamResponse, <twig:Turbo:Frame>, <twig:Turbo:Stream:Append>, <twig:Turbo:Stream:Replace>, turbo:before-fetch-request. Also trigger when the user asks "how to update part of the page without reload", "how to make navigation feel like SPA", "how to lazy-load a section", "how to do inline editing", "how to push real-time updates from server", "how to use Mercure with Turbo". Do NOT trigger for client-side JS behavior (use stimulus), server-rendered reactive components (use live-component), or reusable static UI (use twig-component).

インストール方法を見る

含まれるファイル(4)

  • SKILL.md11.2 KB
  • references/api.md10.8 KB
  • references/gotchas.md9.2 KB
  • references/patterns.md11.6 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Turbo

Hotwire Turbo provides SPA-like speed with server-rendered HTML. No JavaScript to write. Three components work together:

  • Drive -- Automatic AJAX navigation for all links and forms (zero config)
  • Frames -- Scoped navigation that updates only one section of the page
  • Streams -- Server-pushed DOM mutations (append, replace, remove, etc.)

Decision Tree

Need to update the page?
+-- Full page navigation          -> Turbo Drive (automatic, already active)
+-- Single section from user click -> Turbo Frame
+-- Multiple sections from action  -> Turbo Stream (HTTP response)
+-- Real-time from server/others   -> Turbo Stream (Mercure / SSE)

Installation

composer require symfony/ux-turbo

That's it. Turbo Drive is active immediately -- all links and forms become AJAX.

Turbo Drive

Automatic SPA-like navigation. Every <a> click and <form> submit is intercepted, fetched via AJAX, and the <body> is swapped. The browser URL and history update normally.

Disabling for Specific Elements

<!-- Disable on link/form -->
<a href="/external" data-turbo="false">External Link</a>

<!-- Disable for entire section -->
<div data-turbo="false">
    <a href="/normal">Normal link (no Turbo)</a>
</div>

History and Caching

<!-- Replace history instead of push -->
<a href="/page" data-turbo-action="replace">Replace History</a>

<!-- Force full reload when asset changes -->
<link rel="stylesheet" href="/app.css" data-turbo-track="reload">
<script src="/app.js" data-turbo-track="reload"></script>

Turbo Frames

Scope navigation to a section of the page. Links and forms inside a frame update only that frame's content. The rest of the page stays untouched.

Basic Frame

<!-- Page with frame -->
<turbo-frame id="messages">
    <h2>Messages</h2>
    <a href="/messages/1">View Message 1</a>  <!-- Updates only this frame -->
</turbo-frame>

<!-- /messages/1 response must contain a matching frame ID -->
<turbo-frame id="messages">
    <h2>Message 1</h2>
    <p>Content here...</p>
    <a href="/messages">Back to list</a>
</turbo-frame>

The server response is a full HTML page, but Turbo extracts only the matching <turbo-frame> and swaps it in.

Lazy Loading

Load frame content asynchronously after the page renders:

<turbo-frame id="notifications" src="/notifications" loading="lazy">
    <p>Loading...</p>
</turbo-frame>

Target Another Frame

A link inside one frame can update a different frame:

<turbo-frame id="sidebar">
    <a href="/item/1" data-turbo-frame="main-content">View Item</a>
</turbo-frame>

<turbo-frame id="main-content">
    <!-- Content replaced here -->
</turbo-frame>

Break Out of Frame

Navigate the entire page from within a frame:

<turbo-frame id="modal">
    <a href="/dashboard" data-turbo-frame="_top">Go to Dashboard</a>
</turbo-frame>

Frame with Form

Forms inside frames submit and update within that frame:

<turbo-frame id="search-results">
    <form action="/search" method="get">
        <input type="search" name="q">
        <button>Search</button>
    </form>
    <ul>
        {% for item in results %}
            <li>{{ item.name }}</li>
        {% endfor %}
    </ul>
</turbo-frame>

URL Sync

Update the browser URL when a frame navigates (useful for bookmarkable state):

<turbo-frame id="products" data-turbo-action="advance">
    <!-- Browser URL updates when this frame navigates -->
</turbo-frame>

Turbo Streams

Update multiple DOM elements from a single server response. Eight actions available (append, prepend, replace, update, remove, before, after, refresh), each targeting elements by ID or CSS selector.

Stream Actions

<turbo-stream action="append" target="messages">
    <template><div id="msg_1">New message</div></template>
</turbo-stream>

<turbo-stream action="prepend" target="messages">
    <template><div id="msg_0">First!</div></template>
</turbo-stream>

<turbo-stream action="replace" target="notification">
    <template><div id="notification">Updated!</div></template>
</turbo-stream>

<turbo-stream action="update" target="counter">
    <template>42</template>
</turbo-stream>

<turbo-stream action="remove" target="msg_5"></turbo-stream>

<turbo-stream action="before" target="msg_3">
    <template><div id="msg_2">Inserted before</div></template>
</turbo-stream>

<turbo-stream action="after" target="msg_3">
    <template><div id="msg_4">Inserted after</div></template>
</turbo-stream>

<turbo-stream action="refresh"></turbo-stream>

replace and update support an optional method="morph" attribute for smooth DOM morphing instead of full replacement:

<turbo-stream action="replace" method="morph" target="user-card">
    <template><div id="user-card">Updated content</div></template>
</turbo-stream>

Target Multiple Elements (CSS Selector)

Use targets (plural) with a CSS selector to affect multiple elements:

<turbo-stream action="remove" targets=".notification.read"></turbo-stream>

<turbo-stream action="update" targets=".price">
    <template>99.00 EUR</template>
</turbo-stream>

Twig Component Syntax for Streams

Since Symfony UX 2.22+, you can use <twig:Turbo:Stream:*> components instead of raw HTML:

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

<twig:Turbo:Stream:Update target="comment-count">
    {{ count }}
</twig:Turbo:Stream:Update>

<twig:Turbo:Stream:Remove target="msg_5" />

Symfony Integration

Stream Response from Controller

use Symfony\UX\Turbo\TurboBundle;

#[Route('/messages', name: 'message_create', methods: ['POST'])]
public function create(Request $request): Response
{
    $message = new Message();
    // ... handle form

    $this->em->persist($message);
    $this->em->flush();

    // Return stream response for Turbo requests
    $request->setRequestFormat(TurboBundle::STREAM_FORMAT);

    return $this->render('message/create.stream.html.twig', [
        'message' => $message,
        'count' => $count,
    ]);
}

You can also use the TurboStreamResponse helper or TurboStream helper methods for programmatic stream building.

Stream Template

{# templates/message/create.stream.html.twig #}
<turbo-stream action="append" target="messages">
    <template>
        {{ include('message/_message.html.twig', {message: message}) }}
    </template>
</turbo-stream>
<turbo-stream action="update" target="message-count">
    <template>{{ count }}</template>
</turbo-stream>
<turbo-stream action="replace" target="new-message-form">
    <template>
        {{ include('message/_form.html.twig', {message: null}) }}
    </template>
</turbo-stream>

Detect Frame Request

public function show(Request $request, int $id): Response
{
    if ($request->headers->has('Turbo-Frame')) {
        $frameId = $request->headers->get('Turbo-Frame');
        // Return only the frame content (or a full page -- Turbo extracts the frame)
    }

    return $this->render('page/show.html.twig');
}

Mercure Broadcasts (Real-time)

Push changes to all connected browsers via SSE:

use Symfony\UX\Turbo\Attribute\Broadcast;

#[Broadcast]
class Message
{
    // Entity changes broadcast automatically to subscribed clients
}
{# Subscribe to Mercure topic #}
<turbo-stream-source src="{{ mercure('chat-room-1')|escape('html_attr') }}">
</turbo-stream-source>

<div id="messages">
    {# Messages appear here in real-time #}
</div>

Common Patterns

Inline Edit

<!-- Display mode -->
<turbo-frame id="task_{{ task.id }}">
    <span>{{ task.title }}</span>
    <a href="/tasks/{{ task.id }}/edit">Edit</a>
</turbo-frame>

<!-- Edit mode (response from /tasks/1/edit) -->
<turbo-frame id="task_1">
    <form action="/tasks/1" method="post">
        <input name="title" value="Task title">
        <button>Save</button>
        <a href="/tasks/1">Cancel</a>
    </form>
</turbo-frame>

Modal in Frame

<turbo-frame id="modal"><!-- Empty by default --></turbo-frame>

<a href="/items/1/delete" data-turbo-frame="modal">Delete</a>

<!-- /items/1/delete response -->
<turbo-frame id="modal">
    <dialog open>
        <p>Confirm delete?</p>
        <form method="post">
            <button>Delete</button>
        </form>
        <a href="/items" data-turbo-frame="modal">Cancel</a>
    </dialog>
</turbo-frame>

Flash Messages with Stream

<turbo-stream action="prepend" target="flash-messages">
    <template>
        <div class="alert alert-success" role="alert">
            Item saved successfully!
        </div>
    </template>
</turbo-stream>

Key Principles

Server returns full HTML pages. Turbo works best when the server always returns a complete, valid HTML page. Turbo Drive replaces the body, Turbo Frames extract the matching frame. Don't try to return partial HTML snippets (except for Stream templates).

Frame IDs must match. The frame in the response must have the same id as the frame on the page. If they don't match, Turbo shows an error.

Streams are for side effects. Use Streams when a single action needs to update multiple unrelated parts of the page. If you're only updating one section, a Frame is simpler.

Stimulus complements Turbo. Turbo handles navigation and server communication. Stimulus handles client-side behavior (animations, toggles, clipboard). They work together -- Stimulus controllers survive Turbo Frame swaps within their scope, and reconnect properly on Drive navigation.

References

See Also

  • UX Map works inside Turbo Frames. The map Stimulus controller reconnects properly on frame swaps.
  • UX Icons are inline SVG and survive Turbo Drive navigation and Frame swaps with no special handling.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

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, deferred/lazy rendering, or any UI that updates itself based on user input. Code triggers: AsLiveComponent, LiveProp, LiveAction, data-model, data-loading, data-live-action-url, ComponentWithFormTrait, LiveListener, emit, defer, lazy, polling. Also trigger when the user asks "how to build a search that filters as I type", "how to validate a form in real-time", "how to make a reactive component in PHP", "how to build dependent selects", "how to defer component rendering", "how to communicate between components via emit", "how to bind a form to a LiveComponent". Do NOT trigger for static reusable UI without reactivity (use twig-component), pure client-side JS behavior (use stimulus), or page-level navigation (use turbo).

日本語の概要は準備中です。原文の説明を表示しています。

smnandre/symfony-ux-skills1802026年6月15日 更新

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 third-party JS libraries, or building toggles, dropdowns, modals, tabs, clipboard interactions. Code triggers: data-controller, data-action, data-target, data-*-value, data-*-class, data-*-outlet, stimulusFetch lazy, connect(), disconnect(), static targets, static values. Also trigger when the user asks "how do I add a click handler", "how to toggle a class", "how to build a dropdown/modal/tabs", "how to wrap a JS library in Symfony", "add keyboard shortcuts", "lazy-load a controller", "listen to global events", "communicate between controllers". Do NOT trigger for partial page updates without JS (use turbo), server-rendered reactivity (use live-component), or reusable Twig templates (use twig-component).

日本語の概要は準備中です。原文の説明を表示しています。

smnandre/symfony-ux-skills1802026年6月15日 更新

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.

日本語の概要は準備中です。原文の説明を表示しています。

smnandre/symfony-ux-skills1802026年6月15日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

smnandre/symfony-ux-skills1802026年6月15日 更新

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 on-demand icons, creating icon aliases, or styling SVG icons with CSS. Code triggers: <twig:ux:icon />, ux_icon(), UX_ICONS_DEFAULT_ICON_ATTRIBUTES, icon.yaml, icons/, iconify:, lucide:, heroicons:, tabler:, mdi:, bin/console ux:icons:lock, bin/console ux:icons:import. Also trigger when the user asks "how to add an icon", "how to use Lucide/Heroicons/Tabler icons", "how to render an SVG icon in Twig", "how to lock icons for production", "how to create icon aliases", "how to style an icon", "icon not found", "icon not rendering". Do NOT trigger for interactive maps (use ux-map) or general Twig components (use twig-component).

日本語の概要は準備中です。原文の説明を表示しています。

smnandre/symfony-ux-skills1802026年6月15日 更新

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 locators, using custom tile layers, or making maps reactive with LiveComponent. Code triggers: <twig:ux:map />, Map(), Point(), Marker(), Polygon(), Polyline(), Circle(), InfoWindow(), MapOptionsInterface, ComponentWithMapTrait, fitBoundsToMarkers, ux:map:marker:before-create, ux:map:connect, SYMFONY_UX_MAP_DSN. Also trigger when the user asks "how to display a map", "how to add markers", "how to draw a polygon on a map", "how to handle map click events", "how to make a reactive map", "how to use Leaflet in Symfony", "how to use Google Maps in Symfony", "map not showing", "map has zero height". Do NOT trigger for SVG icons (use ux-icons) or general frontend interactivity (use stimulus).

日本語の概要は準備中です。原文の説明を表示しています。

smnandre/symfony-ux-skills1802026年6月15日 更新

smnandre のスキルをすべて見る

このスキルの問題を報告する