Setting up Theme UI for Vertical Rhythm
I’m trying out both vertical rhythm and Theme UI for the first time with this site. Here’s how I’m putting them together.
We’re going to take advantage of the “scales” feature that Theme UI inherits from Styled System. This lets you define, say, a range of font sizes or spacing amounts, and then reference them by index.
Here’s an example, adapted from the Funk preset:
{
fontSizes: [ 12, 14, 16, 20, 24, 32, 48, 64, 96 ],
lineHeights: {
body: 1.625,
heading: 1.2,
},
styles: {
h1: {
fontSize: 5, // 32px
lineHeight: "heading", // 1.2
},
…
}
}
When we want to enforce vertical rhythm, however, it becomes important to tie font size and line height together, so that the two combine to a multiple of the theme’s baseline. Let’s look at how we do that.
Picking a Baseline
First, you’ll need to pick what you want your baseline to be for the vertical rhythm. This is typically going to be the overall height of a line of body text, as well as a common denominator in your between-block spacing.
I chose a body text size of 16px
and a line height of 1.5
, so my baseline is
24px
. Let’s take advantage of the “JS” part of “CSS-in-JS” and save that out
as a constant, because it will come up a lot.
const BASE_FONT = 16;
const BASE_LINE_HEIGHT = 1.5;
const BASELINE = BASE_FONT * BASE_LINE_HEIGHT;
Calculating Font Sizes and Line Heights
As I mentioned in my last vertical rhythm post, I used the Modular
Scale calculator to choose a “harmonious” range
of font sizes. My choice for ratio was 1.414
(“augmented 4th / diminished
5th”).
Using a bit of exponent math, we can generate our font sizes:
const RATIO = 1.414;
// [ 16, 23, 32, 45, 64, 90 ]
const FONT_SIZES = [0, 1, 2, 3, 4, 5].map((n) =>
Math.round(BASE_FONT * RATIO ** n)
);
Given that array of font sizes, we can calculate the line-height
for each size
that will put it on a multiple of our baseline:
// [ 1.5, 1.0435, 1.5, 1.0667, 1.125, 1.0667 ]
const LINE_HEIGHTS = FONT_SIZES.map(
(f) => (Math.ceil(f / BASELINE) * BASELINE) / f
);
The javascript›Math.ceil(…)
part finds the next biggest multiple that will fit
the given font size. For example, javascript›Math.ceil(45 / 24) === 2
, so for
a 45px
font size we’d target fiting in to a baseline multiple of 2 (i.e.
48
).
Setting up the Theme
We now have a calculated a paired of arrays: javascript›FONT_SIZES
and
javascript›LINE_HEIGHTS
. For every font size we have a matching line
height that will maintain the vertical rhythm. That makes them a perfect fit for
Theme UI scales. All we need to do is be consistent with our values for
javascript›fontSize
and javascript›lineHeight
:
{
fontSizes: FONT_SIZES,
lineHeights: LINE_HEIGHTS,
styles: {
root: {
fontSize: 0,
lineHeight: 0,
},
h1: {
fontSize: 5,
lineHeight: 5,
},
…
}
}
Factoring Out Responsive Presets
The last step you can take is factoring out the paired
javascript›fontSize
/ javascript›lineHeight
properties into helper
“presets.” I used this so that I could be consistent with responsive settings for
logical text sizes.
export const TEXT_MEGA = {
// 45px below 768px
// 64px between 768px–960px
// 90px above 960px
fontSize: [3, 4, 5],
lineHeight: [3, 4, 5],
};
{
breakpoints: ['768px', '960px'],
fontSizes: FONT_SIZES,
lineHeights: LINE_HEIGHTS,
styles: {
h1: {
...TEXT_MEGA,
},
…
}
}
There are a few things to note about using the spread operator with themes:
-
Theme UI has support for “variants” to group styles together, though you can only apply a single variant to a style or element at a time. This makes them a better fit for making distinct kinds of a particular element (“primary button” being a common example) rather than utility styles that you’d be combining several of (such as “large text” and “inverted color scheme”).
-
JavaScript object spreads do not have the “deep merge” behavior that is common when thinking about styles. This is not a problem if you’re doing simple properties that you want to override, but is confusing if you’re mixing in nested styles:
const A_UNDERLINE_ON_HOVER = { a: { textDecoration: 'none' }, 'a:hover': {textDecoration: 'underline'}, }, { styles: { h1: { color: 'primary', a: { color: 'inherit' }, // This wipes out the "color: inherit" rule ...A_UNDERLINE_ON_HOVER, }, }, }
All done!
You can see this code in action in the
site-theme.ts
file in this blog’s theme.
Was this useful? Is there something cool I’m missing about Theme UI? Drop me an @-mention or DM on Twitter: @fionawhim