CSS provides fine-grained control over how text is decorated (underlines, strikethroughs, highlights) and transformed (uppercase, lowercase, capitalised). These properties are often underestimated — a precisely styled underline can be a significant branding element, and text-transform is the correct way to enforce visual casing without changing the underlying text. In this lesson you will master every decoration and transformation property, including the modern underline styling API.
Text Decoration Properties
| Property | Controls | Example Values |
|---|---|---|
text-decoration-line |
Which line(s) to draw | underline, overline, line-through, none |
text-decoration-color |
Line colour (independent of text colour) | #4f46e5, currentColor, transparent |
text-decoration-style |
Line style | solid, dotted, dashed, wavy, double |
text-decoration-thickness |
Line stroke width | 2px, 0.1em, from-font |
text-underline-offset |
Gap between text baseline and underline | 3px, 0.2em — positive moves line down |
text-decoration |
Shorthand for line, style, color, thickness | underline dotted #4f46e5 2px |
text-transform Values
| Value | Effect | Best Used For |
|---|---|---|
uppercase |
ALL CAPS regardless of HTML | Labels, badges, navigation items, section headings |
lowercase |
all lowercase | Email display, code-like styling |
capitalize |
First Letter Of Each Word | Titles — prefer real title case in HTML for accessibility |
none |
Original casing restored | Reset inherited transform |
text-shadow
| Syntax | Parameters | Common Use |
|---|---|---|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3) |
x-offset y-offset blur colour | Contrast on image text |
text-shadow: 0 0 20px rgba(79,70,229,0.5) |
No offset — glow effect | Neon / glow text effects |
text-shadow: 1px 1px 0 #fff |
Hard shadow, no blur | Emboss / deboss effects |
| Multiple shadows | Comma-separated list | Stack multiple effects |
text-transform: uppercase changes only the visual rendering — the underlying HTML text is unchanged. Screen readers read the original casing. This is the correct approach: use CSS for visual presentation, keep the HTML semantically correct. Never type text in ALL CAPS in your HTML just to make it appear uppercase visually.text-underline-offset and text-decoration-color: transparent with a hover transition for elegant link underline reveal effects — the underline is always present (preserving layout stability) but only visible on hover. This avoids the layout shift caused by adding/removing underlines that affect text flow.text-decoration: none harms accessibility — links must be visually distinguishable from regular text by colour alone, which fails WCAG colour contrast requirements for users with colour-blindness. Always provide a non-colour visual indicator (underline, bold, border) for links within body text.Basic Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<title>Text Decoration and Transformation</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: 100%; }
body { font-family: 'Inter', system-ui, sans-serif; padding: 40px 32px; background: #f8fafc; color: #1e293b; max-width: 780px; margin: 0 auto; }
h3 { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: #64748b; margin: 32px 0 12px; }
.demo-row { display: flex; flex-wrap: wrap; gap: 24px; margin-bottom: 12px; align-items: center; }
/* ── Custom link underlines ── */
.link-default { color: #4f46e5; }
.link-custom {
color: #4f46e5;
text-decoration-line: underline;
text-decoration-color: #a5b4fc;
text-decoration-thickness: 2px;
text-underline-offset: 4px;
transition: text-decoration-color 0.2s;
}
.link-custom:hover { text-decoration-color: #4f46e5; }
.link-wavy {
color: #0f172a;
text-decoration-line: underline;
text-decoration-style: wavy;
text-decoration-color: #ef4444;
text-underline-offset: 5px;
}
.link-animated {
color: #0f172a;
text-decoration-line: underline;
text-decoration-color: transparent;
text-decoration-thickness: 2px;
text-underline-offset: 4px;
transition: text-decoration-color 0.2s ease;
}
.link-animated:hover { text-decoration-color: #4f46e5; }
/* ── Strikethrough and overline ── */
.strike { text-decoration: line-through; text-decoration-color: #ef4444; text-decoration-thickness: 2px; }
.overln { text-decoration: overline; text-decoration-color: #4f46e5; text-decoration-thickness: 2px; }
/* ── text-transform ── */
.tf-upper { text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700; font-size: 0.8rem; color: #4f46e5; }
.tf-lower { text-transform: lowercase; font-size: 1rem; }
.tf-cap { text-transform: capitalize; font-size: 1rem; }
/* ── text-shadow ── */
.shadow-soft { font-size: 1.5rem; font-weight: 700; text-shadow: 2px 2px 8px rgba(0,0,0,0.2); }
.shadow-glow { font-size: 1.5rem; font-weight: 700; color: #818cf8; text-shadow: 0 0 20px rgba(79,70,229,0.6), 0 0 40px rgba(79,70,229,0.3); background: #1e293b; padding: 8px 16px; border-radius: 8px; }
.shadow-hard { font-size: 1.5rem; font-weight: 700; text-shadow: 3px 3px 0 #4f46e5; }
/* ── Highlight ── */
.highlight {
background: linear-gradient(120deg, #fde68a 0%, #fde68a 100%);
background-repeat: no-repeat;
background-size: 100% 40%;
background-position: 0 85%;
padding-bottom: 2px;
}
</style>
</head>
<body>
<h3>Custom Link Underlines</h3>
<div class="demo-row">
<a href="#" class="link-default">Browser default</a>
<a href="#" class="link-custom">Custom offset + colour (hover me)</a>
<a href="#" class="link-wavy">Wavy red underline</a>
<a href="#" class="link-animated">Fade-in on hover</a>
</div>
<h3>Other Decoration Lines</h3>
<div class="demo-row">
<span class="strike">Was $99.00</span>
<span class="overln">Overline text</span>
</div>
<h3>text-transform</h3>
<div class="demo-row">
<span class="tf-upper">uppercase label</span>
<span class="tf-lower">LOWERCASE TEXT</span>
<span class="tf-cap">capitalize each word</span>
</div>
<h3>text-shadow Variations</h3>
<div class="demo-row">
<span class="shadow-soft">Soft shadow</span>
<span class="shadow-glow">Glow effect</span>
<span class="shadow-hard">Hard offset</span>
</div>
<h3>Gradient Highlight</h3>
<p style="font-size:1.1rem;line-height:1.7">
CSS background gradients can simulate a <span class="highlight">marker highlight effect</span> on inline text without any extra markup.
</p>
</body>
</html>
How It Works
Step 1 — text-underline-offset Separates the Line from Text
text-underline-offset: 4px moves the underline 4px below the text baseline. The default browser underline sits very close to the descenders — letters like g, p, y. An offset of 3–5px gives the underline breathing room and prevents it from cutting through descenders.
Step 2 — text-decoration-color: transparent Enables Hover Reveals
Setting the decoration colour to transparent keeps the underline present in the layout (avoiding text shifting on hover) but visually invisible. A CSS transition on text-decoration-color then smoothly reveals the underline on hover — a modern pattern that replaces the older border-bottom trick.
Step 3 — Wavy Decoration for Spelling Indicators
text-decoration-style: wavy mimics the red squiggly line of spell-checkers. Combined with a red colour and an appropriate text-decoration-line: underline, it creates a familiar visual cue for error states, wrong answers, or flagged content.
Step 4 — text-transform Preserves Semantic Text
The HTML contains “uppercase label” in mixed case. text-transform: uppercase renders it visually as “UPPERCASE LABEL” without changing the DOM. Screen readers read the original “uppercase label” — not shouted text — maintaining correct pronunciation.
Step 5 — Gradient Background Simulates Highlight
The highlight effect uses background: linear-gradient() on an inline span. The gradient is positioned at 85% vertically with a height of 40% — this places the yellow band behind the bottom portion of the text, simulating a marker stroke without any images or extra elements.
Real-World Example: Navigation Link Styles
/* nav-links.css */
.nav { display: flex; gap: 4px; align-items: center; }
/* Base nav link */
.nav-link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
font-size: 0.875rem;
font-weight: 500;
color: #475569;
text-decoration: none; /* remove default browser underline */
border-radius: 8px;
position: relative;
transition: color 0.15s, background 0.15s;
}
/* Hover state */
.nav-link:hover { color: #0f172a; background: #f1f5f9; }
/* Active link — underline indicator */
.nav-link.active {
color: #4f46e5;
font-weight: 600;
}
.nav-link.active::after {
content: '';
position: absolute;
bottom: -1px; left: 14px; right: 14px;
height: 2px;
background: #4f46e5;
border-radius: 2px 2px 0 0;
}
/* Badge on nav item */
.nav-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding: 0 5px;
background: #4f46e5;
color: white;
font-size: 0.65rem;
font-weight: 700;
border-radius: 9999px;
line-height: 1;
}
/* Price tag — strikethrough old price */
.price-old {
text-decoration: line-through;
text-decoration-thickness: 1.5px;
text-decoration-color: #94a3b8;
color: #94a3b8;
font-size: 0.9em;
}
.price-new {
color: #0f172a;
font-weight: 700;
}
Common Mistakes
Mistake 1 — Removing link underlines from body text
❌ Wrong — links cannot be identified by colour-blind users:
a { color: #4f46e5; text-decoration: none; }
/* Fails WCAG 1.4.1 — relies on colour alone to identify links */
✅ Correct — retain underline or add another non-colour indicator:
a {
color: #4f46e5;
text-decoration-line: underline;
text-decoration-color: #a5b4fc;
text-underline-offset: 3px;
}
a:hover { text-decoration-color: #4f46e5; }
Mistake 2 — Using text-transform: capitalize for proper titles
❌ Wrong — capitalize only uppercases the first letter of each word, including articles:
.title { text-transform: capitalize; }
/* "the quick brown fox" becomes "The Quick Brown Fox" — "The" should be lowercase in title case */
✅ Correct — write true title case in HTML; use text-transform for labels only:
<h1>The Quick Brown Fox</h1> <!-- proper title case in markup -->
Mistake 3 — Heavy text-shadow slowing render performance
❌ Wrong — multiple large blur-radius shadows on many elements cause paint jank:
/* Applied to hundreds of list items: */
li { text-shadow: 0 0 30px rgba(0,0,0,0.5), 0 0 60px rgba(0,0,0,0.3), 0 4px 12px rgba(0,0,0,0.4); }
✅ Correct — use text-shadow sparingly on hero/heading elements only:
.hero-title { text-shadow: 0 2px 8px rgba(0,0,0,0.3); }
Quick Reference
| Property | Key Values | Notes |
|---|---|---|
text-decoration-line |
underline, overline, line-through, none | Can combine: underline overline |
text-decoration-color |
Any colour or transparent | transparent enables hover reveal |
text-decoration-style |
solid, dotted, dashed, wavy, double | wavy good for error states |
text-decoration-thickness |
2px, 0.1em, from-font | from-font uses metrics from font data |
text-underline-offset |
3–5px or 0.2em | Separates line from descenders |
text-transform |
uppercase, lowercase, capitalize, none | Visual only — HTML unchanged |
text-shadow |
x y blur colour | Use sparingly — performance cost |