Styling a poem with advanced CSS selectors

15 June 2009, in CSS, HTML | 31 comments »

CSS3 selectors offer endless possibilities of targeting specific HTML elements without the need of extra markup (which was already possible with previous versions of CSS). This time, I’m going to style the popular nursery rhyme “Twinkle Twinkle Little Star” using advanced CSS selectors, all in less than 5 minutes!

Coding a poem

There isn’t a consensus on how a poem should be marked up using HTML: most people believe that each stanza should be a paragraph (p) and each line separated by a line break (br). There have been some mentions to a future poem tag on HTML5, but it’s very unlikely to happen! :)

The markup

There are 3 main elements in our markup: 1) the title; 2) the sub-title, with the author information; and 3) the poem itself. This is the very simple markup we are going to use for this example:

<h1>Twinkle Twinkle Little Star</h1>
	
<h2>by Jane Taylor, 1806</h2>

<p>Twinkle, twinkle, little star,<br />
How I wonder what you are!<br />
Up above the world so high,<br />
Like a diamond in the sky!</p>
	
<p>When the blazing sun is gone,<br />
When he nothing shines upon,<br />
Then you show your little light,<br />
Twinkle, twinkle, all the night.</p>
	
<p>Then the traveller in the dark,<br />
Thanks you for your tiny spark,<br />
He could not see which way to go,<br />
If you did not twinkle so.</p>
	
<p>In the dark blue sky you keep,<br />
And often through my curtains peep,<br />
For you never shut your eye,<br />
Till the sun is in the sky.</p>
	
<p>As your bright and tiny spark,<br />
Lights the traveller in the dark,&mdash;<br />
Though I know not what you are,<br />
Twinkle, twinkle, little star.</p>

Styling the headings

Before starting to style the elements on the page, lets just add some basic resets and defaults:

body {
	background: #FFF;
	color: #111;
	font: 14px Baskerville, "Palatino Linotype", "Times New Roman", Times, serif;
	text-align: center;
	}
	
	div, h1, h2, p {
		margin: 0;
		padding: 0;
		}

We will also wrap our poem inside a div, to center the content nicely on the page:

#poem { 
	margin: auto;
	padding: 20px 0;
	text-align: left;
	width: 390px;
	}

Next, we will center our headings and override the default bolds that headings have:

h1, h2 {
	font-weight: normal;
	text-align: center;
	}

Now, we will set the font size of the main heading, the h1 tag; add a nice margin to the bottom; and a small line-height (because the font is so large, this way if our heading is very long, the lines won’t be too far apart from each other):

h1 {
	font-size: 34px;
	line-height: 1.2;
	margin-bottom: 10px;
	}

For the h2 heading, with the author and date details, we will use a different, lighter colour; set a smaller font-size; make it italic, for that extra special touch; and also add a bottom margin:

h2 {
	color: #666;
	font-size: 18px;
	font-style: italic;
	margin-bottom: 30px;
	}

The poem

For the body of the poem itself, we will add a larger line-height, so that each line has some white space around it and is easier to read. We will also add a bottom margin to each paragraph, to visually separate each stanza:

p {
	line-height: 1.5;
	margin-bottom: 15px;
	}

The magical selectors

And now we can start having fun! First, lets add a decorative element before and after the author details. This will be done using the :before and :after pseudo-elements in conjunction with the content CSS property:

h2:before {
	content: '— ';
	}
			
h2:after {
	content: ' —';
	}

Now, let’s make the first letter of the poem really big — a drop cap. For that, we will be using the adjacent sibling selector (+) and the :first-letter pseudo-element. The adjacent sibling selector lets us target selectors that directly follow each other and are within the same parent element (for example, inside the same div). We will target the first letter of all paragraphs that follow an h2 tag, which, in our example, is only the first paragraph of the poem:

#poem h2 + p:first-letter {
	float: left;
	font-size: 38px;
	line-height: 1;
	margin: 2px 5px 0 0;
	}

So we floated the first letter to the left, so that the rest of the text wraps around it; used a bigger font size; reduced the line-height, so that the following lines aren’t pushed down; and slightly repositioned the letter within the paragraph with some margin settings.

Next, we’ll make the first line of each paragraph display the letters in small-caps, and, to make it easier to read, add some extra space between each letter:

#poem p:first-line {
	font-variant: small-caps;
	letter-spacing: 1px;
	}

To wrap things up, we’ll just add an extra bottom margin to our last paragraph, so that the poem is well separated from whatever comes next. For that, we will use the :last-child pseudo-element, which lets us target an element that is the last element of its parent:

#poem p:last-child {
	margin-bottom: 30px;
	padding-bottom: 30px;
	}

And we’re done!

Some notes on browser support

Don’t expect this to work equally on every major browser. The latest versions of Safari, Firefox and Opera do support all the selectors. But… Internet Explorer 8 doesn’t support the :last-child pseudo-class; Internet Explorer 7 doesn’t support :last-child or the :before and :after pseudo-elements; and IE6 doesn’t support the :first-letter pseudo-element, on top of that.

Conclusion

This is a very basic tutorial on how CSS advanced selectors can be used in our stylesheets to address specific HTML elements without having to use extra classes, IDs or bloated markup. It will not work on older browsers, like Internet Explorer 6, which will render only the most basic CSS properties (with few exceptions), but that won’t make the text unreadable — it will simply add a new layer of detail to modern browsers, as if it was a little present for people who are up-to-date.

View example Download source files

Further reading

There are 31 comments:

  1. Thanks for a very nice article! Really useful CSS tips!

    If you’ll allow me to nitpick (it’s only a very small one), I would argue that semantically speaking the author information is not technically a sub-heading, especially with the inclusion of the word ‘by’. I’d be tempted to simply include that information in a p tag and give it a class ‘author’ or something like that.

  2. inayaili says:

    Actually I thought about doing that for the example, but I also wanted to have a class-free code and use as many tags as possible, so I stuck with h2 :)

  3. Bob Hay says:

    Very very nice!
    I think you inspired my next project.
    In 1998, I HTML-ed an old book of poems. I think I’ll revisit that. It should be pretty easy since it’s very old-school HTML – done before I ever heard of the font tag.
    Thanks.

  4. Thanks great, Bob! Thanks for letting me know :)

  5. Bob Hay says:

    I’ve done my project. You can see it at http://www.netnik.com/khayyam/

    Maybe you can help with a selector. In this poem, every third line is indented (right now with nbsp’s, but I’d rather take those out.) I tried using “#poem p:nth-child(3) {…” but that’s not working. Any ideas?

  6. inayaili says:

    Ooh! That wouldn’t work because it’s not the 3rd paragraph (and only the third), it’s every three *lines* you want.

    I would use a paragraph for each line, instead of breaking the lines with br, and then use this CSS:

    p { margin-bottom:2px; }

    h3 + p { font-variant:small-caps; }

    p + h3 { margin-top:20px; }

    p:nth-of-type(4n-1) { text-indent:20px; }

    It will only be visible by Safari, probably. Let me know if it worked!

  7. inayaili says:

    Forgot to mention – the :nth-of-type selector lets you target only that type (in this case, only paragraphs) and ignore whatever is in the middle.

    If you used :nth-of-child it would count with the h3 tags and other tags between paragraphs, so it wouldn’t make the math properly.

  8. Bob Hay says:

    I think I’ll leave it as is for now. I will revisit it once FF and IE catch up. When I was trying it out yesterday, Chrome seemed to understand the selector, too.
    Thanks!

  9. Will says:

    Looks absolutely fantastic, very creative. Just too bad that IE can’t seem to render it to the same quality as a more modern web browser. Alas, the people using IE wont care how it looks…

  10. art says:

    very nice. some styles are new for me

  11. It’s a marvelous tutorial about CSS

  12. Richard says:

    <<#poem h2 + p:first-letter In (x)HTML+CSS, floated elements need to have a width declared. Only elements with an intrinsic width (html, img, input, textarea, select, or object) are not affected <<

    Inayaili,
    Really interesting article, thanks. I tried similar for a friend but getting a W3C CSS validation error identical to message above from your example page!
    Using IE8 (like millions of others) and dropcap displays fine.
    But – can you comment on how to rid CSS validation of this error? Is it something we have to accept? Is it Ok?
    Richard
    Hong Kong

  13. inayaili says:

    @Richard: Both the XHTML and the CSS in the example validate, as XHTML 1.0 Strict and CSS 3, respectively. It’s true that a floated element should have a width, but for this case that wouldn’t be practical.

  14. Richard says:

    >Here’s the CSS2.1 error: 65 Unknown pseudo-element or pseudo-class :last-child<

    But we are using CSS 2.1, right? Great to be out ahead pushing the bleeding edge. And I am still using IE8 for testing cos most of Planet Earth are using same. :)
    Will have to live with my unvalidated poem pages. :(
    Best. Richard

  15. inayaili says:

    @Richard: we are using CSS3, :last-child was only introduced with the CSS3 specs.

  16. Matthew says:

    This is really neat, but it doesn’t address my consistent frustration with formatting poetry on the web: indentation. In some [poetic] styles, a line of poetry needs to pick up visually where the last line left off, and there are currently only two ways to do this: pre tags or non-breaking spaces.

    The first ruins your typography and makes it look crappy (can you style pre? I’ve never tried, now that I think about it) and the second doesn’t account for different resolutions or browsers well.

    Example at http://silverpenpub.net/poetry/january

  17. Florent V. says:

    @inayaili:
    I’ll add to Mark’s nitpicking regarding the H2 tag, which is not appropriate here (though it’s not a big deal).

    In HTML4, solutions include putting both the poem’s title and the author in a single H1 (break it in two SPAN elements, or a STRONG and a SPAN maybe), or using a neutral paragraph-level element for the author (P or DIV). You could even change your exemple to use a simple P for the author with no class, then use selectors such as:
    #poem h1 + p:before
    #poem h1 + p + p:first-letter

    In HTML5, the best solution will be to wrap your title and author in a HGROUP element, in which you can use a H1 and a H2 (the H2 won’t count for the document’s outline), or a H1 and a P. Then, in CSS, use selectors such as:
    #poem hgroup h1
    #poem hgroup p

    @Matthew:
    You have two problems. The first one is that neither HTML nor CSS were designed to account for specific use cases such as precise poem layout and typography. Content formatting according to very specific rules would call for a ad hoc XML format.

    The second problem, and the most important one, is that what you’re trying to do just doesn’t work for text of changing sizes (for whatever reasons) on varying displays. For instance, the very idea of a line of poetry that always gets displayed as a single line of text doesn’t work on screens (think mobile screens, people that considerably increase text size for a comfortable read, etc.). You have to either drop the nifty plays on spacing that print poetry uses, or decide to reproduce the rigidity of print media into the screen.

    If you want to reproduce the rigidity of print media, what you need is:
    - Clearly defined fonts. Using a so-called “universal” font might not be enough. Using @font-face (CSS3) is better, but the support for that is young and there are quite a few technical and legal issues.
    - Define font sizes in a non-changing unit. Pixels might work. Points might be good too (they would be my preferred choices if they were decently implemented in operating systems — which is only the case on Linux, i think).
    - Don’t create your gaps using spaces or invisible characters. Imitating the movable type of old by using quad space characters might be fun, but a SPAN with display:inline-block and a fixed width should be better.

    • Matthew says:

      Thanks for the detailed reply! I have a web designer friend (who actually pointed me to this article this morning) and we kick around ideas on a regular basis for addressing the print vs. web typography gap.

      I might give the SPAN with display:inline-block method a try. I recognize that rigid formatting of poetry on web pages just isn’t going to happen, and I’m cool with that. The best work-around we’ve considered so far would be either using a screenshot of the page for the work or something like sIFR (which probably still wouldn’t do the trick and leads to similar problems with fonts+legalities).

    • inayaili says:

      Thanks for the long comment, Florent. Have you thought of writing your own article on this? I guess it would be rather useful! :)

      I think, at least originally, the pre tag would be the one indicated to style poems that have these requirements. I think that’s even mentioned on the new HTML5 specs. But I honestly never really looked into styling this tag other than for the odd coding snippet.

      Good discussion!

  18. Ted Goas says:

    With all the CSS3 articles out there these days, it’s nice to see a focus on CSS selectors. Thanks, I enjoyed looking at the code!

  19. Michelle says:

    wow thanks for such a fab article, I have found it to be really useful.

  20. Catherine says:

    I generally use an empty line between stanzas, rather than have each stanza as a separate paragraph; there are non-html reasons for this, including that there are — and should be — sentences that run across stanzas; a stanza in poetry is not the same as a paragraph in prose.

    Additionally, capping — or small capping — the beginning of each line really went out with WCW, and anymore signals a poem which does not “come after” modernism except in time. In other words, perfectly appropriate for “Twinkle, Twinkle” but not for most poems, and furthermore, arguably for no poems in html (modern technology).

  21. bruce says:

    IMO, a stanza in a poem is one paragraph, and authorial line breaks should be marked up with the br element. (Poems/ lyrics and postal addresses seem to me the only real uses of for the br element).

    So changing the mark up to one paragraph per line – and therefore losing any ability to distinguish stanza from stanza is breaking semantics.

    I do feel your pain, though; I wrote about the lack of ability to format poetry in 2005: http://www.brucelawson.co.uk/2005/what-i-want-from-css-3/

  22. grant says:

    IE 8 doesn’t support the :last-child
    IE 7 doesn’t support :last-child
    IE6 doesn’t support the :first-letter

    And then Web designer doesn’t support IE!

  23. Yeah.. lets make design which is don’t support IE!

  24. pupster says:

    Is there anyway to make a drop cap on a h1 or h2 selector?

  25. Robson Sobral says:

    Actually, IE6 supports :first-letter since you keep the space after the selector.

    Sei que parece idiota, mas é assim mesmo. Teste aí. De qualquer forma, ele não aceita o adjacent selector no seu código. Que legal escrever em português! Tchau!

  26. Jim Adcock says:

    It doesn’t work imho because it doesn’t line wrap correctly.

  27. Ferren MacIntyre says:

    There are often places where I want to put that upset validators. In a list, for instance, where a vertical space can act as a thematic break. What’s with this fascist claim of ‘inappropriate’?

Leave a comment: