To size a browser text preview, measure the laid-out DOM element at the intended viewport width. Use Canvas for single-line text metrics. This guide shows how to compare both on a Mac and understand why their widths and heights differ.
The practical task is to find how much space a synthetic card label needs as the browser viewport changes. A useful result records the viewport, available text width, font settings, and measured box together. A width without those conditions is difficult to reproduce.
Which text width and height should you measure?
Browser APIs answer several different sizing questions. Choose the measurement that matches the decision you need to make before calculating anything.
| Your question | Measurement | What it represents |
|---|---|---|
| How far does one unwrapped string advance horizontally? | Canvas measureText(text).width |
Text advance width |
| What are the bounds of the Canvas glyph shapes? | Canvas actualBoundingBox… properties |
Glyph bounds relative to the alignment point and baseline |
| How much layout space does a card occupy? | Element getBoundingClientRect() |
Its bounding border box, including padding and borders |
| Where are the text fragments after wrapping? | Range getClientRects() |
Rectangles associated with the selected text |
| What rectangle encloses those text fragments? | Range getBoundingClientRect() |
Their combined bounding rectangle |
Canvas advance width and glyph width can differ: italic shapes may extend beyond their advance. For glyph bounds, calculate width as actualBoundingBoxLeft + actualBoundingBoxRight and height as actualBoundingBoxAscent + actualBoundingBoxDescent. These measurements use CSS pixels. TextMetrics — MDN
An element measurement includes padding and borders, while a text-only Range measures selected text using font metrics. Neither should be described as an exact rectangle of painted pixels. The W3C specification defines horizontal Range text rectangles using ascent, descent, and text advance. Element: getBoundingClientRect() method — MDN, CSSOM View Module — W3C
For a browser size preview, use the element box to allocate space and the Range rectangles to inspect text flow. Keep Canvas measurements as a separate reference for the unwrapped string.
Set the viewport and text conditions first
In Chrome on your Mac, open DevTools, enable Toggle device toolbar, and choose Responsive under Dimensions. Enter the intended width and height in the viewport fields. Chrome documents both numeric entry and draggable viewport resizing. For a desktop preview, use the desktop device type when that control is available. Simulate mobile devices with device mode — Chrome for Developers
Distinguish viewport width from text width. A card may have a maximum width, outer spacing, padding, and borders. Increasing the browser width will stop increasing its text area once the card reaches its maximum width.
For example, suppose a card has an illustrative border-box width of 360px, horizontal padding of 16px on each side, and two 1px borders. Its available content width is 360 − 32 − 2 = 326px. That is the width available for wrapping. The element’s bounding rectangle still reports 360px. This follows the documented border-box measurement behavior. Element: getBoundingClientRect() method — MDN
Match the intended font family, weight, size, style, line height, spacing, and wrapping rules. In particular, white-space: normal collapses whitespace and permits wrapping, while pre-wrap preserves whitespace and source newlines while still allowing wrapping. A preview using one cannot reliably represent the other. white-space — MDN
Also distinguish font size from line height. In horizontal writing, line-height participates in determining line-box height; it does not describe the visible height of the letters. A 16px font with 24px line height therefore requires a different height calculation from its Canvas glyph bounds. line-height — MDN
Run a complete synthetic text preview
Open a blank tab, open DevTools, select Console, and run the following complete browser script once. It creates a neutral card and a measurement report. No library, external font, existing selector, or HTML file is required. Reload the blank tab before running another copy.
The card follows the viewport until it reaches a 480px border-box width. The report updates when the window or emulated viewport resizes. This example is deliberately limited to a plain horizontal text block with uniform styling. The browser-dependent code has been inspected; it was not executed in a browser during preparation.
(async () => {
const text =
"Sample collection: round forms, soft gradients, and simple patterns.";
document.body.style.margin = "0";
const card = document.createElement("div");
card.textContent = text;
card.lang = "en";
card.dir = "ltr";
card.style.cssText = [
"box-sizing: border-box",
"width: min(480px, calc(100% - 48px))",
"margin: 24px",
"padding: 16px",
"border: 1px solid #777",
"font: normal 400 16px/24px Arial, sans-serif",
"font-kerning: normal",
"letter-spacing: 0px",
"word-spacing: 0px",
"white-space: normal",
"overflow-wrap: normal",
"word-break: normal",
"hyphens: none",
"color: #222",
"background: #f4f4f4"
].join(";");
const report = document.createElement("pre");
report.style.cssText =
"margin: 24px; white-space: pre-wrap; overflow-wrap: anywhere;";
document.body.append(card, report);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) {
report.textContent = "A Canvas 2D context is unavailable.";
return;
}
// Establish layout before waiting for the fonts used by that layout.
card.getBoundingClientRect();
await document.fonts.ready;
const format = value =>
Number.isFinite(value) ? value.toFixed(2) : "unavailable";
function measure() {
const style = getComputedStyle(card);
const box = card.getBoundingClientRect();
const horizontalExtras =
parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) +
parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
const range = document.createRange();
range.selectNodeContents(card);
const textBox = range.getBoundingClientRect();
const fragments = Array.from(range.getClientRects());
ctx.font = "normal 400 16px Arial, sans-serif";
ctx.fontKerning = "normal";
ctx.direction = "ltr";
ctx.textAlign = "left";
ctx.textBaseline = "alphabetic";
const metrics = ctx.measureText(text);
const inkWidth =
metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight;
const inkHeight =
metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
report.textContent = [
"All dimensions below are CSS pixels.",
`Layout viewport: ${document.documentElement.clientWidth} x ${document.documentElement.clientHeight}`,
`Card border box: ${format(box.width)} x ${format(box.height)}`,
`Available text width: ${format(box.width - horizontalExtras)}`,
`Range bounds: ${format(textBox.width)} x ${format(textBox.height)}`,
`Range fragment count: ${fragments.length}`,
`Canvas unwrapped advance: ${format(metrics.width)}`,
`Canvas glyph bounds: ${format(inkWidth)} x ${format(inkHeight)}`,
`Computed line height: ${style.lineHeight}`
].join("\n");
}
measure();
window.addEventListener("resize", measure);
})().catch(error => console.error("Preview failed:", error));
The element and Range calls use the browser’s actual layout. Canvas measures the same synthetic string as one unwrapped run. The HTML Standard’s Canvas text preparation replaces ASCII whitespace with spaces; passing a newline to measureText() does not create a CSS paragraph with multiple lines. HTML Standard: The canvas element — WHATWG
The script waits for document.fonts.ready after establishing layout. MDN states that this promise fulfills once loading and layout operations for used fonts have finished. It does not mean every declared font has loaded or that the originally preferred face must be the one displayed. Document: fonts property — MDN
Read the results and decide whether the text fits
Start with Available text width, then compare the card height at two viewport sizes. When wrapping adds a line, the card’s height changes, while the Canvas unwrapped advance can remain unchanged. That combination helps distinguish a wrapping change from a font change.
For this example, the following widths are illustrative calculations rather than measured font results. They assume the layout viewport has the stated width and the script’s styles remain unchanged.
| Layout viewport width | Card border-box width | Available text width |
|---|---|---|
360px |
312px |
278px |
800px |
480px |
446px |
1200px |
480px |
446px |
The last two rows explain why widening a browser may leave text wrapping unchanged: the card has already reached its maximum width. Changing the viewport alone cannot give that card more content space.
For a simple block containing only uniform text, an illustrative three-line layout at 24px line height occupies 72px of line-box space. Add 32px vertical padding and 2px of borders for an illustrative 106px card height. This arithmetic is useful for understanding the result; the measured DOM height remains the deciding value when the content or styling becomes more complex. line-height — MDN
Do not automatically treat Range fragment count as a universal line count. The Range API returns text and element rectangles according to its selection rules. Rich text, nested elements, and fragmented layout can produce a more complicated set of rectangles. The sample selects only a single plain text node to keep the diagnostic understandable. CSSOM View Module — W3C
Likewise, avoid estimating wrapped height with ceil(unwrappedWidth / availableWidth). That formula assumes text can break at arbitrary horizontal positions. Browser wrapping must follow the configured whitespace and breaking behavior, so measure the wrapped block instead. white-space — MDN
What should you check when measurements disagree?
Check the measurement boundary first. A full-width card rectangle and a narrower Range rectangle can both be correct. The card reserves layout space; the Range describes its selected text. Canvas glyph height supplies a third value with a different purpose.
Next, check font readiness. If you introduce a declared web font, document.fonts.load(fontSpecification, sampleText) can explicitly request matching font faces before measurement. Handle rejection rather than silently substituting a successful result. The optional text argument filters by Unicode ranges; it does not verify individual glyph coverage. FontFaceSet: load() method — MDN
Preserve shaping boundaries when calculating a label’s width. Measure the complete string instead of adding independently measured character widths. The Canvas specification prepares a positioned glyph run using the current font and text settings; its result is the appropriate unit for that string. Keep this sample’s Canvas comparison limited to its stated font settings, rather than assuming it reproduces every CSS typography feature. HTML Standard: The canvas element — WHATWG
For an existing page, measure its actual component in place. Moving text into a generic hidden box changes the layout conditions unless you also reproduce its relevant styles and containing context. Elements without associated layout boxes cannot supply useful normal box measurements, and CSS transforms affect the rectangles reported by these APIs. The synthetic example avoids both complications. CSSOM View Module — W3C
How can you repeat the preview at several browser sizes?
For occasional checks, enter each viewport size manually and record the report. Compare widths around the point where wrapping changes, rather than checking only a very narrow and a very wide layout.
For repeated checks, Playwright supports explicit viewport dimensions and a separate deviceScaleFactor setting. Its documentation also allows overriding a device preset’s viewport. This provides a supported automation path: establish the viewport, load the page, wait for its fonts and relevant content, then collect the DOM measurements in the browser. Emulation — Playwright
Keep CSS dimensions separate from display pixel density. Playwright exposes viewport and scale factor as distinct settings, while Canvas text metrics are expressed in CSS pixels. A denser display does not, by itself, require doubling the CSS width allocated to a label. Emulation — Playwright, TextMetrics — MDN
A useful preview result is therefore a small record of conditions: browser, viewport, text, typography, content width, and measured card height. Use the DOM result to size the wrapped component, retain Canvas metrics for single-run questions, and repeat the measurement in each browser whose rendering you need to represent.
Sources
- CSSOM View Module — W3C.
- HTML Standard: The canvas element — WHATWG.
- TextMetrics — MDN Web Docs.
- Element: getBoundingClientRect() method — MDN Web Docs.
- Document: fonts property — MDN Web Docs.
- FontFaceSet: load() method — MDN Web Docs.
- white-space — MDN Web Docs.
- line-height — MDN Web Docs.
- Simulate mobile devices with device mode — Chrome for Developers.
- Emulation — Playwright.