CSSだけでradio chackboxをカスタマイズする内容ってほとんどがHTML構造気持ち悪いよね。

マークアップエンジニアの  です。

CSSだけでRadioボタンの作り方みたいな内容の記事をよくお見かけしますが殆どのマークアップが気持ち悪く感じるのは僕だけでしょうか?

大体が

<section>
    <input type="radio" name="hoge" value="テストRadio" id="radio_item" checked />
    <label for="radio_item" class="radio">TestRadio</label>
</section>

繰り返す様ですが
コレって気持ち悪いし使いづらいと思うのは僕だけでしょうか?

気持ち悪いと思う点

  1. labelタグの使い方
  2. チェックさせる為にid属性をわざわざ振っちゃう
  3. radioボタンに紐づくtextが存在しない場合にどうする気ですか?
  4. Tab移動意識してますか?

で、気持ち悪い気持ち悪いと言っていても問題は解決しないので
マークアップ構造が美しくRadio CheckBoxをデザイン的にする方法を考えました。

こうできたら良いなと思う点

  1. マークアップが美しい
  2. id属性とかわざわざ振らない
  3. textが存在しなくても空の要素とかできない
  4. Tab移動意識する

実際にやってみてできたやつ

f:id:nanndemoiikara:20150527182603p:plain

このイクラみたいなRadioボタンがデザイン的であるかはさておき、
こうできたら良いなという点は全てクリアしています。

2017/02/10 追記

ブラウザ上で好きなデザインで生成するやつ作りました。

Radio Check Generater

対応ブラウザ

で動作確認済です。

実際にやってみたソースコード

HTML

<section>
	<label class="radio">
		<input type="radio" name="hoge" value="hoge" checked>
	</label>
	<label class="radio">
		<input type="radio" name="hoge" value="hoge">
	</label>
</section>
<section>
	<label class="radio_text">
		<input type="radio" name="hogehoge" value="hoge" checked>TestRadio1
	</label>
	<label class="radio_text">
		<input type="radio" name="hogehoge" value="hoge">TestRadio2
	</label>
</section>
<section>
	<label class="checkbox">
		<input type="checkbox" name="huga" value="hoge" checked>
	</label>
	<label class="checkbox">
		<input type="checkbox" name="huga" value="hoge">
	</label>
</section>
<section>
	<label class="checkbox_text">
		<input type="checkbox" name="hugahuga" value="hoge" checked>TestCheck1
	</label>
	<label class="checkbox_text">
		<input type="checkbox" name="hugehuge" value="hoge" checked>TestCheck1
	</label>
</section>

こんな感じでlabelタグでinputタグを囲ってTextデータが存在しなくても空の要素が作成されませんし、わざわざid属性を振るなんてばかばかしい事をする必要がありません。


先ほどのよくあるコードでTextデータが存在しないかつCSSを適用したい場合のマークアップはおそらく
こんな感じになるんじゃないでしょうか?

<input type="radio" id="hoge" name="hoge" >
<label for="hoge">&nbsp;</label>

id属性って(W3C的に)重複が認められないのでチェックボックスが羅列されてるフォームの場合、よくあるコードを使っているマークアップエンジニアさんはどのように実装しているのでしょうか?

私の環境では、連番のid属性で実装とかしていたらフロントエンドエンジニアに椅子を投げつけられてしまいますので私は怖くてできません。

マークアップはこれで、どうやってCSSやるの?無理じゃん」

となるかもしれませんが、要件がIE9以降であるならば
さいつよなCSSプロパティbox-shadowが使用できます。

/*Radioのみ*/
label.radio {
	position      : relative;
	display       : inline-block;
	width         : 15px;
	height        : 15px;
	border        : 1px solid #666;
	border-radius : 100%;
	overflow      : hidden;
	cursor        : pointer;
}
label.radio:before {
	content          : '';
	display          : block;
	width            : 11px;
	height           : 11px;
	border-radius    : 100%;
	position         : absolute;
	top              : 2px;
	left             : 2px;
	z-index          : 1;
	background-color : #D65; 
}
label.radio input[type="radio"] {
	-moz-appearance: none;
	-webkit-appearance: none;
	margin     : 0px;
	position   : absolute;
	z-index    : 2;
	top        : -2px;
	left       : -23px;
	width      : 20px;
	height     : 20px;
	display    : block;
	box-shadow : 20px 0px #FFF;
}
label.radio input[type="radio"]:checked {
	box-shadow : none;
}
label.radio input[type="radio"]:focus {
	box-shadow : 20px 0px #FFF;
	opacity    : 0.2;
}

基本はこんな感じです。
これは「Text無しのRadio」です。
なんか「壊れかけのRadio」と語感がよく似ていますが
「Text無しのRadio」です。


必要ないとは思いますが解説すると

  • labelでクリックできる領域とチェックされている状態(便宜上イクラと呼びます。)の描画を行います。
  • あとはinputの実体(?)をposition指定で表示領域外に表示させます。
  • 表示領域外のinput実体(?)に「display:block;」をかけます。
  • labelのbeforeに設定されているイクラに対して、「Text無しのRadio」がチェックされていない場合は、背景白のbox-shadow上からかぶせて隠しています。
  • 「壊れかけのRadio」「Text無しのRadio」がチェックされたらbox-shadowがnoneになります。
  • チェック動作に関しては「本当の幸せ教えてよ」「Text無しのRadio」がlabelで囲われているので上に被さっているlabelをクリックしたらチェックされます。
  • Tab移動に気を使い横に「Text無しのRadio」「壊れかけのRadio」を吹っ飛ばします。(縦にすると移動時に適切な領域が表示されない為)


/*RadioとText*/
label.radio_text {
	cursor       : pointer;
	position     : relative;
	padding-left : 5px;
	margin-right : 20px;
	overflow     : hidden;
	padding-left : 20px;
	display      : inline-block;
}
label.radio_text:before {
	position      : absolute;
	width         : 15px;
	height        : 15px;
	border        : 1px solid #666;
	border-radius : 50%;
	left          : 0px;
	top           : 4px;
	content       : '';
	z-index       : 3;
}
label.radio_text:after {
	content          : '';
	position         : absolute;
	width            : 11px;
	height           : 11px;
	border-radius    : 100%;
	left             : 3px;
	top              : 7px;
	background-color : #D65;
	z-index          : 1;
}
label.radio_text input[type="radio"] {
	-moz-appearance: none;
	-webkit-appearance: none;
	position   : absolute;
	z-index    : 2;
	width      : 20px;
	height     : 20px;
	left       : -23px;
	top        : 1px;
	margin     : 0px;
	box-shadow : 20px -1px #FFF;
}
label.radio_text input[type="radio"]:checked {
	box-shadow : none;
}
label.radio_text input[type="radio"]:focus {
	opacity    : 0.2;
	box-shadow : 20px -1px #FFF;
}

こちらも基本構造は殆ど同じです。
ただ違う点は、Textが入っているので
beforeでイクラの大枠を作って
afterでイラクの赤丸を作っています。
で、input部分については先ほどのradio単体の場合と相違ありません。







CheckBoxのText有無も同じ様に実装します。

/*CheckBox */
label.checkbox {
	cursor     : pointer;
	width      : 20px;
	height     : 20px;
	border     : 1px solid #B3B3B3;
	background : #fff;
	overflow   : hidden;
	position   : relative;
	display    : inline-block;
	box-sizing : border-box;
}
label.checkbox input[type="checkbox"] {
	-moz-appearance: none;
	-webkit-appearance: none;
	margin     : 0;
	padding    : 0;
	position   : absolute;
	left       : 20px;
	width      : 20px;
	height     : 20px;
	left       : -40px;
	box-shadow : 39px 0px #FFF;
	z-index    : 2;
}
label.checkbox input[type="checkbox"]:checked {
	box-shadow : none;
}
label.checkbox input[type="checkbox"]:checked:focus {
	box-shadow : 39px 0px #666;
	opacity    : 0.1;
}
label.checkbox input[type="checkbox"]:focus {
	box-shadow : 39px 0px #EEE;
}
label.checkbox:after {
	content           : '';
	position          : absolute;
	top               : 40%;
	left              : 5px;
	display           : block;
	margin-top        : -8px;
	width             : 8px;
	height            : 12px;
	border-right      : 3px solid #D65;
	border-bottom     : 3px solid #D65;
	transform         : rotate(45deg);
	-webkit-transform : rotate(45deg);
	-moz-transform    : rotate(45deg);
	z-index           : 1;
}

/*CheckBoxとText */
label.checkbox_text {
	cursor       : pointer;
	position     : relative;
	padding-left : 25px;
	margin-right : 20px;
	overflow     : hidden;
	position     : relative;
	padding-left : 25px;
	display      : inline-block;
	box-sizing   : border-box;
}
label.checkbox_text:before {
	content  : '';
	position : absolute;
	width    : 20px;
	height   : 20px;
	left     : 0px;
	top      : 0;
	border   : 1px solid #B3B3B3;
	z-index  : 3;
}
label.checkbox_text:after {
	content           : '';
	position          : absolute;
	top               : 40%;
	left              : 6px;
	display           : block;
	margin-top        : -8px;
	width             : 8px;
	height            : 12px;
	border-right      : 3px solid #D65;
	border-bottom     : 3px solid #D65;
	transform         : rotate(45deg);
	-webkit-transform : rotate(45deg);
	-moz-transform    : rotate(45deg);
	z-index           : 1;
}
label.checkbox_text input[type="checkbox"] {
	-moz-appearance: none;
	-webkit-appearance: none;
	position   : absolute;
	left       : -40px;
	width      : 20px;
	height     : 20px;
	display    : block;
	box-shadow : 41px 0px #FFF;
	z-index    : 2;
	margin     : 0px;
	padding    : 0px;
}
label.checkbox_text input[type="checkbox"]:checked {
	box-shadow : none;
}
label.checkbox_text input[type="checkbox"]:checked:focus {
	box-shadow : 40px 0px #666;
	opacity    : 0.1;
}
label.checkbox_text input[type="checkbox"]:focus {
	box-shadow : 41px 0px #EEE;
}


こんな感じで疑似要素でチェック後の表示を行って
box-shadowでそれを隠す感じでCSSを書く。

反省点・改善点

  1. RadioTextの処理が乱雑なので背景が白以外のときはちょっと頭をひねる必要があります。
  2. IE8に対応したい
  3. 「-moz-appearance: none」とかやってしまっているあたりが若干卑怯な感じがするけどLinuxWindowsFireFoxではコレが無いとなぜかbox-shadowとoverflow:hiddenの組み合わせでバグる

まとめ

無理している感は否めませんが、前述した問題点は解決しています。
これならわざわざid属性振る様な事をしなくても良いし、見比べてもどこからどこまでがform controlの領域なのかが明確でマークアップが健全な感じがします。*1

id振るパティーン
<label for="test">テスト</label>
<input type="radio" value="1" id="test" />テスト
labelで囲うパティーン
<label>
     <input type="radio" value="1" />テスト
</label>


また、プロパティでafter,beforeをまとめちゃえばもっと短くかけます。(説明の便宜上分けました。)
Tableタグでチェックボックス表示させてなんか処理させるような管理画面とかで使用できるかもしれません。
余談ですが、「イクラ」と表記している所を1箇所だけ「イラク」にしておきました。
気付きましたでしょうか?

2017/02/10 追記

ブラウザ上で好きなデザインで生成するやつ作りました。

Radio Check Generater

*1:「id振るパティーン」がW3C的に健全ではないとかではないです。※あくまで個人の感想で個人差があります。ただ、1ページにユニークなid属性を多用してしまう構造になりがちな点では「id振るパティーン」ではid属性の管理が難しいのではないかと思います。http://waic.jp/docs/WCAG-TECHS/H91.html