「利用規約に同意しないと送信できない」「必須項目を全て入力するまでボタンが押せない」——こういったUIは、フォームのユーザビリティを高める定番パターンです。
この記事では、チェックボックスの状態に連動してボタンの disabled を切り替える実装を、単一チェックボックスの基本から複数チェック・条件組み合わせ・アクセシビリティ対応まで体系的に解説します。
disabled 属性の仕組みと注意点
ボタンの活性・非活性は disabled プロパティで制御します。まず動作を正確に理解しておきましょう。
| プロパティ/属性 | 効果 | フォーム送信 |
|---|---|---|
button.disabled = true |
クリック不可・グレーアウト | 送信されない |
button.disabled = false |
クリック可能 | 送信される |
aria-disabled="true" |
スクリーンリーダーに「無効」を伝えるのみ | クリック自体は可能 |
aria-disabled="true" だけではボタンのクリックを物理的に止められません。ボタンを本当に無効化したい場合は disabled プロパティを使ってください。aria-disabled は role="button" を使ったカスタム要素(<div> や <a>)に対して使う用途が主です。単一チェックボックスでボタンを活性化する(基本)
利用規約への同意チェックボックスが最も典型的なユースケースです。
<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>
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;
});
disabled を初期値として書く:ページ読み込み直後からボタンを非活性にしたい場合は、HTMLの <button disabled> で初期値を設定するのがシンプルです。JavaScript が遅延読み込みされる場合でも安全に非活性状態を維持できます。複数チェックボックス:全部チェックされたときに活性化
「全ての項目に同意する」など、複数チェックボックスが全てオンの場合のみボタンを有効にするパターンです。
<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つ以上選択してください」のような任意選択パターンです。
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));
Array.from(checks).every(cb => cb.checked) は「全てチェック済み」の判定、Array.from(checks).some(cb => cb.checked) は「少なくとも1つチェック済み」の判定です。ユースケースに合わせて使い分けてください。条件の組み合わせ:チェックボックス + テキスト入力
「利用規約に同意 かつ メールアドレスを入力したときのみ」送信できるフォームの実装です。複数条件の AND 判定は、それぞれの状態を関数でまとめると管理しやすくなります。
<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);
change ではなく input を使います。change はフォーカスが外れたときだけ発火しますが、input は1文字入力するたびに発火するため、リアルタイムに状態を更新できます。N個以上チェックで活性化(選択数を表示)
「3つ以上選択してください」のように選択数に下限を設けるパターンです。選択件数をリアルタイム表示するとユーザーへのフィードバックになります。
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;
});
input と change を登録すると、子要素の全インタラクティブ要素からバブリングで届くイベントを一括で受け取れます。個別に登録するより管理が簡単です。送信中の二重送信防止(ボタン無効化)
チェックボックスとは少し異なりますが、フォーム送信ボタンと一緒によく実装する「送信中は 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 = '送信する';
}
});
よくある質問
<button type="submit" disabled> のように disabled 属性を直接書くのが最もシンプルで安全です。JavaScriptの読み込みが遅延しても安全に非活性状態を保てます。disabled 状態では pointer-events: none が効いているため、title 属性のツールチップがブラウザによって表示されないことがあります。ボタンを <span> で囲んで span に title を付けるか、カスタムツールチップをJavaScriptで実装することで対応できます。button:disabled セレクタでスタイルを上書きします。opacity・cursor: not-allowed・background-color の3つを合わせて指定すると視覚的に伝わりやすいです。<button disabled={!isAgree}> のように state を直接バインドします。Vue では :disabled="!isAgree" です。いずれも state/data が変化するたびに自動で再レンダリングされるため、イベントリスナーの手動管理は不要です。syncBtn() など)を一度呼び出してください。もしくは HTML側で <button> から disabled を取り除き、チェックボックスを checked にした状態で書くこともできます。まとめ
チェックボックスでボタンを活性化する実装のポイントをまとめます。
- ボタンの無効化は
button.disabled = true/falseで制御する - 複数チェック「全て必須」は
every()、「1つ以上」はsome()で判定する - チェックボックス以外の入力も組み合わせるときは isFormReady 関数に判定をまとめると拡張しやすい
- フォーム全体の監視は
form.addEventListener("input"/"change", ...)のイベント委譲が効率的 - 送信中は
disabled = trueにして二重送信を防止する
チェックボックスの「全て選択」連動は【JavaScript】「全て選択」チェックボックスの実装ガイド、フォームバリデーション全体は【JavaScript】フォームバリデーション完全ガイドもあわせてご覧ください。