Hotwire Astra UI は、 Hotwire(Turbo + Stimulus)、 Importmap を用いて、 モーダルをどこまで サーバーレンダリングだけで設計できるか を探る実践的な試みです。
モーダルは、一見すると単純なUI要素です。
しかし実際には、すぐに設計上の圧力点になります。
- 画面遷移せずに送信したいフォーム
- 別アクションの中にネストされる確認フロー
- フォーカス管理やアクセシビリティ
- 背景クリックやESCキーの扱い
- ページ本体と独立して更新されるコンテンツ
多くのRailsアプリケーションでは、モーダル周りに次のようなものが蓄積されていきます。
- 場当たり的なJavaScript
- 条件分岐だらけのテンプレート
- 「今どのモーダルが開いているか」を追うクライアント状態
これ自体が「間違い」というわけではありません。
ただし多くの場合、モーダルをクライアント側の問題として扱っている ことが原因です。
Hotwire は、この前提を静かに崩します。
🧭 Hotwireがもたらした静かな転換:UIはナビゲーションである
Turbo Frame を使うと、ブラウザは次の性質を取り戻します。
UIの状態は、ナビゲーションとして表現できる
モーダルは、JavaScriptで「開く」必要はありません。
サーバーから描画されればよい のです。
これまでの問いは、こうでした。
「このモーダルは、今どんなJavaScript状態であるべきか?」
Hotwireでは、問いが変わります。
「今このフレームに、サーバーは何を描画すべきか?」
小さな転換ですが、設計への影響は大きいものです。
🪟 コアアイデア:常設されたモーダル用 Turbo Frame
Astra UI の中心にあるのは、非常にシンプルな考えです。
モーダルとは、画面の上に浮かんでいる Turbo Frame にすぎない
実装上は、次の1行から始まります。
<%= turbo_frame_tag "astra_modal" %>このフレームは、レイアウトに常に存在します。
astra_modal を target にしたリンクやフォームは、 その内容を モーダルとして 描画します。 ページ本体は一切影響を受けません。
グローバルなJavaScriptは不要。
クライアントルーティングも不要。
テンプレートの二重管理もありません。
📐 なぜ、この構成はスケールするのか
最初は、あまりにも単純に見えるかもしれません。
しかし、この単純さは積み重なります。
- 常にサーバーが唯一の真実となる
- ブラウザ履歴が直感的に振る舞う
- 戻る/進む操作が自然に動く
- Turboのキャッシュ戦略と衝突しない
- エラー表示も通常のRailsビューと同じ
何より重要なのは、
デバッグすべき「見えないクライアント状態」が存在しない ことです。
🪜 状態マシンなしのスタック型モーダル
モーダル設計で、早い段階で問題になるのが「重ね合わせ」です。
- サインアップ内の「ログインはこちら」
- 編集画面内の「削除確認」
- 複数ステップの操作フロー
よくある解決策は次のようなものです。
- モーダルマネージャ
- z-index の調整地獄
- 状態スタックの管理
Hotwire では、これらは必須ではありません。
Astra UI では、スタックを ネストされたナビゲーション として扱います。
astra_modal
└── modal A
└── astra_modal_next
└── modal B
└── astra_modal_next
└── modal C
各モーダル層は、
- それぞれ独立した Turbo Frame を持ち
- 個別に閉じることができ
- 他の層と干渉しません
グローバル管理は不要。
モーダルマネージャも不要。
必要なのは、明示的な target だけです。
♿ 構造としてのアクセシビリティ
アクセシビリティは、後付けのチェックリストになりがちです。
しかしサーバーレンダリング中心の設計では、 多くの課題が 構造の問題 になります。
- 描画時にフォーカスを置ける
- ARIA属性がテンプレートの一部になる
- ESCや背景クリックが局所的に管理できる
- キーボード操作が状態管理に依存しない
Astra UI では、アクセシビリティを 機能ではなく、設計の帰結 として扱います。
🧠 なぜJavaScriptがほとんど存在しないのか
Hotwire Astra UI における JavaScript の役割は限定的です。
- 背景クリックの処理
- ESCキーの処理
- フォーカス管理
次のような状態は、クライアントには存在しません。
- どのモーダルが開いているか
- 今どのステップか
- 次に何を閉じるべきか
それらはすべて、
- どのフレームを target にするか
- サーバーが何を描画するか
- リクエストが成功したか
という形で表現されます。
これにより、Rails本来の思考モデルと一致した設計になります。
📦 実践からライブラリへ
Astra UI は、最初からライブラリとして作られたものではありません。
繰り返し浮かんだ問いは、これでした。
「サーバーレンダリングUIは、どこまで実用的なのか?」
現時点での答えは、思っていたより、ずっと遠くまで です。
ライブラリとして存在している理由は、
- 有効だったパターンを固定化するため
- 繰り返しを減らすため
- 安全なデフォルトを提供するため
ただし、意図的に小さく保たれています。
Hotwireを理解していれば、Astra UIはすでに理解しているはずです。
🎯 結びに
Hotwire は、UIの捉え方を変えます。
クライアント状態ではなく、描画される状態 としてUIを見る
モーダルは、その考え方を試すのにちょうど良い題材です。
小さく、目に見え、そして過剰設計されやすいからです。
Hotwire Astra UI は、 その考え方を真面目に突き詰めた結果にすぎません。
