input[type="checkbox"]やinput[type="radio"]を悪用してJSの出番を減らそう

本稿は『[CSS Advent Calendar 2016 - Qiita](http://qiita.com/advent-calendar/2016/css)』21日目の記事です．
なんかみんな「ぼくがかんがえたさきょうのしーえすえすせっけい」について語る雰囲気っぽですが，ここは空気を読まずに小ネタでいきます．

ぜんぜんどうでもいい話ですが，僕はCSS設計ネタでは『[morishitterのCSSの書き方（2016年夏） - morishitter blog](http://morishitter.hatenablog.com/entry/2016/07/29/204642)
』と『[Clean and Unbreakable CSS // Speaker Deck](https://speakerdeck.com/creasty/clean-and-unbreakable-css)』が好きです．

---

## TL;DR
- `:checked`擬似クラスと兄弟セレクタ`~`を利用すると…
    - JSを書かずに「ステート（on/offの2値）によるスタイルを切り替え」が実現できる
- `label`要素と`input[type="checkbox"]`または`input[type="radio"]`を利用すると…
    - JSを書かずに「ある要素がクリックされたらステート（on/offの2値）が変わる」が実現できる

## サンプル: Pure-CSSなTODOリスト
タスクの完了状態の切り替え，およびタスクを1つ選択することができる（さすがにCSSだけで永続化は不可能，たぶん）．

> [![https://gyazo.com/cbb4adceeda21cb54723731f1c3c017b](https://i.gyazo.com/cbb4adceeda21cb54723731f1c3c017b.gif)](https://gyazo.com/cbb4adceeda21cb54723731f1c3c017b)
>
> <cite>[Pure CSS TODO list | CodePen][pen-todo]</cite>

マークアップはこんな感じ（pugでごめんなさい）．

```jade
// completeId, selectId, taskは変数
li.TaskList__item
  input.TaskList__item-complete(type="checkbox" id=completeId)
  input.TaskList__item-select(type="radio" name="TaskList__item-select" id=selectId)

  label.TaskList__item-icon(for=completeId)
  label.TaskList__item-body(for=selectId) #{task}
```

### 実装
以降のスニペットについては，関係ないところは省略してある．

```jade
// display: noneになってる
input.TaskList__item-complete(type="checkbox" id=completeId)

// 見えてるチェックボックスはlabelの::before疑似要素
// forでcheckboxのidを指定しているので，これをクリックするとcheckboxの状態も変わる
label.TaskList__item-icon(for=completeId)
.TaskList__item-body #{task}
```

```css
/*
 * チェックボックスの枠
 */
.TaskList__item-icon::before {
  font-family: "FontAwesome";
  content: "";
  display: block;
  border: 1px solid #444;
  border-radius: 4px;
  margin: calc(2 * var(--spacing)) var(--spacing);
  width: var(--TaskList__item-icon-size);
  height: var(--TaskList__item-icon-size);
  line-height: var(--TaskList__item-icon-size);
  cursor: pointer;  
}

/*
 * チェックボックスの中身
 * FontAwesomeの`fa-check`
 * 「チェックが入ったチェックボックスと同じ階層にある.TaskList__item-iconの::before疑似要素」にチェックマークを入れる
 */
.TaskList__item-complete:checked ~ .TaskList__item-icon::before {
  content: "\f00c";
}

/*
 * 「チェックが入ったチェックボックスと同じ階層にある.TaskList__item-body」に取り消し線を入れる
 */
.TaskList__item-complete:checked ~ .TaskList__item-body {
  text-decoration: line-through;
}
```

タスクの選択も同様．


### 実用するには…
以下についてJSでサポートしてあげる:

- checkbox/radioの`checked`属性に永続化されてるタスクの状態を反映する
- checkbox/radioのchangeイベントを拾ってWeb API叩くなりする


## Pure CSSだとなにが嬉しいの？
~~単純に楽しいだけ．~~

JSとしては`checked`属性と`change`イベントだけ見てればいいので，見た目とかはすべてCSSに丸投げできる．
よくあるクリックイベント拾って`$.addClass()`して…みたいなのを書かなくていい~~，という建前~~．
取りうる状態が2種類しかない場合（on/off, open/close, complete/incomplete, running/stopped, etc.）に雑にスタイルを切り替えたい場合は利用価値があるかも．

微妙にテストが書きづらくなるとか，ムダに詳細度上げてる説があったり等のデメリットもあるので濫用しすぎない方がいいのかもしれない．


## 他には？
- [ハンバーガーメニューをCSSだけでやるやつ][pen-hamburger]とか


## おわりに
CSSには他にも[attr()](https://developer.mozilla.org/ja/docs/Web/CSS/attr)や[CSSカウンタ](https://developer.mozilla.org/ja/docs/Web/Guide/CSS/Counters)のような使い所の難しいおもしろ機能がいっぱいあるので，じゃんじゃん悪用してたのしくCSSを書いていきましょう．
暇な時に仕様書を眺めてみると，新たな発見があるかもしれません．

個人的には[attr()が`content`プロパティ以外にも利用できるようになる](https://developer.mozilla.org/ja/docs/Web/CSS/attr#Browser_compatibility)のが非常に楽しみです．

[pen-todo]: http://codepen.io/izumin5210/pen/aBMJZd
[pen-hamburger]: http://codepen.io/erikterwan/pen/EVzeRP