npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@canonical/styles-typography

v0.20.0

Published

Typography baseline alignment engines and scale tokens for the Canonical Design System.

Downloads

226

Readme

@canonical/styles-typography

Baseline grid alignment for the Canonical Design System. This package provides three interchangeable CSS engines that snap text baselines to a configurable grid, plus a semantic token mapper that bridges @canonical/design-tokens typography tokens to the engine's variable contract.

Quick Start

Import the default engine (cap-unit) and set your baseline height:

@import url("@canonical/styles-typography");

:root {
  --baseline-height: 0.5rem;
}

That's it. All h1h6 and p elements will align to the baseline grid. The default engine uses the CSS cap unit and requires no JavaScript font extraction.

How It Works

The browser adds invisible half-leading above and below each line of text. The exact amount depends on the font's internal metrics, the computed font-size, and line-height. This makes vertical alignment between different text elements unpredictable.

The baseline engines solve this by computing where the first baseline falls within a line box, then applying a padding-top / margin-bottom pair that nudges the element so its baseline lands exactly on a grid line. The complementary margin-bottom ensures the element's total outer height remains a multiple of --baseline-height.

 line-height (computed)
 +----------------------------------------------+
 |  half-leading (top)                           |
 |  +----------------------------------------+  |
 |  | ascender area                           |  |
 |  | - - - - cap line - - - - - - - - - - - |  |
 |  | glyph body                              |  |
 |  | - - - - baseline - - - - - - - - - - - |  |  ← snap this to the grid
 |  | descender area                          |  |
 |  +----------------------------------------+  |
 |  half-leading (bottom)                        |
 +----------------------------------------------+

The mod() CSS function does the heavy lifting:

--top-nudge: calc(
  var(--baseline-height) -
  mod(var(--baseline-position), var(--baseline-height))
);

Multi-line blocks stay on-grid because line-height is always set to a multiple of --baseline-height. The nudge only compensates for the first line's half-leading offset.

Engines

Three engines implement the same grid-snap pattern with different trade-offs. Import the one that fits your constraints directly, or use index.css which re-exports the default (cap-unit).

baseline-cap.css — Cap unit (default)

@import url("@canonical/styles-typography/src/baseline-cap.css");

Uses the browser-native cap CSS unit to resolve font metrics at render time. No JavaScript extraction step, no per-font variables. Changing font-family on an element automatically updates the 1cap value the engine uses.

The baseline position formula is (line-height + 1cap) / 2 — the browser resolves 1cap from the font's OpenType tables natively.

| Browser | Minimum version | |---------|-----------------| | Chrome | 117+ | | Safari | 17.2+ | | Firefox | 97+ |

baseline-metrics.css — Extracted metrics

@import url("@canonical/styles-typography/src/baseline-metrics.css");

The original engine with the widest browser support. Requires three CSS variables extracted from the font file using the extract-font-data CLI:

:root {
  --ascender: 1068;
  --descender: -292;
  --units-per-em: 1000;
}

The baseline position is computed from these metrics: ((line-height - line-height-scale) / 2) + ascender-scale. More verbose, but works everywhere mod() is supported.

baseline-trim.css — Text-box-trim hybrid

@import url("@canonical/styles-typography/src/baseline-trim.css");

The most modern approach. Uses text-box: trim-both cap alphabetic to remove half-leading entirely, then compensates with mod()-based margin to restore grid alignment. Results in tighter content boxes (useful for buttons, cards, optical centering).

| Browser | Minimum version | Notes | |---------|-----------------|-------| | Chrome | 133+ | | | Safari | 18.2+ | | | Firefox | — | Not yet implemented |

Falls back gracefully: if text-box-trim is unsupported, the element keeps its default half-leading and the nudge still applies.

Consumer Contract

Every engine reads the same set of CSS custom properties per element:

| Variable | Scope | Description | |----------|-------|-------------| | --baseline-height | :root | Grid unit size (e.g. 0.5rem) | | --font-size | element | Font size as a <length> | | --line-height-multiplier | element | Line height in baseline-height units | | --line-height | element | Optional override: explicit line height, bypasses the multiplier | | --space-after | element | Optional: extra bottom margin in baseline-height units |

The metrics engine additionally requires on :root:

| Variable | Description | |----------|-------------| | --ascender | Font ascender value (unitless, from OpenType tables) | | --descender | Font descender value (unitless, negative) | | --units-per-em | Font units-per-em value |

Token Mapper

The mapper.css file bridges the semantic typography tokens from @canonical/design-tokens to the engine's variable contract. It is imported automatically by the default engine (baseline-cap.css).

The design tokens provide variables like:

--typography-heading-1-font-size
--typography-heading-1-line-height    (unitless ratio)
--typography-heading-1-font-weight
--typography-heading-1-letter-spacing
--typography-heading-1-font-family

The mapper converts these into the engine variables for each element (h1h6, p), including computing --line-height-multiplier by snapping the typographic line-height to the nearest baseline-grid unit:

--line-height-multiplier: round(
  up,
  calc(font-size × line-height-ratio / baseline-height),
  1
);

Package Structure

src/
  index.css              ← re-exports baseline-cap.css (default)
  baseline-cap.css       ← cap-unit engine
  baseline-metrics.css   ← extracted-metrics engine
  baseline-trim.css      ← text-box-trim + cap hybrid
  mapper.css             ← semantic token → engine variable bridge
  scripts/
    extractFontData.ts   ← CLI for extracting font metrics
example/
  index.html             ← interactive demo with engine switcher
  serve.ts               ← dev server with live-reload
  scripts/               ← sidebar, font picker, content presets
  fonts/                 ← bundled .woff2 files for the demo
  styles/                ← demo layout and debug styles

Extracting Font Metrics

The extract-font-data CLI reads OpenType metrics from a font file. This is only needed when using the baseline-metrics engine.

bun run extract-font-data ./path/to/font.ttf

Output:

Add the following variables to your CSS and follow the instructions in the README:
:root {
  --ascender: 1068;
  --descender: -292;
  --units-per-em: 1000;
}

Pass --all to see the full metrics table including computed nudge values for a given line-height:

bun run extract-font-data ./path/to/font.ttf 1.5 --all

Caveats

The extractor uses opentype.js to parse font files. It works with most .ttf, .otf, and .woff files but may fail on fonts without TrueType or CFF outlines:

error: Font doesn't contain TrueType or CFF outlines.

Development

Start the interactive example with live-reload:

bun run dev

This serves the example at http://localhost:3333/example/. The sidebar lets you:

  • Switch engines — toggle between cap-unit, metrics, and text-trim in real time
  • Switch fonts — pick from bundled fonts with auto-applied metrics
  • Tune per-tag — adjust font-size, line-height multiplier, and space-after for each heading level and paragraph
  • Adjust baseline — change the baseline grid height and see the alignment update

The baseline grid is rendered as a red 1px line overlay so alignment errors are immediately visible.

Browser Support

All engines require mod() for the grid-snap calculation:

| Browser | mod() support | |---------|-----------------| | Chrome | 125+ | | Safari | 17.4+ | | Firefox | 128+ |

The cap-unit and text-trim engines additionally require the cap CSS unit (Chrome 117+, Safari 17.2+, Firefox 97+) and round() function respectively. See each engine section above for specific requirements.