A mixed-size text line can exceed both spans’ line-height values because baseline alignment combines their upper and lower extents. This guide shows how to diagnose that extra height and adjust a two-label row in a Mac desktop browser.

Why doesn’t the tallest span determine the line height?

Consider an abstract page header containing a large Label A beside a small Label B. Both spans have line-height: 40px, yet their containing row measures more than 40 CSS pixels. That result follows from how inline layout aligns boxes before calculating the enclosing line.

For ordinary inline text, the browser calculates each box’s height contribution, aligns the boxes vertically, then encloses their combined extents. With baseline alignment, the highest top and lowest bottom can belong to different spans. The result can therefore exceed either individual contribution. The parent also contributes an invisible minimum-size box, traditionally called the strut. W3C: Line height calculations

The baseline is the alignment reference for the text. It is not the vertical midpoint of its line-height contribution. Giving differently sized text equal line heights does not give both pieces equal distances above and below that baseline. The initial vertical-align value is baseline. MDN: vertical-align

Keep three measurements separate: the font’s ascent and descent metrics, the visible bounds of the particular letters, and the space contributed to line layout. A span’s colored background or selection rectangle should not be treated as a direct measurement of the enclosing line box; CSS distinguishes inline painting geometry from line-height calculations. W3C: Leading and half-leading

How can two 40px contributions produce a 48.4px line?

For ordinary baseline-aligned text with an explicit line height, the useful calculation is: subtract the font’s ascent plus descent from the line height, then distribute the remaining space equally above and below. This extra space is called leading; each half is half-leading. The CSS Inline Layout Level 3 Working Draft describes this calculation and allows negative leading. Its draft status does not establish browser support for every newer property it contains. W3C: Calculating layout bounds

The following inputs are illustrative, not measurements of Arial or any Mac font. Assume a parent strut whose contribution matches the small span, with no other inline content or vertical offsets.

Contribution Font ascent Font descent Line height Half-leading Above baseline Below baseline
Large span 32px 8px 40px 0px 32px 8px
Small span 9.6px 2.4px 40px 14px 23.6px 16.4px
Parent strut 9.6px 2.4px 40px 14px 23.6px 16.4px

For this baseline-aligned example, take the greatest extent on each side independently: max(32, 23.6, 23.6) + max(8, 16.4, 16.4) = 48.4px. Each contribution totals 40px, but the large span sets the top while the small span and parent set the bottom. This worked result is derived from the documented layout calculation, not a predicted browser measurement. W3C: Inline layout model

The diagnostic question is therefore: which participant sets each edge? Reducing the small span’s line height may remove its lower contribution while leaving an identical contribution from the parent strut.

How can you reproduce and inspect it on a Mac?

Use Chrome on your Mac for this walkthrough. Open a new tab, navigate to about:blank, then press Command + Option + J to open the Console. Chrome documents this shortcut for running JavaScript. Chrome DevTools: Overview

Run the complete program below in that blank tab. It creates three synthetic rows with the same labels, prints their measurements, and uses a shadow root to keep the demonstration styles together. No package installation or external library is required. Running it again replaces its previous demonstration.

(async () => {
  document.getElementById('line-box-demo')?.remove();
  const host = document.createElement('div');
  host.id = 'line-box-demo';
  document.body.append(host);
  const root = host.attachShadow({ mode: 'open' });
  const style = document.createElement('style');
  style.textContent = `
    :host { display: block; padding: 24px; color: #222; background: #fff; }
    .label { margin: 20px 0 6px; font: 14px Arial, sans-serif; }
    .row { font: 12px/40px Arial, sans-serif; white-space: nowrap;
      margin: 0; padding: 0; border: 0; background: #e8eef4; }
    .large { font-size: 40px; line-height: 40px; }
    .small { font-size: 12px; line-height: 40px; }
    .trim-span .small, .trim-both .small { line-height: 12px; }
    .trim-both { font-size: 40px; }
  `;
  root.append(style);
  const cases = [
    ['', 'Shared 40px line-height'],
    ['trim-span', 'Smaller span only'],
    ['trim-both', 'Parent and smaller span']
  ];
  const rows = cases.map(([className, label]) => {
    const caption = document.createElement('p');
    caption.className = 'label';
    caption.textContent = label;
    const row = document.createElement('div');
    row.className = `row ${className}`;
    for (const [size, text] of [['large', 'Label A'], ['small', ' Label B']]) {
      const span = document.createElement('span');
      span.className = size;
      span.textContent = text;
      row.append(span);
    }
    root.append(caption, row);
    return { label, row };
  });
  await document.fonts.ready;
  console.table(rows.map(({ label, row }) => ({
    case: label,
    parentLineHeight: getComputedStyle(row).lineHeight,
    largeLineHeight: getComputedStyle(row.children[0]).lineHeight,
    smallLineHeight: getComputedStyle(row.children[1]).lineHeight,
    rowHeight: row.getBoundingClientRect().height
  })));
})();

The program waits for document.fonts.ready, which resolves after the document’s font loading and related layout work have completed. If you later introduce another font or change the content, measure again after that change has settled. MDN: FontFaceSet.ready

Read the three rows as a controlled comparison. The first gives both spans and the parent a 40px line height. The second reduces only the small span’s line height to 12px. The third also changes the parent’s font size to 40px, making its strut match the large text’s font size and line height. If the second row stays tall but the third shrinks, that comparison identifies the parent’s contribution.

Use your browser’s results rather than expecting the illustrative 48.4px value. The program measures each row with getBoundingClientRect(), which returns an element’s bounding rectangle including padding and borders. Here the rows have neither, contain a single unwrapped line, and have automatic height, making their heights useful proxies for the line-box heights. Measuring a span with the same API would measure that span’s rectangle, not directly expose the enclosing line box. MDN: getBoundingClientRect()

What should you check in an existing layout?

Control-click the relevant text and choose Inspect. In Elements, inspect both spans and their containing block. Use Styles to trace declarations and Computed to see the applied values; enable Show All when needed to inspect inherited properties. Chrome documents these views and their property filters. Chrome DevTools: CSS features reference

Record font-family, font-size, line-height, display, and vertical-align for each participant. Start with the parent rather than looking only at the two spans. Then use this sequence to isolate the cause:

  1. Confirm the layout model. Establish whether the children participate in normal inline flow or are flex or grid items.
  2. Confirm a single line. Use a wide test area and short synthetic labels so wrapping does not confuse the measurement.
  3. Check alignment. Temporarily remove baseline shifts or non-default vertical alignment before applying the simple calculation above.
  4. Change one contributor. Reduce the small span’s line height, measure, then test the parent’s font and line-height contribution separately.
  5. Restore realistic conditions. Recheck the intended font, available width, and text after identifying the cause.

Also check inheritance. A unitless line-height: 1.2 passes down a multiplier that each descendant applies to its own font size. A length such as 40px passes down a fixed length unless overridden; percentages compute against the declaring element’s font size. Those choices can give small text unexpectedly generous leading. normal is font- and browser-dependent, so it is unsuitable as a fixed numeric assumption. MDN: line-height values and inheritance

Which adjustment fits the intended result?

Choose the adjustment according to what the two labels represent. A compact header made of separate labels has different requirements from words embedded in a wrapping paragraph.

Intended result Adjustment to test What to verify
Keep a shared text baseline Give the small span an appropriate line height and align the parent’s font metrics with the main text The parent strut no longer supplies an unwanted lower extent
Align the tops of separate labels Test vertical-align: top on both inline spans The changed baseline relationship suits the design
Center separate labels within a row Use a flex container with align-items: center Narrow-width behavior and item wrapping still suit the layout
Keep continuous paragraph flow Retain inline text and use deliberate inherited line-height values All resulting lines accommodate the mixed text

vertical-align: top aligns an element and its descendants to the top of the line. middle has a different definition: it aligns the element’s midpoint with the parent baseline plus half the parent’s x-height. It therefore does not mean geometric centering within the whole row, and changing alignment does not guarantee a particular final height. MDN: vertical-align values

For two independent labels, flexbox provides explicit cross-axis alignment through align-items. Center alignment and baseline alignment express different design choices. Switching to flexbox changes the layout context, so use it when the labels are separate items and assess the result at narrower widths. MDN: align-items

A smaller line height can also let text paint beyond its layout contribution. Check neighboring lines after tightening spacing; a compact measured box alone does not establish that the text fits without overlap. W3C: Leading and half-leading

Can canvas measurements replace browser line layout?

Canvas measurements answer a related question. WHATWG defines measureText() and distinguishes font ascent/descent metrics from the actual bounding bounds of the particular text. These measurements are useful for canvas drawing, but an ink-bounds measurement does not incorporate the surrounding DOM parent’s strut, inline alignment, and line-height contributions. Do not use it alone to predict this row’s height. WHATWG: TextMetrics

Preserve text-shaping boundaries when experimenting. CSS requires shaping to continue across inline boundaries when there is no effective formatting change, while certain boundaries—including non-initial vertical alignment—require shaping to break. Avoid wrapping individual characters merely to measure them: splitting grapheme clusters or ligatures can produce inconsistent results. Keep complete labels or meaningful text runs intact. W3C: Text processing and shaping across element boundaries

To resolve the extra height, identify the participant that sets the top and the participant that sets the bottom, including the parent. Adjust their line-height contributions or choose the alignment model that matches the labels’ purpose, then measure the complete row again with its intended text and font.

Sources