symfony-ux について
Symfony UXは、Symfony開発者向けのフロントエンドツールセットです。サーバーサイドレンダリングを基本に、Stimulus、Turbo、TwigComponent、LiveComponentなどを組み合わせ、必要な箇所に操作性を加えられます。
主な機能
- 01Stimulusによる軽量なJavaScript動作
- 02Turboによるナビゲーションと部分更新
- 03TwigComponentによる再利用可能なUIコンポーネント
- 04LiveComponentによるサーバー側のリアクティブな操作
- 05UX IconsによるインラインSVGアイコンの描画
- 06UX Mapによるインタラクティブマップの設定
活用シーン
- 01既存HTMLにトグルやタブの動作を追加
- 02ページ全体を再読み込みせずページネーションやモーダルを更新
- 03リアルタイム検証付きのフォームや検索を構築
- 04Turbo Streamsで複数のページ領域を更新
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ smnandre/ symfony-ux-skills --skill symfony-uxインストール後、Claude Code で/symfony-uxを入力して読み込まれたか確認します。
詳細
Symfony UX
Symfony 向けのモダンなフロントエンドスタックです。サーバー側でレンダリングした HTML を使い、最小限の JavaScript でリアクティブな UI を構築できます。
Symfony UX はプログレッシブエンハンスメントの考え方に基づいています。プレーンな HTML から始め、必要な箇所にだけインタラクティブ性を加え、クライアント側の JavaScript よりサーバーサイドレンダリングを優先します。各ツールは特定の課題を解決します。用途に合った最もシンプルなものを選んでください。
決定ツリー:どのツールを使う?
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
これらのツールは自然に連携します。一般的なページでは、ナビゲーションに Turbo Drive、部分セクションに Turbo Frames、再利用可能な UI 要素に TwigComponents、リアクティブなフォームや検索に LiveComponents、サーバーとの往復通信が不要なクライアント側の動作に Stimulus を使用します。
クイック比較
| 機能 | Stimulus | Turbo | TwigComponent | LiveComponent |
|---|---|---|---|---|
| JavaScript が必要 | はい(最小限) | いいえ | いいえ | いいえ |
| サーバーで再レンダリング | いいえ | はい(ページ/フレーム) | いいえ | はい(AJAX) |
| 状態管理 | JS のみ | URL/サーバー | Props(不変) | LiveProp(可変) |
| 双方向バインディング | 手動 | いいえ | いいえ | data-model |
| リアルタイム対応 | 手動 | はい(Streams+Mercure) | いいえ | はい(ポーリング/emit) |
| 遅延読み込み | はい(stimulusFetch) | はい(遅延フレーム) | いいえ | はい(defer/lazy) |
UX Icons と UX Map は、上記のツールを補完するユーティリティパッケージです。Icons はインライン SVG のレンダリングを提供し(ローカルファイルと 200,000 個以上の Iconify アイコンに対応)、Map は PHP を中心とした設定でインタラクティブな地図を提供します(Leaflet または Google Maps)。どちらも TwigComponents、LiveComponents、Turbo Frames 内で動作します。Map には、LiveComponents でリアクティブな地図を扱うための専用の ComponentWithMapTrait もあります。
インストール
# 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)
よくあるパターン
パターン 1:静的コンポーネント(TwigComponent)
インタラクティブ性のない再利用可能な UI です。ボタン、カード、アラート、バッジなどに使用します。
#[AsTwigComponent]
final class Alert
{
public string $type = 'info';
public string $message;
}
{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ type }}" {{ attributes }}>
{{ message }}
</div>
<twig:Alert type="success" message="Saved!" />
パターン 2:コンポーネント+JS の動作(TwigComponent + Stimulus)
クライアント側のインタラクティブ性を備えた、サーバー側でレンダリングされるコンポーネントです。インタラクションが見た目だけのもの(切り替え、アニメーション、サードパーティ製 JS ライブラリなど)で、サーバーのデータを必要としない場合に使用します。
#[AsTwigComponent]
final class Dropdown
{
public string $label;
}
{# 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>
パターン 3:サーバーリアクティブコンポーネント(LiveComponent)
ユーザー入力に応じて AJAX 経由で再レンダリングされるコンポーネントです。検索ボックス、フィルター、リアルタイム検証を行うフォームなど、インタラクションのたびにサーバーのデータが必要な場合に使用します。
#[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);
}
}
<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>
パターン 4:フレームベースのナビゲーション(Turbo Frame)
ページ全体を再読み込みせずにページの一部を更新します。ページネーション、インライン編集、タブ付きコンテンツ、サーバーから読み込むモーダルなどに使用します。
<turbo-frame id="product-list">
{% for product in products %}
<a href="{{ path('product_show', {id: product.id}) }}">
{{ product.name }}
</a>
{% endfor %}
</turbo-frame>
パターン 5:複数セクションの更新(Turbo Stream)
1 つのサーバー応答から、ページの複数箇所を更新します。ページのいくつかの部分に影響するフォーム送信後などに使用します。
#[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,
]);
}
{# 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:Turbo:Stream:Append target="comments">
{{ include('comment/_comment.html.twig') }}
</twig:Turbo:Stream:Append>
パターン 6:Turbo Frame 内の LiveComponent
複雑な UI を組み合わせて構築します。フレームがナビゲーションの範囲を限定し、その範囲内のリアクティブな動作を LiveComponent が処理します。
<turbo-frame id="search-section">
<twig:ProductSearch />
</turbo-frame>
パターン 7:リアルタイム更新(Mercure + Turbo Stream)
SSE を介してサーバー側のイベントを、接続中のすべてのブラウザーにブロードキャストします。
use Symfony\UX\Turbo\Attribute\Broadcast;
#[Broadcast]
class Message
{
// Entity changes broadcast automatically
}
<turbo-stream-source src="{{ mercure('chat')|escape('html_attr') }}">
</turbo-stream-source>
<div id="messages">...</div>
何をいつ使うか
Stimulus — 既存のHTMLにJSの動作を追加する場合、サードパーティーライブラリ(チャート、日付選択ツール、地図など)を統合する場合、クライアント側のみのインタラクション(切り替え、タブ、クリップボードなど)を実装する場合など、JavaScriptの実行を完全に制御する必要があるときに使います。
Turbo Drive — SPAのようなナビゲーションに使います。自動で動作し、設定は不要です。インストールするだけで、すべてのリンクとフォームがAJAXになります。data-turbo="false"を指定すると、必要な箇所だけ無効にできます。
Turbo Frames — インライン編集、セクション内のページネーション、モーダルコンテンツの読み込み、遅延読み込みされるサイドバーなど、ページ内の単一セクションを読み込んだり更新したりする場合に使います。
Turbo Streams — 複数のページセクションを一度に更新する場合、リアルタイム配信(Mercureを使用)する場合、フォーム送信後にフラッシュメッセージを表示する場合、削除確認後にリストとカウンターを更新する場合などに使います。
TwigComponent — ボタン、カード、アラート、フォームウィジェットなど、再利用可能なUI要素に使います。スタイルとマークアップを統一でき、初回レンダリング後のサーバーとのやり取りは不要です。コンポーネントの組み合わせや入れ子にも対応します。
LiveComponent — リアルタイム検証を行うフォーム、結果をリアルタイムで表示する検索、データバインディング(Vue/Reactのような仕組みですが、サーバー側でレンダリングします)、ユーザー操作に応じて状態が変化するコンポーネントなど、JavaScriptの記述を完全に避けたい場合に使います。
UX Icons — テンプレートでSVGアイコンをレンダリングします。200以上のIconifyアイコンセット(Lucide、Tabler、Heroicons、MDIなど)とローカルのSVGファイルに対応しています。アイコンは<svg>としてインライン化されるため、アイコンフォントや実行時のHTTPリクエストは不要です。<twig:ux:icon name="lucide:check" />を使います。
UX Map — マーカー、ポリゴン、ポリライン、円、情報ウィンドウを備えたインタラクティブな地図を表示します。PHPで地図を構築し(new Map())、Twigでレンダリングします(ux_map(map))。Leaflet(無料)とGoogle Mapsに対応しています。ComponentWithMapTraitを使うと、LiveComponents内でもリアクティブな地図を利用できます。
ツールの組み合わせ
+-----------------------------------------------------+
| Page |
| +------------------------------------------------+ |
| | Turbo Drive (automatic full-page AJAX) | |
| | +------------------------------------------+ | |
| | | Turbo Frame (partial section) | | |
| | | +------------------------------------+ | | |
| | | | LiveComponent (reactive) | | | |
| | | | +------------------------------+ | | | |
| | | | | TwigComponent (static) | | | | |
| | | | | + Stimulus (JS behavior) | | | | |
| | | | +------------------------------+ | | | |
| | | +------------------------------------+ | | |
| | +------------------------------------------+ | |
| +------------------------------------------------+ |
+-----------------------------------------------------+
ファイル構成
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)
避けるべきアンチパターン
静的コンテンツにLiveComponentを使わないでください。 初回読み込み後に再レンダリングされないコンポーネントには、代わりにTwigComponentを使います。LiveComponentではAJAXリクエストや状態のシリアライズなど、不要なオーバーヘッドが発生します。
Turbo Frameで十分な場合にTurbo Streamsを使わないでください。 ページ内の1つのセクションだけを更新するなら、Turbo Frameのほうがシンプルで、特別なレスポンス形式も必要ありません。
Turboで処理できる場合にStimulusを使わないでください。 リンクやフォームの操作にStimulusコントローラーを書く前に、Turbo Drive/Framesですでに処理できないか確認してください。
Turbo Driveに逆らわないでください。 Turboを使うとリンクやフォームの動作がおかしくなる場合は、Turboを無効にするのではなく、通常はサーバーが適切な完全なHTMLページを返すように修正します。
アイコンと地図に関するアンチパターン
UX Iconsが使える場合にアイコンフォントを使わないでください。 インラインSVGはアクセシビリティに優れ、スタイルを適用しやすく、追加のHTTPリクエストも必要ありません。
マーカーがある場合に地図の中心やズームを固定しないでください。 fitBoundsToMarkers()を使って、表示範囲に自動で合わせます。
地図コンテナーに高さを明示するのを忘れないでください。 高さが指定されていないと<div>が0pxに縮み、地図が表示されません。
オンデマンドアイコンを有効にしたままデプロイしないでください。 Iconify APIへの実行時HTTPリクエストを避けるため、デプロイ前にphp bin/console ux:icons:lockを実行してください。
関連スキル
各ツールの詳しいドキュメントについては、専用スキルを参照してください。
- Stimulus:コントローラー、ターゲット、値、アクション、アウトレット、遅延読み込み
- Turbo:Drive、Frames、Streams、Mercureとの統合、
<twig:Turbo:Stream:*>コンポーネント - TwigComponent:Props、ブロック、計算プロパティ、匿名コンポーネント、属性
- LiveComponent:LiveProp、LiveAction、data-model、フォーム、emit/listen、ポーリング、defer/lazy
- UX Icons:Iconifyのオンデマンド利用、ローカルSVG、アイコンセット、エイリアス、
ux:icons:lockCLI - UX Map:Leaflet/Google Maps、マーカー、ポリゴン、ポリライン、円、
ComponentWithMapTrait