【JavaScript】チェックボックスの状態でボタンを活性化・非活性化する方法|単一・複数・条件組み合わせ・disabled属性の使い方まで解説

「利用規約に同意しないと送信できない」「必須項目を全て入力するまでボタンが押せない」——こういったUIは、フォームのユーザビリティを高める定番パターンです。

この記事では、チェックボックスの状態に連動してボタンの disabled を切り替える実装を、単一チェックボックスの基本から複数チェック・条件組み合わせ・アクセシビリティ対応まで体系的に解説します。

スポンサーリンク

disabled 属性の仕組みと注意点

ボタンの活性・非活性は disabled プロパティで制御します。まず動作を正確に理解しておきましょう。

プロパティ/属性 効果 フォーム送信
button.disabled = true クリック不可・グレーアウト 送信されない
button.disabled = false クリック可能 送信される
aria-disabled="true" スクリーンリーダーに「無効」を伝えるのみ クリック自体は可能
aria-disabled は disabled の代わりにはならない:aria-disabled="true" だけではボタンのクリックを物理的に止められません。ボタンを本当に無効化したい場合は disabled プロパティを使ってください。aria-disabledrole="button" を使ったカスタム要素(<div><a>)に対して使う用途が主です。

単一チェックボックスでボタンを活性化する(基本)

利用規約への同意チェックボックスが最も典型的なユースケースです。

HTML
<form id="agreeForm">
  <label class="agree-label">
    <input type="checkbox" id="agreeCheck">
    <a href="/terms/" target="_blank">利用規約</a>に同意する
  </label>
  <button type="submit" id="submitBtn" disabled>送信する</button>
</form>
CSS(disabled 時のスタイル)
button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  /* pointer-events: none; を追加するとホバーも無効になる */
}

/* 活性化時にトランジションでなめらかに変化 */
button {
  transition: opacity 0.2s, background-color 0.2s;
}

button:not(:disabled) {
  background: #0284c7;
  color: #fff;
  cursor: pointer;
}
単一チェック → ボタン活性化
const agreeCheck = document.getElementById('agreeCheck');
const submitBtn  = document.getElementById('submitBtn');

agreeCheck.addEventListener('change', () => {
  submitBtn.disabled = !agreeCheck.checked;
});
HTML に disabled を初期値として書く:ページ読み込み直後からボタンを非活性にしたい場合は、HTMLの <button disabled> で初期値を設定するのがシンプルです。JavaScript が遅延読み込みされる場合でも安全に非活性状態を維持できます。

複数チェックボックス:全部チェックされたときに活性化

「全ての項目に同意する」など、複数チェックボックスが全てオンの場合のみボタンを有効にするパターンです。

HTML(複数同意)
<form id="multiAgreeForm">
  <div class="check-list">
    <label><input type="checkbox" class="must-check"> 利用規約に同意する</label>
    <label><input type="checkbox" class="must-check"> プライバシーポリシーに同意する</label>
    <label><input type="checkbox" class="must-check"> 18歳以上であることを確認した</label>
  </div>
  <button type="submit" id="multiSubmitBtn" disabled>登録する</button>
</form>
全部チェック必須パターン
const mustChecks    = document.querySelectorAll('.must-check');
const multiSubmitBtn = document.getElementById('multiSubmitBtn');

function updateBtn() {
  // every() で全チェックボックスが checked かどうか確認
  const allChecked = Array.from(mustChecks).every((cb) => cb.checked);
  multiSubmitBtn.disabled = !allChecked;
}

// 全チェックボックスに同じハンドラを登録
mustChecks.forEach((cb) => cb.addEventListener('change', updateBtn));

複数チェックボックス:1つ以上チェックされたときに活性化

「関心のあるカテゴリを1つ以上選択してください」のような任意選択パターンです。

1つ以上チェック必須パターン
const optionChecks = document.querySelectorAll('.option-check');
const nextBtn      = document.getElementById('nextBtn');

function updateNextBtn() {
  // some() で少なくとも1つ checked かどうか確認
  const anyChecked = Array.from(optionChecks).some((cb) => cb.checked);
  nextBtn.disabled = !anyChecked;
}

optionChecks.forEach((cb) => cb.addEventListener('change', updateNextBtn));
every() と some() の使い分け:Array.from(checks).every(cb => cb.checked) は「全てチェック済み」の判定、Array.from(checks).some(cb => cb.checked) は「少なくとも1つチェック済み」の判定です。ユースケースに合わせて使い分けてください。

条件の組み合わせ:チェックボックス + テキスト入力

「利用規約に同意 かつ メールアドレスを入力したときのみ」送信できるフォームの実装です。複数条件の AND 判定は、それぞれの状態を関数でまとめると管理しやすくなります。

HTML(同意 + メール入力)
<form id="comboForm">
  <div class="field-group">
    <label for="comboEmail">メールアドレス <span class="required">*</span></label>
    <input type="email" id="comboEmail" placeholder="example@mail.com" autocomplete="email">
  </div>
  <label class="agree-label">
    <input type="checkbox" id="comboAgree">
    利用規約に同意する
  </label>
  <button type="submit" id="comboBtn" disabled>送信する</button>
</form>
条件組み合わせの活性化判定
const comboEmail = document.getElementById('comboEmail');
const comboAgree = document.getElementById('comboAgree');
const comboBtn   = document.getElementById('comboBtn');

function isFormReady() {
  const emailFilled = comboEmail.value.trim() !== '';
  const agreed      = comboAgree.checked;
  return emailFilled && agreed;
}

function syncBtn() {
  comboBtn.disabled = !isFormReady();
}

// チェックボックスと入力フィールドの両方を監視
comboAgree.addEventListener('change', syncBtn);
comboEmail.addEventListener('input',  syncBtn);
input イベントと change イベントの違い:テキスト入力の監視には change ではなく input を使います。change はフォーカスが外れたときだけ発火しますが、input は1文字入力するたびに発火するため、リアルタイムに状態を更新できます。

N個以上チェックで活性化(選択数を表示)

「3つ以上選択してください」のように選択数に下限を設けるパターンです。選択件数をリアルタイム表示するとユーザーへのフィードバックになります。

N個以上選択必須パターン
const tagChecks = document.querySelectorAll('.tag-check');
const tagBtn    = document.getElementById('tagBtn');
const tagCount  = document.getElementById('tagCount');
const MIN_SELECT = 3;

function updateTagBtn() {
  const checked = Array.from(tagChecks).filter((cb) => cb.checked).length;
  tagCount.textContent = `${checked} / ${tagChecks.length} 選択中`;
  tagBtn.disabled = checked < MIN_SELECT;

  // 残り必要数を伝える
  const remaining = MIN_SELECT - checked;
  tagBtn.title = remaining > 0
    ? `あと${remaining}つ選択してください`
    : '';
}

tagChecks.forEach((cb) => cb.addEventListener('change', updateTagBtn));
// 初期化
updateTagBtn();

フォーム全体の入力チェックでボタンを制御する(応用)

チェックボックスだけでなく、テキスト・セレクト・ラジオなどフォーム内の全要素が有効な状態のときにボタンを活性化する汎用パターンです。

フォーム全体を監視する汎用実装
/**
 * フォーム内の全インタラクティブ要素を監視してボタンを制御する
 * @param {HTMLFormElement} form
 * @param {HTMLButtonElement} btn
 * @param {() => boolean} isReady - 送信可能かどうかを返す関数
 */
function bindFormReady(form, btn, isReady) {
  function sync() {
    btn.disabled = !isReady();
  }
  // input・change・select すべてのイベントを一括監視(バブリング利用)
  form.addEventListener('input',  sync);
  form.addEventListener('change', sync);
  sync(); // 初期状態を設定
}

// 使い方
const form = document.getElementById('fullForm');
const btn  = document.getElementById('fullFormBtn');

bindFormReady(form, btn, () => {
  const name  = form.querySelector('#fullName').value.trim();
  const email = form.querySelector('#fullEmail').value.trim();
  const agree = form.querySelector('#fullAgree').checked;
  // メール形式の簡易チェック
  const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  return name !== '' && emailOk && agree;
});
フォームへのイベント委譲(Event Delegation):フォーム要素に inputchange を登録すると、子要素の全インタラクティブ要素からバブリングで届くイベントを一括で受け取れます。個別に登録するより管理が簡単です。

送信中の二重送信防止(ボタン無効化)

チェックボックスとは少し異なりますが、フォーム送信ボタンと一緒によく実装する「送信中は disabled にする」パターンも紹介します。

送信中の二重送信防止
const form      = document.getElementById('agreeForm');
const submitBtn = document.getElementById('submitBtn');

form.addEventListener('submit', async (e) => {
  e.preventDefault();

  // 送信開始 → ボタンを無効化してテキストを変更
  submitBtn.disabled     = true;
  submitBtn.textContent  = '送信中...';

  try {
    await fetch('/api/submit', {
      method: 'POST',
      body: new FormData(form),
    });
    submitBtn.textContent = '送信完了!';
  } catch (err) {
    console.error(err);
    // 失敗したらボタンを再度有効化
    submitBtn.disabled    = false;
    submitBtn.textContent = '送信する';
  }
});

よくある質問

Qページ読み込み直後からボタンを disabled にするにはどうすればいいですか?
AHTMLの <button type="submit" disabled> のように disabled 属性を直接書くのが最もシンプルで安全です。JavaScriptの読み込みが遅延しても安全に非活性状態を保てます。
Qdisabled ボタンにホバーしたときのツールチップを表示できますか?
Adisabled 状態では pointer-events: none が効いているため、title 属性のツールチップがブラウザによって表示されないことがあります。ボタンを <span> で囲んで spantitle を付けるか、カスタムツールチップをJavaScriptで実装することで対応できます。
Qdisabled ボタンを CSS で styled-component などの独自スタイルに合わせるには?
Abutton:disabled セレクタでスタイルを上書きします。opacitycursor: not-allowedbackground-color の3つを合わせて指定すると視覚的に伝わりやすいです。
QReact や Vue で同じことをするにはどう書きますか?
AReact では <button disabled={!isAgree}> のように state を直接バインドします。Vue では :disabled="!isAgree" です。いずれも state/data が変化するたびに自動で再レンダリングされるため、イベントリスナーの手動管理は不要です。
Qチェックボックスの初期状態が checked の場合、ボタンも最初から有効にしたいです。
AJavaScriptの初期化処理として、イベントリスナー登録後に状態更新関数(syncBtn() など)を一度呼び出してください。もしくは HTML側で <button> から disabled を取り除き、チェックボックスを checked にした状態で書くこともできます。

まとめ

チェックボックスでボタンを活性化する実装のポイントをまとめます。

  • ボタンの無効化は button.disabled = true/false で制御する
  • 複数チェック「全て必須」は every()、「1つ以上」は some() で判定する
  • チェックボックス以外の入力も組み合わせるときは isFormReady 関数に判定をまとめると拡張しやすい
  • フォーム全体の監視は form.addEventListener("input"/"change", ...) のイベント委譲が効率的
  • 送信中は disabled = true にして二重送信を防止する

チェックボックスの「全て選択」連動は【JavaScript】「全て選択」チェックボックスの実装ガイド、フォームバリデーション全体は【JavaScript】フォームバリデーション完全ガイドもあわせてご覧ください。