トグル

UI 要素名
Toggle, Switch
サーバ接続
不要
ステート管理
aria-checkedもしくはHTML checkbox
使用技術
Stimulus, HTML Checkbox
デモ
関連ページ

ここで作るのは下記のようなUIです。

2つの方法で作ります。デモはこちらです: StimulusバージョンCheckboxバージョンです。

考えるポイント

  • サーバとの非同期通信
    • 不要
  • ステート管理:トグルのオン・オフステート
    • aria-checked HTML属性を使うやり方 → JavaScript要
    • HTMLの<input type="checkbox">を使うやり方 → JavaScript不要

Stimulus版のコード

app/views/components/toggle_stimulus.html.erb
<% set_breadcrumbs [["Toggle Stimulus", component_path(:toggle)]] %>

<%= render 'template',
           title: "Toggle Stimulus",
           description: "Toggle implemented with Stimulus" do %>
  <!-- Enabled: "bg-indigo-600", Not Enabled: "bg-gray-200" -->
  <div class="text-center">
    <button type="button"
            class="group bg-gray-200 aria-checked:bg-indigo-600
            relative inline-flex h-6 w-11 flex-shrink-0
            cursor-pointer rounded-full border-2 border-transparent
            transition-colors duration-200 ease-in-out
            focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
            role="switch"
            aria-checked="false"
            data-controller="switch"
            data-action="click->switch#toggle keydown.space:stop:prevent->switch#toggle"
    >
      <span class="sr-only">Use setting</span>
      <!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" -->
      <span aria-hidden="true"
            class="translate-x-0 group-aria-checked:translate-x-5
            pointer-events-none inline-block h-5 w-5
            rounded-full bg-white shadow ring-0
            transition duration-200 ease-in-out"
      ></span>
    </button>
  </div>
<% end %>
app/javascript/controllers/switch_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
  }

  toggle() {
    this.element.ariaChecked = this.element.ariaChecked === "true" ? "false" : "true"
  }
}
  • トグルは<button>タグで実装しています。data-controller="switch"SwitchController(Stimulus)を接続します
  • HTML属性のdata-action="click->switch#toggle keydown.space:stop:prevent->switch#toggle"により、このトグルはマウスのクリックおよびスペースキーに応答するようになります。スペースキーでも使えることははアクセシビリティの要件です
  • HTML属性のdata-actionにより、SwitchController(Stimulus)toggle()が呼び出されます。ここではaria-checked属性を"true"/“false"の間で切り替えています
  • <button>タグのCSS classのaria-checked:bg-indigo-600により、aria-checked="true"の時だけボタンの背景が青く表示されるようになります
  • <span aria-hidden="true" ...>の要素はトグルの真ん中の丸いところですが。CSSクラスにgroup-aria-checked:translate-x-5と書くことで、CSSだけで左右に移動させます。

Checkbox版のコード(JavaScriptを使わない)

ブラウザネイティブの<input><select>タグは自身の中にステートを持ちます。JavaScriptでステートを管理しなくても、HTMLタグ自身にステートを保持させ、CSSでステートを確認し、画面に反映させられます。

ここではHTMLのcheckboxを使います。これはDaisy UIのトグルと似た仕組みです。

app/views/components/toggle_checkbox.html.erb
<% set_breadcrumbs [["Toggle Checkbox", component_path(:toggle)]] %>

<%= render 'template',
           title: "Toggle Checkbox",
           description: "Toggle implemented with a Checkbox" do %>
  <!-- Enabled: "bg-indigo-600", Not Enabled: "bg-gray-200" -->

  <div class="text-center">
  <label class="bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer select-none rounded-full border-2 border-transparent
                transition-colors duration-200 ease-in-out outline-none
                has-[:focus]:ring-2 active:ring-2 ring-indigo-600 ring-offset-2
          has-[:checked]:bg-indigo-600"
         role="switch"
  >
    <input type="checkbox" class="peer opacity-0 w-0 border-none"/>
    <span class="sr-only">Use setting</span>
    <!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" -->
    <span aria-hidden="true"
          class="translate-x-0 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow ring-0
                 transition duration-200 ease-in-out
                 peer-[:checked]:translate-x-5"
    ></span>
  </label>
  </div>
<% end %>
  • HTMLチェックボックス要素(<input type="checkbox"...>)はネイティブでステートを持ちます。動きとしてはトグル的にオン・オフを切り替えます
  • HTMLチェックボックスのステートは:checked擬似セレクタを使ってCSSから読み取れます
  • さらにTailwind CSSのpeer擬似セレクタと組み合わせると、チェックボックスステートに応じてトグル全体の表示をCSSだけで切り替えられます
  • ネイティブなHTML要素なので、アクセシビリティーの要件(スペースキーで切り替えられること)なども満たします

React的思考との対比

  • React的な発想だと、コンポーネントに1つのステートを持たせて、その内容によって2つのHTML要素のマークアップそのものを変えることが多いでしょう。これはReactでは条件付きレンダーと呼ばれています
  • しかしStimulusではHTMLの変更は一般に最小化します。今回のStimulus版では、1つのHTML要素だけを変更し、CSSにより他のHTML要素の表示は自動的に変更されました。また Checkbox版では<input type="checkbox">のステートを使用しましたので、HTMLの変更は一切不要でCSSのみで実装できました。
  • Reactの考え方とHotwireの考え方の違いについては関連ページでも解説していますので参照してください。
UI 要素名
Toggle, Switch
サーバ接続
不要
ステート管理
aria-checkedもしくはHTML checkbox
使用技術
Stimulus, HTML Checkbox
デモ
関連ページ