TechEarl

Truncate Text with a CSS Ellipsis (Single Line and Multiline)

Add a CSS ellipsis to truncate text on one line with text-overflow, clamp to a fixed number of lines with line-clamp, and put the dots at the start instead of the end.

Ishan Karunaratne⏱️ 7 min readUpdated
Share thisCopied
Truncate text with a CSS ellipsis: the three properties single-line truncation needs, multiline line-clamp, and a start-of-string ellipsis with direction.

To truncate text on a single line with a CSS ellipsis, you need three properties together plus a width constraint:

css
.truncate {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  max-width: 100%; /* or any fixed width the box can overflow */
}

Miss any one of the three and the never shows up. That is the single most common mistake with this technique, so it is worth understanding why each line is load-bearing before we get to clamping multiple lines or moving the dots to the front of the string.

Why all three properties are required

text-overflow: ellipsis on its own does nothing. It only describes what to render when text is clipped at the edge of its box, so the other two properties have to actually create that clip first:

  • white-space: nowrap stops the text wrapping to a second line. Without it, the text just flows down and there is no horizontal overflow to clip, so no ellipsis.
  • overflow: hidden clips whatever spills past the box. text-overflow only applies to a block container element whose inline content is being clipped by overflow. Leave it at the default visible and the text runs straight past the edge, dots and all.
  • text-overflow: ellipsis is the only one of the three that draws the . The default is clip, which chops the text mid-glyph with no marker.

And then the width. There has to be a defined boundary for the text to overflow. A block element fills its container, so max-width (or width) on the element, or simply a parent narrower than the content, gives the overflow something to happen against. On a flex child, the classic gotcha is that flex items default to min-width: auto and refuse to shrink below their content, so the ellipsis never triggers. Add min-width: 0 to the flex item:

css
.flex-child {
  min-width: 0; /* lets a flex item shrink below its content so it can truncate */
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

Truncate to multiple lines with line-clamp

Single-line truncation is solved. For a card description that should show, say, three lines and then trail off, you want a line clamp. The reliable cross-browser recipe is the WebKit one, and it is a fixed three-property incantation:

css
.clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; /* number of lines to keep */
  overflow: hidden;
}

Notice white-space: nowrap is gone here. Multiline clamping needs the text to wrap normally; the display: -webkit-box plus -webkit-box-orient: vertical plus -webkit-line-clamp trio is what limits it to N lines and appends the ellipsis. All three are mandatory together, the same all-or-nothing rule as the single-line case. Despite the -webkit- prefix, this works in Chrome, Edge, Firefox, and Safari, so it is safe to ship today.

The newer, unprefixed line-clamp property is defined in the CSS Overflow specification and has started shipping in Chromium, but it is not yet supported everywhere, so it is not Baseline as of mid-2026. Treat it as progressive enhancement, not a replacement:

css
.clamp-modern {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3; /* the standard property, for browsers that have it */
  overflow: hidden;
}

Keep the -webkit- lines as the fallback and let line-clamp take over where it exists. Either way, set overflow: hidden, and if your text has explicit line breaks you may need a fixed max-height as a backstop.

Put the ellipsis at the start instead of the end

Sometimes you want the end of a string visible and the start cut off, like a file path where the filename matters more than the directory (…/project/src/index.js). There is no dedicated property for this, but you can flip the truncation direction with direction: rtl:

css
.truncate-start {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  direction: rtl;
  text-align: left;
}

direction: rtl makes the overflow happen on the left edge, so the ellipsis lands at the start; text-align: left keeps the text reading left-aligned. It works, but two real caveats:

  • It is broken in Safari, which has historically truncated the wrong end with this trick. Test there before relying on it.
  • rtl reorders neutral characters. Punctuation, +, and symbols can jump around because the bidirectional algorithm reorders them. Wrap the value in unicode-bidi: plaintext (or insert a left-to-right mark) to keep the visual order sane.

Because of the Safari bug, for anything load-bearing I truncate the start in JavaScript (measure the width, slice the string, prepend ) rather than fight the RTL hack. The CSS approach is fine for a forgiving, decorative case.

Where to put it in a layout

A truncated label often sits next to other CSS that controls its box. If you are positioning the label vertically against an icon, vertical centering with writing-mode and friends covers the layout side. To inject the truncated string from data rather than markup, generated content with the content property and attr() pairs well with this. And when the truncation depends on sibling state (a hovered row, an open panel), styling on a parent with the :has() selector is the modern way to drive it without JavaScript.

See also

Sources

Authoritative references this article was fact-checked against.

TagsCSStext-overflowellipsisline-clamptruncate textwhite-spaceoverflow

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

Stream a fetch() Response in JavaScript (NDJSON, line-by-line)

Read a fetch() response as it arrives instead of buffering the whole body. Async-iterate response.body, decode with TextDecoderStream, and split NDJSON line by line with a dependency-free TransformStream. The same pattern that consumes SSE and streaming LLM token responses.