Contact Form 7(以下 CF7)のお問い合わせフォームにラジオボタンを足そうとしたら、画面に [radio* your-type ...] という文字列がそのまま表示されて固まった経験、ありませんか?

この記事を書いている私自身、このブログのコンタクトフォームを改修したときに、まさにその画面を見て手が止まりました。そこから「必須にしたいだけなのに [radio*] が効かない」「<label> で囲んだらCF7に警告された」と、短時間で2回立て続けに詰まることに。

日本語の記事を調べても、対処法が断片的に散らばっていて、なかなか全体像が掴みにくいと感じました。だからこそ、同じところで詰まった方が最短で答えに辿り着けるように、つまずいたポイントと対処を端的にまとめておきたいと考えています。

この記事で分かること
  • [radio*] というショートコードが存在しない理由(CF7の設計思想)と、正しい書き方
  • <label> でラジオ群を囲むとCF7が警告を出す本当の理由
  • <fieldset> + <legend> でアクセシブルに実装する方法(WCAG 2.1 準拠)

本記事は 2026年4月時点の Contact Form 7 6.1.5 で検証した内容です。バージョンアップにより挙動が変わる可能性がありますので、実装時はお使いの環境でもご確認ください。

CF7でラジオボタンを足そうとしたら画面が崩壊した話

このブログのコンタクトフォームで、「お問い合わせの種別」をラジオボタンで選ばせる仕様に変更したのが始まりでした。CF7 のドキュメントを眺めつつ、テキストフィールドと同じ感覚で [radio* ...] と書いてみたら、保存した瞬間にフォーム画面がショートコード文字列をそのまま吐き出す状態になり、想定外の表示に戸惑いました。

同じ状況で検索窓に飛び込んでくる方は、これから紹介する2つの落とし穴のどれかに引っかかっていることが多いと感じています。

  • 落とし穴 (1): [radio*](アスタリスク付き)と書いたら、ショートコードが生テキストで出力された
  • 落とし穴 (2): <label> でラジオ群を囲んだら、CF7管理画面に警告メッセージが出た

それでは、それぞれの落とし穴を順番に見ていきます。

落とし穴 (1): [radio*] と書いたらショートコードが生テキストで出た

症状

最初のつまずきは、こんなコードを書いたときに起きました。

[radio* your-type use_label_element "選択肢A" "選択肢B" "選択肢C"]

テキストフィールド([text*])やメール([email*])と同じ感覚で、「必須なのだからアスタリスクを付ければよい」と考えた結果です。ところが保存してフロント側で確認すると、[radio* your-type ...] という文字列がそのままページに表示されてしまいました。

原因

CF7 のフォームタグハンドラに登録されているラジオ・チェックボックス系のタグは、公式ドキュメント(Checkboxes, radio buttons and menus)を見る限り [radio] / [checkbox] / [checkbox*] の3つだけです。[radio*] は存在しません。

CF7 は登録されていないショートコードを見つけても、エラーではなく 「知らないもの」としてそのまま文字列出力 する挙動を取ります。だからパースされずに画面に露出してしまうわけです。

では、なぜ CF7 は [radio*] を用意していないのでしょうか。ソースコードを読むと、その設計思想が見えてきます。CF7 のラジオ・チェックボックスのバリデーション処理(modules/checkbox.phpwpcf7_checkbox_validation_filter)には、次のような実装が入っています。

PHP
$is_required = $tag->is_required() || 'radio' == $tag->basetype;

つまり、radio タグは basetype == 'radio' である時点で、常に必須扱いとして扱われるのです。アスタリスク(*)が付いているかどうかに関わらず、ラジオは自動で必須 validation の対象になります。

この設計の裏には「ラジオボタンはそもそも単一選択を前提にした UI なので、ほぼ必須入力として運用されるはず。だったら * を付ける記法は冗長だ」という割り切りがあると読み取れます。だから CF7 は [radio*] を別途用意せず、[radio] だけでシンプルに必須扱いするよう統一しているわけです。

対処

以上を踏まえると、対処はとてもシンプルです。アスタリスクを外した [radio] の形にするだけで、必須 validation はすでに効いています。

[radio your-type use_label_element "選択肢A" "選択肢B" "選択肢C"]

この書き方で未選択のまま送信すると、CF7 が自動的に「入力してください」というエラーメッセージで送信を止めてくれます。functions.php にカスタムバリデーションを追加する必要はありません。

なお、ラジオには default:N というオプションもあり、これを付けると N 番目の選択肢が初期状態で選ばれた状態 になります。ただし、これは UX 上の任意オプション(「最初から先頭を選択状態にしておきたい」という見た目の調整)であって、必須 validation とは別物です。

[radio your-type default:1 use_label_element "選択肢A" "選択肢B" "選択肢C"]

個人的には、アンケートや問い合わせ種別のように「能動的に選ばせたい」場面では default を付けないほうが、ユーザーに選択の意識を持ってもらいやすいと感じています。先頭プリセレクトのまま送られてしまうと、意図しない回答が混ざる可能性もあるためです。見た目の都合で必要な場合にのみ任意で付ける、くらいの位置付けで十分だと捉えています。

落とし穴 (2): <label> でラジオ群を囲んだらCF7に警告された

症状

ショートコードが無事に出力されるようになったあと、今度は視覚的に「質問ラベルとラジオ群を <label> で囲めばアクセシブルになるのでは」と考え、次のように書きました。

<label>
  お問い合わせの種別
  [radio your-type use_label_element "選択肢A" "選択肢B" "選択肢C"]
</label>

すると CF7 の管理画面に「複数のフォームコントロールが単一の label 要素内に置かれています」という警告が出ました。

原因

HTML 仕様上、<label>1つのフォームコントロールに対応付ける要素 です。ラジオボタン群は <input type="radio"> が複数存在するため、まとめて <label> で囲むと「複数コントロールの内包」という仕様違反になります。

さらに厄介なのが use_label_element オプションで、これを付けた場合は 各ラジオも個別に <label> で囲まれる ため、外側 <label> の中に内側 <label> が入る 入れ子構造 も発生します。HTML 仕様では <label> の入れ子も禁止なので、二重の違反状態になるわけです。CF7 のバリデータはこれを丁寧に検出してくれていました。

対処

ラジオやチェックボックスのような「選択肢のグループ」を示すときの正解は、<fieldset><legend> の組み合わせです。HTML 仕様側で「複数のフォームコントロールを1つの質問としてまとめる」ための要素が、最初から用意されているからです。

<fieldset>
  <legend>お問い合わせの種別 <span class="fn-label-required">必須</span></legend>
  [radio your-type use_label_element "選択肢A" "選択肢B" "選択肢C"]
</fieldset>

この書き方に直すと、CF7 の警告は消え、HTML 仕様・アクセシビリティの両面で正しい構造になります。詳しい要素定義は MDN: <fieldset>MDN: <legend> が参考になります。

ただし、<fieldset> はブラウザデフォルトで枠線が描画され、<legend> は枠線と重なって表示される独特の挙動を持ちます。デザイン上そのまま使いにくいことが多いので、次のようにリセットしておくと扱いやすくなります。

CSS
.wpcf7-form fieldset {
  border: none;
  padding: 0;
  margin: 0 0 20px;
}
.wpcf7-form legend {
  padding: 0;
  font-weight: 700;
}

CSS 設計の考え方としては、このリセット部分を Object 層や Layout 層に置くか、フォーム固有の Project 層に置くかで迷うところです。普段から FLOCSS で設計している方は、FLOCSSの基本の考え方に沿ってフォーム専用のプロジェクトコンポーネントとして切り出すと、他ページへの副作用を抑えやすいと感じています。

アクセシビリティの視点で見直す — WCAG 2.1 が求める「グループ化」

前章で <fieldset> + <legend> に置き換える対処を紹介しましたが、これは単に「CF7 の警告を消すための回避策」ではありません。アクセシビリティの国際ガイドライン WCAG 2.1 が明確に推奨している実装パターンでもあります。

達成基準 1.3.1「情報及び関係性」

WCAG 2.1 達成基準 1.3.1 情報及び関係性は、「視覚的に伝えられている情報の構造や関係性を、支援技術にも伝わる形で表現する」ことを求めています。

ラジオボタン群を「同じ質問に対する選択肢である」と視覚的に理解できるのは、近接して配置されているからだけではなく、同じ質問ラベルの下にまとまっていることが読み取れるからです。この「グループであるという情報」を支援技術に伝える手段が、まさに <fieldset> + <legend> だと捉えています。

達成基準 3.3.2「ラベル又は説明」

WCAG 2.1 達成基準 3.3.2 ラベル又は説明は、「ユーザーの入力を求めるコントロールには、ラベルまたは説明を提供する」ことを求めています。

各ラジオには個別の <label> が付き(use_label_element の役割)、グループ全体には <legend> が付く。この二重構造で初めて、「何についての質問か」と「各選択肢が何か」が両方とも適切にラベル付けされた状態になります。

スクリーンリーダーでの読み上げ例

正しい構造で実装されたラジオ群は、スクリーンリーダーでおおむね次のように読み上げられます。

「お問い合わせの種別、必須、ラジオボタングループ、3項目中1番目、選択肢A」

<legend> の内容がグループ名として最初に読み上げられ、続けて「グループであること」「全体で何項目か」「何番目を選択中か」が伝わります。視覚情報が得られないユーザーでも、質問の全体像を素早く把握できる流れになっているわけです。

use_label_element を付ける意味

CF7 のラジオショートコードには use_label_element というオプションがあり、これを付けると各選択肢が <input><label> のペア で出力されます。付けない場合、<label> が付かず、クリック可能領域がラジオの小さな丸の部分だけになってしまいます。

モバイルでの操作性・アクセシビリティ両面で恩恵が大きいため、特別な理由がなければ常に付けておくことをおすすめしたいオプションです。

よくある質問(FAQ)

[radio*](アスタリスク付き)は使えますか?

CF7 の正規のフォームタグハンドラには [radio] / [checkbox] / [checkbox*] のみが登録されており、[radio*] は存在しません。正しい理由はシンプルで、CF7 の内部実装上、radio タグは自動的に必須扱いになるため、* マーカーが冗長だからです。[radio your-type ...](アスタリスクなし・default も不要)のシンプルな書き方で、未選択送信時の必須バリデーションはすでに効いています。

default:0 で「初期選択なし」にできますか?

default:N は 1-indexed(先頭が1)のため、default:0 は範囲外となり「default 指定なし」と同等の扱いに落ち着きます。空送信してしまった場合も、CF7 が自動で必須 validation を行い「入力してください」エラーで止めてくれるので、functions.php での追加対処は不要です。そもそも「初期選択なし」にしたいのであれば、default オプションごと削除して [radio your-type ...] と書けばそのまま実現できます。

<fieldset> のデフォルトの枠線を消しても大丈夫ですか?

視覚的な枠線は必須ではなく、CSS でリセットしても問題ないと考えています。<fieldset> + <legend> の構造的な意味(支援技術向けのグループ化)は HTML 要素そのものが担っているため、見た目を整えても機能は保たれます。

use_label_element は付けたほうがよいですか?

付けることをおすすめしています。クリック範囲が各選択肢のラベル文字列まで広がり、モバイルでも操作しやすくなります。アクセシビリティ面でも <input><label> が明示的に関連付けられるメリットがあります。

チェックボックスで単一選択にする checkbox* exclusive とは違うのですか?

checkbox* exclusive は見た目がチェックボックスのまま単一選択を強制するオプションで、ラジオとは見た目が異なる点が最大の違いです。ラジオは「選択肢が相互排他であること」が視覚的にも明確なため、UX 上はラジオを選ぶ場面が多いと感じています。

まとめ

CF7 のラジオボタン実装でつまずきやすいのは、だいたい次の2点に集約されると感じています。

  • [radio*] は存在しない[radio](アスタリスクなし)で書く。CF7 がラジオを自動で必須扱いするため、これだけで未選択送信はエラーになる
  • <label> でラジオ群を囲むと警告が出る<fieldset> + <legend> でグループ化する

この2点さえ押さえれば、[radio](シンプル・default なし)+ use_label_element + <fieldset> / <legend> の組み合わせが、シンプルで保守しやすいベースパターンになります。必要以上にカスタムバリデーションを組む前に、まずは CF7 が標準で提供してくれる挙動を活かすのが近道だと捉えています。

アクセシビリティ対応(WCAG 2.1)は「特別な追加作業」というより、正しい HTML 構造を書くこと自体が大部分をカバーしてくれる 領域だと捉えています。<fieldset> + <legend> に書き換えるだけで、達成基準 1.3.1 と 3.3.2 への対応が同時に進むのはその好例です。

フォーム周りの実装は、HTML の意味構造・WordPress のフィルター・アクセシビリティと、フロントエンド学習の要素が凝縮された領域だと感じています。学習の全体像を改めて俯瞰したい方は、フロントエンド学習ロードマップで次に取り組むべきテーマを確認していただくと、今回の内容も位置づけやすくなるはずです。

同じところで詰まっていた方の参考になれば嬉しいです。