Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. Truncate text with an ellipsis in CSS, one line or many
Web Development

Truncate text with an ellipsis in CSS, one line or many

Truncating with text-overflow ellipsis in CSS needs three properties, not one. Drop white-space nowrap and text wraps instead of truncating.

By Max Arthur
Co-Founder & Content Marketer·September 13, 2026·10 Min read
White text reading 'How to truncate text with an ellipsis in CSS' on a dark purple dotted background

Truncating one line of text with an ellipsis takes three properties, and all three are required:

.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

text-overflow gets the credit and does the least. It only decides what marker to draw; the other two create the conditions where a marker is needed at all.

Here is what each omission does, measured on the same 150px box containing "the quick brown fox jumps over the lazy dog" at 14px monospace.

What each missing property costs

Setup Rendered height Result
All three 17px One line, ellipsis
No white-space: nowrap 51px Wraps to three lines
No overflow: hidden 17px Text spills out of the box
No text-overflow: ellipsis 17px Clipped hard, no ellipsis

The second row is the one to look at. Without nowrap the height tripled: the text wrapped instead of truncating, so there was never any overflow for text-overflow to act on. Measured, scrollWidth equalled clientWidth at 150px, meaning nothing overflowed at all.

That is why "I added text-overflow: ellipsis and nothing happened" is nearly always a missing white-space: nowrap. The property is working; there is simply nothing to truncate.

The third row spills visibly, and the fourth clips mid-character with no indication that anything is missing. Each failure looks different, which makes them easy to diagnose once you know the table.

Why three properties

Following the chain makes it memorable:

  1. white-space: nowrap stops the text wrapping, so a long string becomes one very wide line.
  2. That line is now wider than its box, so overflow: hidden decides what to do about the part outside.
  3. text-overflow: ellipsis replaces the clipped edge with … instead of a hard cut.

Remove any link and the chain breaks earlier than the ellipsis.

The box needs a width

A fourth requirement that is easy to miss: the element must have a constrained width, or there is nothing to overflow.

Block elements get this for free, since they fill their parent. Two cases do not:

Inline elements are sized by their content, so text-overflow never applies. display: inline-block or block fixes it, as covered in the display property.

Flex and grid children size to their content by default, and a long string makes the item wider than its track rather than truncating. The fix is the one CSS rule nobody guesses:

.flex-child {
  min-width: 0;
}

A flex item's min-width defaults to auto, which means "at least as wide as my content". That default overrides the container's constraint, so the item refuses to shrink and the text never truncates. Setting min-width: 0 lets it shrink and the three properties start working.

In grid, the equivalent is min-width: 0 on the item or minmax(0, 1fr) on the track. This is the most common reason truncation works in isolation and fails inside a real layout.

Multiple lines

For a fixed number of lines, use line clamping:

.clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

Measured, that rendered at 34px, exactly two 17px lines, with the rest replaced by an ellipsis.

The -webkit- prefixes look like a hack and are not: this is the one prefixed feature that became a de facto standard, works in every current browser, and is specified. The unprefixed line-clamp is arriving, and writing both is the safe approach:

.clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

Note that display: -webkit-box replaces whatever display the element had, so a flex or grid container cannot also be a clamp container. Wrap it in an extra element when you need both.

There is no white-space here and no text-overflow. Clamping is a different mechanism, and mixing in nowrap breaks it, because the text has to be allowed to wrap in order to produce the lines you are counting.

Truncating in the middle, and from the start

text-overflow truncates the end. Two variations come up.

From the start, useful for file paths where the end matters more:

.truncate-start {
  direction: rtl;
  text-align: left;
}

That flips which end is clipped. It also reorders punctuation at the edges of the string in ways that can look wrong, so test it with real values.

In the middle, for something like verylongfilename….pdf, has no CSS solution. It needs JavaScript to split the string and measure, and the usual approach is two spans with the first truncated and the second holding the last few characters.

Truncating versus breaking

Truncation is one answer to text that is too long. Often the right answer is to let it wrap differently instead, and the three properties that control that are frequently confused.

overflow-wrap: break-word;   /* break a long word only if it would overflow */
word-break: break-all;       /* break anywhere, even mid-word, always */
hyphens: auto;               /* break at syllables, with a hyphen */

overflow-wrap: break-word is the one you almost always want. It leaves normal text alone and only intervenes when a single unbreakable string, such as a URL or a long token, would push past the box.

word-break: break-all breaks anywhere regardless, which makes ordinary prose hard to read. It is right for displaying hashes or codes and wrong for sentences.

hyphens: auto needs the language declared on the document (<html lang="en">) so the browser knows the syllable rules. With it, justified or narrow text reads noticeably better.

The decision is straightforward: truncate when the value is an identifier the user does not need in full (a file name in a list, a title in a table row), and wrap when the content is meant to be read. Truncating a paragraph is almost always the wrong call.

In tables

Table cells resist truncation for the same reason flex items do: they size to their content by default.

table { table-layout: fixed; width: 100%; }
td {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

table-layout: fixed is the missing piece. Without it the browser uses automatic layout, which sizes columns to fit their content and therefore widens the table rather than truncating anything. With it, column widths come from the first row and the cells are constrained, so the three truncation properties can act.

This is the standard pattern for a data table with a long description column, and it is why the same CSS that works on a <div> appears to do nothing inside a <td>.

A reusable utility

Worth defining once rather than repeating three properties:

.truncate {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.clamp-2,
.clamp-3 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.clamp-2 { -webkit-line-clamp: 2; line-clamp: 2; }
.clamp-3 { -webkit-line-clamp: 3; line-clamp: 3; }

Including min-width: 0 in the utility itself is the detail that saves time later, because it makes the class work inside a flex layout without the caller having to know about the flex minimum size rule.

If you use a utility framework, these already exist as truncate and line-clamp-2, built the same way.

Accessibility, and what truncation hides

Truncated text is still in the DOM, so screen readers announce the whole thing. That is usually right.

Two problems remain.

A sighted user cannot read it. If the full value matters, give them a way to see it:

<span class="truncate" title="The full text here">The full text here</span>

A title gives a native tooltip on hover, though not on touch and not on keyboard focus, so it is a partial answer rather than a complete one. For anything important, a real tooltip or an expand control is better.

Do not truncate with JavaScript by cutting the string. Replacing the text with a shortened version removes it from the accessibility tree as well as from the screen, and it cannot adapt when the box resizes. CSS truncation keeps the content intact and re-evaluates on every resize for free. This is the strongest argument for the CSS approach over any JavaScript alternative.

Detecting whether text is truncated

There is no CSS selector for "is currently overflowing", so showing a tooltip only when needed takes a comparison:

const isTruncated = el.scrollWidth > el.clientWidth;

scrollWidth is the full content width, clientWidth is the visible box. Measured on the working example, those were 331 and 150, so the check returns true. On the wrapping version both were 150, correctly returning false.

For the multi-line clamp, compare heights instead:

const isClamped = el.scrollHeight > el.clientHeight;

Run either after fonts have loaded and on resize, since both can change the answer.

When not to truncate at all

Truncation is a way of fitting content into a space that is too small, and it is worth asking whether the space is the problem.

A title in a card rarely needs truncating if the card can grow. Clamping to two lines is a design decision about grid alignment, not a technical necessity, and align-items: start on the grid often looks better than clamping every card to the shortest one.

A navigation label that does not fit is usually too long rather than badly sized. Shortening the label is better than showing "Account settin…".

A table column can often be given more room by reducing another one, or by moving to a card layout on narrow screens rather than truncating six columns into unreadability.

The cases where truncation genuinely wins are ones where the content is unbounded and the layout must stay predictable: user-supplied file names, email subject lines, arbitrary URLs, a chat preview. In each of those the full value can be any length and the row height must not depend on it.

The test worth applying: if truncating hides information the user needs to make a decision, the layout is wrong, not the text.

Three common mistakes

Adding text-overflow: ellipsis alone. Without nowrap the text wraps and nothing ever overflows, so the property has nothing to do.

Truncating inside a flex item without min-width: 0. The item refuses to shrink below its content, so the text pushes the layout wide instead of truncating.

Cutting the string in JavaScript. It loses the full value for assistive technology and does not respond to resizing. Let CSS do it.

Where the ellipsis goes

A detail that surprises people the first time they truncate a right-to-left language or a value with trailing punctuation.

text-overflow: ellipsis clips at the end of the line in the writing direction, not at the physical right edge. In a right-to-left document the ellipsis appears on the left, which is correct and looks wrong if you were expecting a fixed side.

The property also accepts two values, one per edge:

text-overflow: ellipsis ellipsis;   /* both ends, when both overflow */

And it accepts a custom string in some engines:

text-overflow: " [more]";

Support for the string form is uneven, so treat it as an enhancement rather than something to rely on. The two-value form matters mainly for scrollable single-line fields, where content can overflow at either end.

Quick reference

/* one line */
.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* several lines */
.clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

/* inside flex or grid */
.item { min-width: 0; }
el.scrollWidth > el.clientWidth;    /* is it truncated? */

Want to see each property removed one at a time? Start with the CSS track, or read CSS overflow for the property doing the clipping.

Keep reading

More from the blog

White text reading 'Why your z-index is not working' on a dark purple dotted background
September 17, 2026·7 min readWeb Development

z-index not working? It is the parent, not the number

A child with z-index 9999 loses to a sibling with z-index 1 when the parent has opacity. Same markup without it, and 9999 wins.

Read more
White text reading 'CSS variables explained' on a dark purple dotted background
September 16, 2026·10 min readWeb Development

CSS variables in practice: scope, fallbacks and theming

Custom properties inherit and cascade, which is what separates them from Sass variables. An invalid value falls back further than you expect.

Read more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentReact DeveloperPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs Mimo
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs Sololearn
Devpuff vs freeCodeCamp
Read All Comparisons