- 下のような指定を
:rootに指定しておく。
:where(:root) {
overflow-wrap: anywhere; /* 収まらない場合に折り返す */
/* word-break: initial; 単語の分割はデフォルトに依存(初期値のため指定しなくて良い) */
line-break: strict; /* 禁則処理を厳格に適用 */
}- テキストの行の折り返し方法を指定するプロパティ。
- 基本的に明示する値は
prettyorbalanceの 2 択で、balanceではすべての行が同じくらいの長さになるように調整されるのに対して、prettyは最後の行が一つの単語だけで終わることを防ぐ。 - 英語では最後の行に一つだけ配置された単語を widows and orphans と呼び、テキストが読みにくくなるとして避けるべき対象とされている。そのため、
:lang(en)の場合はテキスト全体にprettyを指定するのが良い。 - 日本語では本文はベタ組みにすることが原則であるため、段落に
text-wrapの措定は行わない。見出しの引き締めには貢献できるので、text-alignがstartorendならpretty、centerならbalanceを指定するのが良い。 - しかし、現在の Safari では日本語におけるそれの挙動がバグっているため、日本語の場合は
text-wrap: prettyの指定をしないほうが良い。Safari のバグが修正されたら指定することを推奨する。
:where(:is(h1, h2, h3, h4, h5, h6, p, caption):lang(en)) {
text-wrap: pretty;
}
.-text-center {
text-align: center;
text-wrap: balance;
}font-feature-settings: 'palt'で文字詰めができるが、日本語では本文はベタ組みにすることが原則であるためデフォルトでは指定しない。- 見出しに関しては文字詰めを行ったほうが可読性が向上するので指定を行う。
:where(h1, h2, h3, h4, h5, h6, caption) {
&:lang(ja) {
font-feature-settings: "palt";
}
}- フォントの特定のスタイルや装飾を制御することができるプロパティ。
- 原則的には
font-feature-settingsの上位互換であるが、使用頻度の高い'palt','pkna'に代わる方法が提供されていないので使い所は限られる。 - 基本的には数字リストやローディング、料金テーブルのように数字を目立たせる箇所では
font-variant-numeric: tabular-numsを指定して数字の幅を均等にするのに用いると良い。
.-tabular-nums {
font-variant-numeric: tabular-nums;
}- プロポーショナルフォントの文字間隔を、隣り合う文字の組み合わせによって文字詰めすることを「カーニング」と呼ぶが、メトリクスカーニングと呼ばれる方法で、カーニングを制御するプロパティ。
- 初期値は
autoでブラウザ側に委ねられる。 - 日本語の場合は本文はベタ組みにすることが原則であるため、
font-kerning: normalの適用は可読性を悪化させる原因となる。基本的には見出しに利用し、デフォルトではnoneにしておく。 - 英語の場合はカーニングがあったほうが可読性は上げるため、デフォルトで
normalを指定する。
:where(:root) {
&:lang(en) {
font-kerning: normal;
}
&:lang(ja) {
font-kerning: none;
}
}
:where(h1, h2, h3, h4, h5, h6, caption) {
font-kerning: normal;
}- 日本語のなどの文字と英数字の間にスペースを入れるかどうかを制御するプロパティ。
- Web に関しては原則的に日本語のと英数字の間にスペースを入れるのが可読性が高くなるとされており、デフォルトで
text-autospace: normalを指定するのが良い。 - ただし、
pre要素は等幅フォントでずれる、time要素は「2025年」のような日付表記には空白が入らない、input要素やtextarea要素のようなユーザーが入力する要素では自動挿入が入ると挙動不審になる恐れがあるためtext-autospace: no-autospaceを明示する。
:where(:root) {
text-autospace: normal;
}
:where(pre, time, input:not([type="button" i], [type="submit" i], [type="reset" i]), textarea, [contenteditable]) {
text-autospace: no-autospace;
}- 約物(句読点や括弧など)とそれ以外の文字との間のスペースを制御するプロパティ。現状は Chrome のみ。
- 通常の Web の文章は段落を「字下げ」することはなく、かつ見出しの最初の括弧の空白を消すほうが審美性に優れているためデフォルトでは
text-spacing-trim: trim-startを指定する。 - ただし、
pre要素はズレる可能性があるため、継承されないようにし、ズレないことを保証するためにtext-spacing-trim: space-allを明示する。
:where(:root) {
text-spacing-trim: trim-start;
}
:where(pre) {
text-spacing-trim: space-all;
}- ハーフレディングを除去するプロパティ
text-box-trimはテキストコンテンツの上端と下端のどちらを切り取るかを指定する。trim-startは上端、trim-endは下端、trim-bothは両方。- ハーフレディングを除去する目的なら
trim-both、ハーフレディングの除去は無くていいけど横並びになった画像と見出しの上端を合わせたい…というケースではtrim-startを指定するケースが多い。 text-box-edgeは切り取る空間の大きさを指定する。既定値はtextで、上部をtext-over baseline、下部をtext-under baselineの位置でトリミングする。- 原則的には英文は
cap alphabeticで上端を X の上限、下端を x の下限でトリミングするのが良い。しかし、日本語の場合は詰まりすぎる場合があるので既定値のtextが良い。 - 切り取る空間の大きさはフォントに依存する。ヒラギノフォントは
textだと多少のアキが発生する。厳格に除去したいのであればmargin-block: calc((1em - 1lh) / 2)で詰めるのを推奨する。
- 原則的には英文は
line-height: 1の代替手段としても優秀。line-height: 1は改行した際に表示が著しく悪くなることからアンチパターンであり、デザイン上line-height: 1が定義されている場合でもtext-box-trimを使用するべき。- Figma の場合はハーフレディング込みで余白が算出される(ハーフレディングを切り取る定義がされていない場合)が、 Adobe 製のツールはハーフレディングを含まずに余白が算出される。 Adobe 製のツールでカンプが作成されている場合は全称セレクタですべての要素に適用するのも選択肢。
.-trim-both {
text-box-trim: trim-both;
&:lang(en) {
text-box-edge: cap alphabetic;
}
}- インライン軸に句読点を包括できる余白が存在するのが条件だが、
hanging-punctuation: last allow-endの指定で「段落最後の閉じカッコ」と「行末の句読点」がぶら下がるようにできる。現在は Safari のみだが、そのうち Chrome にも来る予定である。 - 行頭や行末に位置する約物文字(句読点や括弧、引用符など)を行ボックスの外側にぶら下げることは、行揃えを改善するために有効である。印刷組版では、段落の開始引用符「“」や行末の句点「。」を行の外側に出して、他の行と揃えを取る手法が用いられている。
- 原則的には段落でのみ適用する。
- 注意点としては、約物を行外に出すことでコンテナの論理幅からはみ出すため、場合によっては水平スクロールバーが出ることがある。インライン軸に
paddingとセットで指定したほうがいい。 - 英語の場合は
hanging-punctuation: first allow-end lastが良さそう(参考)
.-hanging {
hanging-punctuation: last allow-end;
&:lang(en) {
hanging-punctuation: first allow-end last;
}
}- 指定した行数でテキストを省略するプロパティ。ベンダープレフィックスなしの
line-clampは全コアブラウザでサポートされていないため、 flex のベンダープレフィックス版の指定display:-webkit-boxとflex-directionに価する-webkit-box-orientと合わせてベンダープレフィックス付きで指定する必要がある。 - そのままだと三点リーダー込でオーバーフローするので、
overflowプロパティではみ出した部分を非表示にする必要がある。この際、overflow-y: clipとすること。 先述したhanging-punctuationと組み合わせた時にoverflow: hiddenだと約物が切り取られ、overflow-y: hiddenだとX軸のoverflowはautoにマッピングされてスクロールバーが表示されるためである。(※こういうイレギュラーを防ぐため、また Scroll-driven Animation やposition: stickyの動きを阻害しないためにもhiddenではなくclipを優先して使ったほうがいい) - 汎用レイアウトとして定義しておくと良い。
.composable-line-clamp {
display: -webkit-box;
overflow-block: clip;
-webkit-box-orient: block-axis;
-webkit-line-clamp: var(--composable-line-clamp--limit, 3);
@supports not (overflow-block: clip) {
overflow-y: clip;
}
}- 日本語の見出しは文節区切りでの改行と可読性がよくなる。
word-break: auto-phraseで文節区切りでの改行を行うことができるが、Chrome 系のみ。- Safari or Firefox で文節区切りでの改行を行う場合は BudouX を導入する。しかし、導入コストはあるため Chrome 系のみ文節区切りでの改行を行い、その他は従来のままというプログレッシブ・エンハンスメントの考えを持つのも選択肢。
- 本文まで全て文節改行すると、段落内で妙な空行が生まれて可読性を悪化させるので避けること。見出し・キャプション・短文詩などは文節改行が望ましい典型例であり、そういった必要な箇所に限定して使うようにする。
:where(h1, h2, h3, h4, h5, h6, caption) {
&:lang(ja) {
word-break: auto-phrase;
}
}https://github.com/google/budoux
- 長い単語がすべて次の行へ送られると、その前後の行で不自然なスペースが生じてしまう。単語の途中で改行しつつ、不自然にならない音節の位置にハイフンを挿入して改行する処理のことを「ハイフネーション」と呼ぶ。
hyphens: autoを指定することで、自動的に単語の途中でハイフネーションを行うことができる。- 注意点としては
hyphens: autoは言語依存のため、lang="en"属性などを付与して現在の言語を明示化する必要がある。
<div class="-hyphens" lang="en">
<p>You think that's where it's at</p>
<p>But is that where it's supposed to be?</p>
<p>You're getting it all over me, ex-rated</p>
</div>.-hyphens {
hyphens: auto;
}- 主にテキストを大文字表記にする
text-transform: uppercaseが最も使用される。 - VoiceOver では略語として定義されているものはアルファベット毎に読み上げる(例:IT→「アイティー」、ADD→「エーディーディー」)ので、読み間違いを防ぐために依然としてこのテクニックは重要。
- ただし、CSSで各セレクタ毎に指定すると「全て大文字」とそれ以外が入れ込んだ時に辛いのでユーティリティクラスとして定義しておく。
.-uppercase {
text-transform: uppercase;
}font-sizeをメディアクエリ or コンテナクエリで切り分けるのは辛いのでclamp()関数を使って最小値〜最大値の範囲でフォントサイズを滑らかに変化させるようにする。- ただし、推奨値の計算を CSS のみで行うのも辛い。
progress()関数もしくは CSS 標準の@functionが広まればこのあたりは改善するが、現状では厳しい。 - オンラインジェネレーターで算出するのは行き来するコストが掛かり、コメントを残さないと推奨値の計算手順が分からない、といった理由からなるべく避けたほうがいい。
- Sass を使ってるなら自前の
@functionを作成するようにする。CSS 標準でやりたいならユーティリティクラスを作ってカスタムプロパティを受け渡しできるようにすると良い。 - サイト全体の調和を優先するなら
svi、コンポーネント毎にスコープを切り分けるならコンテナクエリ +cqiのように相対先の単位を出し分けできるようにしておく。
.-fluid-text {
--_u-min-width: var(--fluid-text--min-width, 375);
--_u-max-width: var(--fluid-text--max-width, 1280);
--_u-min-font-size: var(--fluid-text--min-font-size, 14);
--_u-max-font-size: var(--fluid-text--max-font-size, 16);
--_u-base-font-size: var(--fluid-text--base-font-size, 16);
--_u-relative-unit: var(
--fluid-text--relative-unit,
100svi
); /* 100svi or 100cqi */
--_u-slope: calc(
(var(--_u-max-font-size) - var(--_u-min-font-size)) /
(var(--_u-max-width) - var(--_u-min-width))
);
--_u-intercept: calc(
var(--_u-min-font-size) - var(--_u-slope) * var(--_u-min-width)
);
--_u-font-size: clamp(
var(--_u-min-font-size) / var(--_u-base-font-size) * 1rem,
var(--_u-slope) * var(--_u-relative-unit) + var(--_u-intercept) /
var(--_u-base-font-size) * 1rem,
var(--_u-max-font-size) / var(--_u-base-font-size) * 1rem
);
font-size: var(--_u-font-size);
}※ --_u- 始まりなのはコンポーネントのローカルカスタムプロパティ(例:--_min-font-size)とのバッティングを防ぐため。
- 今までは
rem指定が有効に働くケースは Chrome の文字拡大機能(not ズーム機能)のみであり、「誰が使ってるか分からないマイナーな機能をサポートするコストが見合わない」「Chrome がpxでも文字拡大できるようにすればいい」といった意見にも賛同できるところがあったため「どっちでもいい」というスタンスを取ってきたが、<meta name="text-scale" />の登場によって考えが変わった。(参考) text-scaleが有効な場合、OS の文字スケール設定とブラウザの文字スケール設定の両方に比例してフォントサイズが拡大・縮小される。すべてのデバイスにおいて OS レベルの文字スケール設定を尊重する簡単な方法は存在しないから、remとかem使って実装してるサイトであればそれを OS レベルの文字スケール設定として再定義すればいいのでは?って感じらしい。pxでは動作しないことが(Draft段階であるものの)仕様にも書かれている。- 単純に「
remが常に正解」「pxはダメ」というような二者択一ではない。全てをremで定義するのはズーム機能との棲み分けができていない。 pxorremは「この値はユーザーがデフォルトフォントサイズを大きくしたとき、一緒に大きくなるべきか?」 を使い分けの基準にすべき。- テキストサイズ、段落の垂直マージン、行間などはフォントサイズと連動させた方がいいので
remなどを使う。 - 一部装飾的な境界線幅、細かいデザインディテールなどは文字拡大に伴って大きくなっても嬉しくないので
pxを使う。 - テキストを含むコンテンツの幅やメディアクエリやコンテナクエリのブレイクポイントは文字拡大時に窮屈になったりレイアウトが崩壊する可能性があるから
remなどを使った方がいい。 - 水平方向の
paddingは文字サイズと連動すると一行あたりの文字数が減り、可読性を落とす可能性があるのでpxを検討する。
- テキストサイズ、段落の垂直マージン、行間などはフォントサイズと連動させた方がいいので
- 重要なことはremを使う以上ブラウザの文字拡大機能を有効にして検証すべきだということ。アクセシビリティ重視のために
remを使いましょうと言いつつ、:rootにfont-size: 10pxなどの固定値を指定する実装をたまに見かけるが、文字拡大機能を有効にして検証すれば動いていないことは明白であるはず。「font-sizeにはremを使う」というルールが独り歩きして肝心なことを見落としている。
- 余談であるが、メディアクエリやコンテナクエリのブレイクポイントには
calc()やmax()などの数学関数が利用できる。 - ブレイクポイントを
remやemに変換する場合は@media (width >= calc(768 / 16 * 1em))のように指定したほうが計算機を使う手間が省け、元のピクセル値がわかりやすくなるメリットがあるので推奨する。- メディアクエリはビューポートを基準とするため、メディアクエリの中ではブラウザの文字サイズを基準に計算される。そのため、
remもemもフォントサイズの変更が行われていない場合は16px相当になる。 - コンテナクエリの場合は
container-type: inline-sizeを指定した要素のフォントサイズを基準として計算される。
- メディアクエリはビューポートを基準とするため、メディアクエリの中ではブラウザの文字サイズを基準に計算される。そのため、
- 各見出しにおいて手動で改行を行う場合は、ポエムなどの文章構造的に意味のある改行以外では
<br>は使わずに CSS で改行制御を行う。 - 原則的には
display: inline flow-rootを指定したspan要素か、親要素にdisplay: block flex+flex-wrap: wrapを指定するようにする。 - 手動改行は多くのケースでは日本語都合であるため、
:lang(ja)の時のみ上記のアプローチを行い、その他の言語では「<span>そのものを無いもの」とする方針にする。
.-br {
display: contents;
&:lang(ja) {
display: block flow;
}
}
.-wbr {
display: contents;
&:lang(ja) {
display: inline flow-root;
}
}文章によっては改行によって分割されると不都合なワードも存在する。
分割させたくない文字と文字の間には「(zeroゼロ width幅 joiner接合子)」を挿入することで分離禁止な単語として明示することができる。
<p>marginが相‍殺するのを防止するために、なるべくmarginの向きは上方向に統一しておきます。</p>上記のケースでは「相殺する」の「殺」が頭に来ると不穏な言葉が生まれてしまうので、それを防止している。
:where(:root) {
text-spacing-trim: trim-start;
text-autospace: normal;
line-break: strict;
overflow-wrap: anywhere;
&:lang(ja) {
font-kerning: none;
}
&:lang(en) {
font-kerning: normal;
}
}
:where(:is(h1, h2, h3, h4, h5, h6, p, caption):lang(en)) {
text-wrap: pretty;
}
:where(h1, h2, h3, h4, h5, h6, caption) {
font-kerning: normal;
&:lang(ja) {
font-feature-settings: "palt";
word-break: auto-phrase;
}
}
:where(pre) {
text-spacing-trim: space-all;
}
:where(pre, time, input:not([type="button" i], [type="submit" i], [type="reset" i]), textarea, [contenteditable]) {
text-autospace: no-autospace;
}
.-text-center {
text-align: center;
text-wrap: balance;
}
.-trim-both {
text-box-trim: trim-both;
&:lang(en) {
text-box-edge: cap alphabetic;
}
}
.-hanging {
hanging-punctuation: last allow-end;
&:lang(en) {
hanging-punctuation: first allow-end last;
}
}
.-uppercase {
text-transform: uppercase;
}
.-hyphens {
hyphens: auto;
}
.-tabular-nums {
font-variant-numeric: tabular-nums;
}
.-br {
display: contents;
&:lang(ja) {
display: block flow;
}
}
.-wbr {
display: contents;
&:lang(ja) {
display: inline flow-root;
}
}
.-fluid-text {
--_u-min-width: var(--fluid-text--min-width, 375);
--_u-max-width: var(--fluid-text--max-width, 1280);
--_u-min-font-size: var(--fluid-text--min-font-size, 14);
--_u-max-font-size: var(--fluid-text--max-font-size, 16);
--_u-base-font-size: var(--fluid-text--base-font-size, 16);
--_u-relative-unit: var(
--fluid-text--relative-unit,
100svi
); /* 100svi or 100cqi */
--_u-slope: calc(
(var(--_u-max-font-size) - var(--_u-min-font-size)) /
(var(--_u-max-width) - var(--_u-min-width))
);
--_u-intercept: calc(
var(--_u-min-font-size) - var(--_u-slope) * var(--_u-min-width)
);
--_u-font-size: clamp(
var(--_u-min-font-size) / var(--_u-base-font-size) * 1rem,
var(--_u-slope) * var(--_u-relative-unit) + var(--_u-intercept) /
var(--_u-base-font-size) * 1rem,
var(--_u-max-font-size) / var(--_u-base-font-size) * 1rem
);
font-size: var(--_u-font-size);
}
.composable-line-clamp {
display: -webkit-box;
overflow-block: clip;
-webkit-box-orient: block-axis;
-webkit-line-clamp: var(--composable-line-clamp--limit, 3);
@supports not (overflow-block: clip) {
overflow-y: clip;
}
}※kiso.cssを使用している場合はいくつかの指定が不要になる。