Aangepaste tekstopmaak

Gebruik de native text-decoration-property's om onderstrepingen, bovenstrepingen en doorhalingen op te maken in plaats van ze na te bootsen met randen, achtergrondovergangen of pseudo-elementen. Een native decoratie volgt de tekst over afgebroken regels, volgt de statistieken van het lettertype, slaat staartletters over en wordt overgenomen door geneste elementen, dus dat hoeft niet opnieuw te worden geïmplementeerd. Gebruik de op vakken gebaseerde technieken aan het einde van deze handleiding alleen als de versiering de tekst moet omlijnen of omwikkelen in plaats van erlangs te lopen.

Tekstdecoraties zijn van toepassing op alle tekst, niet alleen op links. Als onderstreepte tekst geen link is, zorg je dat deze niet kan worden verward met een link. Als u een link versiert, gebruikt u een echte <a href> die naar de bestemming verwijst (voeg target="_blank" rel="noopener" toe om een nieuw tabblad te openen), niet href="javascript:void(0)" met een scripthandler. Een echte link behoudt het gedrag van klikken met de middelste muisknop, link kopiëren en schermlezer.

De lijn opmaken

Stel de lijn, stijl, kleur en dikte in met de afkorting text-decoration en de afstand tot de tekst met text-underline-offset. Plaats de kiezer in :where(), zodat de standaardwaarden geen specificiteit hebben en een latere overschrijving wint, zonder dat er aangepaste eigenschappen nodig zijn.

: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 accepteert solid, double, dotted, dashed en wavy. text-underline-offset is alleen van toepassing op onderstrepingen.

Uitgangspunten voor het ontwerp:

  • Dikte: ongeveer 0.06em tot 0.1em voor hoofdtekst. Gebruik een dikkere lijn (0.1em tot 0.15em) voor koppen en benadrukte statussen.
  • Verschuiving: ongeveer 0.15em tot 0.25em. Stippellijnen en golvende lijnen hebben iets meer ruimte nodig dan ononderbroken lijnen, dus verhoog de verschuiving hiervoor.
  • Kleur: currentColor zorgt dat de lijn overeenkomt met de tekst. Een zachtere mix van de tekstkleur die duidelijker wordt als je de cursor erop plaatst, houdt lange reeksen links rustig.
  • Stijl: solid voor links, dotted voor termen en afkortingen en wavy voor annotaties zoals fouten.

Bepalen waar de regel wordt overgeslagen

Inkt overslaan

Standaard worden onderstrepingen en bovenstrepingen onderbroken rond glyph-delen die ze kruisen, zoals staarten. Stel text-decoration-skip-ink in om het gedrag te kiezen:

.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;
}
  • Behoud auto voor Latijnse tekst en declareer deze niet: dit is de standaardinstelling.
  • Gebruik all als de onderstreping dicht genoeg bij de basislijn staat om ideografische tekens te kruisen of als tekst met gemengde scripts er consistent moet uitzien.
  • Gebruik none voor een heel dikke lijn of een opzettelijk decoratieve lijn, waarbij gaten eruitzien als onderbrekingen.

Ruimtes overslaan

text-decoration-skip-spaces bepaalt of de lijn onder spaties wordt getekend. De standaardwaarde, start end, laat spaties aan het begin en eind onopgemaakt.

.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;
}

Declareer text-decoration-skip-spaces niet, tenzij de decoratie de opvulling (none) zichtbaar moet bedekken of tussen elk woord moet worden afgebroken (all). Gewone inline tekst, zoals de tekst van een link, heeft geen voorloop- of volgspaties, dus de declaratie doet niets.

De lijn inkorten of verlengen met een inzet

text-decoration-inset verplaatst het begin en einde van de regel. Een positieve waarde trekt een uiteinde naar binnen, een negatieve waarde duwt het naar buiten. Eén waarde verplaatst beide uiteinden. Met 2 waarden stel je het begin en het einde afzonderlijk in. Percentages verwijzen naar de inline grootte van het versierde vak.

/* 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); }

Als je een onderstreping wilt tonen bij de muisaanwijzer zonder rand of achtergrondverloop, knip je het einde met 100% en zet je het terug naar 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;
  }
}

Een aangepaste lijn tekenen met een pseudo-element

Gebruik een pseudo-element als de lijn een vorm moet zijn die text-decoration niet kan tekenen, zoals een kronkel, en onthul deze door te knippen. Er is geen extra opmaak nodig, dus de link blijft een gewone <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 */
  }
}

Tekst omlijnen met een vak

Met 2 box-eigenschappen kun je tekst versieren door deze te omlijnen in plaats van er een lijn langs te trekken. Geef de tekst opvulling zodat de omtrek de tekst niet raakt.

/* 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 vervangt alleen de rand. De achtergrond is nog steeds het rechthoekige vak, dus vertrouw er niet op dat de achtergrond een vorm vult.

Als alleen de hoeken een andere vorm nodig hebben, zoals een afgeschuind, afgerond of ingekerfd label, gebruik je corner-shape met border-radius. De achtergrond en rand volgen de hoeken en niet-ondersteunde browsers vallen terug op gewone afgeronde hoeken.

.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 */
}

Een techniek kiezen

  • text-decoration (lijn, inkt overslaan, spaties, inspringing): De lijn moet langs de tekst lopen, meelopen met de tekst en de lettertypestatistieken volgen. Dit is de standaardkeuze.
  • Pseudo-element met border-shape en clip-path: de lijn is een aangepaste vorm of heeft een tekenanimatie nodig. Kies deze optie alleen als text-decoration-style de look niet kan produceren, omdat er extra positionering nodig is.
  • background-clip: border-area: de tekst heeft een overvloeiende contour nodig. Gebruik een gewone border-color als een ononderbroken contour voldoende is.
  • corner-shape: Alleen de hoeken hebben een andere vorm nodig, zoals afgeschuind, afgerond of ingekerfd. Combineer dit met border-radius, waarmee je de hoekgrootte instelt.
  • border-shape: De omtrek heeft een vorm nodig die border-radius niet kan tekenen, zoals een tag of pijl. Gebruik border-radius als afgeronde hoeken voldoende zijn.
  • Inzet bij aanwijzen: de moeite waard om toe te voegen als een link een aanwijsmogelijkheid nodig heeft die verder gaat dan een kleurwijziging. Sla dit over voor tekst die niet interactief is.

Reservestrategieën

Elke fallback houdt de tekst leesbaar en behoudt de kernversiering. Formuleer de behoefte in termen van je Baseline-doel: als het browsers zonder een property bevat, pas je de overeenkomende fallback toe.

Inkt overslaan all

Als je Baseline-doel text-decoration-skip-ink: all niet ondersteunt, wordt de declaratie genegeerd en behoudt de browser auto. Latijnse staartletters worden nog steeds overgeslagen. Alleen scripts die niet door auto worden gedekt, zoals Japans, worden niet meer overgeslagen. Dit is graceful degradation, dus er is geen extra code nodig.

Ruimtes overslaan

Als je Baseline-doel text-decoration-skip-spaces niet ondersteunt, gebruikt de browser de eigen standaard verwerking van spaties. Dit is graceful degradation: de lijn wordt getrokken onder spaties die de browser niet overslaat.

Inset

Als je Baseline-doel text-decoration-inset niet ondersteunt, wordt de declaratie genegeerd. De link behoudt dan de normale, volledig zichtbare onderstreping en de overgang heeft niets om te animeren. Dit is graceful degradation: de onthulling bij de muisaanwijzer is verloren gegaan en de link blijft onderstreept.

Grensgebied

Als je Baseline-doel background-clip: border-area niet ondersteunt, wordt de declaratie genegeerd en wordt de achtergrond onder het hele randvak getekend, waardoor de tekst wordt gevuld in plaats van alleen de omtrek. Neem GEEN genoegen met een effen rand als het kleurverloop deel uitmaakt van het ontwerp. Reproduceer in plaats daarvan het kleurverloop en pas background-clip: border-area alleen toe binnen @supports.

Gebruik een ondoorzichtige laag als de achtergrond van het element een bekende vlakke kleur is. Schilder de oppervlaktekleur over het opvullingsvak en de kleurovergang onder het randvak, zodat de kleurovergang alleen door de transparante rand wordt getoond:

.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 */
  }
}

Gebruik een gemaskeerd pseudo-element als de binnenkant transparant moet blijven. Het pseudo-element bevat het kleurverloop en een masker snijdt alles weg behalve de opvullingsring:

@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;
  }
}

Randvorm

Als je Baseline-doel border-shape niet ondersteunt, wordt de declaratie genegeerd en behoudt het element de rechthoekige rand. Een afgeronde rechthoek is alleen een acceptabele reserveoptie als de vorm puur decoratief is. Als de vorm een betekenis heeft, zoals een tag, pijl of callout, reproduceer je deze zo nauwkeurig mogelijk en pas je border-shape alleen binnen @supports toe.

Teken de omtrek met een bijgesneden pseudoelement. Houd een transparante rand aan zodat het vak dezelfde grootte heeft als in het ondersteunde geval, vul het pseudo-element met de kleur van de omtrek en snijd het in een ring met een evenodd clip-path: de buitenste vorm en daarna dezelfde vorm ingezet met de lijndikte.

.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%);
  }
}

Als u een gevulde vorm wilt in plaats van een omtrek, plaatst u clip-path: polygon(...) met dezelfde punten rechtstreeks op het element.

Teken voor de onderstreping van het pseudo-element een rechte lijn met border-block-end op .draw::after en pas de kronkel block-size, border en border-shape alleen toe binnen @supports (border-shape: circle(50%)). De lijn is recht, maar wordt nog steeds getekend, omdat de clip-path-animatie niet afhankelijk is van de vorm.

clip-path snijdt ook focusringen en box-shadow bij. Vertrouw niet op een contourlijn op het bijgesneden element om de focus te tonen. Stijl :focus-visible in plaats daarvan met een kleur- of achtergrondwijziging.