To truncate text on a single line with a CSS ellipsis, you need three properties together plus a width constraint:
.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: nowrapstops 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: hiddenclips whatever spills past the box.text-overflowonly applies to a block container element whose inline content is being clipped byoverflow. Leave it at the defaultvisibleand the text runs straight past the edge, dots and all.text-overflow: ellipsisis the only one of the three that draws the…. The default isclip, 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:
.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:
.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:
.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:
.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.
rtlreorders neutral characters. Punctuation,+, and symbols can jump around because the bidirectional algorithm reorders them. Wrap the value inunicode-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
- Generated content with the content property and attr(): inject the text you are truncating from a data attribute.
- Vertical text in CSS with writing-mode and text-orientation: the layout side when a truncated label sits in a tight vertical space.
- Styling a parent on its children with the :has() selector: drive truncation from sibling or descendant state, no JavaScript needed.
Sources
Authoritative references this article was fact-checked against.
- text-overflow (MDN Web Docs)developer.mozilla.org
- -webkit-line-clamp (MDN Web Docs)developer.mozilla.org
- line-clamp (MDN Web Docs)developer.mozilla.org





