استخدِم سمات text-decoration الأصلية لتنسيق الخطوط السفلية والعلوية والنصوص المشطوبة، بدلاً من تزييفها باستخدام الحدود أو تدرّجات الخلفية أو العناصر الزائفة. يتبع الزخرفة الأصلية النص عبر الأسطر الملتفة، ويتتبع مقاييس الخط، ويتخطى الحروف الهابطة، ويتم توريثه من خلال العناصر المتداخلة، لذلك لا يلزم إعادة تنفيذ أي من ذلك. استخدِم الأساليب المستندة إلى المربّعات في نهاية هذا الدليل فقط عندما يجب أن يحدّد الزخرفة النص أو يلتف حوله بدلاً من أن يمتد على طوله.
تنطبق زخارف النص على أي نص، وليس على الروابط فقط. إذا كان النص المسطّر ليس رابطًا، تأكَّد من أنّه لا يمكن الخلط بينه وبين رابط. عند تزيين رابط، استخدِم عنصر <a href> حقيقيًا يشير إلى الوجهة (أضِف target="_blank" rel="noopener" لفتح علامة تبويب جديدة)، وليس href="javascript:void(0)" مع معالج نصوص برمجية. يحتفظ الرابط الحقيقي بسلوك النقر بزر الماوس الأوسط ونسخ الرابط وقارئ الشاشة.
تنسيق الخط
يمكنك ضبط الخط والنمط واللون والسُمك باستخدام الاختصار 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للتعليقات التوضيحية، مثل الأخطاء.
التحكّم في مكان تخطّي السطر
تخطّي الحبر
By default, underlines and overlines break around glyph parts that cross them, such as descenders. اضبط 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عندما تكون الزوايا الدائرية كافية.- Inset hover reveal: worth adding when a link needs a hover affordance beyond a color change. تخطّيه للنص غير التفاعلي.
الاستراتيجيات الاحتياطية
يضمن كل خيار احتياطي إمكانية قراءة النص ويحافظ على الزخرفة الأساسية. صِغ الحاجة من حيث هدفك في Baseline: إذا كان يتضمّن متصفّحات بدون سمة، طبِّق الإجراء الاحتياطي المطابق.
تخطّي الحبر all
إذا كان هدفك الأساسي لا يتوافق مع text-decoration-skip-ink: all، يتم تجاهل البيان ويحتفظ المتصفّح بـ auto. لا تزال الحروف اللاتينية النازلة يتم تخطيها، فقط النصوص التي لا يغطيها auto، مثل اليابانية، تفقد ميزة تخطي الحبر. هذا هو التكيّف مع الإصدارات الأقدم، لذا لا حاجة إلى رمز إضافي.
تخطّي المساحات
إذا كان هدفك الأساسي لا يتوافق مع text-decoration-skip-spaces، سيستخدم المتصفّح طريقة المعالجة التلقائية للمسافات. هذا هو التكيّف مع الإصدارات الأقدم: يتم رسم الخط تحت المسافات التي لا يتخطاها المتصفح.
مساحة داخلية
إذا كان هدفك الأساسي لا يتيح استخدام text-decoration-inset، سيتم تجاهل
البيان، وبالتالي سيحتفظ الرابط بالخط السفلي العادي المرئي بالكامل
ولن يكون هناك أي تأثير حركي للانتقال. هذا هو التكيّف مع الإصدارات الأقدم: يتم فقدان ميزة الكشف عند التمرير ويظل الرابط مسطرًا.
منطقة حدودية
إذا كان هدفك الأساسي لا يتيح استخدام 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 مع تغيير اللون أو الخلفية بدلاً من ذلك.