下線、上線、取り消し線をスタイル設定するには、枠線、背景グラデーション、疑似要素で代用するのではなく、ネイティブの text-decoration プロパティを使用します。ネイティブ デコレーションは、折り返された行に沿ってテキストを追跡し、フォントの指標を追跡し、グリフのディセンダーをスキップし、ネストされた要素を介して継承されるため、これらを再実装する必要はありません。このガイドの最後にあるボックスベースの手法は、装飾がテキストに沿って表示されるのではなく、テキストを囲む必要がある場合にのみ使用してください。
テキスト装飾は、リンクだけでなく、あらゆるテキストに適用されます。下線付きのテキストがリンクでない場合は、リンクと間違えられないようにしてください。リンクを装飾する場合は、スクリプト ハンドラを含む href="javascript:void(0)" ではなく、リンク先を指す実際の <a href> を使用します(新しいタブを開くには target="_blank" rel="noopener" を追加します)。実際のリンクでは、中央クリック、リンクのコピー、スクリーン リーダーの動作が維持されます。
線のスタイルを設定する
text-decoration の省略形を使用して、線、スタイル、色、太さを設定し、text-underline-offset を使用してテキストからのギャップを設定します。セレクタを :where() でラップすると、デフォルトの詳細度がゼロになり、カスタム プロパティを必要とせずに、後でオーバーライドできるようになります。
:where(.decorated) {
/* Thickness is relative to the font size (0.08em is an example value), so the line is not too heavy
on small text or too light on large text, and max() keeps it at least 1px so it never disappears. */
text-decoration: underline solid currentColor max(1px, 0.08em);
/* Use em so the gap scales with the font size. 0.2em clears most descenders; example value. */
text-underline-offset: 0.2em;
}
/* Example hover state: a softer line that firms up on hover and focus. The 50% mix and
0.12em thickness are example values. */
:where(.decorated) {
text-decoration-color: color-mix(in srgb, currentColor, transparent);
}
:where(.decorated:is(:hover, :focus-visible)) {
text-decoration-color: currentColor;
text-decoration-thickness: max(1px, 0.12em);
}
/* Other lines are a different text-decoration-line on the same properties */
.decorated-over { text-decoration-line: overline; }
.decorated-through { text-decoration-line: line-through; }
text-decoration-style には、solid、double、dotted、dashed、wavy を指定できます。text-underline-offset は下線にのみ適用されます。
設計の出発点:
- 太さ: 本文の場合は
0.06em~0.1em程度。見出しや強調状態には、太めのフォント(0.1em~0.15em)を使用します。 - オフセット: 約
0.15em~0.25em。点線や波線は実線よりも少しスペースが必要なため、オフセットを大きくします。 - 色:
currentColorでは、線の色がテキストの色と一致します。テキストの色を柔らかくして、ホバー時に強調されるようにすると、リンクが長く続いても落ち着いた印象になります。 - スタイル: リンクには
solid、用語と略語にはdotted、エラーなどのアノテーションにはwavyを使用します。
行をスキップする場所を制御する
インクの飛び
デフォルトでは、下線と上線は、ディセンダーなど、それらと交差するグリフ部分の周囲で途切れます。text-decoration-skip-ink を設定して、動作を選択します。
.decorated {
/* auto: skip ink for most scripts (default). none: draw straight through.
all: also skip ink in scripts where auto does not, such as Japanese or Chinese. */
text-decoration-skip-ink: all;
}
- ラテン文字のテキストには
autoを使用し、宣言しないでください。これがデフォルトです。 - 下線がベースラインに近く、表意文字のグリフと交差する場合や、複数の文字体系が混在するテキストを統一感のある見た目にする必要がある場合は、
allを使用します。 noneは、非常に太い線や、意図的に装飾された線に使用します。この場合、隙間は切れ目のように見えます。
スペースをスキップする
text-decoration-skip-spaces は、スペースの下に線を引くかどうかを決定します。デフォルトの start end では、先頭と末尾のスペースは装飾されません。
.decorated {
/* Spaces collapse by default, so there is nothing to skip. Preserve them when the
leading or trailing spaces are meaningful, such as padded input values. */
white-space: pre;
/* none: decorate every space. all: skip every space, so each word is underlined separately.
start | end | start end: skip only leading, trailing, or both. */
text-decoration-skip-spaces: all;
}
装飾がパディング(none)を視覚的に覆う必要がある場合や、すべての単語の間で改行する必要がある場合(all)を除き、text-decoration-skip-spaces を宣言しないでください。リンクのテキストなどの通常のインライン テキストには、先頭または末尾にスペースがないため、宣言は何も行いません。
インセットを使用して線をトリミングまたは延長する
text-decoration-inset は、線の始点と終点を移動します。正の値を指定すると端が内側に引っ張られ、負の値を指定すると外側に押し出されます。1 つの値を指定すると両端が移動し、2 つの値を指定すると開始位置と終了位置が個別に設定されます。パーセンテージは、装飾されたボックスのインライン サイズを指します。
/* Small insets soften the ends of the line; larger ones read as a deliberately short line.
Both values are examples. */
.trimmed { text-decoration-inset: var(--deco-inset, 0.1em); }
.extended { text-decoration-inset: calc(var(--deco-inset, 0.1em) * -1); }
枠線や背景のグラデーションなしで、ホバー時に下線を表示するには、末尾を 100% トリミングして 0 に戻します。
.reveal {
text-decoration: underline; /* Always visible, including for reduced motion */
}
/* Only hide and animate the line when the user has not asked for reduced motion. */
@media (prefers-reduced-motion: no-preference) {
.reveal {
/* Trim the end by the full inline size so the line starts with zero length. */
text-decoration-inset: 0 100%;
/* 150ms to 300ms feels responsive without lagging; ease-out starts quickly. Example values. */
transition: text-decoration-inset var(--reveal-duration, 0.25s) ease-out;
}
/* Include :focus-visible so keyboard users get the same reveal as pointer users. */
.reveal:is(:hover, :focus-visible) {
text-decoration-inset: 0 0;
}
}
疑似要素を使用してカスタムラインを描画する
波線など、text-decoration で描画できない形状の線が必要な場合は、疑似要素を使用し、クリッピングによって表示します。追加のマークアップは必要ないため、リンクはプレーンな <a> のままです。
<a class="draw" href="#">Hover to draw underline</a>
.draw {
position: relative;
display: inline-block;
color: inherit;
text-decoration: none;
padding-block-end: 0.4em; /* Space for the line below the text; example value */
}
.draw::after {
content: "";
position: absolute;
inset: auto 0 0 0;
block-size: 0.4em; /* Height of the squiggle; example value */
pointer-events: none;
border: var(--deco-thickness, max(1px, 0.08em)) solid var(--deco-color, currentColor);
/* A single shape strokes along its path, using the border width as the stroke. Example squiggle. */
border-shape: shape(from 0% 50%,
curve to 20% 50% with 5% 0% / 15% 100%,
smooth to 40% 50% with 35% 100%,
smooth to 60% 50% with 55% 100%,
smooth to 80% 50% with 75% 100%,
smooth to 100% 50% with 95% 100%);
}
/* Only hide and animate the line when the user has not asked for reduced motion;
otherwise it stays fully drawn. */
@media (prefers-reduced-motion: no-preference) {
.draw::after {
/* Clip the line away from the right edge. The negative block insets keep the stroke
from being cut off vertically. */
clip-path: inset(-1em 100% -1em 0);
/* A slightly slower draw than a hover reveal reads as drawing; example duration. */
transition: clip-path var(--draw-duration, 0.6s) ease-out;
}
.draw:is(:hover, :focus-visible)::after {
clip-path: inset(-1em 0 -1em 0); /* Fully drawn */
}
}
代わりにボックスでテキストを囲む
2 つのボックス プロパティは、テキストに沿って線を引くのではなく、テキストを囲むことで装飾します。アウトラインがテキストに触れないように、テキストにパディングを設定します。
/* Gradient outline with a see-through middle */
.ring {
padding: 0.2em 0.75em; /* Example values: enough room that the outline does not touch the text */
/* The border width is the visible outline thickness. Relative to the font size, with a 2px
minimum so it stays visible on small text; example value. */
border: var(--ring-width, max(2px, 0.12em)) solid transparent;
background: linear-gradient(135deg, var(--ring-start, #2563eb), var(--ring-end, #db2777)) border-box;
/* Paint the background only under the border, so the inside stays transparent. */
background-clip: border-area;
}
/* Border that follows a shape that border-radius cannot draw, such as a tag with a pointed end */
.tag {
padding: 0.2em 1.5em 0.2em 0.9em; /* Example values: extra room at the point so the text is not clipped */
border: var(--shape-stroke, max(2px, 0.08em)) solid var(--shape-color, currentColor); /* Example stroke width */
/* A single shape strokes along its path. Two shapes fill the area between them.
This polygon is an example; use shape() for curves. */
border-shape: polygon(0 0, 85% 0, 100% 50%, 85% 100%, 0 100%);
}
border-shape は枠線のみを置き換えます。背景は長方形のボックスのままなので、シェイプを塗りつぶすために使用しないでください。
角のみを異なる形状にする必要がある場合(ベベル、スクープ、ノッチ付きのラベルなど)は、代わりに corner-shape と border-radius を使用します。背景と枠線が角に沿って表示され、サポートされていないブラウザでは通常の角丸にフォールバックされます。
.label {
padding: 0.2em 0.9em; /* Example values */
border: var(--shape-stroke, max(2px, 0.08em)) solid var(--shape-color, currentColor);
border-radius: 0.6em; /* Corner size; example value */
corner-shape: bevel; /* round (default) | scoop | bevel | notch | square | squircle */
}
手法を選択する
text-decoration(線、インクのスキップ、スペース、インセット): 線はテキストに沿って走り、テキストとともに折り返され、フォント指標に従う必要があります。これがデフォルトの選択肢です。border-shapeとclip-pathを含む疑似要素: 線がカスタム形状であるか、描画アニメーションが必要です。text-decoration-styleでは追加の配置が必要なため、目的の見た目を実現できない場合にのみ、この方法を選択してください。background-clip: border-area: テキストにグラデーションのアウトラインが必要です。実線のアウトラインで十分な場合は、プレーンなborder-colorを使用します。corner-shape: 角のみに異なる形状(ベベル、スクープ、ノッチなど)が必要な場合。角のサイズを設定するborder-radiusと組み合わせて使用します。border-shape: アウトラインに、border-radiusが描画できない形状(タグや矢印など)が必要です。角を丸くするだけで十分な場合は、border-radiusを使用します。- インセット ホバー表示: 色の変化以外のホバー アフォーダンスがリンクに必要な場合に追加する価値があります。インタラクティブでないテキストの場合はスキップします。
代替戦略
各フォールバックは、テキストの読みやすさを維持し、コア装飾を維持します。Baseline ターゲットの観点からニーズをフレーム化します。プロパティのないブラウザが含まれている場合は、一致するフォールバックを適用します。
インクの飛び all
Baseline ターゲットが text-decoration-skip-ink: all をサポートしていない場合、宣言は無視され、ブラウザは auto を保持します。ラテン文字のディセンダーは引き続きスキップされます。auto がカバーしていないスクリプト(日本語など)のみ、インクのスキップがなくなります。これはグレースフル デグラデーションであるため、追加のコードは必要ありません。
スペースをスキップする
Baseline ターゲットが text-decoration-skip-spaces をサポートしていない場合、ブラウザは独自のデフォルトのスペース処理を使用します。これはグレースフル デグラデーションです。ブラウザがスキップしないスペースの下に線が引かれます。
インセット
Baseline ターゲットが text-decoration-inset をサポートしていない場合、この宣言は無視されるため、リンクには通常の完全に表示される下線が維持され、トランジションにはアニメーション化するものがありません。これは、ホバー表示が失われ、リンクに下線が引かれたままになるという、グレースフル デグラデーションです。
国境地域
Baseline ターゲットが background-clip: border-area をサポートしていない場合、宣言は無視され、背景はボーダーボックス全体の下にペイントされ、アウトラインだけでなくテキスト全体が塗りつぶされます。グラデーションがデザインの一部である場合は、単色の枠線で妥協しないでください。代わりにグラデーションを再現し、@supports 内にのみ background-clip: border-area を適用します。
要素の背景が既知のフラットカラーである場合は、不透明なレイヤを使用します。パディング ボックスの上にサーフェス カラーを塗り、ボーダーボックスの下にグラデーションを塗ります。これにより、グラデーションは透明なボーダーを通してのみ表示されます。
.ring {
border: var(--ring-width, max(2px, 0.12em)) solid transparent; /* Example width */
/* Fallback: an opaque surface layer hides the gradient inside the border. */
background:
linear-gradient(var(--surface, canvas) 0 100%) padding-box,
linear-gradient(135deg, var(--ring-start, #2563eb), var(--ring-end, #db2777)) border-box;
}
@supports (background-clip: border-area) {
.ring {
background: linear-gradient(135deg, var(--ring-start, #2563eb), var(--ring-end, #db2777)) border-box;
background-clip: border-area; /* The inside stays transparent */
}
}
内側を透明にする必要がある場合は、マスクされた疑似要素を使用します。疑似要素はグラデーションを保持し、マスクはパディング リング以外のすべてを切り取ります。
@supports not (background-clip: border-area) {
.ring {
position: relative;
border-color: transparent;
}
.ring::before {
content: "";
position: absolute;
inset: calc(var(--ring-width, max(2px, 0.12em)) * -1); /* Cover the border box */
padding: var(--ring-width, max(2px, 0.12em)); /* Ring thickness */
border-radius: inherit;
background: linear-gradient(135deg, var(--ring-start, #2563eb), var(--ring-end, #db2777));
/* Keep only the area between the content box and the border box. */
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
pointer-events: none;
}
}
枠線の形状
Baseline ターゲットが border-shape をサポートしていない場合、宣言は無視され、要素は長方形の枠線を維持します。角丸長方形は、形状が純粋に装飾的な場合にのみ、代替として使用できます。タグ、矢印、コールアウトなど、形状に意味がある場合は、できるだけ忠実に再現し、@supports の内側にのみ border-shape を適用します。
クリップされた疑似要素でアウトラインを描画します。ボックスがサポートされているケースと同じサイズになるように透明な枠線を維持し、疑似要素をアウトラインの色で塗りつぶし、evenodd clip-path でリング状にカットします。外側の形状、そしてストローク幅でインセットされた同じ形状です。
.tag {
--stroke: max(2px, 0.08em); /* Example stroke width */
position: relative;
padding: 0.2em 1.5em 0.2em 0.9em; /* Example values */
border: var(--stroke) solid transparent;
}
@supports not (border-shape: circle(50%)) {
.tag::before {
content: "";
position: absolute;
inset: calc(var(--stroke) * -1); /* Cover the border box */
background: var(--shape-color, currentColor);
pointer-events: none;
/* Outer shape, then the inner shape inset by the stroke. The inner points are approximate on
diagonal edges, so the stroke looks slightly thinner there. */
clip-path: polygon(evenodd,
0 0, 85% 0, 100% 50%, 85% 100%, 0 100%,
var(--stroke) var(--stroke),
calc(85% - var(--stroke)) var(--stroke),
calc(100% - var(--stroke) * 2) 50%,
calc(85% - var(--stroke)) calc(100% - var(--stroke)),
var(--stroke) calc(100% - var(--stroke)));
}
}
@supports (border-shape: circle(50%)) {
.tag {
border-color: var(--shape-color, currentColor);
border-shape: polygon(0 0, 85% 0, 100% 50%, 85% 100%, 0 100%);
}
}
輪郭ではなく塗りつぶされた形状にするには、同じポイントを持つ clip-path: polygon(...) を要素に直接配置します。
疑似要素の下線については、.draw::after で border-block-end を使用して直線を引きます。block-size、border、border-shape の波線は @supports (border-shape: circle(50%)) 内にのみ適用します。線は直線ですが、clip-path アニメーションは形状に依存しないため、描画が続きます。
clip-path は、フォーカス リングと box-shadow もクリップします。クリップされた要素の輪郭に頼ってフォーカスを示すのではなく、色や背景を変更して :focus-visible のスタイルを設定します。