Taming vertical white space with leading-trim and Stitches

Rude Ayelo

/

March 2021

I've been designing and developing websites for a while now and one of the trickiest issues I always come accross is handling vertical white space between text nodes. When working on a layout in a visual design app, my typical workflow is:

  1. 1.Place text right near the previous/next one
  2. 2.Nudge up/down on a 4px basis until it looks right
  3. 3.Repeat

That way I'm able to ship solid and crisp designs quite quickly.

Translating those designs to HTML/CSS is a whole different story, text bounding boxes are defined by the line-height in use and it's not so easy to separate text blocks using a consistent spacing system.

That's why a while ago I created leading-trim, a little CSS-in-JS helper to trim spacing above and below the first and last lines of a text block. You can check what it does in this demo:

Trim vertical white space
24px margin

leading-trim demo

36px margin

By default text elements include vertical space based on its line-height value. The effect of that extra space may be overlooked or worked around, but when working with precise scales and layout components, there's probably no room for random spacing going around your text.

24px margin

leading-trim is a JavaScript port of EightShapes's Text Crop mixin (source). It returns a CSS styles object ready to be used with any CSS-in-JS library that let's you inject styles with nested pseudo-elements.

24px margin
Check leading-trim on Github
24px margin

Since I'm using Stitches to write the styles for this site, I thought I could make a little wrapper around leading-trim using its utils feature

import { createCss } from "@stitches/react";
import { leadingTrim } from "leading-trim";

const { styled } = createCss({
  theme: {
    fonts: {
      sans:
        "Source Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif",
    },
    fontSizes: {
      1: "16px",
      2: "20px",
    },
    lineHeights: {
      1: "1.5",
      2: "1.4",
    },
  }
  utils: {
    textSize: ({ theme }) => (value: `$${keyof typeof theme.fontSizes}`) => {
      const size = value.substr(1);

      return {
        textSize: value,
        margin: 0,
        ...leadingTrim({
          lineHeight: Number(theme.lineHeights[size]),
          reference: {
            textSize: 40,
            lineHeight: 1,
            topCrop: 4,
            bottomCrop: 8,
          },
        }),
      };
    },
  },
});

const Text = styled("p", {
  textSize: "$1"
});

I'm keeping a direct relation between fontSizes and lineHeights so font size $1 goes well with line height $1, that eases a bit the definition of the util. In this site the definition is a bit more complex to allow setting line-height on the fly and also adjusting leading-trim's correction:

textSize: "$1 / $1 / 2 / -1";

The value taken by the util is typed to match the fontSizes provided in the theme, but in this site I'm losely typing it to allow other values like clamp($6, 5vw, $7) and have responsive font sizes (leadingTrim returns sizes in em so it works perfectly fine). In that case parsing the size requires looking up for a specific token:

const sizes = value.match(/\$(\d+)/g);
const largestSize = sizes[sizes.length - 1].substring(1);

leadingTrim returns an object of CSS styles so we can destructure it right away

Since I started using this technique I feel way more confident about the outcome of my code and I don't go into visual design tools that much anymore.

Drop me a line on Twitter if you'd be interested in an npm package wrapping this util to easily include leading-trim in your Stitches based sites.

If your'e interested go check leading-trim on Github, which has some more information and links about the topic.