Text Effects
Plain, readable text is the base of every good page. Sometimes, though, you need a little more: a glowing title on a festival poster, outlined letters on a sports banner, a chat preview cut neatly at one line, a card summary that stops after three lines, or a label that runs up the side of a table.
In this lesson you will build those effects with text-shadow, the text stroke, text-overflow, line clamping, writing-mode and hyphens. The basics of line height, alignment and underlines are in Styling Text, and fonts are in Fonts & Web Fonts.
Layered text shadows
You met text-shadow in Borders, Radius & Shadows: an x offset, a y offset, an optional blur and a colour. The real power comes from listing several shadows, separated by commas. The first shadow in the list is drawn on top.
- Glow: no offset, a large blur, a bright colour. Stack two glows for a stronger effect.
- Outline: four sharp shadows of 1px or 2px, one in each direction.
- Raised or pressed letters: one light shadow and one dark shadow on opposite sides.
- Long shadow: several sharp shadows, each moved one more pixel.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Text shadows</title>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 1.5rem; background: #1e1b4b; color: white; }
h1 { font-size: 3rem; margin: 0 0 1.25rem; }
.glow {
color: #fef9c3;
text-shadow: 0 0 8px #facc15, 0 0 24px #f97316;
}
.outline {
color: #1e1b4b;
text-shadow: 2px 0 #fbbf24, -2px 0 #fbbf24, 0 2px #fbbf24, 0 -2px #fbbf24;
}
.long {
color: #f472b6;
text-shadow: 1px 1px #9d174d, 2px 2px #9d174d, 3px 3px #9d174d, 4px 4px #9d174d;
}
.pressed {
color: #312e81;
background: #a5b4fc;
padding: 0.25rem 0.75rem;
text-shadow: 0 1px 0 rgb(255 255 255 / 60%), 0 -1px 0 rgb(0 0 0 / 30%);
}
</style>
</head>
<body>
<h1 class="glow">Diwali Mela</h1>
<h1 class="outline">Rohtak Cricket Club</h1>
<h1 class="long">Chai Break</h1>
<h1 class="pressed">Class 10 Results</h1>
</body>
</html>The four-shadow outline is an old trick that works everywhere, but it gets uneven and lumpy with thick outlines or curved letters. For proper outlines, use the text stroke below.
Keep text shadows for headings and short labels. A blurry shadow behind every paragraph makes body text harder to read, not easier.
Outlined text with text stroke
-webkit-text-stroke draws a line along the edge of every letter. It is a shorthand for -webkit-text-stroke-width and -webkit-text-stroke-color, and -webkit-text-fill-color sets the inside colour.
.hollow {
color: #0f172a; /* fallback if the stroke is not drawn */
-webkit-text-fill-color: transparent; /* empty letters */
-webkit-text-stroke: 2px #0f172a;
}
.sticker {
color: white;
-webkit-text-stroke: 6px #be123c;
paint-order: stroke fill; /* draw the stroke first, behind the fill */
}This property is non-standard: it started as a WebKit (Safari) feature and is not part of any CSS specification module. It is, however, written down in the WHATWG Compatibility Standard, a document that records old features browsers must support for the web to keep working, and Chrome, Edge, Firefox and Safari all support it with the -webkit- prefix. It is safe to use, but always give the text a normal color too.
The stroke is centred on the edge of each letter, so half of it covers the inside of the letter. Thick strokes make thin letters look squashed. paint-order: stroke fill paints the stroke first and the fill on top, so only the outer half shows. That gives clean "sticker" lettering, and it works in current versions of all major browsers.
Cutting text to one line
A one-line ellipsis needs three properties together: white-space: nowrap, overflow: hidden and text-overflow: ellipsis (see Styling Text). Inside a flex row, there is one more trap. A flex item will not shrink below the width of its content by default, and with nowrap the content is the whole sentence, so the ellipsis never appears and the row bursts out of its box. Add min-width: 0 to the flex item that holds the text.
Clamping to several lines
To show at most three lines and end with "...", use the line-clamp pattern:
.summary {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}The prefixes look old, but this exact combination is now written into the CSS specification and works in all major browsers. A standard, unprefixed line-clamp property is also specified, but at the time of writing it is not yet available in stable browsers, so use the pattern above.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ellipsis and line clamp</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; padding: 1rem; color: #1f2937; }
.chats { list-style: none; padding: 0; margin: 0 0 1.5rem; max-width: 20rem; border: 1px solid #d1d5db; border-radius: 10px; }
.chats li { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 0.75rem; }
.chats li + li { border-top: 1px solid #e5e7eb; }
.initial { flex: none; width: 2.5rem; height: 2.5rem; border-radius: 50%; display: grid; place-content: center; background: #ccfbf1; font-weight: 700; }
.text { flex: 1; min-width: 0; } /* delete this line to see the problem */
.text p { margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #6b7280; }
.card { max-width: 20rem; padding: 1rem; border: 1px solid #d1d5db; border-radius: 10px; }
.card h2 { margin: 0 0 0.4rem; font-size: 1.1rem; }
.summary {
margin: 0;
line-height: 1.5;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
</style>
</head>
<body>
<ul class="chats">
<li><span class="initial">A</span><div class="text"><strong>Ananya</strong><p>Did you book the Shatabdi tickets to Lucknow for Friday evening, or should I do it?</p></div></li>
<li><span class="initial">K</span><div class="text"><strong>Kabir</strong><p>Practice is at 6 tomorrow, bring the new bat and the spare gloves.</p></div></li>
</ul>
<article class="card">
<h2>A bakery worth the queue</h2>
<p class="summary">Isha reached the old bakery in Pune's Camp area at seven in the morning and still found a queue outside. The shrewsbury biscuits were warm, buttery and gone within minutes. She also tried the mawa cake, which was soft and not too sweet, and a plain bun that tasted better than any cake she had eaten that year.</p>
<a href="#bakery">Read the full review</a>
</article>
</body>
</html>Each chat preview stops at one line with an ellipsis. Delete min-width: 0 and run it again: the long messages push the list wider than its box. The card summary stops after exactly three lines. Hidden text is still in the page, so screen readers read all of it, but sighted users need a link like "Read the full review" to see the rest.
Vertical text with writing-mode
writing-mode decides which way lines of text run. It changes the layout, not just the drawing: a vertical element really takes up a tall, narrow space.
| Value | Lines run | New lines go |
|---|---|---|
horizontal-tb | Left to right (default) | Downwards |
vertical-rl | Top to bottom | Right to left |
vertical-lr | Top to bottom | Left to right |
sideways-lr | Bottom to top, letters lying sideways | Left to right |
vertical-rl is used for Chinese and Japanese. For English it is handy for book spines, narrow table headings and side labels. Latin letters lie on their side by default; text-orientation: upright stands each letter up instead, which suits very short words. sideways-lr and sideways-rl are newer values that now work in current versions of all major browsers.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>writing-mode</title>
<style>
body { font-family: system-ui, sans-serif; padding: 1rem; }
.shelf { display: flex; gap: 4px; align-items: end; padding: 0 0.5rem; border-bottom: 10px solid #78350f; width: fit-content; }
.book { writing-mode: vertical-rl; height: 11rem; padding: 0.6rem 0.4rem; color: white; font-weight: 600; border-radius: 3px 3px 0 0; }
.b1 { background: #b91c1c; }
.b2 { background: #1d4ed8; height: 9rem; }
.b3 { background: #047857; writing-mode: sideways-lr; }
.b4 { background: #7c3aed; text-orientation: upright; letter-spacing: 2px; height: 8rem; }
</style>
</head>
<body>
<div class="shelf">
<div class="book b1">Class 10 Mathematics</div>
<div class="book b2">Hindi Vyakaran</div>
<div class="book b3">Atlas of India</div>
<div class="book b4">GK</div>
</div>
</body>
</html>The first two spines read from top to bottom, the third reads from bottom to top, and "GK" has its letters standing upright. The shelf grows to fit the books because the vertical text takes real space in the layout. Logical properties such as padding-block follow the writing mode; see Logical Properties.
Hyphens
In narrow columns, long words leave ragged gaps or overflow. hyphens lets the browser split words across lines with a hyphen:
.column {
hyphens: auto; /* browser adds hyphens using the language's rules */
}
.product-code {
hyphens: none; /* never split these */
}manual(the default) splits words only where you placed a soft hyphen, written­in HTML. The hyphen appears only if the word is actually split.autolets the browser choose break points. It needs the rightlangattribute, such as<html lang="en">, because rules differ by language, and support varies between languages and browsers.nonenever hyphenates, even at soft hyphens.
hyphenate-character changes the character shown at the break, if a design or language needs something other than a hyphen.
Common mistakes
- Shadows on body text. Blur behind paragraphs reduces readability. Use shadows on headings and labels.
- Text stroke without a fallback colour. If you set the fill to
transparent, always set a normalcolortoo. - Ellipsis in a flex row without
min-width: 0. The text item refuses to shrink, so the ellipsis never appears. - Missing parts of the line-clamp pattern.
-webkit-line-clampneedsdisplay: -webkit-boxand-webkit-box-orient: vertical. - Rotating text with
transformfor vertical labels. Usewriting-modeso the layout makes room. hyphens: autowith nolangattribute. The browser does not know which hyphenation rules to use.
Key points
text-shadowaccepts a comma-separated list; the first shadow is on top. Use it for glows, outlines and depth on short text.-webkit-text-strokeoutlines letters. It is non-standard but supported in all major browsers; addpaint-order: stroke fillto keep the stroke outside the fill.- One-line ellipsis needs
nowrap,overflow: hiddenandtext-overflow: ellipsis, plusmin-width: 0inside flex rows. - Multi-line clamping uses
display: -webkit-box,-webkit-box-orient: verticaland-webkit-line-clamp. writing-modeturns text vertical and changes the layout;text-orientation: uprightstands letters up.hyphens: autosplits long words using the page'slang;­marks manual break points.
Exercise
Make a match card for a school cricket final in Jaipur:
- a title "Final Day" with an orange glow made from two text shadows;
- the team name "Pink City XI" in white letters with a thick dark stroke drawn behind the fill;
- a match report paragraph clamped to two lines;
- a vertical "LIVE" label on the left side of the card using
writing-mode.
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Final Day</title>
<style>
body { font-family: system-ui, sans-serif; padding: 1rem; }
.match { display: flex; max-width: 24rem; background: #172554; color: white; border-radius: 12px; overflow: hidden; }
.live { writing-mode: vertical-rl; background: #dc2626; padding: 0.75rem 0.4rem; font-weight: 700; letter-spacing: 0.2em; }
.body { padding: 1rem; min-width: 0; }
h1 { margin: 0; color: #fff7ed; text-shadow: 0 0 6px #fb923c, 0 0 18px #ea580c; }
.team { font-size: 1.8rem; font-weight: 800; color: white; -webkit-text-stroke: 5px #0f172a; paint-order: stroke fill; }
.report { margin: 0; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
</style>
</head>
<body>
<article class="match">
<div class="live">LIVE</div>
<div class="body">
<h1>Final Day</h1>
<p class="team">Pink City XI</p>
<p class="report">Chasing 164 in 20 overs, Pink City XI lost two early wickets before Farhan and Isha built a calm partnership of 78 runs, taking the game deep into the final over at the Sawai Mansingh ground.</p>
</div>
</article>
</body>
</html>The label is vertical because of writing-mode, so the flex row automatically makes room for it. paint-order: stroke fill keeps the thick stroke behind the white letters.
Quick Quiz
1. In text-shadow: 0 0 4px red, 0 0 12px blue; which shadow is drawn on top?
With several shadows, the first in the list is painted on top of the later ones.
2. What is true about -webkit-text-stroke?
It began as a WebKit feature, is recorded in the Compatibility Standard and works in Chrome, Edge, Firefox and Safari.
3. A one-line ellipsis inside a flex row does not appear and the row overflows. What is the usual fix?
Flex items do not shrink below their content width by default; min-width: 0 allows it.
4. Why is writing-mode: vertical-rl better than transform: rotate(-90deg) for a vertical label?
A transform only rotates the drawing, while writing-mode changes how the box is laid out.
Learn CSS with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
