Text Effects

CSS · Lesson 14 of 55 8 min read Updated

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.
HTMLTry it yourself
<!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.

CSS
.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:

CSS
.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.

HTMLTry it yourself
<!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.

ValueLines runNew lines go
horizontal-tbLeft to right (default)Downwards
vertical-rlTop to bottomRight to left
vertical-lrTop to bottomLeft to right
sideways-lrBottom to top, letters lying sidewaysLeft 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.

HTMLTry it yourself
<!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:

CSS
.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 &shy; in HTML. The hyphen appears only if the word is actually split.
  • auto lets the browser choose break points. It needs the right lang attribute, such as <html lang="en">, because rules differ by language, and support varies between languages and browsers.
  • none never 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 normal color too.
  • 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-clamp needs display: -webkit-box and -webkit-box-orient: vertical.
  • Rotating text with transform for vertical labels. Use writing-mode so the layout makes room.
  • hyphens: auto with no lang attribute. The browser does not know which hyphenation rules to use.

Key points

  • text-shadow accepts a comma-separated list; the first shadow is on top. Use it for glows, outlines and depth on short text.
  • -webkit-text-stroke outlines letters. It is non-standard but supported in all major browsers; add paint-order: stroke fill to keep the stroke outside the fill.
  • One-line ellipsis needs nowrap, overflow: hidden and text-overflow: ellipsis, plus min-width: 0 inside flex rows.
  • Multi-line clamping uses display: -webkit-box, -webkit-box-orient: vertical and -webkit-line-clamp.
  • writing-mode turns text vertical and changes the layout; text-orientation: upright stands letters up.
  • hyphens: auto splits long words using the page's lang; &shy; 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
HTML
<!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?

2. What is true about -webkit-text-stroke?

3. A one-line ellipsis inside a flex row does not appear and the row overflows. What is the usual fix?

4. Why is writing-mode: vertical-rl better than transform: rotate(-90deg) for a vertical label?

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