stimulus について
Stimulus の利用方法を扱うスキルです。data 属性で既存の HTML に動作を追加するフレームワークで、HTML を基準に、コントローラーの接続、型付き値、アクション、outlet、遅延読み込みを説明します。
主な機能
- 01data 属性によるコントローラー接続
- 02イベントとコントローラーアクションの紐付け
- 03型付き値とターゲットの利用
- 04outlet によるコントローラー間連携
- 05コントローラー依存関係の遅延読み込み
- 06Symfony と Twig との統合
活用シーン
- 01サーバー描画 HTML への動作追加
- 02ボタン、フォーム、入力イベントの処理
- 03関連するコントローラーインスタンスの連携
- 04要素が表示されたときの重い依存関係の読み込み
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ smnandre/ symfony-ux-skills --skill stimulusインストール後、Claude Code で/stimulusを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/api.mdgotchas.mdpatterns.md詳細
Stimulus
JavaScript オブジェクトをデータ属性を介して HTML に接続する、軽量な JavaScript フレームワークです。Stimulus は HTML をレンダリングするのではなく、サーバーでレンダリングされた HTML に動作を追加します。
基本的な考え方は、HTML が信頼できる唯一の情報源であり、JavaScript コントローラーが要素に接続され、データ属性がその配線を担うというものです。AssetMapper を使えば、ビルド手順は必要ありません。
クイックリファレンス
data-controller="name" attach controller to element
data-name-target="item" mark element as a target
data-action="event->name#method" bind event to controller method
data-name-key-value="..." pass typed data to controller
data-name-key-class="..." configure CSS class names
data-name-other-outlet=".selector" reference another controller instance
コントローラーのひな形
// assets/controllers/example_controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['input', 'output'];
static values = { url: String, delay: { type: Number, default: 300 } };
static classes = ['loading'];
static outlets = ['other'];
connect() {
// Called when controller connects to DOM
}
disconnect() {
// Called when controller disconnects -- clean up here
}
submit(event) {
// Action method
}
}
ファイルの命名規則:hello_controller.js は data-controller="hello" に対応します。サブディレクトリでは区切り文字として -- を使います:components/modal_controller.js は data-controller="components--modal" に対応します。
HTML の接続例
基本的なコントローラー
<div data-controller="hello">
<input data-hello-target="name" type="text">
<button data-action="click->hello#greet">Greet</button>
<span data-hello-target="output"></span>
</div>
サーバーからの値(Twig)
値属性を介してサーバーのデータをコントローラーに渡します。値は型付けされ、自動的に解析されます。
<div data-controller="map"
data-map-latitude-value="{{ place.lat }}"
data-map-longitude-value="{{ place.lng }}"
data-map-zoom-value="12">
</div>
使用できる型:String、Number、Boolean、Array、Object。値が変更されると、{name}ValueChanged() コールバックが呼び出されます。
アクション
形式は event->controller#method です。要素の種類ごとにデフォルトのイベント(ボタンは click、入力欄は input、フォームは submit)があるため、イベントは省略できます。
{# Explicit event #}
<button data-action="click->hello#greet">Greet</button>
{# Default event (click for button) #}
<button data-action="hello#greet">Greet</button>
{# Multiple actions on same element #}
<input type="text"
data-action="focus->field#highlight blur->field#normalize input->field#validate">
{# Prevent default #}
<form data-action="submit->form#validate:prevent">
{# Keyboard shortcuts #}
<div data-action="keydown.esc@window->modal#close">
<input data-action="keydown.enter->modal#submit keydown.ctrl+s->modal#save">
{# Global events (window/document) #}
<div data-action="resize@window->sidebar#adjust click@document->sidebar#closeOutside">
CSS クラス
CSS クラス名を外部化して、コントローラーを汎用的なものに保ちます。
<button data-controller="button"
data-button-loading-class="opacity-50 cursor-wait"
data-button-active-class="bg-blue-600"
data-action="click->button#submit">
Submit
</button>
// In controller
this.element.classList.add(...this.loadingClasses);
複数のコントローラー
要素には複数のコントローラーを設定できます。
<div data-controller="dropdown tooltip"
data-action="mouseenter->tooltip#show mouseleave->tooltip#hide">
<button data-action="click->dropdown#toggle">Menu</button>
<ul data-dropdown-target="menu" hidden>...</ul>
</div>
アウトレット(コントローラー間の通信)
CSS セレクターを使って、ほかのコントローラーのインスタンスを参照します。
<div data-controller="player"
data-player-playlist-outlet="#playlist">
<button data-action="click->player#playNext">Next</button>
</div>
<ul id="playlist" data-controller="playlist">
<li data-playlist-target="track">Song 1</li>
<li data-playlist-target="track">Song 2</li>
</ul>
// In player controller
static outlets = ['playlist'];
playNext() {
const tracks = this.playlistOutlet.trackTargets;
// ...
}
遅延読み込み(重い依存関係)
要素がビューポートに表示されたときにのみ、コントローラーの JS を読み込みます。チャートライブラリ、エディター、マップなど、重い依存関係を持つコントローラーに使用します。
/* stimulusFetch: 'lazy' */
import { Controller } from '@hotwired/stimulus';
import Chart from 'chart.js';
export default class extends Controller {
connect() {
// Chart.js is only loaded when this element enters the viewport
}
}
/* stimulusFetch: 'lazy' */ コメントは、ファイルの先頭行に記述する必要があります。
Symfony / Twig との統合
生のデータ属性を使う方法を推奨します。どこでも使えて読みやすく、特別なヘルパーも必要ありません。
{# Raw attributes (preferred) #}
<div data-controller="search"
data-search-url-value="{{ path('api_search') }}">
複雑なケースや、属性をプログラムで生成する場合には Twig ヘルパーを利用できます。
{# Twig helper #}
<div {{ stimulus_controller('search', { url: path('api_search') }) }}>
{# Chaining multiple controllers #}
<div {{ stimulus_controller('a')|stimulus_controller('b') }}>
{# Target and action helpers #}
<input {{ stimulus_target('search', 'query') }}>
<button {{ stimulus_action('search', 'submit') }}>
主な原則
HTML が主導し、JS が応答します。 コントローラーはマークアップを作成するのではなく、既存の HTML に動作を追加します。コントローラー内で DOM を生成している場合は、TwigComponent または LiveComponent のほうが適していないか検討してください。
1 つのコントローラーにつき、1 つの役割にします。 ドロップダウンコントローラーはドロップダウンを、ツールチップコントローラーはツールチップを扱います。巨大なコントローラーを作るのではなく、同じ要素に複数のコントローラーを組み合わせてください。
disconnect() でクリーンアップします。 connect() でイベントリスナー、タイマー、サードパーティーライブラリのインスタンスを追加する場合は、disconnect() で必ず削除してください。ページが切り替わると、Turbo ナビゲーションによってコントローラーは切断され、再接続されます。
データ属性より値を使います。 コントローラーが読み取る、または監視する必要があるデータには、生の data-* 属性ではなく、Stimulus の値(型付きで、変更コールバックを備えています)を使ってください。
参考資料
- 完全な API(ライフサイクル、ターゲット、値、アクション、クラス、アウトレット):references/api.md
- パターン(デバウンス、fetch、モーダル、フォームなど):references/patterns.md
- 注意点(よくある間違い、デバッグ、Turbo との互換性):references/gotchas.md
関連項目
- UX Map は、マップコンテナー要素で Stimulus 互換のイベント(
ux:map:connect、ux:map:marker:after-createなど)をディスパッチします。カスタム Stimulus コントローラーを使って、マップの動作を拡張できます。 - UX Icons は SVG のみで構成されているため Stimulus との統合は不要ですが、値の変更を介して Stimulus でアイコン名を動的に切り替えることができます。