Preserving vertical rhythm with CSS and jQuery

Preserving vertical rhythm with CSS and jQuery

Vertical rhythm is a core component of many type-based design approaches, here web developer Matthew Wilcox shows us the basics, and how to deal with irksome images…

Assuming you design from the content out, the first decision to effect your design has to be type related. Even by not choosing a typeface you’ve done something that impacts your site. Type is core to print and web design, and it’s complex; there are a lot of accumulated terms, practices, rules, and techniques that go into its good use. This article is concerned with one technique for managing one aspect of type, one that’s been hard to do online but is routine in print: maintaining a consistent vertical rhythm, which in turn allows us to achieve a professional layout.

Laying out type

In print, for any item with a sizeable amount of text, the foundation of the design is likely to be a baseline-grid. It’s used to bring structure to the page and guides the vertical flow of content. Almost everything is placed with respect to that baseline grid. Don’t worry if you don’t recognise the terms, no-one is unfamiliar with baselines or baseline grids; you already know about them. Think back to school, when you undoubtedly wrote on lined paper – as you wrote you placed the bottom of your letters neatly onto each of the lines on the paper. The baseline and baseline grid in action. The baseline is an imaginary line onto which the bottom of letters align. If you look at this article now, you’ll “see” a baseline, even though there isn’t really a line. Your brain puts one there for you, it’s why you can read sentences. The lines on lined paper? They’re a baseline grid. Straight so your sentences are straight, and set at regular intervals so your text has a regular vertical rhythm.

Vertical rhythm

Vertical rhythm dictates where on the page text is located. It is one component that effects our ability to scan and read blocks of text, and it helps inform our emotional responses. When text has a strong vertical rhythm and good spacing we feel it is professional, considered, authoritative, and comfortable to read. When text has poor rhythm and spacing we feel it is less considered, less professional, and often harder to read. Vertical rhythm is one part usability and one part aesthetics.

Conducting the rhythm

Unfortunately the web is still the poor cousin of print in terms of its ability to enact some fundamental practices regarding type. On the web we can’t use a baseline grid in the same way a print designer (or child at school) does – we can’t align the text’s baseline to a document’s baseline grid. CSS has no concept of a baseline grid. So, our text won't sit exactly on the lines of a baseline grid. Instead, it will be centred vertically in the gap between the lines. It’s the best the web can do.

Let's get practical with an example document. Firstly, we’ll set the beat by making a visible baseline grid. To do this we’ll use a repeating background image to draw regular horizontal lines 22px apart:

  1. html { background: #fff url(baseline_22.png); }

Hurrah, we have our lined paper!

You will note that nothing lines up. To make everything line up we want the bottom edge of all elements to hit one of those lines. The easiest way to do that is make sure all elements take up a vertical height (including margins) that is a multiple of 22. Here’s some CSS that does just that. I’m using the REM unit, but giving an EM fallback for browsers that don’t understand REM. I’m also including the PX unit equivalent in comments. If you don’t yet understand REM/EM then you could just use the px values instead – they’re all equivalent:

  1. html { /* font-size: 16px, line-height: 22px */
  2.         font: 100%/1.375 "Helvetica Neue", Helvetica, Arial, sans-serif;
  3.         background: #fff url(baseline_22.png); }
  4.                  
  5. h1, h2, h3, h4, h5, h6 { /* margin-top and bottom are both 22px */
  6.         /* em fallback */ margin: 1.375em 0;
  7.         margin: 1.375rem 0; }  
  8. h1 { /* font-size is 32px, line-height is 44px */
  9.         /* em fallback */ font-size: 2em;
  10.         font-size: 2rem; line-height: 1.375; }
  11. h2 { /* font-size is 26px, line-height is 44px */
  12.         /* em fallback */ font-size: 1.75em;
  13.         font-size: 1.75rem; line-height: 1.5714285714; }
  14. h3, h4, h5, h6 { /* font-size is 22px, line-height is 22px */
  15.         /* em fallback */ font-size: 1.375em;
  16.         font-size: 1.375rem; line-height : 1; }
  17. p, ul, blockquote { /* bottom margin is 22px, line-height is inherited from html (22px) */
  18.         /* em fallback */ margin-top: 0; margin-bottom: 1.375em;
  19.         margin-top: 0; margin-bottom: 1.375rem; }

Let’s take a look at what that gives us. Notice how all of the text aligns nicely? It doesn’t sit on the baseline, but it does have a predictable vertical rhythm. It’s nice and tidy.

Dealing with images

Images make things more complicated. Take a look what happens to our rhythm when we include some. They disrupt it like a skip in a record – the tempo is right but the timing is off. The alignment becomes shifted. It’s because images are unlikely to have a height that’s a multiple of the baseline, so the bottom edge doesn’t line up with our baseline grid.

To fix it all we really need to do is add a margin to each image, making the bottom of the margin line up with our grid. Which is simple enough to automate with a little JavaScript. Here’s our basic plan:

  1. Figure out the height of each image.
  2. See how many times the baseline value divides into the vertical space the image currently takes up, and get the remainder.
  3. Subtract the remainder from the baseline to give the offset we need to apply on the image.
  4. Apply the offset as a margin to the bottom of the image.

The bottom of the image’s vertical space would now align correctly with the baseline grid. Here’s a basic function in jQuery that does this:

  1. $(window).bind('load', function(){
  2.         $("img").each(function() {
  3.                 /* variables */
  4.                 var this_img   = $(this);
  5.                 var baseline   = 22;
  6.                 var img_height = this_img.height();
  7.  
  8.                 /* do the maths */
  9.                 var remainder  = parseFloat(img_height%baseline);
  10.  
  11.                 /* how much do we need to add? */
  12.                 var offset     = parseFloat(baseline-remainder);
  13.  
  14.                 /* apply the margin to the image */
  15.                 this_img.css("margin-bottom",offset+"px");
  16.         });
  17. });

Why the window.bind line? Because we have to wait until the images are loaded before we can reliably get their sizes. Here’s an example with this basic code running.

Improving the jQuery

The world is rarely straight-forward, and so it turns out here – we have to deal with quite a few implementation details. What’s wrong with the function we have? Plenty:

  • It produces nasty results with images that are inline rather than floated or block.
  • It seems buggy on some images, specifically the ones in containers.
  • It doesn’t deal with liquid layouts.
  • It’s not very re-usable.

We don’t want to apply this behaviour to images that are inline, like the smiley face in the example. Inline images are aligned so the bottom edge sits on the same baseline as the text (not the baseline grid). That means the image is offset vertically. Neither CSS nor JS give us a way to find out where the baseline for a text element is, so we don’t know the offset. We must ignore inline images, and apply our fix only to images that are set to display:block (fortunately, any floated image is automatically set to display block).

If an image is in a container the margin applied to the image may be hidden because of overflow settings on the container. Also, we may not want the margin on the image, but on the container element instead. In the example, we’d rather have margins on the white box than on the image inside the box, so we can avoid getting weird gaps that appear in the box.

The function only runs once, but on a liquid design the images change height when the browser is re-sized (try resizing the example to something quite narrow to see this). Resizing breaks the rhythm again. We need the function to run after the browser’s been resized as well as after page load. Liquid layouts also introduce other problems; images can be fractional pixels high, for example 132.34px. That in turn can cause unexpected results, even if we apply a fractional margin (if you’re interested, here’s why: trac.webkit.org/wiki/LayoutUnit). So, we’re going to need to massage the image into a whole pixel height to avoid layout bugs caused by fractional pixels.

Lastly, we should make this into a more re-usable function. In fact, with the complexity a practical solution needs over the theoretical solution, we should make a plug-in that can be re-used in other projects.

In the last example you’ll find the code that achieves all of this. The plug-in JavaScript is heavily commented so you can follow along. You can use the plug-in by calling it as follows:

  1. $(window).bind('load', function(){
  2.         $("img").baselineAlign();
  3. });

Or, you can tell the plug-in to apply the margin to a named container, if one exists as a parent of the image:

  1. $(window).bind('load', function(){
  2.         $("img").baselineAlign({container:'.popup'});
  3. });

Conclusion

Keeping a good vertical rhythm is a subtle but effective design practice used regularly in print. You now know the basic principles, have a working knowledge of baselines and the baseline grid, and know some of the limitations of CSS text layout versus print. You also know how to work around those limitations, compose your documents to any vertical rhythm you like, and you have a tool to help deal with disruptive image content.

As CSS matures we continue to get more features in-line with our print cousins, so a good understanding of type will become more important for creating quality websites. If you’d like to learn more about type in general I highly recommend www.thinkingwithtype.com (and buying the book to go with it). If you’re after CSS articles about type treatment there are numerous articles both here and elsewhere on the web. I’d also recommend catching up on the latest from Mark Boulton and Elliot Jay Stocks, both of whom talk often about type in relation to web design specifically.

Have fun!

11 comments

Comment: 1

This is very interesting to see.

Back in November I was working towards a similar solution, to use jQuery to keep vertical rhythm in a document containing images.

My base function is very similar to your own, although I hit stumbling blocks that still require further attention.

Namely:
Adjusting margins within a responsive design where the hit of the image alters as the browser window is resized.
Margins that don't end up too huge when images are at particular "annoying" dimensions.
Tying vertical rhythm in text across different containers. By this I mean where text sits in other coumns/elements - we can see on your example page that the caption text in each container doesn't match the rhythm o the main content, even before the smiley face upsets the flow.

I love this concept, and I'm pleased to see someone writing about it to prove I'm not mad for thinking this way. I'd love to keep working towards solving the issues that still arise.

Comment: 2

Apologies for typo's in previous post, can't see a way to edit...

"hit of the image" should read "height of the image"

Comment: 3

@looponthehook

Cool I'm glad you like it! The jQuery plugin is open source and hosted at https://github.com/mattwilcox/jQuery-Baseline-Align if you'd like to make any contributions - I'm all for improving the plugin!

If I understand you right, the problem is that you've got images resizing when the browser resizes, and this breaks the margin? If that's the case, this plugin solves it for you :) And the irregular margin issue - the plugin should give pretty consistent results there too.

-Matt

Comment: 4

I wrote a very basic script that does the same thing for my blog but yours looks a lot more robust so will try it out. By the way I've always subscribed to Eric Meyer's view that line-heights should be unitless.

Comment: 5

Cheers for the great and interesting blog post which i have definitely found absolutely fascinating. Its great to see the way you have preserved vertical rhythm throughout of your site with the use of CSS and Jquery. Definitely worth a read, ill be keeping this blog in mind for future reference.

Comment: 6

Interesting article. Vertical rhythm is something I've been trying to get my head around for ages, but I haven't been able to implement it reliably yet. Every time I try it always seems to end up being more effort than it's worth. Collapsing margins always catch me out.

I like the idea of a jQuery plugin to fix the image issue. It does look slightly odd though with varying amounts of whitespace below each image in your example. Have you considered using jQuery to wrap the images in a div with overflow:hidden and setting its height to the multiple of your baseline closest to the height of the image? You would essentially be cropping the image slightly, but it would always fit exactly on your baseline grid. There would be other points to consider, like if the image is floated etc, but I think it could work. If I get some time I may post back an example.

A small note on a point you made:

"I’m using the REM unit, but giving an EM fallback for browsers that don’t understand REM"

Using em as a fallback on your headings throws the vertical rhythm out for browsers that don't support rem, it is much better to use px as a fallback. For example; the margins on the h2 tag end up being 38px when defined using ems as it is calculated relevant to the h2s font-size (28px).

Also, I agree with @tyssen's point about unitless line-heights.

Olly

Comment: 7

Here is an example of what I mentioned in my previous post. It needs to be tidied up and tested properly but seems to work quite well as a proof of concept. Let me know what you think.

Olly

$(document).ready(function(){

// Get baseline.
var baseline = parseFloat($("html").css("line-height"));

// Wrap images inside a span element with overflow hidden and fix height
// to the closest multiple of the baseline.
$("img").load(function() {
var img = $(this),
height = Math.floor(img.height() / baseline) * baseline,
wrap = $('').addClass('baseline-image').css({
'float' : img.css('float'),
'height' : height,
'margin-bottom' : img.css('margin-bottom'),
'margin-left' : img.css('margin-left'),
'margin-right' : img.css('margin-right'),
'margin-top' : img.css('margin-top'),
'max-width' : img.css('max-width'),
'overflow' : 'hidden',
'width' : img.width()
});
img.wrap(wrap);
}
)

// Re-calculate image height on window resize.
$(window).resize(function() {
$('.baseline-image img').each(function() {
var img = $(this),
height = Math.floor(img.height() / baseline) * baseline;
img.parent().height(height);
});
});
});

Comment: 8

@tysenn + @nievo - the code is already using unit-less line-heights :)

@nievo - I'd considered it, but it's an aesthetic choice. I'd rather have images that are not arbitrarily cropped and slightly 'off' whitespace than perfect whitespace and mangled images. My personal taste is that I don't mind the whitespace anyway, it's not off very often at all - one of the goals and benefits of the JS plugin, if you compare the final page to the first one with images in it.

It doesn't have to throw off the vertical rhythm using EM rather than REM. Simply adjust the EM value until it's right - which is what happens here, the math has already been done and this example doesn't break whether the browser uses REM or EM. Other content pages would break if things became nested; a limit of EM's (and why REM was made).

Comment: 9

@MattWilcox - So you are. I must have been looking at the helpful comment that says what the resultant lin-height would be in pixels :-)

Good point. I would look pretty awful if you had an image 43px heigh and it got cropped to 22px just to fit the line height. It wouldn't be so much of an issue for large images though.

It's the fallback for heading margins that throw things out in your example:

h1, h2, h3, h4, h5, h6 {
margin: 1.375em 0;
margin: 1.375rem 0;
}

For browsers that understand rem the margin will be 1.375 x the root font size (22px in this case, same as the line-height). For browsers that don't understand rem the margins will be 1.375 x the heading font size. If you open up firebug or chrome dev tools and disable the rem rule you should see what I mean.

You're right you could use ems for a fallback. When I said it's 'better' to use pixels, I probably should have said easier :-)

Comment: 10

@nievo - ahhh damn, you're right! bit of a mess-up with my em fallback there, good spot :)

Comment: 11

Hi folks,

I created a small jQuery plugin to achieve something very much like what u guys are trying to do here. It's reusable and I think it works pretty well. I would love it if someone could give it a whirl and lets me know what they think.

It works by adjusting the margin top for inline images / objects (iframe for instance). For block elements it sets a margin bottom.

Find it on my github:
https://github.com/dagomar/rhythm.js
June issue on sale now!

The Week in Web Design

Sign up to our 'Week in Web Design' newsletter!

Hosting Directory
.net digital edition
Treat yourself to our geeky merchandise!
site stat collection