GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

css リセット 9選 — 基本からセーフエリアまで一気に整理

9つのcss リセットパターンのうち、1つはノッチのセーフエリアに合わせて余白を再計算し、1つはモーションを減らす環境で遷移時間がほぼ0まで縮み、1つは角ばった初期の角が滑らかな曲線に変わります。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

以下の9つはどれもブラウザ名を判定するコードを持ちません。あるものはクラスや疑似クラスの切り替えでどちらのルールが有効になるかを決め、あるものは定数と連動した数値を都度計算し直し、あるものは機能そのものに対応しているかを一度だけ尋ねて分岐します。9つのデモはページが落ち着くと自動的に動き出し、最初のポインター・キーボード・入力操作でただちに手動操作へ切り替わり、prefers-reduced-motion: reduce が有効な環境でもすべて問題なく動作し続けます。

デモ

下の各パネルをドラッグ・フォーカス・入力・切り替えしてみてください — 数値の読み取り行がその場で更新されるので、変化を推測ではなく目で確かめられます。

9つがこの順番である理由

9つをこう並べ替えると、素のcss リセットを書くときに実際に何を払っているかが一番見えやすくなります — あるときはクラス名ひとつ、あるときは数値ひとつ、あるときはsupports() の問いひとつです。

パターン 仕組み 動いたフレーム(24中) 累積面積 平均強度
01 ブラウザ既定値リセット ルール切り替え 12 19.15% 104.5
02 ボックスモデル比較 再計算 22 2.36% 122.3
03 見出しの折り返し調整 機能検査 23 3.253% 139.6
04 メディア基本比率固定 再計算 18 11.703% 137.6
05 フォーム要素のフォント継承 ルール切り替え 22 13.463% 95.8
06 キーボードフォーカスリング ルール切り替え 15 6.39% 94.2
07 ノッチのセーフエリア余白 再計算 23 1.788% 110.3
08 モーション削減対応 再計算 13 5.87% 122.2
09 角丸の形状比較 機能検査 9 1.044% 118.6

クラスターA — クラスか疑似クラスがどちらのルールを勝たせるか決める

3つのパターン(01・05・06)は数値をまったく扱いません。RESET_SCOPE を切り替える、.inherited クラスをオン・オフする、マウスではなくキーボードでボタンに到達する — このどれかが2つの素朴なルールのどちらを効かせるかを決め、あとはカスケードに任せます。01番は余白とリスト表示を一括で消すため、9項目の中で最も広い累積面積(19.15%)を占めます。

クラスターB — 名前付き定数が再計算の対象を決める

4つのパターン(02・04・07・08)はスライダー・ドラッグ・スイッチといったライブな入力を読み取り、値が変わるたびに名前付き定数に対して測定値を計算し直します。4つとも環境を判定してはおらず、BORDER_PX・MAX_FRAME_PX・SAFE_AREA_SCALE・REDUCED_MOTION_SCALE という定数にそれぞれ依存しています。

クラスターC — supports() の問いが一度だけ分岐先を決める

2つのパターン(03・09)は CSS.supports() に直接イエス・ノーを尋ね、時間をかけて矩形を比較する代わりにその答えだけで分岐します。03番はこの中で最も多くのフレームで動き(24中23、07番と並んで最多)、行の均等化が起こるたびに下のバーグラフごと再描画されるためです。

項目別コード

以下の各コードは、そのデモの実際のcss リセットコードをそのまま連続で切り出したものです。コメントだけ日本語に訳し、識別子は表示されている通りに残しています。

01ブラウザ既定値リセット — 要素ごとに違う余白とリスト表示をひとつに揃える出発点

ブラウザごとに異なる既定の余白とリストの点表示をすべて消し、すべての要素が同じ出発点から始まります。RESET_SCOPE を 'full' に切り替えると、そこにリンクの下線を消す処理まで追加で乗ります。23フレーム中12フレームで動き、9項目の中で最大の累積面積(19.15%)を平均強度104.5で記録しました。範囲ボタンのクリックが最初の操作としてカウントされます。

ブラウザ既定値リセット — 要素ごとに違う余白とリスト表示をひとつに揃える出発点
新しいタブで開く

01 · ブラウザ既定値リセット — 要素ごとに違う余白とリスト表示をひとつに揃える出発点 — 計測 累積面積 19.15% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

margin: 0list-style: noneRESET_SCOPE
.panel.reset {
  .demoHeading, .demoPara, .demoList { margin: 0 0 $sp-2; }
  .demoList { padding: 0; list-style: none; }
  .demoList li { padding: $sp-1 0; }
}

.panel.reset.full .demoLink {
  color: $subject-blue-light;
  text-decoration: none;
}

02ボックスモデル比較 — 枠線と内側の余白がサイズ計算に含まれるかを見せるカード

2つの計算方式を切り替えると、同じ幅の値でも枠線と内側の余白を含むかどうかでカードの実際の大きさが変わる様子を並べて比較できます。スライダーを動かすたびにborder-boxとcontent-boxそれぞれの矩形を実測し直し、23フレーム中22フレームで動いて平均強度122.3・累積面積2.36%を記録しました。幅スライダーのドラッグ、あるいはポインターを押し下げる操作のどちらも最初の操作としてカウントされます。

ボックスモデル比較 — 枠線と内側の余白がサイズ計算に含まれるかを見せるカード
新しいタブで開く

02 · ボックスモデル比較 — 枠線と内側の余白がサイズ計算に含まれるかを見せるカード — 計測 累積面積 2.36% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

box-sizingcontent-boxborder-box
  function apply() {
    borderBox.style.width = declared + 'px';
    contentBox.style.width = declared + 'px';
    // 実測 — 計算値を仮定せず、実際のレンダー結果を測り直す
    var bRect = borderBox.getBoundingClientRect();
    var cRect = contentBox.getBoundingClientRect();
    guide.style.transform = 'translateX(' + declared + 'px)';
    widthRange.value = String(Math.round(declared));
    // 理論値 = 宣言幅 + 枠線・パディング両辺 — 実測(getBoundingClientRect)と一致するか併せて示す
    var theory = declared + 2 * (BORDER_PX + PADDING_PX);
    readout.textContent = '宣言 ' + Math.round(declared) + 'px・border-box 実測 ' +
      Math.round(bRect.width) + 'px・content-box 実測 ' + Math.round(cRect.width) +
      'px(理論値 ' + Math.round(theory) + 'px)';
  }

03見出しの折り返し調整 — 最終行に単語がひとつだけ残らないよう整える段落

長い見出しが複数行に折り返るとき、各行の文字数が均等にそろい、最終行に単語がひとつだけ取り残される形を防ぎます。CSS.supports('text-wrap', 'balance') を一度だけ検査し、対応している環境でだけ再調整を行います。23フレーム中23フレームで動き(この中で07番と並んで最多)、平均強度139.6は9項目のうち最も高い値でした。トグルのクリックが最初の操作としてカウントされます。

見出しの折り返し調整 — 最終行に単語がひとつだけ残らないよう整える段落
新しいタブで開く

03 · 見出しの折り返し調整 — 最終行に単語がひとつだけ残らないよう整える段落 — 計測 累積面積 3.253% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

text-wrap: balanceCSS.supportstext-wrap
  function render() {
    var widths = lineWidths();
    var maxW = heading.getBoundingClientRect().width || 1;
    var html = '';
    for (var i = 0; i < MAX_BARS; i++) {
      var w = widths[i] || 0;
      html += '<div class="bar" style="transform: scaleX(' + (w / maxW).toFixed(3) + '); opacity: ' + (widths[i] ? 1 : 0) + '"></div>';
    }
    bars.innerHTML = html;
    readout.textContent = '行数 ' + widths.length + '行・偏差 ±' + Math.round(stdDev(widths)) + 'px';
  }

  function apply(next) {
    on = next && supported;
    heading.style.textWrap = on ? 'balance' : 'wrap';
    toggleBtn.setAttribute('aria-pressed', String(on));
    render();
  }

04メディア基本比率固定 — 画像と動画が親要素の幅を超えないよう合わせる枠

画像と動画がテキストのように扱われて下に隙間ができる問題をなくし、親要素の幅を超えないまま元の比率を保って表示されます。枠のハンドルを最小幅と最大幅の間でドラッグするたびに、元画像と調整後の画像それぞれの実際の描画幅を測り直し、23フレーム中18フレームで動いて累積面積11.703%・平均強度137.6を記録しました。ハンドルの押し下げ・移動・離す操作のすべてが操作としてカウントされます。

メディア基本比率固定 — 画像と動画が親要素の幅を超えないよう合わせる枠
新しいタブで開く

04 · メディア基本比率固定 — 画像と動画が親要素の幅を超えないよう合わせる枠 — 計測 累積面積 11.703% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

max-width: 100%height: autogetBoundingClientRect
  function apply() {
    rawFrame.style.width = frameW + 'px';
    fitFrame.style.width = frameW + 'px';
    // 実測 — 2つの画像の実際のレンダー幅を毎フレーム測り直す(自然幅220pxはsvgのwidthに由来)
    var fitW = fitImg.getBoundingClientRect().width;
    var rawW = rawImg.getBoundingClientRect().width;
    var over = Math.max(0, Math.round(rawW - frameW));
    readout.textContent = '枠 ' + Math.round(frameW) + 'px・そのまま ' + Math.round(rawW) +
      'px(超過 ' + over + 'px)・調整後 ' + Math.round(fitW) + 'px';
  }

  var start = 0;
  function loop(ts) {
    if (!start) start = ts;
    if (isDemo && !reduce) { frameW = 180 + 50 * Math.sin((ts - start) / 900); }
    apply();
    requestAnimationFrame(loop);
  }

05フォーム要素のフォント継承 — ボタンと入力欄を周囲の文字と同じ書体に揃えるルール

ブラウザがボタンと入力欄に個別に与えていた書体と文字サイズを取り消し、周囲の段落とまったく同じ書体・サイズ・色をそのまま引き継がせます。ルール自体は button css リセットの中でもいちばん単純な形で、inherit に設定された3つのプロパティ以外は何も変えません。23フレーム中22フレームで動き、累積面積13.463%・平均強度95.8を記録しました。トグルのクリック・入力欄へのフォーカス・文字入力のいずれも操作としてカウントされます。

フォーム要素のフォント継承 — ボタンと入力欄を周囲の文字と同じ書体に揃えるルール
新しいタブで開く

05 · フォーム要素のフォント継承 — ボタンと入力欄を周囲の文字と同じ書体に揃えるルール — 計測 累積面積 13.463% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

font-family: inheritfont-weight: inheritfont-size: inherit
// リセットの主役 — font: inherit系のプロパティをトグルで比較する
.wrap.inherited {
  .field, .ghostBtn { font-size: inherit; font-weight: inherit; font-family: inherit; color: $subject-cream; }
}

.readout {
  color: $subject-cream;
  font-size: 11px;
  opacity: .85;
}

06キーボードフォーカスリング — マウスクリックでは出ず、キーボード移動時だけ現れる枠線

同じボタンでもマウスでクリックしたときは枠線が現れず、Tabキーで移動して到達したときだけはっきりとした枠線が現れ、アクセシビリティと見た目のすっきりさを両立します。:focus-visible と :focus:not(:focus-visible) が、どちらの入力手段かをスクリプトで判定することなく2つを分けています。23フレーム中15フレームで動き、これはルール切り替えクラスターの中で最も少ない値で、累積面積6.39%・平均強度94.2を記録しました。クリック・フォーカス・keydownのいずれも操作としてカウントされます。

キーボードフォーカスリング — マウスクリックでは出ず、キーボード移動時だけ現れる枠線
新しいタブで開く

06 · キーボードフォーカスリング — マウスクリックでは出ず、キーボード移動時だけ現れる枠線 — 計測 累積面積 6.39% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

:focus-visible:focus:not(:focus-visible)outline-offset
// 主役 — :focus-visibleはキーボードでの到達時にだけリングを与える。マウスクリックの:focusには付かない。
// outlineの色遷移は滑らかにするが、リング自体の出現・消失は即時にする。
.padBtn:focus-visible,
.padLink:focus-visible {
  outline: 3px solid $subject-mint;
  outline-offset: 2px;
  background: rgba($subject-mint, .16);
}

.padBtn:focus:not(:focus-visible),
.padLink:focus:not(:focus-visible) {
  outline: none;
}

07ノッチのセーフエリア余白 — 画面の欠けた部分でも内容が隠れないよう押し広げる余白

ノッチと下部バーのある画面を模した枠の中で、コンテンツ下部の余白がその欠けた領域の分だけ自動的に広がり、ボタンが隠れません。SAFE_AREA_SCALE という定数がシミュレートした余白の値に掛け合わされ、その結果を getComputedStyle で読み戻します。23フレーム中23フレームで動き(03番と並んでこの中で最多)、累積面積1.788%・平均強度110.3を記録しました。スライダーのドラッグ、あるいはポインターを押し下げる操作のどちらも操作としてカウントされます。

ノッチのセーフエリア余白 — 画面の欠けた部分でも内容が隠れないよう押し広げる余白
新しいタブで開く

07 · ノッチのセーフエリア余白 — 画面の欠けた部分でも内容が隠れないよう押し広げる余白 — 計測 累積面積 1.788% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

env(safe-area-inset-top)env(safe-area-inset-bottom)--safe-top
  function apply(t) {
    // t: 0〜1、シミュレートした端末のセーフエリア進行率 — 実際のenv()変数と同じCSSカスタムプロパティに代入する
    var top = Math.round(MAX_INSET_PX * t * SAFE_AREA_SCALE);
    var bottom = Math.round(MAX_INSET_PX * t);
    root.style.setProperty('--safe-top', top + 'px');
    root.style.setProperty('--safe-bottom', bottom + 'px');
    slider.value = String(Math.round(t * 100));
    // 実測 — padding-topが実際に何px描画されたかをgetComputedStyleで測り直す
    var cs = getComputedStyle(content);
    readout.textContent = '上 ' + parseFloat(cs.paddingTop).toFixed(0) + 'px・下 ' + parseFloat(cs.paddingBottom).toFixed(0) + 'px';
  }

08モーション削減対応 — システム設定に応じてアニメーションがほぼ止まる遷移

モーションを減らす環境を模したスイッチを入れると、同じカードの遷移が派手に動く状態からほぼ瞬時に現れる状態へと変わります。REDUCED_MOTION_SCALE がシミュレーション時に基本の継続時間を10分の1まで掛け合わせ、強制的なリフローで原点に戻した状態を画面に反映してから移動が始まります。23フレーム中13フレームで動き、累積面積5.87%・平均強度122.2を記録しました。再生ボタンのクリック・シミュレーションスイッチの切り替え・スライダー操作・遷移終了のいずれも操作としてカウントされます。

モーション削減対応 — システム設定に応じてアニメーションがほぼ止まる遷移
新しいタブで開く

08 · モーション削減対応 — システム設定に応じてアニメーションがほぼ止まる遷移 — 計測 累積面積 5.87% · 動いたフレーム 13/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

prefers-reduced-motionmatchMediaREDUCED_MOTION_SCALE
  function currentDurationMs() {
    return reduceSim ? Math.round(BASE_DURATION_MS * REDUCED_MOTION_SCALE) : BASE_DURATION_MS;
  }

  function applyDuration() {
    dot.style.transitionDuration = currentDurationMs() + 'ms';
  }

  function play() {
    if (running) return;
    running = true;
    applyDuration();
    dot.classList.remove('at-end');
    // 強制リフロー — 原点に戻した状態を一度画面に反映してから移動を開始する
    void dot.offsetWidth;
    runStart = performance.now();
    dot.classList.add('at-end');
  }

09角丸の形状比較 — 円弧の角と滑らかな曲線の角を並べて見るカード

同じ半径の値でも、円弧で急に曲がる既定の角と、曲率がなだらかに変わる滑らかな角を並べることで、違いが一目でわかります。CSS.supports('corner-shape', 'squircle') が、両方のカードにすでに共通で入っている border-radius の上に重なる追加ルールひとつだけを許可するかどうかを決めます。23フレーム中9フレームしか動かず、これは9項目の中で最も少ない値で、累積面積も9項目中最小の1.044%・平均強度118.6でした。トグルのクリックが操作としてカウントされます。

角丸の形状比較 — 円弧の角と滑らかな曲線の角を並べて見るカード
新しいタブで開く

09 · 角丸の形状比較 — 円弧の角と滑らかな曲線の角を並べて見るカード — 計測 累積面積 1.044% · 動いたフレーム 9/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

corner-shape@supportsCSS.supports
// 主役 — border-radiusは両方のカードにJSで同じ値が入る。corner-shapeだけが右側のカードに1行追加される。
@supports (corner-shape: squircle) {
  .cardShape {
    corner-shape: squircle;
  }
}

フレームワークなしでcss リセットはどこまでできるか

こうしたcss リセット 書き方は、環境ごとに違う既定値の差を埋めるだけのものです。どこにも対応していない機能を追加することはできませんし、そのページ自身が決めていないデザインの判断を代わりに下すこともできません。09番がその境界をはっきり示しています — 両方のカードを実際に丸めているのは共通の border-radius の値そのもので、corner-shape のルールは対応環境でその丸まった角の曲がり方をさらに整えているだけです。土台のルールを取り除けば、整える対象すら残りません。リセットの仕事は「同じ既知の出発点から始める」ところで終わり、その先に何を積み上げるかはそのつど別の判断です。

どこで壊れるか — 落とし穴

モーションを減らすと何が残らないか

3つのパターン(01・05・06)は prefers-reduced-motion: reduce の下でCSSトランジションだけを取り除きます — どの範囲が有効か、フォントが継承されているか、どのコントロールにフォーカスがあるかといった状態そのものは変わらず、ただイージングなしで切り替わるだけです。03番は少し違います — reduceは自動デモループの再トリガーだけを止め、クリックした瞬間のトグル動作自体はそのまま働きます。4つのパターン(02・04・07・09)はそもそも取り除くトランジションを持たず、スライダー位置・ドラッグ位置・supports() の判定はどれも静的な値でモーションではありません。08番だけは別枠で、モーション削減はこの項目が副作用として受けるものではなく実演している対象そのものなので、matchMedia の実測とシミュレーションスイッチはその継続時間の変化を意図的に目に見えるようにするために存在します。

01番を壊すいちばん簡単な方法は、ルールが足りないことではなく逆方向です — RESET_SCOPE を 'full' に広げると、すべてのリンクの下線まで一緒に消えてしまい、余白やリストの点と違って下線はテキストがクリックできることを示す唯一の視覚的な手がかりであることが少なくありません。このリセットは代わりの手がかりを自動では足してくれませんので、範囲を広げるときは枠線・色の変化・アイコンといった新しいリンク表現と必ずセットにするべきで、デモのパネルがそれなしでも一見問題なく見えるからといって6tfe57vw「安全」だと思い込んではいけません。

3つのバリエーションとアクセシビリティ

バリエーション 変更した値 効果
リセット範囲を広げる 01 RESET_SCOPE minimal → full RESET_SCOPE を full にすると clearedList() が CLEARED_MINIMAL に link-decoration を加えた CLEARED_FULL を返し、消える初期値が3項目から4項目に増えてリンクの下線まで消えます
セーフエリア余白の倍率を上げる 07 SAFE_AREA_SCALE 1.0 → 1.5 SAFE_AREA_SCALE を 1.5 に上げると top = MAX_INSET_PX × t × SAFE_AREA_SCALE なので、同じスライダー値でもセーフエリアの余白が1.5倍に広がり、内容がさらに内側へ寄ります
モーション削減時の遷移速度をさらに落とす 08 REDUCED_MOTION_SCALE 0.1 → 0.02 REDUCED_MOTION_SCALE を 0.02 に下げると currentDurationMs() が BASE_DURATION_MS × 0.02 を返し、モーション軽減シミュレーションでの遷移時間が 1/10 から 1/50 に縮んでほぼ瞬時に終わります

01・03・05・08 の切り替えボタンは aria-pressed で状態を色だけに頼らず伝え、02・04・05・07・08 は晴眼者向けラベルだけでは伝わらない文脈を aria-label で補い、01・03・05・08 は06番の専用スタイルとは別に独自の :focus-visible 枠線を宣言しています — こうしたcss リセット おすすめの実装は、状態を見せるだけでなく伝えることも同時に行っています。02・04・07・09 は値ひとつを比べるだけの静的なカードなので、reduce の有無にかかわらずいつも同じ姿でアクセスできます。

FAQ

このコレクションはまとめて使うものですか、それとも1項目ずつ使えますか?

どちらもできます。9つはそれぞれ環境ごとに異なる既定値のどこか一箇所に対応していて、余白・サイズ・タイポグラフィ・メディア・モーション・形状をまとめてカバーしますが、必要な1項目だけを取り出しても問題なく動きます。上のコードブロックはどれも単独でそのまま貼り付けて使える形です。

デモが自分では何も触っていないのに動き出すのはなぜですか?

9つそれぞれが is-demo クラスとライブな prefers-reduced-motion の判定でガードされた独自の requestAnimationFrame ループを回しているためで、そのパネルに触れるまでは自動で動き続けます。最初のポインター・キーボード・入力イベントがそのクラスを外し、そこから先は操作した人に制御が渡ります。

css リセットコードは1項目だけをアーカイブなしでコピーできますか?

できます — 上のコードブロックはどれもその項目の実際のソースをそのまま連続で切り出したものなので、ひとつのブロックを新しいファイルに貼り付けるだけでライブデモと同じ動作を再現でき、アーカイブ側に隠れた追加設定は一切ありません。

参考: allショートハンドプロパティ、box-sizingプロパティ、text-wrapプロパティ、prefers-reduced-motionメディア特性、:focus-visible疑似クラス。

関連記事: webフォントを読み込まずにタイポグラフィの既定値を試したい方はcss タイポグラフィ プレイグラウンド 9種 — webフォントを読み込まずに、09番が半径ひとつだけで比べる円弧と滑らかな角はcss corner-shape 9選 — border-radiusで切らない角の処理が九通りの組み合わせでさらに広く扱っており(この編の09番はその軸をリセットの文脈でカード一枚に絞って示すにとどまります)、環境ごとの既定値の差をさらに見たい方はcss ブラウザ対応 9選 — 実務で使うフォールバック集もあわせてご覧ください。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。