UK Phone Number Regex for JavaScript and Python (Tested)
Strip spaces, turn +44 into 0, then check for 07 plus nine digits. Tested JavaScript and Python code for every UK number type, E.164 storage, display spacing and libphonenumber.
To cut off long text with an ellipsis (…) in CSS, the element needs three properties together: white-space: nowrap so the text stays on one line, overflow: hidden so the extra text is hidden, and text-overflow: ellipsis so the browser draws "…" where the text is cut. The element also needs a limited width, which it gets automatically if it's a block element inside a narrower container. If the ellipsis isn't showing, one of those four things is missing, or the element is inside a flexbox or grid that lets it grow, in which case add min-width: 0 to the flex item or use minmax(0, 1fr) for the grid column. For two or three lines, text-overflow can't help: use -webkit-line-clamp with display: -webkit-box instead.
I built a test page with every case in this post, a working version and each common way it breaks, and loaded it in headless Chrome 154 on 6 October 2026. A small script measured each element's width and height and whether its text was wider than the box (overflows=true means the text is wider than the space it has, whether or not it's hidden), and I checked a screenshot of the page by eye to confirm where the "…" actually appeared. The measurements below are pasted from that run.
Here's the class I use, with a comment on each line:
/* 1. The three lines you need for one-line truncation */
.truncate {
white-space: nowrap; /* keep it on one line */
overflow: hidden; /* hide what does not fit */
text-overflow: ellipsis; /* show … where it is cut */
}
And here's what each test box measured. The boxes are all 260px wide (262px including the border) and hold the same module title, "Introduction to Computer Science and Programming in Python":
$ node post04/measure.mjs
c1 width=262px height= 26px lines=1 overflows=true
c2 width=262px height= 50px lines=2 overflows=false
c3 width=262px height= 26px lines=1 overflows=true
c4 width=440px height= 17px lines=1 overflows=false
c4b width=260px height= 24px lines=1 overflows=true
overflows=true) and hidden, and the screenshot shows "Introduction to Computer Science …". This is the working version.white-space: nowrap: the text simply wrapped onto a second line (50px tall). Nothing overflowed sideways, so there was nothing for an ellipsis to replace. This is the most common reason for "text-overflow ellipsis not working".overflow: hidden: the text stayed on one line and overflowed like c1, but instead of being hidden it ran straight out of the box, past the border. text-overflow only applies when overflow is actually being hidden, so without it there's no "…".<span>: the span was 440px wide, the width of its text, so nothing overflowed. Inline elements grow to fit their content and ignore width, so an ellipsis never triggers on them.display: block: 260px wide, overflowing and hidden, ellipsis showing. display: inline-block with a max-width also works, if you want it to sit in a line of text.How does the element get a "limited width"? A block element (like div, p, h3 or li) is automatically as wide as its container, so if the container is narrower than the text, you're done. You only need to set a width or max-width yourself when nothing around it limits the width. People often search for "text overflow ellipsis without width": the answer is that you don't need a width property, but the element does need a constrained width from somewhere.
text-overflow doesn't hide anything itself. It only controls what is drawn at the edge where overflow: hidden cuts the text, either a plain clip (clip, the default) or "…" (ellipsis). MDN's text-overflow reference also describes a two-value form for the start and end of the line, but browser support for that is limited, so I stick to the single ellipsis value. The full text is still in the page, so it's still read by screen readers, found by Ctrl+F, copied when someone selects it, and indexed by search engines. Truncation is purely visual.
This is the one that wastes the most time. You add the three lines, it works in a test, then you put it in a flex row next to an icon and the layout breaks: the row gets wider than its container and the text isn't truncated at all. Here's the setup from my test page, an icon and a block of text in a 320px row:
/* 5. flexbox */
.row { display: flex; align-items: center; gap: 8px; width: 320px; border: 1px solid #999; margin: 0 0 14px; }
.icon { flex: 0 0 32px; height: 32px; background: #7cb6ff; border-radius: 6px; }
.row .text { flex: 1; }
.row .text.shrinkable { min-width: 0; } /* the fix */
<h4>5 flex child, no min-width</h4>
<div id="r5" class="row"><div class="icon"></div><div class="text"><h3 id="c5" class="truncate">Introduction to Computer Science and Programming in Python</h3><small>Year 1 · 20 credits</small></div></div>
<h4>5b flex child with min-width: 0</h4>
<div id="r5b" class="row"><div class="icon"></div><div class="text shrinkable"><h3 id="c5b" class="truncate">Introduction to Computer Science and Programming in Python</h3><small>Year 1 · 20 credits</small></div></div>
c5 width=476px height= 24px lines=1 overflows=false container=322px content=516px
c5b width=280px height= 24px lines=1 overflows=true container=322px content=320px
c6 width=262px height= 26px lines=1 overflows=true
h3 was 476px wide and the row's content measured 516px, inside a container only 322px wide. The h3 was as wide as its own text, so from its point of view nothing overflowed (overflows=false), and in the screenshot the full title runs out past the right-hand border.min-width: 0 on the flex item: the h3 shrank to 280px, the row's content fits in its 320px, and the title ends in "…".The reason is a default that surprises almost everyone. Flex items have min-width: auto, which means a flex item won't shrink below the width of its content. The content here is a line of text that isn't allowed to wrap, so its "minimum" width is the whole line. The .text wrapper refuses to get any narrower than that, so the h3 inside it is never narrower than its text, so it never overflows, so the ellipsis never appears. Setting min-width: 0 on the flex item removes that floor and lets flex: 1 squeeze it to the space that's left.
There's a detail here that explains why the bug seems random. If you put the truncation class directly on the flex item, it often works without the fix, because an element with overflow: hidden gets an automatic minimum width of zero. It breaks when the truncated text is nested inside the flex item, like the h3 inside .text here, which is how real components (card titles, list items, chat previews) are usually built. The rule of thumb: put min-width: 0 on every flex item between the flex container and the truncated text.
Case c6 in the measurements put all three properties on an element that also had display: flex. Its text overflowed and was hidden, but the screenshot shows the text simply chopped mid-word, with no "…". The text inside a flex container becomes an anonymous flex item, and text-overflow on the container doesn't apply to it. Wrap the text in its own element (a span or h3) and truncate that instead.
Grid has the same problem in a slightly different form. A column defined as 1fr really means minmax(auto, 1fr), and that auto minimum is the content's width:
/* 7. grid */
.grid { display: grid; grid-template-columns: 40px 1fr; width: 320px; border: 1px solid #999; margin: 0 0 14px; }
.grid.fixed { grid-template-columns: 40px minmax(0, 1fr); }
c7 width=476px height= 24px lines=1 overflows=false container=322px content=516px
c7b width=280px height= 24px lines=1 overflows=true container=322px content=320px
With grid-template-columns: 40px 1fr, the title was 476px wide in a 320px grid and wasn't truncated (c7). Changing the second column to minmax(0, 1fr) let it shrink to 280px with the ellipsis showing (c7b). Setting min-width: 0 on the grid item works too, but I prefer fixing it in the column definition, because the fix then lives in one place, next to the layout that caused the problem.
If you're choosing between the two layouts in the first place, I compared them in CSS flexbox vs grid. For truncation, both need the same "allow it to shrink" fix.
text-overflow only works on a single line. For "show two lines, then …", which you'll see on almost every card layout, news list and product grid, you need line clamping:
/* 8. several lines */
.clamp-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.clamp-3 { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; }
.clamp-std { line-clamp: 2; overflow: hidden; }
c8 width=262px height= 50px lines=2 overflows=true
c8b width=262px height= 74px lines=3 overflows=true
c8c width=262px height=122px lines=5 overflows=false
c8d width=262px height=122px lines=5 overflows=false
The test paragraph is a module description that takes five lines (122px) at this width when nothing limits it (c8d). With the clamp:
-webkit-line-clamp: 2: exactly 2 lines (50px tall), and the second line ends "…in Pytho…".-webkit-line-clamp: 3: exactly 3 lines (74px tall).line-clamp: 2 on its own: no effect at all. It's still five lines, 122px tall. The script also asked the browser directly: CSS.supports('line-clamp', '2') returned false in Chrome 154, while the -webkit- version returned true.So, despite the prefix, the -webkit-line-clamp version is the one to use. All four lines are needed: display: -webkit-box and -webkit-box-orient: vertical set up the old box layout that the clamp relies on, -webkit-line-clamp sets the number of lines, and overflow: hidden hides the rest. If you leave out any of them, you'll get "css line clamp not working". Notice that white-space: nowrap must not be there, because the text has to wrap to fill the lines. You can add line-clamp: 2 alongside for future browsers, but don't rely on it alone.
Two more things to watch for. First, the clamp works on the element's own text, so apply it to the paragraph itself, not to a wrapper with several children. Second, don't add padding at the bottom of a clamped element: the hidden third line can peek through the padding. Put the padding on a parent instead.
Tables have their own sizing rules, and an auto-sized table grows to fit unbreakable content. I put each table inside a 400px-wide wrapper:
/* 10. tables */
table { width: 100%; border-collapse: collapse; margin: 0 0 14px; }
td { border: 1px solid #999; padding: 4px; }
table.fixed { table-layout: fixed; }
c10 width=642px height= 33px lines=1 overflows=false container=400px content=701px
c10b width=330px height= 33px lines=1 overflows=true container=400px content=400px
c10c width=330px height= 33px lines=1 overflows=true container=400px content=400px
table-layout: fixed: the cell stayed at 330px and the text ended in "…". With a fixed layout, column widths come from the table's width and the first row, not from the content.max-width: 0 on the cell: also works, with the normal layout. It's a well-known trick: the cell can no longer insist on its content width, so it takes the space the table gives it. It's useful when you can't change table-layout for the whole table.Sometimes the end of the text is the important part, such as file names, where you want to see the extension, or long reference numbers. CSS can't truncate in the middle on its own, so I do it in JavaScript and keep the full name in a title attribute:
// middle.mjs: keep the start and the end, cut the middle (good for file names)
function truncateMiddle(text, max = 24, mark = "…") {
if (text.length <= max) return text;
const keep = max - mark.length;
const head = Math.ceil(keep / 2);
const tail = Math.floor(keep / 2);
return text.slice(0, head) + mark + text.slice(text.length - tail);
}
for (const name of [
"dissertation-final.docx",
"dissertation-final-v7-REALLY-final-amended.docx",
"IMG_20261006_084512_holiday_in_the_lake_district.jpg",
]) {
console.log(String(name.length).padStart(2), "->", truncateMiddle(name));
}
$ node middle.mjs
23 -> dissertation-final.docx
47 -> dissertation…mended.docx
52 -> IMG_20261006…istrict.jpg
Short names are left alone. Long ones keep the start and the end, so "dissertation…mended.docx" still tells you what kind of file it is. Do this when you render, and keep the original string for links and downloads.
Add a title attribute with the full text so mouse users can see it on hover. It's not shown on touch screens or to keyboard users, so for anything important, such as a product name in a basket, also make sure the full text is available somewhere else: on the detail page, in an expandable row, or in a tooltip that opens on focus. Since the full text is still in the HTML, screen readers already read all of it. Using the right element (an h3 for a card title rather than a div) helps those users even more, as I explained in HTML semantic tags.
If you use Tailwind, you don't need to write any of this CSS by hand, but it's worth knowing what the utility classes contain, because the same rules about flexbox and grid still apply. I compiled a tiny file with Tailwind's CLI and printed the rules it generated for the three classes that matter:
$ cat tw.html
<div class="flex"><div class="min-w-0 flex-1"><h3 class="truncate">Title</h3><p class="line-clamp-2">Text</p></div></div>
$ npx @tailwindcss/cli -i tw.css -o tw.out.css # then the rules for our classes:
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.line-clamp-2 { overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.min-w-0 { min-width: 0px; }
tailwindcss 4.3.3
truncate is exactly the three-line single-line recipe from the start of this post, and line-clamp-2 is the four-line -webkit- clamp, with no white-space: nowrap. So the classic Tailwind bug is the same as the plain CSS one: truncate on a title inside a flex row does nothing until you add min-w-0 to the flex item that wraps it, and inside a grid you need a column such as grid-cols-[40px_minmax(0,1fr)]. If your ellipsis isn't working in Tailwind, check for a missing min-w-0 first.
nowrap means it wraps (c2). No overflow: hidden means it spills out (c3).span or a grows to fit its text (c4). Make it display: block or inline-block with a max-width.min-width: 0 in flexbox. Needed on each flex item between the container and the text (c5 vs c5b).1fr in grid. Use minmax(0, 1fr) (c7 vs c7b).line-clamp without the prefix and the box display. In Chrome 154, unprefixed line-clamp alone did nothing (c8c).position: absolute. Pulling the text out of the flow with absolute positioning hides the symptom but breaks the layout elsewhere. If you need a refresher on when absolute positioning is the right tool, see CSS position: absolute vs relative.If you want somewhere to practise, truncated card titles are a natural fit for a list of saved items, like the habit tracker built with localStorage: long habit names, a flex row with an icon and a streak count, and exactly the min-width: 0 problem described above.
Usually one of four things: white-space: nowrap is missing, overflow: hidden is missing, the element is inline (such as a span) so it has no limited width, or it's inside a flex item or grid column that grows to fit the text. Add the missing property, use display: block, or add min-width: 0 to the flex item.
Use display: -webkit-box, -webkit-box-orient: vertical, -webkit-line-clamp: 2 and overflow: hidden on the text element, without white-space: nowrap. Change the 2 to 3 for three lines. In my test in Chrome 154, the unprefixed line-clamp property on its own had no effect.
Wrap the text in its own element with the three ellipsis properties, then add min-width: 0 to the flex item that contains it (and to any flex items in between). Flex items default to min-width: auto, which stops them from shrinking below their content's width.
You don't need the width property, but the element must have a limited width from somewhere. A block element inside a narrower container already has one. An inline element, or a flex or grid item that's allowed to grow, doesn't, so the text never overflows and no ellipsis appears.
No, the full text stays in the HTML, so search engines and screen readers still get all of it. The risk is for sighted users who can't see the rest, so add a title attribute or show the full text elsewhere, and avoid truncating important information such as prices or error messages.
// note
This is a learning note from studying the web. It is one small topic, written so I can remember it. It is not a course and not a claim that I have finished the subject.
If a sentence is wrong, say so from the contact page and name this title. Drafts never appear here. Related notes, when they exist, are other published posts, and the same sample rule applies to each of them.
Strip spaces, turn +44 into 0, then check for 07 plus nine digits. Tested JavaScript and Python code for every UK number type, E.164 storage, display spacing and libphonenumber.
git branch -d name deletes the local branch, git push origin --delete name deletes the remote one. Tested errors, pruning stale branches, bulk clean-ups and how to undo a delete.
Pass sort() a compare function: a.price - b.price for numbers, a.name.localeCompare(b.name) for strings. Dates, multiple properties, toSorted() and the mistakes to avoid, all tested.