CSS Text Overflow Ellipsis: 1 Line, 2 Lines and Flexbox
One-line ellipsis needs white-space: nowrap, overflow: hidden and text-overflow: ellipsis. Tested fixes for flexbox, grid and tables, plus 2- and 3-line truncation with line-clamp.
The most reliable UK phone number regex is a short one applied to a cleaned-up number. First normalise what the user typed: remove spaces, hyphens, dots and brackets, drop a (0), and turn a leading +44, 0044 or 44 into 0. Then a UK mobile is /^07\d{9}$/ (07 followed by nine more digits, 11 digits in all), and a stricter version that leaves out 070 personal numbers and 076 pagers is /^07(?:[1-57-9]\d{8}|624\d{6})$/. Any UK number, landline or mobile, is /^0(?:1\d{8,9}|[2357]\d{9}|8\d{8,9}|9\d{9})$/. Store the result in international E.164 form (+447700900123) and format it with spaces only for display. A regex checks the shape of a number, not whether it's in use. If that matters, use libphonenumber, as covered further down.
Trying to do all of that with one regex on the raw input is why so many patterns online are long and still reject real numbers. Below is a small, tested set of helpers for JavaScript and Python, a comparison with the popular one-line regex, results from Google's libphonenumber in both languages, and a browser gotcha with the HTML pattern attribute that silently turns validation off. Every test number comes from the ranges Ofcom reserves for TV and radio drama, plus the libraries' own example numbers, so none of them belongs to a real person. Everything was run on 6 October 2026 with Node 20.19, Python 3.13.5 and headless Chrome 154.
Before writing a pattern, it helps to know what you're matching. In national format (starting with 0), a UK number is 10 or 11 digits long, and the first two digits tell you what kind of number it is:
In international format, you drop the leading 0 and add the UK country code, +44. So 07700 900123 becomes +44 7700 900123, and written without spaces, +447700900123. That compact form is called E.164, and it's what SMS services, phone systems and most databases expect. You'll also see 0044 in place of + (the international dialling prefix from a UK phone) and, in email signatures, +44 (0)7700 900123. The (0) is common but strictly incorrect, because you shouldn't dial the 0 after +44.
Ofcom publishes ranges set aside for fiction, which makes them perfect test data: mobiles 07700 900000 to 900999, London 020 7946 0000 to 0999, Leeds 0113 496 0000 to 0999, and others for freephone, premium and UK-wide numbers. The full list is on Ofcom's page of telephone numbers for use in TV and radio drama. Use these in your tests, documentation and screenshots rather than making numbers up, because a made-up number might belong to someone.
Here's the module. It's split into three steps so each one is easy to read and test on its own:
// uk-phone.mjs: normalise, validate and format UK phone numbers (no libraries)
// 1. Turn whatever the user typed into national digits: "07700900123"
export function normaliseUkPhone(input) {
let s = String(input).trim();
s = s.replace(/\(0\)/g, ""); // "+44 (0)20 ..." -> drop the (0)
s = s.replace(/[\s\-().]/g, ""); // spaces, hyphens, brackets, dots
if (s.startsWith("+44")) s = "0" + s.slice(3);
else if (s.startsWith("0044")) s = "0" + s.slice(4);
else if (/^44\d{10}$/.test(s)) s = "0" + s.slice(2); // "447700900123"
return /^0\d{9,10}$/.test(s) ? s : null;
}
// 2. Patterns that run on the normalised digits
export const UK_MOBILE = /^07(?:[1-57-9]\d{8}|624\d{6})$/; // not 070 or 076 (except 07624)
export const UK_PHONE = /^0(?:1\d{8,9}|[2357]\d{9}|8\d{8,9}|9\d{9})$/; // any UK number type
export function isUkMobile(input) {
const n = normaliseUkPhone(input);
return n !== null && UK_MOBILE.test(n);
}
export function isUkPhone(input) {
const n = normaliseUkPhone(input);
return n !== null && UK_PHONE.test(n);
}
// 3. Store in E.164, show in the usual UK spacing
export function toE164(input) {
const n = normaliseUkPhone(input);
return n && UK_PHONE.test(n) ? "+44" + n.slice(1) : null;
}
export function formatUkPhone(input) {
const n = normaliseUkPhone(input);
if (!n || !UK_PHONE.test(n)) return null;
if (n.startsWith("02")) return `${n.slice(0, 3)} ${n.slice(3, 7)} ${n.slice(7)}`; // 020 7946 0123
if (/^07/.test(n)) return `${n.slice(0, 5)} ${n.slice(5)}`; // 07700 900123
if (/^01\d1|^011/.test(n) && n.length === 11) return `${n.slice(0, 4)} ${n.slice(4, 7)} ${n.slice(7)}`; // 0113 496 0123
if (/^01/.test(n)) return `${n.slice(0, 5)} ${n.slice(5)}`; // 01632 960123
return `${n.slice(0, 4)} ${n.slice(4, 7)} ${n.slice(7)}`; // 0808 157 0123
}
Step by step:
normaliseUkPhone() trims the input, removes a (0), strips spaces, hyphens, brackets and dots, and converts +44, 0044 and a bare 44 followed by ten digits into a leading 0. If what's left isn't a 0 followed by 9 or 10 digits, it returns null. Letters, a missing 0 or the wrong length all fail here.UK_MOBILE accepts 071 to 075 and 077 to 079, plus 07624. UK_PHONE accepts every UK number type: 01 with 9 or 10 more digits, 02, 03, 05 and 07 with 10 more, 08 with 9 or 10 more (some freephone numbers are shorter), and 09 with 10 more.toE164() returns the storage format and formatUkPhone() returns the display format, using the spacing people are used to: 07700 900123 for mobiles, 020 7946 0123 for London, 0113 496 0123 for the big cities with 011x and 01x1 codes, 01632 960123 for other areas, and 0808 157 0123 for everything else.Here are 20 real-world inputs run through it:
// test.mjs: run the hand-written helpers over real-looking input
import { normaliseUkPhone, isUkMobile, isUkPhone, toE164, formatUkPhone } from "./uk-phone.mjs";
const inputs = [
"07700 900123", "+44 7700 900123", "0044 7700 900 123", "+44 (0)7700 900123",
"07700-900-123", "447700900123", "020 7946 0123", "(0113) 496 0123",
"01632 960123", "0808 157 0123", "0306 999 0123", "0909 879 0123",
"07400 123456", "070 1234 5678", "076 4012 3456", "07924 123456", "0612 345678", "7700 900123",
"0770 090 012", "077009001234",
];
console.log("input".padEnd(20), "normalised".padEnd(12), "mobile", " any ", "E.164".padEnd(14), "display");
for (const raw of inputs) {
console.log(
raw.padEnd(20),
String(normaliseUkPhone(raw)).padEnd(12),
String(isUkMobile(raw)).padEnd(6),
String(isUkPhone(raw)).padEnd(6),
String(toE164(raw)).padEnd(14),
formatUkPhone(raw)
);
}
$ node test.mjs
input normalised mobile any E.164 display
07700 900123 07700900123 true true +447700900123 07700 900123
+44 7700 900123 07700900123 true true +447700900123 07700 900123
0044 7700 900 123 07700900123 true true +447700900123 07700 900123
+44 (0)7700 900123 07700900123 true true +447700900123 07700 900123
07700-900-123 07700900123 true true +447700900123 07700 900123
447700900123 07700900123 true true +447700900123 07700 900123
020 7946 0123 02079460123 false true +442079460123 020 7946 0123
(0113) 496 0123 01134960123 false true +441134960123 0113 496 0123
01632 960123 01632960123 false true +441632960123 01632 960123
0808 157 0123 08081570123 false true +448081570123 0808 157 0123
0306 999 0123 03069990123 false true +443069990123 0306 999 0123
0909 879 0123 09098790123 false true +449098790123 0909 879 0123
07400 123456 07400123456 true true +447400123456 07400 123456
070 1234 5678 07012345678 false true +447012345678 07012 345678
076 4012 3456 07640123456 false true +447640123456 07640 123456
07924 123456 07924123456 true true +447924123456 07924 123456
0612 345678 0612345678 false false null null
7700 900123 null false false null null
0770 090 012 0770090012 false false null null
077009001234 null false false null null
The first six rows are the same mobile number typed six different ways: plain, with +44, with 0044, with +44 (0), with hyphens, and as 447700900123 with no plus. All six normalise to 07700900123, validate as a mobile, and come out as the same E.164 string and the same display format. That's the main point of normalising first: users type numbers however they like, and you shouldn't reject a correct number because of a space or a hyphen.
The rest of the table shows the edges:
07400 123456 and 07924 123456 (libphonenumber's own example mobiles) pass the mobile check.070 1234 5678 (a personal number) and 076 4012 3456 (a pager) are valid UK numbers but fail the strict mobile check. If you're collecting a number to send an SMS verification code to, that's what you want.0612 345678 fails, because 06 isn't used. 7700 900123 fails because the leading 0 is missing; I chose not to guess, but you could add it if your form makes the format clear. 0770 090 012 is a digit short, and 077009001234 is a digit too long.One display quirk: my formatter shows the personal number as 07012 345678. Personal numbers are usually written 070 1234 5678, and libphonenumber gets that right, as you'll see below. If display spacing matters for every number type, that's a good reason to use the library.
The Python version mirrors the JavaScript one, using only the standard library's re module. I used fullmatch() rather than match() so the pattern has to cover the entire string, which saves you from forgetting the $ anchor:
"""uk_phone.py: normalise, validate and format UK phone numbers (standard library only)."""
import re
UK_MOBILE = re.compile(r"07(?:[1-57-9]\d{8}|624\d{6})")
UK_PHONE = re.compile(r"0(?:1\d{8,9}|[2357]\d{9}|8\d{8,9}|9\d{9})")
def normalise_uk_phone(text: str) -> str | None:
s = str(text).strip().replace("(0)", "")
s = re.sub(r"[\s\-().]", "", s)
if s.startswith("+44"):
s = "0" + s[3:]
elif s.startswith("0044"):
s = "0" + s[4:]
elif re.fullmatch(r"44\d{10}", s):
s = "0" + s[2:]
return s if re.fullmatch(r"0\d{9,10}", s) else None
def is_uk_mobile(text: str) -> bool:
n = normalise_uk_phone(text)
return n is not None and UK_MOBILE.fullmatch(n) is not None
def is_uk_phone(text: str) -> bool:
n = normalise_uk_phone(text)
return n is not None and UK_PHONE.fullmatch(n) is not None
def to_e164(text: str) -> str | None:
n = normalise_uk_phone(text)
return "+44" + n[1:] if n and UK_PHONE.fullmatch(n) else None
if __name__ == "__main__":
for raw in ["07700 900123", "+44 (0)7700 900123", "0044 7700-900-123",
"020 7946 0123", "070 1234 5678", "7700 900123"]:
print(f"{raw:20} mobile={is_uk_mobile(raw)!s:5} any={is_uk_phone(raw)!s:5} e164={to_e164(raw)}")
$ python3 uk_phone.py
07700 900123 mobile=True any=True e164=+447700900123
+44 (0)7700 900123 mobile=True any=True e164=+447700900123
0044 7700-900-123 mobile=True any=True e164=+447700900123
020 7946 0123 mobile=False any=True e164=+442079460123
070 1234 5678 mobile=False any=True e164=+447012345678
7700 900123 mobile=False any=False e164=None
The results agree with JavaScript row for row. +44 (0)7700 900123 and 0044 7700-900-123 both normalise to the same number, the London landline is valid but not a mobile, the 070 personal number is a valid UK number but not a mobile, and the number without its leading 0 is rejected. If you print these in a report or a table, f-string formatting is what lines up the columns, as in the {raw:20} above.
Keep the same rules on the server even if you check in the browser. Browser checks are for convenience: anyone can turn off JavaScript or send a request directly. If your form is handled by PHP, my guides to PHP form validation and sanitisation and the PHP contact form with mail() show where a check like this fits; PHP's preg_match accepts the same patterns.
Search for "uk mobile number regex" and most answers give a single pattern that tries to handle the prefix and the spaces in one go. Here's a typical one, tested on raw input:
// oneline.mjs: the popular one-line regex, tested on raw input
const UK_MOBILE_RAW = /^(?:(?:\+|00)44\s?(?:\(0\)\s?)?|0)7\d{3}\s?\d{6}$/;
for (const raw of ["07700 900123", "+44 7700 900123", "0044 7700 900123", "+44 (0)7700 900123",
"07700 900 123", "07700-900-123", " 07700900123", "07012 345678"]) {
console.log(JSON.stringify(raw).padEnd(22), UK_MOBILE_RAW.test(raw));
}
$ node oneline.mjs
"07700 900123" true
"+44 7700 900123" true
"0044 7700 900123" true
"+44 (0)7700 900123" true
"07700 900 123" false
"07700-900-123" false
" 07700900123" false
"07012 345678" true
It handles the four formats it was designed for, then fails three perfectly reasonable ones: 07700 900 123 (spaced 5-3-3, which plenty of people use), 07700-900-123 (hyphens), and " 07700900123" (a leading space, which happens all the time with copy and paste or autofill). It also accepts 07012 345678, a personal number, because 7\d{3} allows any 07 prefix. You could keep adding alternatives to the pattern, but each one makes it harder to read and easier to break. Normalising first, then matching a tiny pattern, avoids the whole problem. It's the same approach I recommended for email validation with regex: keep the pattern simple and handle the mess around it in plain code.
A regex can tell you a number has the right shape. It can't tell you whether that range has actually been allocated. For that you need number-range data, and the standard source is Google's libphonenumber, available as libphonenumber-js for JavaScript and phonenumbers for Python. Here are the same kinds of input run through both:
// library.mjs: the same inputs through libphonenumber-js (full metadata)
import { parsePhoneNumberFromString } from "libphonenumber-js/max";
import pkg from "libphonenumber-js/package.json" with { type: "json" };
const inputs = ["07700 900123", "+44 (0)7700 900123", "020 7946 0123", "0113 496 0123",
"01632 960123", "0808 157 0123", "0909 879 0123", "07400 123456", "070 1234 5678",
"076 4012 3456", "07924 123456", "0612 345678", "0770 090 012"];
for (const raw of inputs) {
const p = parsePhoneNumberFromString(raw, "GB");
console.log(
raw.padEnd(20),
p ? `valid=${String(p.isValid()).padEnd(5)} type=${String(p.getType()).padEnd(15)} country=${p.country ?? "-"} ${p.number}`
: "could not parse"
);
}
console.log("libphonenumber-js", pkg.version);
$ node library.mjs
07700 900123 valid=false type=undefined country=GB +447700900123
+44 (0)7700 900123 valid=false type=undefined country=- +447700900123
020 7946 0123 valid=true type=FIXED_LINE country=GB +442079460123
0113 496 0123 valid=true type=FIXED_LINE country=GB +441134960123
01632 960123 valid=false type=undefined country=GB +441632960123
0808 157 0123 valid=true type=TOLL_FREE country=GB +448081570123
0909 879 0123 valid=true type=PREMIUM_RATE country=GB +449098790123
07400 123456 valid=true type=MOBILE country=GB +447400123456
070 1234 5678 valid=true type=PERSONAL_NUMBER country=GB +447012345678
076 4012 3456 valid=true type=PAGER country=GB +447640123456
07924 123456 valid=true type=MOBILE country=GB +447924123456
0612 345678 valid=false type=undefined country=GB +44612345678
0770 090 012 valid=false type=undefined country=GB +44770090012
libphonenumber-js 1.13.14
"""library.py: the same checks with the phonenumbers package (Python port of libphonenumber)."""
import phonenumbers
from phonenumbers import PhoneNumberFormat, PhoneNumberType, NumberParseException
TYPES = {v: k for k, v in vars(PhoneNumberType).items() if k.isupper()}
for raw in ["07700 900123", "+44 (0)7700 900123", "020 7946 0123", "07400 123456",
"070 1234 5678", "0612 345678", "not a number"]:
try:
n = phonenumbers.parse(raw, "GB")
except NumberParseException as e:
print(f"{raw:20} parse error: {e}")
continue
print(f"{raw:20} valid={phonenumbers.is_valid_number(n)!s:5} "
f"type={TYPES[phonenumbers.number_type(n)]:15} "
f"e164={phonenumbers.format_number(n, PhoneNumberFormat.E164):14} "
f"national={phonenumbers.format_number(n, PhoneNumberFormat.NATIONAL)}")
print("phonenumbers", phonenumbers.__version__)
$ python library.py # inside a venv with phonenumbers installed
07700 900123 valid=False type=UNKNOWN e164=+447700900123 national=07700 900123
+44 (0)7700 900123 valid=False type=UNKNOWN e164=+447700900123 national=07700 900123
020 7946 0123 valid=True type=FIXED_LINE e164=+442079460123 national=020 7946 0123
07400 123456 valid=True type=MOBILE e164=+447400123456 national=07400 123456
070 1234 5678 valid=True type=PERSONAL_NUMBER e164=+447012345678 national=070 1234 5678
0612 345678 valid=False type=UNKNOWN e164=+44612345678 national=612345678
not a number parse error: (1) The string supplied did not seem to be a phone number.
phonenumbers 9.0.40
Three results are worth pausing on:
07700 900123 is invalid to both libraries (valid=false, type unknown), and so is the "no area" 01632 960123. Those ranges are deliberately never allocated, and the libraries know that. My regex accepts them, because they have the right shape. So use drama numbers to test your formatting and normalising code, but don't expect them to pass a library check, and use the library's example numbers (such as 07400 123456) to test library-based validation.PERSONAL_NUMBER for 070, PAGER for 076, TOLL_FREE for 0808, PREMIUM_RATE for 0909, FIXED_LINE for landlines and MOBILE for real mobile ranges. If you only want numbers that can receive a text, check getType() === "MOBILE" in JavaScript or number_type(n) == PhoneNumberType.MOBILE in Python.070 1234 5678 for the personal number, where my simple formatter gave 07012 345678. It also gives you E.164 directly with p.number or format_number(n, PhoneNumberFormat.E164).I used the full "max" metadata in JavaScript (libphonenumber-js/max), because the smaller default build can't report number types. The library adds a noticeable amount of code to a page, so a sensible split is the regex in the browser for instant feedback, and the library on the server, where size doesn't matter. In Python, pip install phonenumbers inside a virtual environment is all you need; I tested version 9.0.40.
Neither approach proves a number is in use or belongs to the person typing it. If that matters, for example for two-factor login, send a one-time code by SMS and ask them to type it back.
For a phone field, use <input type="tel" autocomplete="tel">. type="tel" doesn't validate anything by itself, because phone formats vary so much around the world, but it brings up the number keypad on phones, and autocomplete="tel" lets the browser fill in the user's number. I covered the other input types in HTML form input types and validation.
You can add a pattern attribute for a quick check before submit, but there's a trap. Current Chrome compiles pattern with the regex v flag, which is stricter about which characters can appear unescaped inside square brackets. I tested three versions of a loose "digits, spaces, plus and brackets" pattern:
// pattern.mjs: does an <input pattern> for UK phone numbers actually validate?
import puppeteer from "puppeteer-core";
const browser = await puppeteer.launch({ executablePath: "/usr/bin/google-chrome", headless: true, args: ["--no-sandbox"] });
const page = await browser.newPage();
const warnings = [];
page.on("console", m => warnings.push(m.text()));
await page.setContent(`
<input id="a" type="tel" pattern="[0-9 +()-]{10,20}">
<input id="b" type="tel" pattern="[0-9 +()\\-]{10,20}">
<input id="c" type="tel" pattern="[0-9 +\\(\\)\\-]{10,20}">`);
const patterns = await page.$$eval("input", els => els.map(e => e.getAttribute("pattern")));
for (const value of ["07700 900123", "+44 (0)20 7946 0123", "call me maybe"]) {
const res = await page.evaluate(v => [...document.querySelectorAll("input")].map(i => {
i.value = v; return i.checkValidity();
}), value);
console.log(JSON.stringify(value).padEnd(24), "a:", String(res[0]).padEnd(6), "b:", String(res[1]).padEnd(6), "c:", res[2]);
}
patterns.forEach((p, i) => console.log("abc"[i], "=", p));
console.log("console:", warnings.length ? "\n " + warnings.join("\n ") : "(none)");
await browser.close();
$ node pattern.mjs
"07700 900123" a: true b: true c: true
"+44 (0)20 7946 0123" a: true b: true c: true
"call me maybe" a: true b: true c: false
a = [0-9 +()-]{10,20}
b = [0-9 +()\-]{10,20}
c = [0-9 +\(\)\-]{10,20}
console:
Pattern attribute value [0-9 +()-]{10,20} is not a valid regular expression: Uncaught SyntaxError: Failed to execute 'checkValidity' on 'HTMLInputElement': Invalid regular expression: /[0-9 +()-]{10,20}/v: Invalid character in character class
Pattern attribute value [0-9 +()\-]{10,20} is not a valid regular expression: Uncaught SyntaxError: Failed to execute 'checkValidity' on 'HTMLInputElement': Invalid regular expression: /[0-9 +()\-]{10,20}/v: Invalid character in character class
Patterns a and b look fine and would work as a normal JavaScript regex, but Chrome reports Invalid character in character class and ignores the pattern completely. As a result, "call me maybe" passed validation. No error appears on the page; the only clue is a message in the developer console. Pattern c, with (, ) and - all escaped, works, and correctly rejects the text. If you use pattern, escape every symbol inside the brackets, then test it with something that should fail, not just with something that should pass.
In practice, I keep the HTML pattern loose (or leave it out), normalise and validate with JavaScript on blur or submit so I can show a helpful message, and validate again on the server.
^ and $ (or Python's fullmatch), a pattern can match part of a longer string, so 0770090012300000 would pass a check for 11 digits.+447700900123) in the database and format on display. Otherwise searching and de-duplicating become painful.+ can't be stored. Phone numbers are text that happens to contain digits.pattern. As shown above, an invalid pattern quietly turns off validation.After removing spaces and converting +44 to 0, use ^07\d{9}$, which matches 07 followed by nine digits. To exclude 070 personal numbers and 076 pagers, use ^07(?:[1-57-9]\d{8}|624\d{6})$. Testing raw input with one pattern is possible but rejects common formats such as 07700 900 123.
Normalise the input by removing spaces, hyphens, brackets and dots, and converting a leading +44, 0044 or 44 to 0. Then test it against /^0(?:1\d{8,9}|[2357]\d{9}|8\d{8,9}|9\d{9})$/ for any UK number, or a mobile pattern. Use libphonenumber-js if you need to check that the range has been allocated.
Most UK numbers are 11 digits including the leading 0, such as 07700 900123 or 020 7946 0123. A few landline areas and some freephone numbers have 10. In international format the 0 is dropped and +44 is added, so a mobile is +44 followed by 10 digits.
Drop the leading 0 and add +44, so 07700 900123 becomes +44 7700 900123, or +447700900123 without spaces (E.164). Don't write +44 (0)7700 900123. It's common, but the 0 shouldn't be dialled after +44.
Use a regex for quick format checks and instant feedback in the browser. Use libphonenumber (libphonenumber-js or Python's phonenumbers) when you need to know whether a number is in an allocated range, what type it is, or how to format it correctly. In my tests, the libraries rejected Ofcom's unallocated drama numbers, while a regex accepted them.
// 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.
One-line ellipsis needs white-space: nowrap, overflow: hidden and text-overflow: ellipsis. Tested fixes for flexbox, grid and tables, plus 2- and 3-line truncation with line-clamp.
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.