כדי לעצב קווים תחתונים, קווים עליונים וקווים חוצים, השתמשו במאפייני text-decoration המקוריים במקום לזייף אותם באמצעות גבולות, שיפועי רקע או רכיבי פסאודו. עיטור מקורי עוקב אחרי הטקסט בשורות שעברו גלישה, עוקב אחרי המדדים של הגופן, מדלג על יורדים של גליפים ועובר בירושה דרך רכיבים בתצוגת עץ, כך שאין צורך להטמיע מחדש אף אחד מהם. השתמשו בשיטות שמבוססות על תיבות בסוף המדריך הזה רק אם העיטור צריך להקיף את הטקסט או לעטוף אותו, ולא לעבור לאורכו.
עיטורי טקסט חלים על כל טקסט, לא רק על קישורים. אם יש טקסט עם קו תחתון שהוא לא קישור, צריך לוודא שאי אפשר לטעות ולחשוב שהוא קישור. כשמוסיפים קישור, צריך להשתמש בתג <a href> אמיתי שמפנה ליעד (מוסיפים target="_blank" rel="noopener" כדי לפתוח כרטיסייה חדשה), ולא בתג href="javascript:void(0)" עם handler של סקריפט. קישור אמיתי שומר על ההתנהגות של לחיצה על הלחצן האמצעי, העתקת קישור וקורא מסך.
עיצוב הקו
מגדירים את הקו, הסגנון, הצבע והעובי באמצעות קיצור הדרך 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;
}
אל תצהירו על text-decoration-skip-spaces אלא אם העיצוב אמור לכסות באופן גלוי את המרווח הפנימי (none) או להפריד בין כל מילה (all). בטקסט רגיל בשורה, כמו טקסט של קישור, אין רווחים בתחילת הטקסט או בסופו, ולכן ההצהרה לא עושה כלום.
חיתוך או הארכה של הקו באמצעות הוספה
text-decoration-inset מזיז את ההתחלה והסוף של הקו. ערך חיובי מושך את הקצה פנימה, וערך שלילי דוחף אותו החוצה. ערך אחד מזיז את שני הקצוות, שני ערכים מגדירים את ההתחלה והסוף בנפרד. האחוזים מתייחסים לגודל של התיבה המעוצבת בשורה.
/* 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 */
}
}
הוספת תיבה מסביב לטקסט במקום קו מתאר
שני מאפייני תיבה מקשטים את הטקסט על ידי הוספת קו מתאר במקום קו לאורך הטקסט. מוסיפים ריפוד לטקסט כדי שקו המתאר לא ייגע בו.
/* 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: אם הוא כולל דפדפנים בלי מאפיין, צריך להחיל את ה-fallback התואם.
דילוג על all בכתב יד
אם יעד Baseline לא תומך ב-text-decoration-skip-ink: all, המערכת מתעלמת מההצהרה והדפדפן שומר על auto. האותיות הלטיניות עם החלק התחתון עדיין נחתכות. רק בכתבים שלא נכללים ב-auto, כמו יפנית, הדיו לא נחתך. זוהי הפחתה חיננית (graceful degradation), כך שאין צורך בקוד נוסף.
דילוג על רווחים
אם גרסת ה-Baseline שלכם לא תומכת ב-text-decoration-skip-spaces, הדפדפן ישתמש בטיפול ברווחים שמוגדר כברירת מחדל. זוהי הפחתה חיננית (graceful degradation): הקו משורטט מתחת לרווחים שהדפדפן לא מדלג עליהם.
מוטמע
אם יעד ה-Baseline לא תומך ב-text-decoration-inset, המערכת מתעלמת מההצהרה, כך שהקישור שומר על הקו התחתון הרגיל והגלוי שלו, ולמעבר אין מה להנפיש. זוהי הפחתה חיננית (graceful degradation): החשיפה במעבר מעל לא פועלת והקישור נשאר עם קו תחתון.
אזור הגבול
אם יעד ה-Baseline לא תומך ב-background-clip: border-area, המערכת מתעלמת מההצהרה והרקע נצבע מתחת לכל תיבת הגבול, וממלא את הטקסט במקום רק את קווי המתאר שלו. אל תסתפקו בגבול אחיד ופשוט אם העיצוב כולל מעבר צבעים. במקום זאת, צריך לשחזר את הגרדיאנט ולהחיל את background-clip: border-area רק בתוך @supports.
השתמשו בשכבה אטומה כשהרקע של הרכיב הוא צבע אחיד ידוע. צובעים את צבע המשטח מעל תיבת המרווח הפנימי ואת הגוון המדורג מתחת לתיבת הגבול, כך שהגוון המדורג יופיע רק דרך הגבול השקוף:
.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, המערכת תתעלם מההצהרה והגבול של הרכיב יישאר מלבני. מלבן מעוגל הוא חלופה מקובלת רק כשהצורה היא דקורטיבית בלבד. כשהצורה נושאת משמעות, כמו תג, חץ או הסבר, צריך לשחזר אותה בצורה הכי מדויקת שאפשר, ולהחיל את border-shape רק בתוך @supports.
מציירים את קווי המתאר באמצעות רכיב פסאודו חתוך. שומרים על גבול שקוף כדי שהתיבה תהיה באותו גודל כמו במקרה הנתמך, ממלאים את הרכיב המדומה בצבע של קו המתאר וחותכים אותו לטבעת באמצעות 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(...) עם אותן נקודות ישירות לרכיב.
כדי להוסיף קו תחתון לאלמנט המדומה, צריך לשרטט קו ישר עם border-block-end ב-.draw::after, ולהחיל את הקו הגלי block-size, border ו-border-shape רק בתוך @supports (border-shape: circle(50%)). הקו ישר אבל עדיין נמשך, כי האנימציה של clip-path לא תלויה בצורה.
המאפיין clip-path חותך גם את טבעות המיקוד ואת box-shadow. אל תסתמכו על קו מתאר ברכיב החתוך כדי להראות את המיקוד. במקום זאת, עצבו את :focus-visible עם שינוי בצבע או ברקע.