日本語Webサイトを意識したモダンリセットCSS「kiso.css」をリリースしました

広告
日本語Webサイトを意識したモダンリセットCSS「kiso.css」をリリースしました。
kiso.cssは、単なるスタイルのリセットを超えて「より良いデフォルト」を提供することを目指したリセットCSSです。その名が示すように、Webサイト構築の「基礎」として機能します。
有用なUAスタイルシートは活かしつつ、独自のスタイルも追加しているため、厳密には「リセットCSS」の定義から外れるかもしれません。しかし、類似のCSSが一般的に「リセットCSS」として紹介されている現状を踏まえ、検索性を考慮して本記事でもそのように呼称します。
kiso.cssはdestyle.cssやUA+を参考にしつつ、独自性も加えています。
インストール
npm / yarn
npm install kiso.css# oryarn add kiso.css
/* import your CSS file */@import "kiso.css";
CDN
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/kiso.css@latest/kiso.css">
ダウンロード
GitHubから直接ダウンロードすることもできます。
日本語組版への配慮
kiso.cssの最大の特徴は、日本語環境での使用を前提とした設計にあります。
現在も多くのリセットCSSが開発・紹介されていますが、その大半は欧文を前提としており、日本語のWebサイトで使用する際には追加の調整が必要になることが少なくありません。
kiso.cssでは、副作用が少ない範囲で日本語に特化したスタイル調整を加えています。
:where(:root) { /* Remove space when punctuation marks are adjacent, and also remove leading spaces in a line. */ text-spacing-trim: trim-start;
/* Improves readability by inserting a small space between Japanese and alphanumeric characters. */ text-autospace: normal;
/* Prevents misreading by applying strict line-breaking rules. */ line-break: strict;}
:where(em:lang(ja)) { /* In Japanese, emphasis is commonly represented by bold text, so `font-weight: bolder;` is set by default. */ font-weight: bolder;}
:where(:is(i, cite, em, dfn, var):lang(ja)) { /* Italic style is not common in Japanese, so the `font-style` is reset. */ font-style: unset;}
主に適用しているスタイル調整は次のとおりです。
text-spacing-trim プロパティ
text-spacing-trim
プロパティは、約物(句読点や括弧など)と他の文字との間のスペースを制御します。
このプロパティをサポートするブラウザでは、デフォルト値のnormal
によって、約物が連続する場合に不要なスペースが自動的に削除されます。
kiso.cssでは、一般的な組版の慣習に則りtext-spacing-trim: trim-start
を指定しています。これにより、normal
の挙動に加え、行頭のスペースも自動的に除去されます。
:where(:root) { /* Remove space when punctuation marks are adjacent, and also remove leading spaces in a line. */ text-spacing-trim: trim-start;}
text-spacing-trim: trim-start
を指定する具体的な理由については、以下の記事で詳細に解説されているため、ここでは割愛します。
一方で、デフォルト値のnormal
でも約物間のスペースは調整されるため、何も指定しない場合でも<pre>
要素などでは意図しない表示のズレが生じる可能性があります。
そのため、<pre>
要素にはtext-spacing-trim: space-all
を明示的に指定し、スペースが維持されるようにしています。
:where(pre) { /* * Since `text-spacing-trim` can affect spacing in `<pre>` elements even with its initial value, the final rendering may depend on the user's font settings. * To ensure consistent alignment, `space-all` is explicitly specified and inheritance is prevented. */ text-spacing-trim: space-all;}
text-spacing-trim
は現在、Google ChromeとMicrosoft Edgeでサポートされています。未対応ブラウザでは無視されるプログレッシブエンハンスメントとして導入可能であるため、積極的に採用しました。
text-autospace プロパティ
text-autospace
プロパティは、日本語などの文字と英数字との間に自動でスペースを挿入するかどうかを制御します。
具体的には、1ic
(漢字「水」の幅を基準とする単位)の8分の1に相当するスペースが、和文と欧文の間に挿入されます。
この機能はMicrosoft Wordなどのワープロソフトでは標準的であり、Webにおいても日本語と英数字の間にスペースを設けることで可読性が向上するとされています。
kiso.cssは、デフォルトでこのアキを有効にするtext-autospace: normal
を設定しています。
:where(:root) { /* Improves readability by inserting a small space between Japanese and alphanumeric characters. */ text-autospace: normal;}
text-autospace
は現在、Safariでサポートされており、Google Chromeでもバージョン139以降でサポートが予定されています(Canary版では利用可能)。こちらもプログレッシブエンハンスメントとして導入できるため、採用しました。
注意点として、text-autospace
を指定しない場合の初期値auto
の挙動がChromeとSafariで異なる点が挙げられます。Chromeではスペースが挿入されるnormal
相当の挙動ですが、Safariでは挿入されないno-autospace
相当の挙動となります。1pxもズレが無くデザインを再現する、文字通り「ピクセルパーフェクト」を行っている方はtext-autospace: no-autospace
を明示しないとChrome 139以降は苦労が水の泡になるのでご注意ください。
「Figmaのデザインと文字の詰まり方が変わるから指定したくない」という場合でも、ブラウザ間の差異をなくすためにtext-autospace: no-autospace
を明示的に指定することが望ましいです。とはいえ、文章の読みやすさを向上させる機能を、デザインツールが未対応だからという理由で採用しないのは本末転倒な気もします。
kiso.cssでは、スペースが自動挿入されると不都合が生じやすい<input>
や<textarea>
、表示がズレる<pre>
、そして慣習的にアキを設けない日付表記で使われる<time>
には、text-autospace: no-autospace
を個別に指定しています。
:where(pre) { /* Set to `no-autospace` as it can cause misalignment with monospaced fonts. */ text-autospace: no-autospace;}
:where(time) { /* Set to `no-autospace` because date notations in typography do not include spaces. */ text-autospace: no-autospace;}
:where( input:not([type="button" i], [type="submit" i], [type="reset" i]), textarea, [contenteditable] ) { /* Set to `no-autospace` because `text-autospace` can insert spaces during input, potentially causing erratic behavior. */ text-autospace: no-autospace;}
text-autospace
の指定については、次の記事を参考にしました。
line-break プロパティ
kiso.cssではline-break: strict
を指定し、厳格な禁則処理を適用しています。さらに、単語の途中でも折り返しを許可するoverflow-wrap: anywhere
も併せて指定しています。
:where(:root) { /* Prevents misreading by applying strict line-breaking rules. */ line-break: strict;
/* Wraps English words mid-word. Specifying `anywhere` also prevents content from overflowing in layouts like `flex` or `grid`. */ overflow-wrap: anywhere;}
厳格な禁則処理により、カギ括弧や句読点など、行頭に配置されるべきでない文字の回り込みを制御します。line-break: strict
のデメリットとして行末に余白が生まれやすくなりますが、読み間違いを防ぐというメリットと比較し、許容できる範囲だと判断して全体に適用しました。
ICSさんの以下の記事を参考にしています。
フォントのスタイルを日本語向けにする
<em>
や<cite>
といった要素は、通常イタリック体(斜体)で表示されます。多くのリセットCSSではこのUAスタイルシートを活かすように設計されていますが、日本語において斜体は一般的に使用されません。
<em>
は「強調」を意味しますが、日本語における強調は多くの場合、太字で表現されます。
そのため、日本語環境では斜体をリセットし、一部の要素には太字を適用するなどの調整を行っています。
:where(address:lang(ja)) { /* Italic style is not common in Japanese, so the `font-style` is reset. */ font-style: unset;}
:where(em:lang(ja)) { /* In Japanese, emphasis is commonly represented by bold text, so `font-weight: bolder;` is set by default. */ font-weight: bolder;}
:where(:is(i, cite, em, dfn, var):lang(ja)) { /* Italic style is not common in Japanese, so the `font-style` is reset. */ font-style: unset;}
詳細度を低く抑える
kiso.cssでは、疑似要素や!important
が明示的に指定されたスタイルを除き、すべてのセレクタを:where()
擬似クラスで囲むことで詳細度を0
にしています。
現代のCSSには、詳細度の競合を回避できるカスケードレイヤー(@layer
)という便利機能がありますが、すべてのプロジェクトで導入するのが難しい場合もあるでしょう。
kiso.cssはスタイルを容易に上書きできるため、カスタマイズ性の高いリセットCSSとなっています。
アクセシビリティへの配慮
リセットCSSで可能なアクセシビリティへの配慮は限定的ですが、キーボード操作時のフォーカスリングを維持しつつ、アウトラインを少し広げるなどの対応を行っています。
他のリセットCSSとの差別化点として、<ul>
や<ol>
のデフォルトのリストマーカーを非表示にする方法が挙げられます。多くのリセットCSSではlist-style: none
が用いられますが、kiso.cssではlist-style-type: ''
を採用しています。
:where(ul, ol, menu) { /* * In Safari, using `list-style: none` prevents screen readers from announcing lists. * `list-style-type: ""` is used to hide markers without affecting accessibility. */ list-style-type: "";}
これは、list-style: none
が指定された<ul>
や<ol>
を、SafariとVoiceOverの組み合わせでは「リスト」として認識しない仕様に基づいています。この仕様は、VoiceOverユーザーからの「リストが多用されるページで、その情報が繰り返し読み上げられるのが煩わしい」というフィードバックに応えたものです。
しかし、デフォルトのリストマーカーはデザイン上の制約が大きく、非表示にしたいケースがほとんどです。そもそも、このフィードバックは<ul>
や<ol>
をflex
やgrid
の便利HTMLとして多用している実装者に問題があり、本来の意味での「リスト」は正しく認識されるべきです。
この問題の解決策としてrole="list"
を明示する方法もありますが、要素が暗黙的に持つrole
を同じ値で上書きする手法には疑問が残りますし、指定漏れのリスクもあります。
そこで、list-style
プロパティ自体は有効なまま、値を空文字にすることでマーカーを見かけ上は非表示にしつつ、スクリーンリーダーには「リスト」として認識させる方法を選択しました。ややトリッキーな手法ですが、より確実な解決策だと考えています。
<menu>
は将来生まれ変わることが予測されるので、ついでに指定しています。
その他、一部の要素には強制カラーモードへの対応も行っています。
リセットの厳選
kiso.cssでは、リセットするスタイルを選んでいます。リセットCSSがなくてもレイアウトが大きく崩れないことを目標としましたが、スタイルが適用されていない要素をその都度リセットするのは手間がかかり、指定漏れのリスクも伴います。また、適度なリセットが無いとHTMLが変更された時に意図せぬUAスタイルシートが悪さをしかねません。
そこで、過去の経験を基に、リセットする方が都合の良いスタイルとそうでないものを洗い出し、前者を選択的にリセットする方針を取りました。
例えば、<input>
や<button>
のborder
、見出し要素の太字などはリセットしてもスクラップアンドビルドされる傾向にあります。これらをリセットしてしまうと再指定の手間が増えるため、あえてスタイルを残しています。同様に、見出しのfont-size
や<button>
のpadding
なども、何かしらで上書きされがちなのでリセットする意義は薄いと判断しました。
これらの要素については、リセットせずにUAスタイルシートを活かすか、基本的なノーマライズに留めています。
:where(button, input, select, textarea),::file-selector-button { /* * These elements are often styled with a border, so a `1px` border is applied by default for consistency. * This ensures readability even for unstyled elements. * When resetting, it's recommended to use `border-color: transparent` instead of `border: none` to account for forced color modes. */ border-width: 1px; border-style: solid;
/* These styles specified in the UA stylesheet are often unnecessary, so they are reset to allow for inheritance. */ border-color: unset; border-radius: unset; color: unset; font: unset; letter-spacing: unset; text-align: unset;}
コメントにもあるように、フォームパーツのborder
はunset
するのではなくborder-color: transparent
などで透過させることを推奨します。強制カラーモードでは背景色が除去されるため、背景色のみで識別させていたフォームパーツの存在が見えなくなる可能性があるためです。border
は透過していても可視化されるためフォールバックとして優秀です。
全称セレクタで一括除去されがちなmargin
についても、一括でのリセットは避け、必要なものだけを選択的にリセットしています。
例えば<dialog>
のセンタリングに有用なmargin: auto
は活かしたままにしていますが、Popover APIで用いられる[popover]
属性を持つ要素はAnchor Positioningで位置を調整することが多く、デフォルトのmargin
は邪魔になるので削除するなどの工夫をしています。
:where([popover]) { /* * While the UA stylesheet's `margin` for `<dialog>` elements is useful for centering with `inset: 0`, * But `margin` for `popover` elements is often obstructive as they frequently use Anchor Positioning. */ margin: unset;}
先述したリストマーカーや、リンクの下線については無効にするほうが圧倒的に実装の頻度は多いのでリセットを行っています。
インラインリンクの実装や、リストマーカーを復活されたい場合は以下のようにrevert
キーワードを指定してロールバックしてください。
.Prose :where(:any-link) { text-decoration-line: revert;}
.Prose :where(ul, ol) { list-style-type: revert;}
要素そのものが見えなくなるような破壊的なリセットは防ぐ
例えば、destyle.cssでは<input>
にappearance: none
が指定されていますが、これは危険です。
チェックボックスやラジオボタンはappearance: none
が指定されると空のボックスのように扱われます。故にスタイル未指定の状態で非表示になってしまいます。
近年はaccent-color
でデフォルトのフォームコントロールの色を手軽に変更できるため、appearance: none
の必要性は薄れています。チェックボックスなどを疑似要素を用いて独自にスタイリングしたい場合に、その都度指定すれば十分だと考えます。
h1はfont-sizeとmarginを指定しておく
近年話題になったネストされたh1
要素のUAスタイル変更に対応するため、normalize.cssのスタイルを参考にfont-size
とmargin-block
を指定しています。
:where(h1) { /* * Adjusts user agent (UA) styles for `h1` elements within sectioning content. * This addresses DevTools warnings that appear when `h1` elements nested within sectioning content lack `font-size` and `margin` properties. * @see https://html.spec.whatwg.org/#sections-and-headings */ margin-block: 0.67em; font-size: 2em;}
gap
プロパティなどで余白を管理する現代的なレイアウトではh1
のmargin
が不要な場面も多いですが、<section>
などの内に配置されたh1
でfont-size
やmargin
が欠けていると開発者ツールで警告が表示されることがあります。これを抑制するためにあえて付与していますので、プロジェクトに応じて独自の判断で適宜リセットしてください。
その他独自で指定したもの
scrollbar-gutter: stable
JavaScriptを書かない2025年のモーダルの実装方法の記事でも触れましたが、スクロールバーの表示・非表示によってコンテンツがガタつく現象や、それに伴うレイアウトシフトを防ぐためにscrollbar-gutter: stable
を標準で搭載しました。これにより、常時スクロールバー分の領域が確保されます。
:where(:root) { /* Prevents layout shift caused by the appearance or disappearance of the scrollbar. */ scrollbar-gutter: stable;}
ボタンのtouch-action: manipulation
iOSでは、ボタンをダブルタップすると意図せず画面が拡大されてしまうことがあります。これを防ぐためにtouch-action: manipulation
を指定しています。
:where( button, input:is([type="button" i], [type="submit" i], [type="reset" i]), [role="tab" i], [role="button" i], [role="option" i] ),::file-selector-button { /* * On iOS, double-tapping a button can cause zooming, which harms usability. * `touch-action: manipulation` is specified to disable zooming on double-tap. * Third-party plugins such as Swiper sometimes use div elements with these roles as buttons, since double-tapping a div can still trigger zooming, it's advisable to specify this property. */ touch-action: manipulation;}
この指定がないと、例えばハンバーガーメニューのボタンをうっかりダブルタップした際に画面が拡大されてハゲそうになります。個人的に必須と考える指定のため、標準で搭載しました。拡大が起こり得るのはボタンのため、リンクなどに指定する必要はないです。
ポップオーバーのoverscroll-behavior-block: contain
モーダルダイアログなど、スクロール可能な固定表示要素内でラバーバンド効果(スワイプでの「戻る」や更新アクション)が有効なままだと、誤操作を誘発しやすくなります。モーダル内をスクロールしているつもりが、意図せずページの更新アクションを誘発してしまう、といったケースです。これも私にとってはストレスゲージを溜める原因のため、UAスタイルシートでoverflow: auto
がされている<dialog>
と[popover]
にはoverscroll-behavior-block: contain
を指定しました。
:where(dialog, [popover]) { /* * When these fixed-position elements are scrolled, preventing scroll chaining on the underlying page and bounce effects on mobile improves usability. * Disabling block-direction scroll chaining is recommended. */ overscroll-behavior-block: contain;}
もちろん、実装によってはこの指定が不要になるケースもありますが、デフォルトで設定しておく価値はあると判断しました。
今回導入を見送ったもの
scroll-behavior: smooth
やimg { inline-size: 100%; }
のような副作用の大きいスタイルは採用していませんが、採用を検討したものの見送ったスタイルもあります。
フォントのカーニング
font-feature-settings: 'palt'
やfont-kerning: normal
などのカーニング関連のプロパティです。
基本的に、本文はカーニングを適用しない「ベタ組み」の方が読みやすいとされるため、全体に適用するとかえって可読性を損なう可能性があります。font-feature-settings: 'palt'
をルートに指定するのは避けて、カーニングは見出しやキャプションなど、適用箇所を限定して使うべきです。
:where(:root) { &:lang(en) { font-kerning: normal; }
&:lang(ja) { font-kerning: none; }}
.Heading { font-kerning: normal;
&:lang(ja) { font-feature-settings: 'palt'; word-break: auto-phrase; }}
その上で、見出しやキャプションと言っても一概に全指定すべきとも言えないので、今回は採用を見送りました。デザインを見ながら設定しましょう。
同様の理由から、文節で改行を制御するword-break: auto-phrase
も導入していません。
見出しは1文字で改行されると不細工になるので、それを防止するためのtext-wrap: pretty
は指定しています。
:where(h1, h2, h3, h4, h5, h6) { /* Prevents the last line of text from ending with a single word, which can look awkward (known as an orphan). */ text-wrap: pretty;}
interpolate-size: allow-keyword
最近のリセットCSSでは、height
やwidth
をauto
やfit-content
といったキーワード値へアニメーションさせることを許可するinterpolate-size: allow-keywords
の指定が散見されます。
このプロパティについては過去記事「CSSのみでdetails要素のアニメーションを実装する方法」で解説しています。
:where(:root) { @media (prefers-reduced-motion: no-preference) { interpolate-size: allow-keywords; }}
私自身のプロジェクトでは採用することが多いのですが、今回は見送りました。「transition
プロパティを使用する際は、対象をall
にせずtransition-property
で明示的に指定しなさい」という条約を破った際に、意図しないアニメーションが起こるリスクを懸念したためです。
また、interpolate-size: allow-keyword
が初期値として設定されなかった背景も理由の一つです。これは下位互換性のために行われ、既存の Web サイトに意図しないアニメーションが突然含まれないようにするためです。もしもkiso.cssを後から導入した際にこの意図しないアニメーションが起こる可能性があります。
そのため、interpolate-size: allow-keyword
は各々で導入することを推奨します。
モーダルが開く際の背面のスクロールを抑制する
JavaScriptを書かない2025年のモーダルの実装方法の記事で紹介した以下のような記述です。
:root:has(:modal) { overflow: hidden;}
書いておいても良い記述だと思いつつ、リセットCSSに含めるのはどうなのかなという良くわからない感想が芽生えたので導入していません。
導入するとしたらbody:has(dialog[open])
だと背面でposition: sticky
な要素が出たり入ったりするので上記の書き方を推奨します。overflow: clip
ではFirefoxで背面抑制が機能しないので注意です。
変更するかもしれないもの
text-size-adjustの変更
スマートフォンを縦向き・横向きに切り替えた際に文字サイズを自動調整する機能が働いて意図せないフォントサイズになる場合があるため、原則的にリセットCSSにはtext-size-adjust: 100%
(iOSではベンダープレフィックスが必要)を指定するのが望ましいです。
その上で、Chrome 138でリリース予定のOSレベルのフォントスケールのCSS環境変数をサポートするために次のような指定に変更するかもしれません。
:where(:root) { /* * Mobile browsers have an algorithm that automatically adjusts font sizes to prevent text from becoming too small. * This controls the auto-adjustment feature to prevent unwanted resizing. */ -webkit-text-size-adjust: 100%; text-size-adjust: 100%;}
:where(:root) { /* * Mobile browsers have an algorithm that automatically adjusts font sizes to prevent text from becoming too small. * This controls the auto-adjustment feature to prevent unwanted resizing. */ -webkit-text-size-adjust: 100%; text-size-adjust: 100%;}
@supports (text-size-adjust: calc(100% * env(preferred-text-scale))) { :where(:root) { /* * Ensure all text on the page automatically scales to the user's preference, and, when necessary, scale non-text sizes by the same factor as well. */ text-size-adjust: calc(100% * env(preferred-text-scale)); }}
OSのフォントサイズを拡大機能を尊重するenv(preferred-text-scale)
環境変数は、ユーザーが設定したテキストの拡大縮小率、つまりOSやユーザーエージェントの「デフォルト」フォントサイズに対してユーザーが行う調整を表します。
通常text-size-adjust
が有効なデバイスでは、text-size-adjust: auto
によって適用される拡大縮小率となります。
上記のtext-size-adjust: 100%
を指定しない(初期値のまま)の場合、OSの文字サイズ拡大機能は自動的に適用されます。とは言え意図せぬフォントサイズの拡大を防止するために指定なしはまだ早計でしょう。
仕様書では開発者は以下のいずれかの方法を取るべきとされています。
text-size-adjust: calc(100% * env(preferred-text-scale))
を設定し、ページ内のすべてのテキストがユーザーの設定に合わせて自動的に拡大縮小されるようにし、必要に応じてテキスト以外のサイズもこの拡大縮小率で調整する。text-size-adjust:none
を設定し、この環境変数やpem
単位を一貫して使用して、関連するテキストやUIをユーザーの設定に合わせて拡大縮小する。
rem
をpem
に置き換えるのはフォールバックが面倒なので多くのケースでは前者のパターンを取るでしょう。
とは言え、まだリリース前の機能ですし、実際のブラウザで綿密な検証を行わなければかえって予期せぬバグを引き起こす可能性も否定できません。そのため、当面は現状の text-size-adjust: 100%
という指定を維持しつつ、方針が固まり次第対応を検討します。
その他、指定している宣言の意図に関しては思考を適宜コメントしているのでCSSを御覧ください。
以上がkiso.cssの紹介です。FBや改善策などがあればPRをください。