You want X lines of text. Anything after that: gracefully cut off. That’s “line clamping” and it is a perfectly legit desire. When you can count on the text being a certain number of lines, you can create stronger and more reliable grids from the elements that contain that text, as well as achieve some symmetric aesthetic harmony.
There are a couple of ways to get it done, none of them spectacular.
In case that explanation wasn’t clean, imagine you have some HTML like this:
<div class="module">
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>
</div>
And you want to limit it to exactly three lines in a container. Like this:
In all these examples, let’s assume we have a “module”.
.module {
width: 250px;
overflow: hidden;
}
The Standardized Way
I used to call this “the weird WebKit flexbox way”, but in an extra weird twist, the spec now includes this as part of the overflow module, old flexbox and all. And, Firefox implemented it just like that. And with Edge-gone-Chromium, this weird technique has gotten a lot more useful instead of less.
.line-clamp {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
Despite the weird syntax, this is awesome and exactly what we need. Here’s a playground for playing with it:
To be fair, it really is weird. Why does it need to be a flexbox thing (the old version at that)? It doesn’t work without that. And it’s extremely fragile. Let’s say you want the module (or the paragraph) to have some padding. You can’t because the padding will expose extra lines. That’s what we get with sorta half-baked originally-non-standardized properties.
The Fade Out Way
The root of this technique is just setting the height of the module in a predictable way. Let’s say you set the line-height
to 1.2em
. If we want to expose three lines of text, we can just make the height of the container 3.6em
(1.2em × 3). The hidden overflow will hide the rest.
But it can be a bit awkward to just cut the text off like that. Ideally, we would add ellipsis, but we can’t reliably position them. So instead, we’ll fade out the text achieving the same kind of communication (“there is more…”).
To fade out the last line, we make a box (a pseudo-element will work great) and overlay a transparent-to-background-color gradient over the top. Making it nearly as wide as the container is best in case the last line is short. Because we know the line-height
, we can make the pseudo-element box exactly one line tall.
.fade {
position: relative;
height: 3.6em; /* exactly three lines */
}
.fade::after {
content: "";
text-align: right;
position: absolute;
bottom: 0;
right: 0;
width: 70%;
height: 1.2em;
background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 50%);
}
The Opera Overflow Way
Opera as its own rendering engine, is long defunct. Just leaving this in for historical reasons.
Like WebKit, Opera has its own way to handle this. They apply ellipsis on the line you wish to. Of course, the clock is ticking for Presto (Opera’s rendering engine pre-Blink) so this isn’t particularly useful. Perhaps it can still inform a future implementation though.
.last-line {
height: 3.6em; /* exactly three lines */
text-overflow: -o-ellipsis-lastline;
}
The Clamp.js Way
Where there is a will there is a way (with JavaScript). I think that’s a saying. Joseph J. Schmitt has an excellent library-free JavaScript thing called Clamp.js for making this happen in a cross-browser way.
var module = document.getElementById("clamp-this-module");
$clamp(module, {clamp: 3});
Make sure you target the element with the text directly inside of it. Originally I tried putting the ID on the module, which worked in Chrome/Safari, but failed in Firefox. Putting it on the <p>
made it work in both (Thx Merri).
The Hide Overflow & Place Ellipsis Pure CSS Way
The trick is to set a max-height
equal to the maximum number of lines multiplied by the line-height
.
html {
--lh: 1.4rem;
line-height: var(--lh);
}
.truncate-overflow {
--max-lines: 3;
position: relative;
max-height: calc(var(--lh) * var(--max-lines));
overflow: hidden;
padding-right: 1rem; /* space for ellipsis */
}
.truncate-overflow::before {
position: absolute;
content: "...";
inset-block-end: 0; /* "bottom" */
inset-inline-end: 0; /* "right" */
}
.truncate-overflow::after {
content: "";
position: absolute;
inset-inline-end: 0; /* "right" */
width: 1rem;
height: 1rem;
background: white;
}
The rest of that is to place an “…” ellipsis at the end of the lines but only if the text exceeds the maximum lines.
The Demos
Update: More Good Ways!
- There is an exceptionally clever all-CSS way to do this posted on the Mobify blog Update: removed link, dead blog, added in the technique here.
- Vesa Piittinen created an alternative method to Clamp.js.
“Unlike Clamp.js it retains all the text within the clamped element and usestext-overflow
to do the magic.” - FT Labs also has created a JavaScript plugin to do the job. It’s nice in that you don’t have to specify the number of lines, it just happens when the text overflows the container, so the design decision stay in the CSS.
- Succinct: “A tiny jQuery plugin for truncating multiple lines of text.”
These examples have been added to the main Pen.
There is also Shave from DollarShaveClub:
The Clamp.js method actually did not work for me (FF 20). I still think the method I prefer is the gradient overlay. Another element could then be placed over the pseudo-element to further indicate more content (a “Read more” link). That said, without some major hacking for IE, this leaves us still looking for a good cross-browser method.
Matt, I see the same on FF 20.0 on Mac.
Here is a screenshot for those interested: http://i.imgur.com/WJGNQmD.png
Ok looks like it is a known issue: https://github.com/josephschmitt/Clamp.js/issues/3
You can see it in action here if FF20: http://codepen.io/jcummins/pen/nvEmf
Here is an updated screenshot: http://i.imgur.com/Ubv3NLl.png
Selectivizr?
Attempting to achieve the elegant truncation has been a bane on my existence since beginning work in web development. Thanks for pointing this feature out; I will definitely be using it.
I’ve implemented a simple jQuery plugin that allows you to achieve something like this and then some.
No love for text-overflow: ellipsis?
It only works when there’s white-space: nowrap active as well.
text-overflow: ellipsis only works on the first and only line of text, though.
The article’s point is, what can we use to indicate overflow when text wraps on multiple lines.
You can actually use
white-space: pre
and it works for each line.What this means you can write some JavaScript to add two line changes or so and then the last line gets ellipsis.
The difficult part is measuring text in an efficient manner.
I wasn’t aware of pre’s cross-over with overflow, Merri, thanks for the info.
Still, that solution requires JavaScript, and it changes the content itself.
Text you copy-paste will have new-lines in it.
That’s not acceptable for my usual use-case, but other people wouldn’t mind.
The w3c targets this problem in its “CSS Overflow Module Level 3”-working-draft.
Based on this very early spec, we theoretically can do the 3-line clamp like so:
.line-clamp {
overflow: fragments;
}
.line-clamp::nth-fragment(1){
max-lines: 3;
}
.line-clamp::nth-fragment(2){
display:none;
}
Granted that it’s an early spec, but that doesn’t actually help in any way. We’re already capable of truncating text after n lines, our problem is putting the ellipsis at the end of the desired lines.
So we have our fragments, and we’re hiding all of them except the first one. But what do we do with the first fragment?
It looks to me that spec is more about addressing the same problem of CSS Regions. But what we actually need is some :overflowing pseudo-class. Designers are asking for it since, like, 1998…
The fade-out-way has a lil gotcha… what if the text fits exactly in 3 lines? In this case you would NOT want to fade out the end of the third line, since there is no more content following. Surely if you know the text fits, you wouldn’t try to clamp it, but if you don’t know…
I like the gradient way too (after a decent standardized clamping method of course). I really do think you’d need a
pointer-events: none;
on the pseudo-element though.As it seems Clamp.js just doesn’t work. So after some trial and error this instead seems to work reliably cross-browser:
It needs some further optimizations, it really doesn’t need to edit the data string like it does and it could just go ahead and create the elements in the first replace callback loop.
The fade out technique is similar to what polygon Polygon uses.
Thanks for the useful post, Chris.
The guys at FT Labs also developed ftellipsis that does the same thing, and falls back to clamping text and positioning an element over the end of the overflowing line in non-Webkit browsers.
Tested it. Like Clamp.js it only seems to work in a Webkit browser.
Added it to the demos, thanks!
… and used wrong name… Anyway. Clamp.js works after all, Chris just targets the wrong element in his sample (id should be in paragraph not in the div).
What I don’t like about Clamp.js is how it removes original text from the element.
Another CSS-only method linked on this blog before can be found here: http://www.mobify.com/blog/multiline-ellipsis-in-pure-css/
As a new comer to Web Design, I find all this little things insane. Even programming games is less of a headache than web design, I was surprised.
I had to do exactly this a few days ago for my site, and the solution I found was to do a word wrap AND count the lines via PHP. Not to mention get a script that would make sure to not count HTML tags as words.
So when is one of you guys going to code a whole new way to make websites, one that is logical, consistent across browsers and does without pain basic things? I’ll send $100 to your paypal when you do it, maybe even more.
WOW! I like Clamp.js it only seems to work in a -webkit- browser.
Some interesting examples of how to achieve this, when we’ve done it in the past we’ve used PHP to accomplish this, with any luck we can do away with that soon!
I don’t get how “The Fade Out Way” can work at all since you can’t guarantee the same font size across browsers.
Even if you set line-height and font-size, the text will still overflow at different areas. Depending on the browser it may stop just before the container ends, but on other browsers the last line can be cut in half by the container.
I recently did some poking around online for a solution to this problem, and I landed on a jQuery plugin called dotdotdot. Clearly, it’s not a pure CSS solution, but CSS effectively controls it’s application, since the library measures the text container, and only applies to overflowing content.
One of the coolest features of ‘dotdotdot’ (and the main reason I chose it over other solutions) is the ability to ensure specific content remains visible, or add custom content when the ellipsis is needed. In my particular case, I was clipping product descriptions on an ecommerce site, and needed to ensure the product ID remained visible at the end of the description, even when an ellipsis is present.
The library has served me very well so far, and I recommend it highly. I haven’t yet needed a ‘dependency-free’ version (no jQuery) yet, but when I do, I’ll likely contact the developer asking for a vanilla-JS port, or do it myself.
Check out https://gist.github.com/depoulo/5832073
CSS only, accounts for the case where you have text as long as the number of lines you want to show (you don’t want dots then), and you can implement a -webkit-line-clamp switch into it easily.
Opera Overflow Way is not work. I guess because the Opera is changing the engine of their own.
I like Clamp.js it only seems to work in a -webkit- browser.