トグルスイッチ(オン/オフ制御)

フォーム 初級

このコンポーネントについて

トグルスイッチは、オン/オフの2値を直感的に切り替えられるUIコンポーネントです。 設定画面での通知ON/OFF・詳細フォームの表示切り替えなど、スイッチ1つで状態を明示したい場面で広く使われます。

このページでは <input type="checkbox"> をCSSでスライド式スイッチに見せる実装で、よく使われる3パターン(決定ボタンで値を取得・複数トグルの一括管理・コンテンツの表示/非表示)を確認・コピペできます。

  • 決定ボタンで値を確定 — 「決定」ボタン押下時に input.checked を読み取り、ON/OFFの状態を表示する。フォーム送信前の確認UIに使えるパターン
  • 複数トグルの一括管理 — 「全てON」「全てOFF」ボタンで複数のトグルを同時切り替え。通知設定画面など設定項目が複数並ぶ場面で定番のパターン
  • トグルでコンテンツを開閉 — トグルのオン/オフに連動して詳細設定パネルをスライドアニメーションで表示/非表示にする

実装のポイント・注意点

トグルスイッチは <input type="checkbox"> を使って実装します。チェックボックスを opacity: 0; width: 0; height: 0; で視覚的に隠し、CSSの :checked 擬似クラスで兄弟要素のスタイルを切り替えることで、外部ライブラリなしでスライドスイッチを作れます。display: none ではキーボードフォーカスが当たらなくなるため使わないのがポイントです。

アクセシビリティ対応として role="switch" を付与すると、スクリーンリーダーがチェックボックスではなくスイッチとして読み上げます(オン状態なら「オン」、オフ状態なら「オフ」と告知)。また aria-labelledby でラベル要素のIDを指定し、スイッチとラベルを紐付けることが重要です。

Pattern 3 のコンテンツ開閉では display: none の切り替えではなく max-height のCSSトランジションを使います。display: noneblock の切り替えではアニメーションが効かないためです。また、「全てON/OFF」ボタンでプログラム的に checked を変更しても change イベントは発火しないため、ボタンクリック後は手動で集計処理を呼び出す必要があります。

デモ

Pattern 1 — 決定ボタン

通知を受け取る

まだ決定されていません

Pattern 2 — 複数の一括管理

  • メール通知
  • プッシュ通知
  • SNS通知
  • 週報通知

0 / 4 項目がON

Pattern 3 — 表示/非表示

詳細設定を表示する

サンプルソース

3つのファイルを同じフォルダに保存し、index.html をブラウザで開くとすぐに動作確認できます。
ファイル名:index.html / style.css / script.js — 保存時の文字コードは UTF-8 を指定してください(Shift-JISだと日本語が文字化けします)。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>トグルスイッチ サンプル</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>

<!-- Pattern 1: 決定ボタン -->
<div class="tg-pattern">
  <p class="tg-pattern-label">Pattern 1 — 決定ボタン</p>
  <div class="tg-field">
    <span id="tg-label">通知を受け取る</span>
    <!-- label の for と input の id を紐付けて、ラベルクリックでもトグルできるようにする -->
    <label class="tg-switch" for="tg-main">
      <input type="checkbox" class="tg-input" id="tg-main"
             role="switch" aria-labelledby="tg-label">
      <span class="tg-track"><span class="tg-thumb"></span></span>
    </label>
  </div>
  <button class="tg-submit" type="button" id="tg-submit">決定</button>
  <!-- aria-live="polite" でスクリーンリーダーが結果変化を読み上げる -->
  <div class="tg-result" id="tg-result" aria-live="polite">
    <p class="tg-result-placeholder">まだ決定されていません</p>
  </div>
</div>

<!-- Pattern 2: 複数の一括管理 -->
<div class="tg-pattern">
  <p class="tg-pattern-label">Pattern 2 — 複数の一括管理</p>
  <div class="tgb-controls">
    <button type="button" class="tgb-btn tgb-btn-on" id="tgb-all-on">全てON</button>
    <button type="button" class="tgb-btn tgb-btn-off" id="tgb-all-off">全てOFF</button>
  </div>
  <ul class="tgb-list">
    <li class="tgb-item">
      <span id="tgb-label-1">メール通知</span>
      <label class="tg-switch" for="tgb-1">
        <input type="checkbox" class="tg-input tgb-input" id="tgb-1"
               role="switch" aria-labelledby="tgb-label-1">
        <span class="tg-track"><span class="tg-thumb"></span></span>
      </label>
    </li>
    <li class="tgb-item">
      <span id="tgb-label-2">プッシュ通知</span>
      <label class="tg-switch" for="tgb-2">
        <input type="checkbox" class="tg-input tgb-input" id="tgb-2"
               role="switch" aria-labelledby="tgb-label-2">
        <span class="tg-track"><span class="tg-thumb"></span></span>
      </label>
    </li>
    <li class="tgb-item">
      <span id="tgb-label-3">SNS通知</span>
      <label class="tg-switch" for="tgb-3">
        <input type="checkbox" class="tg-input tgb-input" id="tgb-3"
               role="switch" aria-labelledby="tgb-label-3">
        <span class="tg-track"><span class="tg-thumb"></span></span>
      </label>
    </li>
    <li class="tgb-item">
      <span id="tgb-label-4">週報通知</span>
      <label class="tg-switch" for="tgb-4">
        <input type="checkbox" class="tg-input tgb-input" id="tgb-4"
               role="switch" aria-labelledby="tgb-label-4">
        <span class="tg-track"><span class="tg-thumb"></span></span>
      </label>
    </li>
  </ul>
  <p class="tgb-summary" id="tgb-summary">0 / 4 項目がON</p>
</div>

<!-- Pattern 3: 表示/非表示 -->
<div class="tg-pattern">
  <p class="tg-pattern-label">Pattern 3 — 表示/非表示</p>
  <div class="tgv-field">
    <span id="tgv-label">詳細設定を表示する</span>
    <label class="tg-switch" for="tgv-main">
      <!-- aria-expanded でパネルの展開状態をスクリーンリーダーに伝える -->
      <input type="checkbox" class="tg-input" id="tgv-main"
             role="switch" aria-labelledby="tgv-label"
             aria-expanded="false" aria-controls="tgv-panel">
      <span class="tg-track"><span class="tg-thumb"></span></span>
    </label>
  </div>
  <!-- max-height トランジションで開閉アニメーションを実現する -->
  <div class="tgv-panel" id="tgv-panel" aria-hidden="true">
    <div class="tgv-panel-inner">
      <p class="tgv-panel-title">詳細設定</p>
      <label class="tgv-option"><input type="checkbox"> オプションA を有効にする</label>
      <label class="tgv-option"><input type="checkbox"> オプションB を有効にする</label>
      <label class="tgv-option"><input type="checkbox"> オプションC を有効にする</label>
    </div>
  </div>
</div>

<script src="./script.js"></script>
</body>
</html>
/* === トグルスイッチ パターン集 ===
   色を変えたいときは :root の変数を書き換えるだけでOKです */
:root {
  --color-accent:  #2B7FE8; /* オン時の色・ボタン色 */
  --color-text:    #1A2332; /* メインテキスト色 */
  --color-muted:   #9AA5B4; /* プレースホルダー等の薄い文字色 */
  --color-border:  #D0D7E0; /* 通常時のボーダー色 */
  --color-bg:      #F4F6F9; /* カード背景色 */
}

*, *::before, *::after { box-sizing: border-box; }
body {
  font-family: sans-serif;
  padding: 24px;
  max-width: 560px;
  margin: 0 auto;
  background: #fff;
}

/* 各パターンを囲むカード */
.tg-pattern {
  margin-bottom: 20px;
  padding: 20px;
  background: var(--color-bg);
  border-radius: 10px;
}
.tg-pattern:last-child { margin-bottom: 0; }
.tg-pattern-label {
  margin: 0 0 14px;
  font-size: 11px;
  font-weight: 700;
  color: var(--color-accent);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ========== トグルスイッチ 共通 ========== */
.tg-switch {
  position: relative;
  display: inline-block;
  width: 52px;
  height: 28px;
  cursor: pointer;
  flex-shrink: 0;
}
/* チェックボックス本体を見えなくする(キーボードフォーカスは残す) */
.tg-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
/* スライドの台座 */
.tg-track {
  position: absolute;
  inset: 0;
  background: #ccc;
  border-radius: 14px;
  transition: background 0.2s;
}
/* スライドのつまみ */
.tg-thumb {
  position: absolute;
  width: 22px;
  height: 22px;
  top: 3px;
  left: 3px;
  background: #fff;
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  transition: transform 0.2s;
}
/* オン状態: 台座をアクセントカラーに変え、つまみを右に移動する */
.tg-input:checked + .tg-track { background: var(--color-accent); }
.tg-input:checked + .tg-track .tg-thumb { transform: translateX(24px); }
/* キーボードフォーカス時のアウトライン(:focus では常に出てしまうため :focus-visible を使う) */
.tg-input:focus-visible + .tg-track {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ========== Pattern 1 — 決定ボタン ========== */
.tg-field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 14px;
}
.tg-submit {
  display: block;
  width: 100%;
  padding: 10px;
  background: var(--color-accent);
  color: #fff;
  border: none;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  font-family: sans-serif;
  margin-bottom: 14px;
  transition: opacity 0.15s;
}
.tg-submit:hover { opacity: 0.85; }
.tg-result {
  font-size: 14px;
  min-height: 20px;
}
.tg-result-placeholder {
  margin: 0;
  color: var(--color-muted);
}
.tg-result.has-value {
  padding: 12px;
  border: 1.5px solid var(--color-border);
  border-radius: 8px;
  text-align: center;
}
/* ON状態: 緑系 */
.tg-result.has-value.is-on {
  border-color: #22c55e;
  background: #f0fdf4;
}
.tg-result.has-value.is-on .tg-result-value { color: #15803d; }
/* OFF状態: グレー系 */
.tg-result.has-value:not(.is-on) { background: #F9FAFB; }
.tg-result.has-value:not(.is-on) .tg-result-value { color: #374151; }
.tg-result-value {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 700;
}

/* ========== Pattern 2 — 複数の一括管理 ========== */
.tgb-controls {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
}
.tgb-btn {
  padding: 8px 16px;
  border-radius: 6px;
  border: 1.5px solid;
  cursor: pointer;
  font-size: 13px;
  font-family: sans-serif;
  transition: opacity 0.15s;
}
.tgb-btn:hover { opacity: 0.8; }
.tgb-btn-on  { background: var(--color-accent); color: #fff; border-color: var(--color-accent); }
.tgb-btn-off { background: #fff; color: #374151; border-color: var(--color-border); }
.tgb-list {
  list-style: none;
  padding: 0;
  margin: 0 0 8px;
  border: 1.5px solid #E5E7EB;
  border-radius: 8px;
  overflow: hidden;
}
.tgb-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  background: #fff;
  border-bottom: 1px solid #E5E7EB;
}
.tgb-item:last-child { border-bottom: none; }
.tgb-summary {
  font-size: 13px;
  color: var(--color-muted);
  text-align: right;
  margin: 0;
}

/* ========== Pattern 3 — 表示/非表示 ========== */
.tgv-field {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* max-height を 0 → 実コンテンツ高さ以上の値へ変化させてスライドアニメーションを実現する */
.tgv-panel {
  overflow: hidden;
  max-height: 0;
  transition: max-height 0.35s ease;
}
.tgv-panel.is-open { max-height: 300px; }
.tgv-panel-inner {
  border: 1.5px solid #E5E7EB;
  border-radius: 8px;
  padding: 14px 16px;
  background: #fff;
  margin-top: 10px;
}
.tgv-panel-title {
  font-size: 13px;
  font-weight: 700;
  margin: 0 0 10px;
  color: var(--color-text);
}
.tgv-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 0;
  font-size: 14px;
  color: #374151;
  cursor: pointer;
}

/* スマホ対応 */
@media (max-width: 480px) {
  body { padding: 16px; }
}
// === Pattern 1: 決定ボタン ===
var tgInput  = document.getElementById('tg-main');
var tgSubmit = document.getElementById('tg-submit');
var tgResult = document.getElementById('tg-result');

// 決定ボタン押下時に input.checked を読み取り ON/OFF を表示する
tgSubmit.addEventListener('click', function () {
  var isOn = tgInput.checked;
  if (isOn) {
    tgResult.innerHTML = '<p class="tg-result-value">ON</p>';
    tgResult.className = 'tg-result has-value is-on';
  } else {
    tgResult.innerHTML = '<p class="tg-result-value">OFF</p>';
    tgResult.className = 'tg-result has-value';
  }
});


// === Pattern 2: 複数の一括管理 ===
var tgbInputs  = document.querySelectorAll('.tgb-input');
var tgbSummary = document.getElementById('tgb-summary');
var totalCount = tgbInputs.length;

// ON件数を数えてサマリーテキストを更新する
function updateSummary() {
  var onCount = Array.from(tgbInputs).filter(function (i) { return i.checked; }).length;
  tgbSummary.textContent = onCount + ' / ' + totalCount + ' 項目がON';
}

tgbInputs.forEach(function (input) {
  input.addEventListener('change', updateSummary);
});

// 「全てON/OFF」ボタンは checked を直接変更するため change イベントが発火しない
// → 変更後に手動で updateSummary() を呼ぶ
document.getElementById('tgb-all-on').addEventListener('click', function () {
  tgbInputs.forEach(function (i) { i.checked = true; });
  updateSummary();
});
document.getElementById('tgb-all-off').addEventListener('click', function () {
  tgbInputs.forEach(function (i) { i.checked = false; });
  updateSummary();
});


// === Pattern 3: 表示/非表示 ===
var tgvInput = document.getElementById('tgv-main');
var tgvPanel = document.getElementById('tgv-panel');

tgvInput.addEventListener('change', function () {
  var isOpen = tgvInput.checked;
  // is-open クラスで max-height を切り替えてスライドアニメーションを起動する
  tgvPanel.classList.toggle('is-open', isOpen);
  // スクリーンリーダーに展開状態を伝える
  tgvInput.setAttribute('aria-expanded', isOpen);
  tgvPanel.setAttribute('aria-hidden', !isOpen);
});

AI用プロンプト

各パターンのプロンプトをコピーしてAIに渡すと、同様のコンポーネントを生成できます。

ChatGPTやClaudeにこのプロンプトを渡すと、同様のコンポーネントをゼロから生成・カスタマイズできます。ライブラリ指定や項目の変更など、要件を追記して使うのがおすすめです。

※ このプロンプトを使ってもデモとまったく同じ動作にならない場合があります。AIの解釈や生成タイミングによって差が出ることをご了承ください。

💡 jQuery・Vue・React など特定のライブラリで実装したい場合は、プロンプトの末尾に「〇〇を使って実装してください」と追記してください。

Pattern 1 — 決定ボタン

# トグルスイッチ(決定ボタンで値を取得)作成依頼

## 概要
「通知を受け取る」ラベル付きのトグルスイッチと決定ボタンを設置し、
ボタン押下時にトグルのオン/オフ状態を取得して画面に表示するUIを実装してください。

## 要件
- 「通知を受け取る」というラベル付きのトグルスイッチを表示する(初期状態: OFF)
- トグルをクリックするとオン/オフが切り替わる
- 「決定」ボタンをクリックしたとき、トグルの現在の状態(ON / OFF)を結果エリアに表示する
- ONの場合 → 緑系のボックスに「ON」と表示する
- OFFの場合 → グレー系のボックスに「OFF」と表示する
- リセットボタンでトグルをOFFに戻し、結果エリアを初期状態に戻す

## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- レスポンシブ対応:必要
- <input type="checkbox"> を CSS でスライド式トグルスイッチに見せる実装とする
- role="switch" と aria-labelledby でアクセシビリティに対応すること

## 動作詳細
「決定」ボタンの click イベントハンドラで input.checked を読み取り、
true なら「ON」、false なら「OFF」を結果エリアに表示する。
ON/OFF それぞれ異なる背景色・枠色のスタイルを適用すること。

## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。

Pattern 2 — 複数の一括管理

# トグルスイッチ(複数の一括オン/オフ管理)作成依頼

## 概要
複数のトグルスイッチを縦並びリストで表示し、個別のオン/オフ操作と
「全てON」「全てOFF」ボタンによる一括操作の両方を実装してください。

## 要件
- 以下4項目のトグルスイッチをリスト形式で表示する(初期状態: 全てOFF)
  - メール通知
  - プッシュ通知
  - SNS通知
  - 週報通知
- 各トグルを個別にクリックしてオン/オフを切り替えられる
- 「全てON」ボタン: クリックで全トグルをONにする
- 「全てOFF」ボタン: クリックで全トグルをOFFにする
- リスト下部に「X / 4 項目がON」という集計テキストをリアルタイムで表示する
- リセットボタンで全てOFFの初期状態に戻す

## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- レスポンシブ対応:必要
- <input type="checkbox"> を CSS でスライド式トグルスイッチに見せる実装とする
- role="switch" と aria-labelledby でアクセシビリティに対応すること

## 動作詳細
querySelectorAll で全トグルの参照をまとめて取得する。
「全てON/OFF」ボタンのクリックは change イベントを発火しないため、
ボタンクリック後は手動で集計更新関数を呼び出すこと。
ON件数は Array.from(inputs).filter(i => i.checked).length で取得する。

## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。

Pattern 3 — 表示/非表示

# トグルスイッチ(コンテンツの表示/非表示切り替え)作成依頼

## 概要
トグルスイッチをオンにすると詳細設定パネルがスライドアニメーションで表示され、
オフにすると非表示になるUIを実装してください。

## 要件
- 「詳細設定を表示する」というラベル付きのトグルスイッチを表示する(初期状態: OFF)
- トグルをONにする → 詳細設定パネルがスムーズにスライドダウンして表示される
- トグルをOFFにする → 詳細設定パネルがスムーズにスライドアップして非表示になる
- 詳細設定パネルの中身は「オプションA / B / C を有効にする」の3つのチェックボックス
- aria-expanded と aria-hidden でアクセシビリティに対応すること
- リセットボタンでトグルをOFFにしてパネルを閉じた初期状態に戻す

## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- レスポンシブ対応:必要
- <input type="checkbox"> を CSS でスライド式トグルスイッチに見せる実装とする
- パネルの開閉アニメーションは max-height の CSS トランジションで実装すること(display:none/block は使わない)

## 動作詳細
.tgv-panel に max-height: 0; overflow: hidden; transition: max-height 0.35s ease; を設定し、
トグルON時に is-open クラスを付与して max-height: 300px; に変化させることでスライドアニメーションを実現する。
input の aria-expanded と panel の aria-hidden をJSで合わせて切り替えること。

## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。