Svelte Capsize Styling: Typography Tooling
Tags: svelte, css, javascript, webdev
Published: 2022-05-04 10:55:46


---
title: "Svelte Capsize Styling: Typography Tooling"
published: "true"
description: "Svelte Capsize styling: how you can use the Capsize typography tooling with SvelteKit to layout text predictably in your ❤️ Svelte app."
tags: "svelte, css, javascript, webdev"
canonical_url: "https://rodneylab.com/svelte-capsize-styling/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdzcqv58a9rs7hnlx3sn.png"
---


## 📐 Capsize Typography Tooling

In this post we will see how you can add Svelte **Capsize** styling to your SvelteKit app.  Although we use **SvelteKit** you should be able easily to adapt the code for **Astro** or Slinkity.  Before that though we should take a look at what Capsize is and the **problems it solves**.  Capsize is a tool which helps you to design **text layouts** in a **predictable way**.  Typically to adjust how big a piece of text appears in the browser window, we change the CSS font-size property.  This works but the spacing above and below the text as it appears in the browser is not easy to determine.  The space below the baseline and above the text is unpredictable and changes from font to font even with fixed font size.

In summary, if you set a font size of `16px` or even `1em`, you will not know predictably what the height of any displayed character will be.  This is quite different to, for example, how `img` or other block elements are defined &mdash; we can reliably and predictably set height for these.  Capsize lets us define a **cap height** to adjust the size of rendered characters.  Further, it gives us parameters needed to trim a **snug bounding box**.  This is fantastic for creating predictable text layouts.

## 🧱 Svelte Capsize Styling: Building

![Svelte Capsize Styling: Typography Tooling: Screenshot shows two charts side by side. Each chart is a mockup os an optometrist test chart with rows of letters growing smaller with each row.  The left chart shows output with capsize applied, while the right, shows without. The capsize letters are snug to the background but the without capsize letters have noticeable gaps above and below letters.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ekem4to7vxjg13t87mi.png)

In the image above we can see Capsize makes it possible to generate snug text elements. Capsize does this by generating a styles object similar to those created by CSS in JS frameworks.  To *sveltify* these styles objects, we will use Svelte actions and custom properties.  Interested to hear if you have another method of attack! Drop a comment below if you do.

Rather than build out an app, in this post we will look at how to set up Capsize and then see a way to apply the generated styles in Svelte.    Although we won&rsquo;t build out an app, there is a <a aria-label="Open the Rodney Lab Git Hub repo" href="https://github.com/rodneylab/svelte-capsize-style" rel="follow noopener noreferrer">full demo on the Rodney Lab GitHub page</a> if you want a closer look at how the implementation works. We will look at SvelteKit code here, though the code should work in  <a href="https://rodneylab.com/tags/astro" aria-label="See posts on Astro J S">Astro</a> or Slinkity.

## ⚙️ Capsize Setup

The only <a aria-label="Open Capsize docs" href="https://seek-oss.github.io/capsize/">setup for Capsize</a> is to install a couple of packages:

```shell
pnpm add -D @capsizecss/core @capsizecss/metrics
```

## 📏 Font Metrics

To get a better understanding of Capsize inputs and outputs, here is a screenshot from a tool on the Capsize site.  It shows metrics for the Courier New font which we use in the demo code.

![Svelte Capsize Styling: Typography Tooling: Capsize Metrics: diagram shows Courier font letters Hg. Cap Height matches the height of uppercase H.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zqmz2b0yd2hhw3xo2vgq.png)

The Em square is a fundamental unit which originates from printing.  It is a square with sides set equal to the width of an uppercase &lsquo;M&rsquo; printed using the typeface.  For us, it represents the space the element takes up on screen and is used to set font size.

We can specify font size or **cap height** as a Capsize input.  Lets say we need tighter control over the height of each row in our comparison chart (above). Setting cap height gives us the control and predictability we a looking for; if we want capital letters to be `48px` high, we can easily have that by setting cap height to that value.  If we were replicating a figma or penpot design with font heights set we might instead opt for setting font size.

## 🚤 Using Capsize in Svelte

Let&rsquo;s start by seeing what Capsize creates for us.  The second row of the With Capsize / Wihtout Capsize comparison charts has cap height set to `48px`.  We&rsquo;ll use that as a concrete value to examine Capsize inputs and outputs.  Since the metrics vary from font to font, we need to specify our font as an input.  Here&rsquo;s the Svelte code for the `48px` cap height example:

```svelte
<script>
  import { createStyleObject } from '@capsizecss/core';
  import courierPrimeMetrics from '@capsizecss/metrics/courierPrime';

  const styles = createStyleObject({
    capHeight: 48,
    lineGap: 24,
    fontMetrics: courierPrimeMetrics,
  });
</script>
```

We import Courier Prime font metrics from `@capsizecss/metrics/courierPrime` in line `3`.  These are then used to generate the styles (lines `5`&ndash;`9`).  Let&rsquo;s take a peek at these metrics:

```javascript
{
  familyName: "Courier Prime",
  capHeight: 1187,
  ascent: 1600,
  descent: -700,
  lineGap: 0,
  unitsPerEm: 2048,
  xHeight: 924
}
```

These match the data from the screenshot of the tool on the Capsize size above.
The first question you will ask is probably how these numbers relate to the font size.    For our precise use case we want to set cap height so we know how high our upper case letters in the chart will be.  The `unitsPerEm` field in the metrics is just the height of the em square. So the ratio of em square height to cap height is `2048 / 1187 = 1.725`.  This means if we set capHeight to `48px` when using Capsize, we want a `48px * 1.725 = 82.8px` font for the without Capsize comparison. 


You can examine the precise font measurements using FontForge.

![Svelte Capsize Styling: Typography Tooling: FontForge Courier Prime Font: Screenshot shows a large letter E with various dimensions marked on the FontForge window](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihav1d38uk8hwls2k1b7.png)

It also provides another route to the metrics above.

![Svelte Capsize Styling: Typography Tooling: FontForge metrics: screenshot shows various measurements for a letter in FontForge.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30h1hobv83rieub73yfn.png)

We won&rsquo;t go into more detail on metrics here, though there is an <a aria-label="Open deep dive on font metrics" href="https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align">excellent Deep dive on font metrics by Vincent De Oliveira</a>.  Google Fonts also provide an <a aria-label="Read about the em square on Google" href="https://fonts.google.com/knowledge/choosing_type/exploring_x_height_the_em_square">alternative explanation of em square</a>.

In the rest of the article we focus on the Svelte implementation.

## 💄 Styles object

The styles object generated for our example looks like this:

```javascript
{
  fontSize: "82.8172px",
  lineHeight: "72px",
  "::before": {
    content: "''",
    marginBottom: "-0.0748em",
    display: "table"
  },
  "::after": {
    content: "''",
    marginTop: "-0.215em",
    display: "table"
  }
}
```

You will notice we have `::before` and `::after` pseudo elements in there.  Svelte has style directives (we will see an example shortly) which can set styles for an element itself, but not for pseudo elements.  Using Svelte actions, it is possible to set custom CSS properties (often also called CSS variables).  Our plan of attack is to use Svelte actions to define all the CSS values we need (pseudo and actual element).  We will define these on a parent wrapper element, so that they are in scope on the actual lines we want to use them in.  So markup is roughly something like this:

```svelte
<span class="container">
  <div class="line with-capsize-line"><slot /></div>
</span>
```

with scoped CSS

```css
  .with-capsize-line { 
    font-size: var(--font-size);
    line-height: var(--line-height);
  }

  .with-capsize-line::before {
    content: var(--before-content);
    margin-bottom: var(--before-margin-bottom);
    display: var(--before-display);
  }

  .with-capsize-line::after {
    content: var(--after-content);
    margin-top: var(--after-margin-top);
    display: var(--after-display);
  }
```

## 🎬 Styles Action

Svelte lets you <a aria-label="Read about how to use C S S custom properties with Svelte stores" href="https://svelte.school/tutorials/how-to-use-variables-in-style-tags-in-svelte">define CSS custom properties on an element</a>.  It takes care of making them available within CSS for you. With that helping hand, effectively we need to transform the container span above into something like:

```svelte
<span --font-size={X} --line-height={Y} --before-content={Z} class="container">
  <div class="line with-capsize-line"><slot /></div>
</span>
```

That is what we will use Svelte actions for. Svelte actions have the node they are applied to as the first parameter.  There can also be a second parameter which takes the value we set on the node.  We will use that to pass the styles object in.  Then the action&rsquo;s job is to split the styles object up into custom properties we want added to the node.  Here is the code:

```javascript
/**
 * @param {HTMLElement} node - action element
 * @param {{fontSize: string; lineHeight?: string; '::before'?: {content?: string; marginBottom?: string; display?: string}; '::after'?: {content?: string; marginTop?: string; display?: string}}} styles - element styles
 */
function setCustomProperties(node, styles) {
  const { fontSize, lineHeight, '::before': before, '::after': after } = styles;

  fontSize && node.style.setProperty('--font-size', fontSize);
  lineHeight && node.style.setProperty('--line-height', lineHeight);

  if (before) {
    const { content, marginBottom, display } = before;
    content && node.style.setProperty('--before-content', content);
    marginBottom && node.style.setProperty('--before-margin-bottom', marginBottom);
    display && node.style.setProperty('--before-display', display);
  }
  if (after) {
    const { content, marginTop, display } = after;
    content && node.style.setProperty('--after-content', content);
    marginTop && node.style.setProperty('--after-margin-top', marginTop);
    display && node.style.setProperty('--after-display', display);
  }
}

/**
 * @param {HTMLElement} node - action element
 * @param {{fontSize: string; lineHeight: string; '::before': {content: string; marginBottom: string; display: string}; '::after': {content: string; marginTop: string; display: string}}} styles - element styles object
 * @returns{{update: (styles: {fontSize: string; lineHeight?: string; '::before'?: {content?: string; marginBottom?: string; display?: string}; '::after'?: {content?: string; marginTop?: string; display?: string}}) => void}} - 
 */
function styles(node, styles) {
  setCustomProperties(node, styles);
  return {
    update(styles) {
      setCustomProperties(node, styles);
    },
  };
}

export default styles;
```

## 🧩 Using the Action: Line Component

The structure of the app is that we create each of the two charts using a Chart Svelte component.  Then each line in a chart is a line Svelte component.  The line components are where we use Capsize.  Here is a possible implementation of the line component, using the new action:

```svelte
<script>
  import { createStyleObject } from '@capsizecss/core';
  import courierPrimeMetrics from '@capsizecss/metrics/courierPrime';
  import capsizeStyles from '$lib/shared/actions/styles';

  /** @type {number} */
  export let capHeight;

  const styles = createStyleObject({
    capHeight,
    lineGap: 24,
    fontMetrics: courierPrimeMetrics,
  });

</script>

<span use:capsizeStyles={styles} class="container">
  <div class="line with-capsize-line"><slot /></div>
</span>
```

You see how we use the action in code in line&nbsp;`17`.

## 💄 Chart Component Styling: Svelte Style Props

As an aside the two charts have different background colours.  In the code where we consume them, we can set the CSS custom property for the background right on the element:

```svelte
<!-- ...TRUNCATED> -->
<Chart --background-colour="hsl(340deg 70% 70%)" withCapsize={false} />
```

Then in the chart component itself, we use this in the CSS

```svelte
<style>
  .container {
    padding: var(--spacing-12) var(--spacing-4);
    background-color: var(--background-colour);
    width: var(--max-width-full);
    height: 100%;
  }
</style>
```

These are called **style props**.  You can <a aria-label="Read about style props in the Svelte docs" href="https://svelte.dev/docs#template-syntax-component-directives---style-props">read more on style props in the Svelte docs</a>.

## ⏱ Final Line Component

For completeness, here is the final Line component code:

```svelte
<script>
  import { createStyleObject } from '@capsizecss/core';
  import courierPrimeMetrics from '@capsizecss/metrics/courierPrime';
  import capsizeStyles from '$lib/shared/actions/styles';

  /** @type {number} */
  export let capHeight;

  /** @type {boolean} */
  export let withCapsize;

  const styles = createStyleObject({
    capHeight,
    lineGap: 24,
    fontMetrics: courierPrimeMetrics,
  });

  const { capHeight: capHeightEm, unitsPerEm } = courierPrimeMetrics;
  const fontSizeToCapHeight = unitsPerEm / capHeightEm;
  const fontSize = `${capHeight * fontSizeToCapHeight}px`;
</script>

{#if withCapsize}
  <span use:capsizeStyles={styles} class="container">
    <div class="line with-capsize-line"><slot /></div>
  </span>
{:else}
  <span class="container">
    <div style:font-size={fontSize} class="line">
      <slot />
    </div>
  </span>
{/if}

<style>
  .line {
    background-color: var(--colour-light);
    color: var(--colour-dark);
    margin: var(--spacing-4) auto;
    font-weight: var(--font-weight-bold);
    font-size: var(--font-size);
    text-align: center;
    width: fit-content;
    line-height: calc(1 / 1.2);
  }

  .with-capsize-line {
    line-height: var(--line-height);
  }

  .with-capsize-line::before {
    content: var(--before-content);
    margin-bottom: var(--before-margin-bottom);
    display: var(--before-display);
  }

  .with-capsize-line::after {
    content: var(--after-content);
    margin-top: var(--after-margin-top);
    display: var(--after-display);
  }
</style>
```

The promised **style directive** example comes in line 29.  We set `font-size` on the div.  We use a style directive here because we have a dynamic property.  This value overrides the one set in the `style` block.

## 🙌🏽 Svelte Capsize Styling: Wrapup

**In this post we looked at**:

- how to use **Capsize with Svelte** for more predictable text layouts,
- adding CSS custom properties to a Svelte element using **Svelte actions**,
- how you can add **style props** to Svelte components, setting CSS custom properties (**CSS variables**).

I do hope there is at least one thing in this article which you can use in your work or a side project. Also let me know if you feel more explanation of the config is needed.

You can see <a aria-label="Open the Rodney Lab Git Hub repo" href="https://github.com/rodneylab/svelte-capsize-style" rel="follow noopener noreferrer">the full SvelteKit code for this project on the Rodney Lab Git Hub repo</a>.  If you run into any issues, you can drop a comment below as well as reach out for a chat on <a aria-label="Jump into the Element Matrix chat room" href="https://matrix.to/#/%23rodney:matrix.org">Element</a>.  Also <a aria-label="Mention on Twitter" href="https://twitter.com/intent/user?screen_name=@askRodney">Twitter @mention</a> if you have suggestions for improvements or questions.

## 🙏🏽 Feedback

If you have found this video useful, see links below for further related content on this site. I do hope you learned one new thing from the video. Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on Twitter, giving me a mention so I can see what you did. Finally be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a aria-label="Reach out on Twitter" href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter and also <a aria-label="Contact Rodney Lab via Telegram" href="https://t.me/askRodney">askRodney on Telegram</a>. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/sveltekit/">SvelteKit</a> as well as <a href="https://rodneylab.com/tags/astro" aria-label="See posts on Astro J S">Astro</a> among other topics. Also <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://rodneylab.com/about/#newsletter">subscribe to the newsletter to keep up-to-date</a> with our latest projects.