CSS text-decoration
text-decoration is the property that draws lines on text: underlines, overlines, and strikethroughs. It’s one of the first properties most people meet when styling links, but there is a lot more to it than the single value none that everyone memorizes. Modern CSS splits it into four longhand sub-properties that give you precise control over which lines are drawn, their color, their visual style, and their thickness — and the way browsers actually paint those lines has some genuinely surprising behavior worth understanding.
Overview / How It Works
text-decoration is a shorthand for four longhand properties: text-decoration-line, text-decoration-style, text-decoration-color, and text-decoration-thickness. Setting text-decoration: underline; is really shorthand for text-decoration-line: underline; text-decoration-style: solid; text-decoration-color: currentcolor; text-decoration-thickness: auto;. Two related (but separate, non-shorthand) properties, text-underline-offset and text-decoration-skip-ink, control the vertical gap between text and underline and whether the line breaks around descenders like the tails on g, y, and p.
Here’s the part that trips people up: decoration lines are not inherited the way color or font-family are, yet they still visually propagate. If you set text-decoration: underline; on a <p> and that paragraph contains a nested <span> or <a>, the underline drawn by the parent continues underneath the child’s text too — even though the child never explicitly declared any text-decoration of its own. This happens because the decoration is associated with the box that declared it, and the rendering engine paints that line across the full extent of that box’s content, including any descendant inline boxes nested inside it. If the descendant sets its own text-decoration (even text-decoration: none;), that creates a new decoration context for that element and its own descendants, but it does not retroactively remove a line already being painted by an ancestor. This is why text-decoration: none; on a link sometimes appears to \”not work\” — usually because a wrapping element further up the tree is the one actually painting the line.
Because text-decoration-color defaults to currentcolor, decoration lines automatically match the element’s text color unless you override it. This is convenient for simple cases (change color and the underline follows) but also means you’ll want an explicit text-decoration-color whenever you want the line to differ from the glyph color, such as a subtle gray underline beneath colored link text.
Syntax
selector {\n text-decoration: <line> <style> <color> <thickness>;\n}
All four shorthand components are optional and can appear in any order, separated by spaces; omitted parts fall back to their initial values.
| Longhand property | Common values | Initial value |
|---|---|---|
text-decoration-line |
none, underline, overline, line-through (can combine multiple, space-separated, e.g. underline overline) |
none |
text-decoration-style |
solid, double, dotted, dashed, wavy |
solid |
text-decoration-color |
any valid color (hex, rgb(), named color, etc.) |
currentcolor |
text-decoration-thickness |
auto, from-font, a length (e.g. 2px), a percentage |
auto |
Two companion properties are not part of the shorthand but are frequently used alongside it:
text-underline-offset— distance between the text baseline and the underline (acceptsauto, a length, or a percentage of the font size). Only affects underlines, not overlines or strikethroughs.text-decoration-skip-ink— controls whether underlines are interrupted where they’d cross a descender; defaults toauto, which already looks good in every modern browser.
Examples
Example 1: A simple, deliberately styled link underline
a {\n text-decoration: none;\n}\n\na.inline-link {\n text-decoration: underline;\n text-decoration-color: #2563eb;\n text-decoration-thickness: 2px;\n}
This targets HTML like: <a href=’#’ class=’inline-link’>example link</a>
Result: Ordinary links on the page lose their default browser underline. The link carrying class=\"inline-link\" gets a crisp 2px solid blue line beneath its text, independent of whatever color the link text itself is set to (since the color was given explicitly rather than left at currentcolor).
This shows the shorthand versus longhand relationship: the second rule re-enables underline after the first rule turned all decoration off, then two longhands fine-tune the color and thickness without needing to repeat the whole shorthand.
Example 2: Sale price strikethrough and a form validation underline
.price--old {\n text-decoration-line: line-through;\n text-decoration-color: #999999;\n text-decoration-thickness: 1px;\n}\n\n.price--new {\n color: #16a34a;\n font-weight: 700;\n}\n\n.form-error {\n text-decoration-line: underline;\n text-decoration-style: wavy;\n text-decoration-color: #dc2626;\n text-decoration-thickness: 2px;\n text-underline-offset: 3px;\n}
Result: Text with class price--old renders with a thin gray line struck through the middle of the digits, signaling a crossed-out original price, while sibling text with price--new renders in bold green with no decoration at all. Text marked form-error gets a red, wavy underline sitting a few pixels below the baseline — visually similar to a spell-checker’s squiggle — drawing the eye to invalid input without relying on color alone.
Notice that .price--old never sets color, so the strikethrough would normally inherit currentcolor from the text — but because an explicit text-decoration-color is given, the line stays gray even if the surrounding text color changes elsewhere in the cascade.
Example 3: Animated underline on navigation links
:root {\n --link-color: #1d4ed8;\n --link-underline-muted: rgba(29, 78, 216, 0.35);\n}\n\n.site-nav a {\n color: var(--link-color);\n text-decoration: underline;\n text-decoration-color: var(--link-underline-muted);\n text-decoration-thickness: 1px;\n text-underline-offset: 4px;\n transition: text-decoration-color 0.2s ease, text-decoration-thickness 0.2s ease;\n}\n\n.site-nav a:hover,\n.site-nav a:focus-visible {\n text-decoration-color: var(--link-color);\n text-decoration-thickness: 2px;\n}
Result: Navigation links display with a faint, semi-transparent blue underline offset 4px below the text at rest. On hover or keyboard focus, the underline smoothly darkens to full blue and thickens to 2px over 0.2 seconds, giving clear, animated feedback without the text itself jumping or reflowing.
This is a realistic pattern: because text-decoration-color and text-decoration-thickness are independently animatable longhands, transitioning them directly (rather than the text-decoration shorthand) gives smoother, more predictable results across browsers.
How It Works Step by Step
- 1. Parsing: the browser expands any
text-decorationshorthand into its four longhand values, filling in initial values for anything omitted. - 2. Box association: the resolved decoration values are attached to the inline box of the element that declared them — not inherited down the tree in the normal sense, but remembered as \”paint this line across this box’s content area.\”
- 3. Line layout: as text is laid out into line boxes, the engine determines where each decorated element’s content spans horizontally, including any nested inline descendants that didn’t start a new decoration context.
- 4. Thickness resolution: if
text-decoration-thicknessisauto, the engine derives a thickness from the font’s own metrics (roughly proportional to font size);from-fontinstead reads the exact underline thickness the font file specifies, which matters for variable and custom web fonts. - 5. Vertical placement: for underlines, the engine uses the font’s recommended underline position, then shifts it by
text-underline-offsetif set; overlines sit at the top of the em box, and strikethroughs sit roughly at the font’s x-height midpoint. - 6. Ink skipping: with
text-decoration-skip-ink: auto(the default), the paint step breaks the underline wherever a glyph’s descender would cross it, avoiding a messy line running through letter tails. - 7. Paint: the line is finally painted in the resolved
text-decoration-color(orcurrentcolor) andtext-decoration-style, after the text glyphs but conceptually as part of the same paint pass, so it always appears attached to that text.
Common Mistakes
Mistake 1: Stripping underlines from every link on the page
a {\n text-decoration: none;\n}
This is syntactically valid CSS, but it’s a well-known accessibility and usability problem: the underline is often the only non-color signal that a piece of inline text is a link, and users who can’t distinguish link-colored text from body text by color alone (including many colorblind users) lose that cue entirely. Removing it globally, with no other visual affordance, makes links harder to find inside paragraphs of text.
p a {\n text-decoration: underline;\n text-decoration-color: currentcolor;\n}\n\nnav a,\n.button-link {\n text-decoration: none;\n}
The corrected version keeps underlines on links that appear inside body copy, where they’re doing real work identifying clickable text, while allowing decoration to be removed deliberately on navigation and button-styled links, which already have other visual affordances like spacing, background, or position.
Mistake 2: Combining multiple decoration lines with a comma instead of a space
.badge {\n text-decoration-line: underline, overline;\n}
The text-decoration-line property accepts multiple line keywords in a single value, but they must be separated by spaces, not commas — a comma creates a completely different kind of value (a comma-separated list, as used in properties like background or transition) and the declaration won’t apply the way you expect.
.badge {\n text-decoration-line: underline overline;\n}
With a space instead of a comma, both lines are combined into one decoration and both an underline and an overline are drawn around the text.
Mistake 3: A missing semicolon silently corrupting the next declaration
.link {\n text-decoration: underline\n color: red;\n}
Forgetting the semicolon after underline means the parser doesn’t know the declaration has ended, so color: red gets swallowed into the value of text-decoration instead of being read as its own separate color declaration — the rule becomes malformed and neither property behaves as intended.
.link {\n text-decoration: underline;\n color: red;\n}
Adding the semicolon restores two clean, independent declarations: the link is underlined and colored red as separate, correctly parsed rules.
Best Practices
- Keep underlines on inline links within body text; reserve
text-decoration: none;for nav bars, buttons, and card-style links that have other visual cues. - Set
text-decoration-colorexplicitly whenever you want the line to differ from the text color, rather than relying oncurrentcolorand fighting it later. - Use
text-underline-offsetto push underlines away from descenders, especially with thicker orwavy/dottedstyles, which look cramped hugging the baseline. - Prefer
text-decoration-thickness: from-fontwhen working with fonts that ship intentional underline metrics, instead of guessing a pixel value. - Animate the individual longhands (
text-decoration-color,text-decoration-thickness) rather than thetext-decorationshorthand for more reliable transitions. - Remember decoration lines painted by a parent element bleed into unstyled descendants; if a nested <span> needs no line, give it its own
text-decoration: none;rather than assuming the parent’s line stops there automatically. - Leave
text-decoration-skip-inkat its defaultautovalue; it already produces cleaner underlines around descenders in every modern browser.
Practice Exercises
- Style a \”Read more\” link so it has no underline at rest, then gains a 2px, offset, wavy underline in an accent color on
:hoverand:focus-visible. - Style a <blockquote> citation line with an overline only (no underline), in a light gray color, positioned using
text-decoration-line: overline;alongside a custom text color. - Build a \”completed to-do item\” style: line-through text in a muted gray color with reduced opacity, making sure any inherited underline from a parent list container is turned off for that item.
Summary
text-decorationis shorthand fortext-decoration-line,text-decoration-style,text-decoration-color, andtext-decoration-thickness.- Decoration lines are painted across the box of the element that declared them and visually continue under nested descendants unless those descendants set their own decoration.
text-decoration-colordefaults tocurrentcolor, so lines follow text color unless overridden.text-underline-offsetandtext-decoration-skip-inkare separate properties that fine-tune underline position and how lines break around descenders.- Multiple line values in
text-decoration-linemust be space-separated, not comma-separated. - Removing underlines from in-content links without another visual cue harms usability and accessibility.
- Animate the longhand decoration properties individually for smoother hover/focus transitions.
