The conceptual frameworks introduced in the first part of this book are the foundations on which a thorough understanding of CSS is laid. Document flow, the various positioning schemes and formatting concepts, and stacking contexts are what make it possible for CSS rules to define the form of content so completely. Transforming a piece of content into multiple alternative presentations is at the core of most CSS development, even if you're ostensibly only working with a single design mock-up.
In this part of the book, we're going to move further into the practical aspects of working with CSS and what it can do. Each chapter focuses on a different cross section of CSS development that draws on numerous concepts from the previous part. Let's dive right in, beginning with exploring the specifics of CSS-generated content.
Chapter 3 | |
CSS-GENERATED CONTENT |
In addition to providing many means to present the content that already exists within a document, CSS provides a mechanism for rendering additional content. This content is called CSS-generated content, or pseudo-content, because it doesn't actually exist as part of the document structure. Instead, it exists in a sort of ghost state, rendered and painted on screen (or in whatever other way is being presented) but not “physically” there. In many instances, interaction with such content is therefore restricted so that it can't be selected by a cursor, accessed via scripting APIs, and so on.
In this chapter we'll explore CSS-generated content in more detail and showcase some of the ways it can be used to enhance presentation. It's important to remember that CSS-generated content, despite its name, is still a presentational effect. Therefore, we'll also supply some guidelines for using CSS-generated content in ways that adhere to development best practices such as progressive enhancement.
Whether or not you realize it, you've probably already used CSS-generated content in your day-to-day web development tasks. In CSS2.1, there are two mechanisms you can use to generate content with style sheets. The one you're already familiar with from previous chapters in this book is the markers, such as bullets and numbers, at the side of list items.
Thanks to their ubiquity, ordered (numbered) lists are a great example of where CSS-generated content is useful. In such lists, each item in the list is numbered according to its position relative to the other items. The first one therefore has the numeral 1 next to it, the second has the numeral 2, and so on. If we were marking up the notes in an ascending scale in such an HTML ordered list, the markup would look like this:
<ol>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
<li>E</li>
<li>F</li>
</ol>
The actual rendering of this list would predictably look something like this:
Interestingly, the numerals at the side of the notes are rendered as well. How is this happening, since nowhere in the HTML are these numerals specified as content? The answer has two distinct components.
First, CSS-generated content provides a means to render CSS boxes and attach them to CSS boxes created by “real” content. As shown earlier, one way to generate these pseudo-content CSS boxes is to create list items or—more precisely—to declare display: list-item
on an element (indeed, any element). Elements with the list-item
value to the display
property automatically generate a marker box, one kind of CSS-generated content. We'll examine marker boxes in much more detail later in this chapter.
The other way to render CSS-generated content is to declare a value for the content
property in a CSS rule that selects the :before
or :after
pseudo-elements. These pseudo-elements are abstracted hooks that allow you to attach CSS boxes to any arbitrary element and then flow whatever content you specified with the content
property into that newly generated box. This mechanism of generating pseudo-content is discussed in the next section.
The second piece of the puzzle to an ordered list's automatic numbering is that CSS also provides a simple mechanism for counting the occurrences of a particular thing (in this case, <li>
elements). The count of these occurrences is saved in a counter, which is somewhat analogous to a simple variable. Each time one of these counters is rendered using CSS, the counter value is retrieved and used to number each of the individual instances being rendered.
The capability to render additional content not in the document itself and to number instances of elements that are in the document tree provides authors with a number of conveniences beyond additional styling hooks. For instance, let's say we want to include sharp notes on our ordered list of the musical scale. In that case, we need to add new list items in between the ones we currently have. Our new markup will therefore look something like this:
<ol>
<li>A</li>
<li>A sharp</li>
<li>B</li>
<li>B sharp</li>
<li>C</li>
<li>C sharp</li>
<li>D</li>
<li>D sharp</li>
<li>E</li>
<li>E sharp</li>
<li>F</li>
<li>F sharp</li>
</ol>
Thankfully, we don't have to worry about renumbering any of the elements ourselves, since the original list was numbered with CSS in the first place. The new list is numbered correctly, too.
As you'd expect, you can manipulate both aspects of this behavior through CSS properties. Let's dive in by generating some pseudo-content first, and then take a closer look at how CSS counters work later in this chapter.
Pseudo-content is generated with CSS by taking advantage of the :before
or :after
pseudo-elements. These pseudo-elements, which can be applied to any element in a document, are effectively placeholders where you can inject other content. The content
property determines what actual content gets injected.
The simplest value you can give the content
property is a string. Unsurprisingly, the result of using a string value is that the generated content is a line of text inside an inline CSS box. For example, if you were feeling particularly poetic, you could prepend the phrase “Quoth the raven” in front of every quoted paragraph in your document like this:
blockquote p:before { content: "Quoth the raven, "; }
When you link this CSS with an HTML snippet such as this one:
<blockquote>
<p>Nevermore.</p>
</blockquote>
the result is a CSS box tree that behaves as though the underlying HTML were changed to something like this:
<blockquote>
<p><span>Quoth the raven, </span>Nevermore.</p>
</blockquote>
In particular, notice that the trailing space character in the content
property results in a trailing space character in the generated content right before the (theoretical) </span>
tag. This is important for inline CSS boxes when concatenating text strings. Also notice that the generated content results in a CSS box that lives “inside” the element to which it is attached. This way, the generated content can naturally inherit the CSS properties of its associated “real” content.
Conversely, using the :after
pseudo-element as shown in the CSS that follows, the CSS box rendering would change accordingly but the “real” HTML content can't be altered.
/* CSS using :after instead of :before */
blockquote p:after { content: ", quoth the raven."; }
<!-- HTML might therefore look like this: -->
<blockquote>
<p>Nevermore.<span>, quoth the raven.</span></p>
</blockquote>
To override a previous rule that renders CSS-generated content, you need to specify a value of none
to the content
property instead of to the display
property. Technically, specifying display: none
does prevent CSS-generated content from being displayed, but the content may still be generated and kept in the user agent's memory. Therefore, it is more explicit and possibly less resource-intensive to say content: none
in the context of pseudo-content. You can also use content: normal
, which does the same thing but takes slightly longer to type (and sounds somewhat strange).
In addition to the none
value or simple strings, the content
property can also accept a url()
value. When a url()
value is used, the user agent determines the appropriate rendering based on the MIME type1 of the referenced URI. In other words, if the url()
value references an image, then the image is rendered as though it were an <img>
element placed in the normal document flow. If the url()
value references an audio file, then screen readers will play the audio when they encounter the element to which the generated content is attached. Some user agents can also render other files such as SVG or PDF documents this way, inserting them directly in line with the rest of the flow of content.2
Here's an example like the one earlier but instead of inserting a string of text, we'll insert an image of a raven:
blockquote p:before { content: url(http://example.com/raven.jpg); }
Again, this has the basic effect of modifying the CSS box tree so that the resultant HTML would look like this:
<blockquote>
<p><img src="http://example.com/raven.jpg" />Nevermore.</p>
</blockquote>
Using CSS-generated content functions most reliably on non-replaced elements (that is, elements that have intrinsic layout dimensions). None of the major browsers save for Opera will apply CSS-generated content to replaced elements such as images because the rendering engine needs to actively change the element's structure to do so. Additionally, as replaced elements reference external resources, they rarely have children. Generating content on an <img>
, for example, forces the browser to render CSS boxes that can be represented in markup like this:
img:before { content: "Hello"; }
img:after { content: "world."; }
<img src="http://example.com/raven.jpg">
<:before>Hello</:before>
<:after>world.</:after>
</img>
Nevertheless, this works beautifully in Opera and isn't technically in violation of the CSS2.1 specification. However, the CSS specifications are unfortunately vague, offering little implementation guidance on how generated content should function with regard to replaced elements. As a result, some of the more compelling uses for generated content are still not cross-browser compatible, although it is hoped that the CSS3 Generated and Replaced Content module will address these concerns.
__________
1. A MIME type is the term used to catalog different kinds of data in a standard, conventional way so that they can be attached to various forms of networked communications, originally email messages. For example, image/gif
is the MIME type for GIF images.
2. Most notably, this includes WebKit-based browsers such as all versions of Safari. Sadly, as of this writing, the Mobile Safari derivative that runs on iPhones and iPod touch devices does not yet support SVG at all.
The attr()
function is similar to the url()
function just shown, but instead of referring to another resource it refers to the value of an attribute of the element to which the CSS-generated content is attached. Using an attr()
value with the content
property can therefore expose some metadata about the content to the user, since such metadata is often encoded in an element's attributes. This can be used to enhance visitors' experience by providing contextual but possibly peripheral information about the content they are viewing.
In fact, many user agents already do this with built-in features. For example, when you provide an element with a title
attribute, web browsers will often reveal the contents of that attribute as a tooltip whenever users hover their cursor over that element. Generating pseudo-content with the attr()
function takes this idea one step further. Instead of relying on individual user agents to expose this multilayered interaction in limited ways, you can proactively feature it and style the information as you see fit.
Here's one example to do exactly that. Along with providing a meaningful semantic value to <img>
elements in their alt
attributes, you could use their title
attributes to provide a caption.3 We'll use Opera to do this, as it's the browser with the most support for CSS-generated content. If you had an <img>
element such as the one shown next, you could use its title
attribute to automatically generate its caption with no markup beyond the element's required attributes:
<img
src="http://upload.wikimedia.org/wikipedia/commons/2/27/
Edgar_Allan_Poe_2.jpg"
alt="" width="100"
title="This photograph of Edgar Allan Poe was taken in 1848 when he was
39, a year before his death." />
The title
attribute in this <img>
element provides everything you need for a caption, so to render it you could use the following CSS rule. The result is shown in Figure 3-1.
img[title]:before {
content: attr(title);
display: block;
}
You use the attr()
function to the content
property to extract the title
attribute's contents. The display
property is simply used to present the caption and the image their own lines, instead of in the same inline box. Alternatively, you could place the image on top of the caption instead of beneath it by injecting the generated content (the caption) :after
the image instead of :before
it.
__________
3. Not only is providing both an alt
attribute (itself required for validation) and a title
attribute sort of handy, it's also a best practice accessibility guideline. See Joe Clark's book, Building Accessible Websites (New Riders Press, 2002), for a detailed explanation of the accessibility uses for the <img>
element's alt
and title
attributes, and how they compare with each other.
The [title]
attribute selector is used to ensure that only <img>
elements with title
attributes are selected by the rule. If the element you select doesn't have a title
attribute, the result of the call to attr(title)
will be the empty string (i.e., it will be the same as ‘’
). Alone, this isn't problematic, but when combined with the display: block;
declaration, even an empty string value creates a line break, which will result in some unintended vertical spacing.
Another interesting characteristic of the content
property is that you can supply it with multiple values and each of them will get injected one after the other, in the order you prescribe. So, for example, using the image caption example earlier, you might want to not only provide the caption but also explicitly name the image's source in the caption itself. You could manually write out the source of the image in the title
attribute, of course, but CSS offers this better way:
img[title]:before {
content: attr(title) " Image retrieved from " attr(src);
display: block;
}
Figure 3-1. Extracting the contents of an element's attribute renders as text
This time, we provide three distinct values to the content
property. Once again, note the leading and trailing single spaces within the quotation marks for the second (plain string) value. With this declaration, your image captions will show the contents of the <img>
element's title
attribute, followed by a space, followed by the words “Image retrieved from” and then another space, and finally ending with the address of the image file itself, as Figure 3-2 shows.
Figure 3-2. The attr()
function can be used any number of times in the content
property.
Another thing to note is that our selector hasn't changed. That's because the src
attribute is required on <img>
elements (for somewhat obvious reasons), so we can safely assume it's always going to be there.
You may also attach CSS-generated content both :before
and :after
any single element, which means that every real element in your markup has at least two (and sometimes up to three) styling hooks for generated content. (:before
is one, :after
is another, and if the element is display: list-item
, that gives us a third.) So, for example, if you wanted the caption above the image and the image source below it, you could use the following CSS to do that:
img[title]:before, img[title]:after { display: block; }
img[title]:before { content: attr(title); }
img[title]:after { content: "Image retrieved from " attr(src); }
In this case, we use the [title]
attribute selector in every rule, even in the rule that uses attr(src)
, since that's how we've determined that an image has a caption.
The key point is to realize that today you always have at least N × 3 potential CSS boxes to style in any document, where N is the number of real elements in your markup (unless these are replaced elements, as noted earlier). Usually, thanks to the prevalence of lists, you actually have a little more than that (although see our cautions on avoiding “list-itis” later in this chapter). You can't attach CSS-generated content to CSS-generated content, however, so you don't get an infinite number of potential CSS boxes to work with, although this capability is included in the current working draft of the CSS3 Generated and Replaced Content module.4 That means that while the following two rules are both valid CSS, the second one will never be used because it can't possibly apply to anything in CSS2:
p:before { content: "I'm a generated box."; }
p:before:before { content: "I'm not ever going to be rendered."; }
If you think you need to do something like this in a style sheet, you may not be making full use of the document's structure. Look at the markup around the element you want to style and see if there are any other styling hooks available to you, such as other nearby elements. If you still can't come up with a way to achieve what you want, you probably have poorly structured markup that you might need to rework.5
Additionally, though we hate to temper any sudden enthusiasm for the incredible potential of CSS-generated content, there are some important styling restrictions that such pseudo-content adheres to. Despite this, styling CSS-generated content can be an incredible boon to many, if not all, designs.
A number of techniques are used in web development today that allow one element to be entirely replaced with another. Typically, this is a presentational effect that lets web designers use images or other media such as Flash movies in place of more limited options. Textual headings are frequent candidates for such media replacement.
Although not widely supported today, the current working draft of CSS3's Generated and Replaced Content module provides this capability directly by building on the functionality provided by previous levels of CSS-generated content. The familiar syntax merely uses the content
property and applies it to a real element, that is, without the use of the :before
or :after
pseudo-classes.
/* Replace a textual headline with an appropriate image. */
h1#company-name { content: url(/company-name.png); }
__________
4. It is proposed that nesting CSS-generated content function merely by chaining ::before
or ::after
pseudo-elements to each other, as described at http://www.w3.org/TR/css3-content/#nesting
.
5. Speaking of reworking markup structure for better semantics and styling hooks, might we recommend Paul Haine's book, HTML Mastery (friends of ED, 2006).
Replacing content in this way can also be used for easily creating low-fidelity alternatives to hi-fi content, instead of the other way around, as we discuss further in Chapter 5. Sadly, as of this writing, the only mainstream browser that the previous CSS rules work in is Opera 9.6. However, we hope that since replacing content in addition to generating content is such a powerful mechanism for web development other browsers will implement this capability sooner rather than later.
As we mentioned earlier, pseudo-content doesn't actually exist as part of the document structure. Once it is generated by a CSS rule, however, it behaves in the same way “real” content would—but only as far as CSS is concerned. This is an important caveat because it means that even after the content is rendered, it is still not accessible outside of CSS (such as by JavaScript) via the DOM. In other words, CSS-generated content is kept very strictly segregated in the presentation layer of your page.
That said, since CSS-generated content is rendered with real CSS boxes, the generated box obeys most of the rules you might expect. They inherit properties from the content to which they are attached as though that content were their parent elements, they have the same interactions with their neighboring CSS boxes according to the rules of document flow, and so on.
One notable difference, however, is that CSS-generated content always remains positioned statically. That is, CSS-generated content always has position: static
and the various values of the position
property do not affect generated content as they would “real” content. The same is also true for the float
property. Sensibly, this is to ensure that CSS-generated content always renders near the content to which it is attached. Due to this restriction, all the following CSS rules are effectively identical to the image caption example in the previous section:
img[title]:before {
content: attr(title) " Image retrieved from " attr(src);
display: block;
position: absolute; /* This does nothing to pseudo-content. */
position: relative; /* Neither does this. */
top: 50px; /* Naturally, this is also ignored. */
}
img[title]:before {
content: attr(title) " Image retrieved from " attr(src);
display: block;
float: right; /* This also has no effect. */
}
Another limitation of the :before
and :after
pseudo-elements is that regardless of their contents, they can only generate a single CSS box. Practically, this means that there is no way to specify that, for instance, the attr(title)
portion of the previous example's declaration should be a block-level box (with display: block
) while the rest of the content
property's values should be inline. In other words, any additional CSS properties you declare in a declaration block that targets CSS-generated content applies to all the content
property's values.
Finally, as hinted at earlier, in all mainstream browsers except for Opera, CSS-generated content will not apply to any replaced elements (that is, elements whose layout dimensions are determined by external resources). Such elements include <img>
elements, <object>
elements, and a number of form controls. Even in Opera, only some elements work this way, and they function because the browser jumps through a number of rendering hoops. The CSS2.1 specification made no restrictions regarding where generated content could be applied, but implementation proved difficult. As the CSS3 specification is still not complete, browser makers are unlikely to enable CSS-generated content on replaced elements in the near future.6
In most situations these limitations are not an issue because generated content should be kept succinct. If you're trying to fit too much into pseudo-content, you might consider reevaluating whether the pseudo-content you're presenting is actually real content. If it is, you should provide the content as part of the underlying document itself instead of declaring it in a style sheet.
Although using strings in the content
property is relatively simple and somewhat limited, there are nevertheless some remarkably useful things you can do with them. For example, you can force text into multiple lines much like you might have done with an HTML <br />
element. Since we're inside of CSS and not HTML, though, using an HTML line break won't work. To understand why that is, let's examine CSS string values and escape sequences in detail.
Regardless of where they appear, string values in CSS behave in a similar way. The most important thing to remember about them is that they are not HTML. This means, for instance, that inserting literal angle brackets without escaping them as HTML entity references (<
and >
) is perfectly legal. In other words, the rule
#example:before { content: "3 < 5"; }
would result in a pseudo-element whose contents are the five characters (including spaces) 3 < 5
and not a broken HTML start tag. Similarly, this rule
#example:before { content: "<"; }
results in a pseudo-element whose contents are the four characters <
and not an HTML-escaped less-than glyph. This tells us that the <
and &
characters are not treated specially by CSS string parsers, even though they are characters with special meaning in SGML-derived languages like HTML and XML.
Within CSS strings, the only character with any special meaning is the backslash (). This character delimits the beginning of an escape sequence, a sequence of characters used to collectively represent a different character, in much the same way as the ampersand (
&
) does in HTML code.
Escape sequences are useful because they allow style sheet authors to represent characters that would normally be ignored or interpreted differently by traditional CSS parsing rules. The most obvious example of this is representing a literal backslash in a CSS string. At first, you might think that the following CSS rule would produce a backslash at the start of every paragraph, but you'd be mistaken.
p:before { content: ""; }
__________
6. The issue with CSS-generated content with regard to replaced elements is in the difficulty of determining its dimensions correctly when the element's initial rendering depends on an external resource to begin with. A Mozilla Firefox bug discusses this issue specifically and may prove interesting. It can be found at https://bugzilla.mozilla.org/show_bug.cgi?id=169334
.
When a CSS parser reads the declaration in this rule, it thinks that the backslash is the start of an escape sequence, and so it ignores it. Next, it encounters a straightened double quote and, since this character is not a legal component in an escape sequence, it recognizes it as the end of the string value and returns. The result is an empty string, sans backslash: “”
.
To get the backslash to appear, we therefore need to escape it, or “undo” its special meaning. This is simple enough. We merely prepend the backslash with another one, like this:
p:before { content: "\"; }
This time when a CSS parser reads the declaration in the rule, it finds the first backslash, switches into its “escape sequence mode,” finds a literal backslash character as part of the string value it is parsing, and then finds the end-of-value straightened quotation mark. The result is what we were originally after, and the value that the CSS parser returns to the renderer is a single backslash: “”
. Note that CSS makes no distinction between single-quoted or double-quoted strings, so in either case two backslashes are needed in code to output one.
A similar situation exists if you wish to produce a literal double-quote within a double-quoted string. Instead of writing content: ““”;
you would write content: ““”;
to tell the CSS parser to treat the second quote as part of a value instead of the end-of-value delimiter. Alternatively, you could use single quotes as the string delimiter (content: ‘"’
).
After the starting backslash, only hexadecimal digits (the numerals 0
through 9
and the English letters A
through F
) are allowed to appear within an escape sequence. In such escape sequences, these digits always reference Unicode code points7 regardless of the character set used in the style sheet itself. As a result, it's possible to uniformly represent characters in a style sheet that are not possible to embed directly inside the style sheet itself. Accented characters (like the “é” in résumé or café) is an example of one class of characters that would need to be escaped in a CSS string if the style sheet were encoded in plain ASCII instead of, say, UTF-8.
One useful application for this is to embed line breaks into generated content. The Unicode code point for the newline character is U+00000A. In a CSS string, this can be written as