Heading

Every size grows from the one beneath it

Body

A base and a ratio produce the whole scale. The glyph height grows with the type so capitals never meet descenders, while the visual leading shrinks.

Here is a long link to demonstrate wrapping behavior. Text is inline editable so you can change it. https://typography.damato.design/font-size/#categories-of-text

Detail

Detail text adds to what has been said. It is the smallest size in the scale, and so it is the size that must stay accessible.
The CSS in use


/* <Text> */
.text {
  overflow-wrap: anywhere;
  text-wrap-style: pretty;

  --fs-detail: calc(var(--base) * pow(var(--ratio), var(--detail-step)));
  --fs-body: calc(var(--base) * pow(var(--ratio), var(--body-step)));
  --fs-heading: calc(var(--base) * pow(var(--ratio), var(--heading-step)));

  --fs-heading-max: calc(var(--fs-heading) * var(--fs-heading-safe-factor));
  --fs-heading-fluid: calc(
    progress(100vw, var(--w-min), var(--w-max))
    * (var(--fs-heading-max) - var(--fs-heading))
    + var(--fs-heading)
  );

  --glyph: calc((var(--cap-height) - var(--descender)) / var(--units-per-em));
  --leading: calc((var(--ascender) - var(--cap-height) + var(--line-gap)) / var(--units-per-em));
  /* Normal at the Detail size, then the space between lines stays the same.
     The ratio multiplied through by 1em: a length, so nothing divides by 1em
     and a grid can round it. */
  --line-height: calc(var(--glyph) * 1em + var(--leading) * var(--fs-detail));

  /* The Body face's line runs from the Detail size to the Body size. */
  --letter-spacing: calc(
    progress(1em, var(--fs-detail), var(--fs-body))
    * (var(--ls-min) - var(--ls-max))
    + var(--ls-max)
  );

  /* Only the categories set font properties; everything else inherits. */
  &:where(body, [data-root]) {
    font-family: var(--ff-body);
    font-size: var(--fs-body);
    line-height: var(--line-height);
    letter-spacing: var(--letter-spacing);
  }

  &:where(h1, h2, h3, h4, h5, h6) {
    font-family: var(--ff-heading);
    font-size: var(--fs-heading-fluid);
    line-height: var(--line-height);
    letter-spacing: var(--ls-heading);
  }

  &:where(figcaption, caption, small) {
    font-family: var(--ff-detail);
    font-size: var(--fs-detail);
    line-height: var(--line-height);
    letter-spacing: var(--letter-spacing);
  }

  &:where(h1, h2, h3, h4, h5, h6, dt, legend, label, th) {
    text-wrap-style: balance;
  }

  /* Words stay whole in labels, which are a word or two, and in table cells,
     which a column would otherwise squeeze until words break between letters. */
  &:where(dt, label, legend, th, td) {
    overflow-wrap: normal;
  }

  &:where(textarea, [contenteditable]) {
    text-wrap-style: stable;
  }

  &:where([data-wrap="true"]) {
    text-wrap-mode: wrap;
  }

  &:where([data-wrap="false"]) {
    text-wrap-mode: nowrap;
  }

  &:where([data-break="true"]) {
    word-break: break-all;
  }

  &:where([data-break="false"]) {
    word-break: keep-all;
  }

  &:where([data-screenreader-only]:not(:focus, :active)) {
    overflow: hidden;
    clip-path: inset(50%);
    height: 1px;
    position: absolute;
    white-space: nowrap;
    width: 1px;
  }
}