تزئینات نوشتار سفارشی

از ویژگی‌های بومی 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 برای حاشیه‌نویسی‌هایی مانند خطاها.

کنترل کردن جایی که خط رد می‌شود

پرش جوهر

به‌طور پیش‌فرض، زیرخط‌ها و روخط‌ها در اطراف بخش‌هایی از نویسه که از آن‌ها عبور می‌کنند، مانند دنباله نویسه، قطع می‌شوند. 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); }

برای آشکار کردن خط زیرین هنگام نگه داشتن نشانگر بدون حاشیه یا گرادیان پس‌زمینه، انتهای آن را ۱۰۰٪ برش دهید و آن را به ۰ برگردانید:

.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 خود قاب‌بندی کنید: اگر شامل مرورگرهای بدون ویژگی است، جایگزین منطبق را اعمال کنید.

جوهر 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 را با تغییر رنگ یا پس‌زمینه سبک‌بندی کنید.