俺の CSS リセット: 2011 冬

年末だからというわけでもないのですが、いつものサイト作りに使う CSS リセットについて見直してみました。今までもちょっとずつ手を入れてはいたのですが、今回はかなり大きく修正しています。というのも、Nicolas GallagherJonathan Neal の両氏による normalize.css を知り、大きく影響を受けたからです。

Normalize.css は「新手の CSS リセット」ではありません。CSS を「リセット」するのではなく「ノーマライズ」する、という新しい考え方です。CSS リセットとノーマライズはどちらも、ブラウザ間で CSS の実装に差異があることを前提にそれらを吸収しようとする、という同じ目的を持っています。ただ、リセットはすべてをまっさらな「さら地」にしようとするのに対し、ノーマライズは使える部分は残しつつ手を入れる必要のある部分だけを整える、という違いがあります。「ブラウザ間の CSS 実装に差異がある」とは言っても、それぞれのブラウザが勝手にてんでバラバラの方向を向いているわけではないので、歩調のあってるとこまでわざわざ殺さず、活かせるものはそのまま活かして、という発想です。

Normalize.css によって具体的にどんなスタイルになるかは デモ をご覧いただくのが手っ取り早いと思います。シンプルなサイトなら、ここにちょっとしたレイアウトのスタイルを追加すればそのまま完成するような、汎用性の高いスタイルです。

とは言え、長いことリセットという手法が一般的であったため、たとえばブロックレベル要素のマージンやリストのマーカーがデフォルトのままだったりすると扱いづらいという場合も多いんじゃないでしょうか。一から自分でコントロールできる新規プロジェクトならいいですが、チームでメンテナンスしていたり、運用・管理を他の人に移譲するものの場合、ある程度はコモンセンスというか浸透している手法でないと採用が難しい、というところもあると思います。とくにリセットもノーマライズもスタイルの基盤を作るもので、その後の CSS 設計のすべてに大きく影響するものなのでなおさらです。

というわけで、ノーマライズの考え方を取り入れつつも影響の大きそうな部分はリセットしておくという、新しいリセットのアプローチを模索しています。以下はそうやって出来た「俺の CSS リセット: 2011 冬」です。Normalize.css については GitHub の Wiki に詳しい解説があるのでそちらも参照してみてください。そこには先人たちの様々な CSS テクニックが盛り込まれており、そのまま採用しないにしても読むだけで得られるものは少なくないと思います。

ソースコード

/*!
 * CSS Reset 2011-12-25
 * https://gist.github.com/1360380
 *
 * Author:  Takeru Suzuki, https://terkel.jp/
 * License: Public domain
 *
 * Inspired by Normalize.css: http://necolas.github.com/normalize.css/
 */



/* HTML5 display definitions */

section, nav, article, aside, hgroup,
header, footer, figure, figcaption, details {
    display: block;
}

video, audio, canvas {
    display: inline-block;
    *display: inline;
    *zoom: 1;
}

audio:not([controls]) {
    display: none;
}

[hidden] {
    display: none;
}



/* The root element */

html {
    font-size: 100%;
    overflow-y: scroll;
    -webkit-text-size-adjust: 100%;
        -ms-text-size-adjust: 100%;
}



/* Sections */

body {
    font-family: sans-serif;
    margin: 0;
}

h1, h2, h3, h4, h5, h6 {
    font-size: 1em;
    margin: 0;
}



/* Grouping content */

p, blockquote, dl, dd, figure {
    margin: 0;
}

hr {
    color: inherit;
    height: auto;
    -moz-box-sizing: content-box;
         box-sizing: content-box;
}

pre {
    font-family: monospace, sans-serif;
    white-space: pre-wrap;
    word-wrap: break-word;
    margin: 0;
}

ol, ul {
    padding: 0;
    margin: 0;
}

li {
    list-style: none;
}



/* Text-level semantics */

a:focus {
    outline: thin dotted;
}

a:hover, a:active {
    outline: 0;
}

strong, b {
    font-weight: bold;
}

small {
    font-size: 0.83em;
}

q {
    quotes: none;
}

abbr[title] {
    border-bottom: 1px dotted;
}

code, samp, kbd {
    font-family: monospace, sans-serif;
}

mark {
    color: black;
    background-color: yellow;
}

sub, sup {
    font-size: 0.83em;
    line-height: 0;
    vertical-align: baseline;
    position: relative;
}

sub {
    bottom: -0.25em;
}

sup {
    top: -0.5em;
}

br {
    letter-spacing: 0;
}



/* Embedded content */

img {
    border: 0;
    -ms-interpolation-mode: bicubic;
}

svg:not(:root) {
    overflow: hidden;
}



/* Tabular data */

table {
    border-collapse: collapse;
    border-spacing: 0;
}

caption {
    padding: 0;
    text-align: left;
}

th, td {
    text-align: left;
    vertical-align: baseline;
    padding: 0;
}



/* Forms */

form {
    margin: 0;
}

fieldset {
    border: 0;
    padding: 0;
    margin: 0;
}

legend {
    border: 0;
    *margin-left: -7px;
}

input, button, select, textarea {
    font-family: inherit;
    font-size: 1em;
    color: inherit;
    margin: 0;
}

input, button {
    line-height: normal;
    vertical-align: inherit;
    *vertical-align: middle;
}

input::-moz-focus-inner,
button::-moz-focus-inner {
    border: 0;
    padding: 0;
}

input[type="search"] {
    -webkit-appearance: textfield;
    -webkit-box-sizing: content-box;
       -moz-box-sizing: content-box;
            box-sizing: content-box;
}

input[type="search"]:focus {
    outline-offset: -2px;
}

input[type="search"]::-webkit-search-decoration {
    -webkit-appearance: none;
}

input[type="checkbox"],
input[type="radio"] {
    box-sizing: border-box;
    padding: 0;
}

input[type="submit"],
input[type="reset"],
input[type="button"],
button {
    cursor: pointer;
    -webkit-appearance: button;
    *overflow: visible;
}

select {
    background-color: inherit;
    line-height: normal;
}

textarea {
    vertical-align: top;
    overflow: auto;
    *font-family: sans-serif;
}

長い… 要素の分類と記述順は HTML5 の仕様書 を参考にしました。以下、それぞれのパートごとにできる限り解説してみます。

HTML5 display definitions

section, nav, article, aside, hgroup,
header, footer, figure, figcaption, details {
    display: block;
}

video, audio, canvas {
    display: inline-block;
    *display: inline;
    *zoom: 1;
}

audio:not([controls]) {
    display: none;
}

[hidden] {
    display: none;
}

まずは HTML5 の新仕様関連で、normalize.css 丸写しです。HTML5 以前の古いブラウザ向けですね。videoaudio などマルチメディア系の要素は個人的に扱った経験がないため詳しくはわからないんですが、とりあえずは display まわりを揃える程度なので、このままいってよさそうです。将来的に実装が進んだときはもうちょっと複雑な記述が必要になるのかもしれません。hidden 属性 は HTML5 で新しく定義されたグローバル属性です。属性セレクタによる指定のため IE6 では非表示になりませんが。

The root element

html {
    font-size: 100%;
    overflow-y: scroll;
    -webkit-text-size-adjust: 100%;
        -ms-text-size-adjust: 100%;
}

次はドキュメントのルートであるところの html への指定で、ここも normalize.css まんまです。

font-size: 100% は、フォントサイズを em 単位で指定した場合に「文字サイズの変更」をおこなうとサイズが極端に変動するという IE6/7 のバグへの対応です。これはいずれかの祖先要素のフォントサイズを % 単位で指定しておくと回避できるので、こうしておけば body 以下で自由に em が使えます。

overflow-y: scroll は IE6/7 以外のブラウザ向けで、ドキュメントの高さがウィンドウより小さくても縦のスクロールバー領域を確保するというものです。

次の -webkit-text-size-adjust: 100%-ms-text-size-adjust: 100% は iOS の文字サイズ自動アジャスト機能に対するもののようです。が、手元の iPhone で軽く検証したりググってみたりしたものの、正直よくわかっていません… とりあえず、この値を none にしてしまうのはどうもよくないらしいとか、IE Mobile がなぜか -webkit-text-size-adjust をサポートしてた (けど直った?) とか、iOS だけじゃなくてデスクトップ版 Safari にも影響があったとか、なんかややこしいらしいってことだけはわかりました。どなたかご教授いただけると嬉しいです! ここでは参考になりそうなリンクだけ並べてお茶を濁します:

Sections

body {
    font-family: sans-serif;
    margin: 0;
}

h1, h2, h3, h4, h5, h6 {
    font-size: 1em;
    margin: 0;
}

bodyfont-family はとりあえずざっくりと指定しておいて、あとはプロジェクトごとに細かくやる (あるいはやらない) という感じ。margin はリセット。ちなみに body のマージンはデフォルトだと IE6/7 でやや大きいです。

hn は normalize.css だと HTML5 のセクショニング要素の対応として h1 { font-size: 2em; } しか指定してない (#41: Normalize headings - Issues - necolas/normalize.css - GitHub) ですが、ここでは h1 から h6 まで font-sizemargin をリセットしてます。

Grouping content

p, blockquote, dl, dd, figure {
    margin: 0;
}

hr {
    color: inherit;
    height: auto;
    -moz-box-sizing: content-box;
         box-sizing: content-box;
}

pre {
    font-family: monospace, sans-serif;
    white-space: pre-wrap;
    word-wrap: break-word;
    margin: 0;
}

ol, ul {
    padding: 0;
    margin: 0;
}

li {
    list-style: none;
}

段落やリストなど、実際にサイトでよく使うとこですね。このへん、normalize.css ではあまりいじってないんですが、ここではけっこうリセットしてます。

まず p などブロックレベル要素の margin はリセット。ulolpadding0 に、li のマーカーもなし。normalize.css では nav 要素の子孫である場合にだけマーカーなしですが、実際問題としていわゆる普通のサイトではマーカーとして独自の画像を用意することが多いと思うので。

hrFirefox のスタイルがちょっと例外的 なので他のブラウザに合わせる方向。IE6/7 以外は上下に 0.5em の margin を持っていますが、IE6/7 では上下の余白がコントロールできないため、きっちり見た目を揃えるのが難しいです。なのであえてリセットせずそのまま残してます。

pre はまず Safari と Chrome でフォントサイズが揃わない問題 への対応として font-family: monospace, sans-serif を指定。あと行が長くなったときに折り返すようにしています。

Text-level semantics

a:focus {
    outline: thin dotted;
}

a:hover, a:active {
    outline: 0;
}

strong, b {
    font-weight: bold;
}

small {
    font-size: 0.83em;
}

q {
    quotes: none;
}

abbr[title] {
    border-bottom: 1px dotted;
}

code, samp, kbd {
    font-family: monospace, sans-serif;
}

mark {
    color: black;
    background-color: yellow;
}

sub, sup {
    font-size: 0.83em;
    line-height: 0;
    vertical-align: baseline;
    position: relative;
}

sub {
    bottom: -0.25em;
}

sup {
    top: -0.5em;
}

br {
    *letter-spacing: 0;
}

いわゆるインライン要素。このあたりは基本的にデフォルトの font-weightfont-style をそのまま活かします。

aoutline プロパティは、Eric Meyer 氏のリセットの古い版 で「ちゃんとあとから自分で定義し直してね!」っていう注釈つきで :focus { outline: 0; } となってた (のちに修正された) のがそのまま広まってしまった感があります。が、それではアクセシビリティ上の問題があるので、そのあたりを考慮したものになってます。以下、参考資料:

strongb は Firefox や WebKit のデフォルトでは bold ではなく bolder という相対値です。つまり、<h1><b>Steve Jobs</b>: 1955–2011</h1> というマークアップで h1font-weightbold (700) の場合、その中の b900 となるわけです (CSS Fonts Module Level 3 - 3.2 Font weight: the font-weight property)。このままでもいいんですが、予期したウェイトよりも太くなってしまうってことがけっこうありそうなので bold に揃えよう、ってことだと思いますたぶん。ウェイトがノーマルかボールドしか使えない場合がほとんどの和文フォントではあまり気にする機会がありませんが。

subsup は行の高さと縦位置の調整です (CSS for and — Gist)。

br親要素に letter-spacing が指定されているときに連続して改行しても認識されない という IE6/7 のバグへの対応。

Embedded content

img {
    border: 0;
    -ms-interpolation-mode: bicubic;
}

svg:not(:root) {
    overflow: hidden;
}

img はリンク時のボーダーを消して、IE7 で拡大縮小したときに綺麗にする、っていういつものやつです。svg は IE9 向けの指定みたいです。

Tabular data

table {
    border-collapse: collapse;
    border-spacing: 0;
}

caption {
    padding: 0;
    text-align: left;
}

th, td {
    text-align: left;
    vertical-align: baseline;
    padding: 0;
}

テーブル関連はセル間の隙間をなくすのに加えて、キャプションとセルの文字方向とパディングをリセットしてます。

Forms

form {
    margin: 0;
}

fieldset {
    border: 0;
    padding: 0;
    margin: 0;
}

legend {
    border: 0;
    *margin-left: -7px;
}

input, button, select, textarea {
    font-family: inherit;
    font-size: 1em;
    color: inherit;
    margin: 0;
}

input, button {
    line-height: normal;
    vertical-align: inherit;
    *vertical-align: middle;
}

input::-moz-focus-inner,
button::-moz-focus-inner {
    border: 0;
    padding: 0;
}

input[type="search"] {
    -webkit-appearance: textfield;
    -webkit-box-sizing: content-box;
       -moz-box-sizing: content-box;
            box-sizing: content-box;
}

input[type="search"]:focus {
    outline-offset: -2px;
}

input[type="search"]::-webkit-search-decoration {
    -webkit-appearance: none;
}

input[type="checkbox"],
input[type="radio"] {
    box-sizing: border-box;
    padding: 0;
}

input[type="submit"],
input[type="reset"],
input[type="button"],
button {
    cursor: pointer;
    -webkit-appearance: button;
    *overflow: visible;
}

select {
    background-color: inherit;
    line-height: normal;
}

textarea {
    vertical-align: top;
    overflow: auto;
    *font-family: sans-serif;
}

そして最後はフォーム関連。やはりここがもっともややこしいですね。基本的に normalize.css まんまなので詳しくはそちらを参照していただきたいんですが、おもに以下のような指定を追加してます:

HTML5 の新しいコントロール型として search 型にけっこうな手間をかけてますが、これからほかの新しい型の実装も進んだとき、今までのようにすべてリセットしてクロスブラウザでピクセルパーフェクトなルックを目指すというのはいよいよ現実的ではないと思います。そういった意味で、フォームこそ CSS ノーマライズという考え方の真価が発揮されるところかもしれません。

メンテナンス

さて、この手のコードは、チームや個人で一度これと決めてしまうとあまり中身を見直されることなくそのまま使い回されるということになりがちです。その結果として、すでに不要になった古い (バッド) ノウハウが残り続け、よくわからないまま「おまじない」として次世代に受け継がれていく… という、あまり好ましくない事態が想定されます。とくに今後、要らないベンダー接頭辞が残り続けているという事態はちょくちょく目にすることになりそうです。ですから、ブラウザの実装の進歩やシェアの変動を踏まえつつ、折を見て内容を吟味して、更新し続けることが重要だと考えます。

というわけで、上記 CSS のソースコードは Gist に置いてます。気がついたときにちょくちょく手を入れると思うので、本稿での記述とは違っていることがあるかもしれません。あと現時点でのデモページも用意しました。HTML の中身は normalize.css のをそのまま拝借しています:

参考

最後に参考にしたリソースのリストを。まずはブラウザのデフォルトのスタイルシートを確認できるサイトです。IE のは公式じゃないし、Opera のは見つけられませんでした:

あと、メジャーな CSS リセットをいくつか:

メリー・クリスマス!