In the realm of CSS layout, lists are an interesting case. The items in a list are simply block boxes, but with an extra bit that doesn’t really participate in the document layout hanging off to one side. With an ordered list, that extra bit contains a series of increasing numbers (or letters) that are calculated and mostly formatted by the user agent, not the author. Taking a cue from the document structure, the user agent generates the numbers and their basic presentation.
None of this content generation could be described in CSS1 terms—and, therefore, it couldn’t be controlled—but CSS2 introduced features that allow list-item numbering to be described. As a result, CSS now lets you, the author, define your own counting patterns and formats, and associate those counters with any element, not just ordered list items. Furthermore, this basic mechanism makes it possible to insert other kinds of content, including text strings, attribute values, or even external resources into a document. Thus, it becomes possible to use CSS to insert link icons, editorial symbols, and more into a design without having to create extra markup.
To see how all these list options fit together, we’ll explore basic list styling before moving on to examine the generation of content and counters.
In a sense, almost anything that isn’t narrative text can be considered a list. The US Census, the solar system, my family tree, a restaurant menu, and even all of the friends you’ve ever had can be represented as a list, or perhaps as a list of lists. These many variations make lists fairly important, which is why it’s a shame that list styling in CSS isn’t more sophisticated.
The simplest (and best-supported) way to affect a list’s styles is to change its marker type. The marker of a list item is, for example, the bullet that appears next to each item in an unordered list. In an ordered list, the marker could be a letter, number, or a symbol from some other counting system. You can even replace the markers with images. All of these are accomplished using the different list-style properties.
To change the type of marker used for a list’s items, use the property list-style-type
.
That’s quite a few keywords, I know—and that’s not even all the values that list-style-type
has historically borne! Some, such as urdu
and hangul-consonant
, are supported by one browser or another, but none of the older values have widespread support. By contrast, the list of values shown above has nearly universal support. Some examples are shown in Figure 15-1.
The list-style-type
property, as well as all other list-related properties, can be applied only to an element that has a display
of list-item
, but CSS doesn’t distinguish between ordered and unordered list items. Thus, you can set an ordered list to use discs instead of numbers. In fact, the default value of list-style-type
is disc
, so you might theorize that without explicit declarations to the contrary, all lists (ordered or unordered) will use discs as the marker for each item. This would be logical, but, as it turns out, it’s up to the user agent to decide. Even if the user agent doesn’t have a predefined rule such as ol {list-style-type: decimal;}
, it may prohibit ordered markers from being applied to unordered lists, and vice versa. You can’t count on this, so be careful.
Historically, user agents treated any unrecognized keyword value as decimal
, as per CSS 2.1. The CSS Lists and Counters Module is less precise about this, as of early 2017, and appears to allow a fallback to either disc
or none
. (Chrome, for example, defaults to none
if an ordered list type is applied to an unordered list.)
If you want to suppress the display of markers altogether, then none
is the value you should use. none
causes the user agent to refrain from putting anything where the marker would ordinarily be, although it does not interrupt the counting in ordered lists. Thus, the following markup would have the result shown in Figure 15-2:
ol
li
{
list-style-type
:
decimal
;}
li
.off
{
list-style-type
:
none
;}
<ol>
<li>
Item the first<li
class=
"off"
>
Item the second<li>
Item the third<li
class=
"off"
>
Item the fourth<li>
Item the fifth</ol>
list-style-type
is inherited, so if you want to have different styles of markers in nested lists, you’ll likely need to define them individually. You may also have to explicitly declare styles for nested lists because the user agent’s style sheet may have already defined them. For example, assume that a user agent has the following styles defined:
ul
{
list-style-type
:
disc
;}
ul
ul
{
list-style-type
:
circle
;}
ul
ul
ul
{
list-style-type
:
square
;}
If this is the case—and it’s likely that this, or something like it, will be—you will have to declare your own styles to overcome the user agent’s styles. Inheritance won’t be enough.
CSS also allows authors to supply string values as list markers. This opens the field to anything you can input from the keyboard, as long as you don’t mind having the same string used for every marker in the list. Figure 15-3 shows the results of the following styles:
.list01
{
list-style-type
:
"%"
;
}
.list02
{
list-style-type
:
"Hi! "
;
}
.list03
{
list-style-type
:
"†"
;
}
.list04
{
list-style-type
:
"⌘"
;
}
.list05
{
list-style-type
:
"
"
;
}
Sometimes, a regular text marker just won’t do. You might prefer to use an image for each marker, which is possible with the property list-style-image
.
Here’s how it works:
ul
li
{
list-style-image
:
url(ohio.gif)
;}
Yes, it’s really that simple. One simple url()
value, and you’re putting images in for markers, as you can see in Figure 15-4.
Of course, you should exercise care in the images you use, as the example shown in Figure 15-5 makes painfully clear:
ul
li
{
list-style-image
:
url(big-ohio.gif)
;}
It’s generally a good idea to provide a fallback marker type in case your image doesn’t load, gets corrupted, or is in a format that some user agents can’t display. Do this by defining a backup list-style-type
for the list:
ul
li
{
list-style-image
:
url(ohio.png)
;
list-style-type
:
square
;}
The other thing you can do with list-style-image
is set it to the default value of none
. This is good practice because list-style-image
is inherited, so any nested lists will pick up the image as the marker, unless you prevent that from happening:
ul
{
list-style-image
:
url(ohio.gif)
;
list-style-type
:
square
;}
ul
ul
{
list-style-image
:
none
;}
Since the nested list inherits the item type square
but has been set to use no image for its markers, squares are used for the markers in the nested list, as shown in Figure 15-6.
Remember that this scenario might not occur in the real world: a user agent may have already defined a list-style-type
for ul ul
, so the value of square
won’t be inherited after all. Instead, you might get a circle, disc, or other symbol.
Any image value is permitted for list-style-image
, including gradient images. Thus, the following styles would have a result like that shown in Figure 15-7:
.list01
{
list-style-image
:
radial
-
gradient
(
closest
-
side
,
orange
,
orange
60%
,
blue
60%
,
blue
95%
,
transparent
);}
.list02
{
list-style-image
:
linear
-
gradient
(
45deg
,
red
,
red
50%
,
orange
50%
,
orange
);}
.list03
{
list-style-image
:
repeating
-
linear
-
gradient
(
-45deg
,
red
,
red
1px
,
yellow
1px
,
yellow
3px
);}
.list04
{
list-style-image
:
radial
-
gradient
(
farthest
-
side
at
bottom
right
,
lightblue
,
lightblue
50%
,
violet
,
indigo
,
blue
,
green
,
yellow
,
orange
,
red
,
lightblue
);}
There is one drawback to gradient markers: they tend to be very small. The size isn’t something that CSS allows you to control, so you’re stuck with whatever the browser decides is a good size. This size can be influenced by things like font size, because the marker size tends to scale with the list item’s content, but that’s about it.
There is one other thing you can do to influence the appearance of list items under CSS: decide whether the marker appears outside or inside the content of the list item. This is accomplished with list-style-position
.
If a marker’s position is set to outside
(the default), it will appear the way list items have since the beginning of the web. Should you desire a slightly different appearance, you can pull the marker in toward the content by setting the value of list-style-position
to inside
. This causes the marker to be placed “inside” the list item’s content. The exact way this happens is undefined, but Figure 15-8 shows one possibility:
li
.first
{
list-style-position
:
inside
;}
li
.second
{
list-style-position
:
outside
;}
In practice, markers given an inside
placement are treated as if they’re an inline element inserted into the beginning of the list item’s content. This doesn’t mean the markers actually are inline elements—you can’t style them separately from the rest of the element’s content, unless you wrap all the other content in an element like span
. It’s just that in layout terms, that’s what they act like.
For brevity’s sake, you can combine the three list-style properties into a convenient single property: list-style
.
For example:
li
{
list-style
:
url(ohio.gif)
square
inside
;}
As you can see in Figure 15-9, all three values are applied to the list items.
The values for list-style
can be listed in any order, and any of them can be omitted. As long as one is present, the rest will fill in their default values. For instance, the following two rules will have the same visual effect:
li
.norm
{
list-style
:
url(img42.gif)
;}
li
.odd
{
list-style
:
url(img42.gif)
disc
outside
;}
/* the same thing */
They will also override any previous rules in the same way. For example:
li
{
list-style-type
:
square
;}
li
{
list-style
:
url(img42.gif)
;}
li
{
list-style
:
url(img42.gif)
disc
outside
;}
/* the same thing */
The result will be the same as that in Figure 15-9 because the implied list-style-type
value of disc
will override the previous declared value of square
, just as the explicit value of disc
overrides it in the second rule.
Now that we’ve looked at the basics of styling list markers, let’s consider how lists are laid out in various browsers. We’ll start with a set of three list items devoid of any markers and not yet placed within a list, as shown in Figure 15-10.
The border around the list items shows them to be, essentially, like block-level elements. Indeed, the value list-item
is defined to generate a block box. Now let’s add markers, as illustrated in Figure 15-11.
The distance between the marker and the list item’s content is not defined by CSS, and CSS does as yet not provide a way to affect that distance.
With the markers outside the list items’ content, they don’t affect the layout of other elements, nor do they really even affect the layout of the list items themselves. They just hang a certain distance from the edge of the content, and wherever the content edge goes, the marker will follow. The behavior of the marker works much as though the marker were absolutely positioned in relation to the list-item content, something like position: absolute; left: -1.5em;
. When the marker is inside, it acts like an inline element at the beginning of the content.
So far, we have yet to add an actual list container; in other words, there is neither a ul
nor an ol
element represented in the figures. We can add one to the mix, as shown in Figure 15-12 (it’s represented by a dashed border).
Like the list items, the list element is a block box, one that encompasses its descendant elements. As we can see, however, the markers are not only placed outside the list item contents, but also outside the content area of the list element. The usual “indentation” you expect from lists has not yet been specified.
Most browsers, as of this writing, indent list items by setting either padding or margins for the containing list element. For example, the user agent might apply a rule such as:
ul
,
ol
{
margin-left
:
40px
;}
This is the basic rule employed by Internet Explorer and Opera. Most Gecko-based browsers, on the other hand, use a rule something like this:
ul
,
ol
{
padding-left
:
40px
;}
Neither is incorrect, but the discrepancy can lead to problems if you want to eliminate the indentation of the list items. Figure 15-13 shows the difference between the two approaches.
The distance of 40px
is a relic of early web browsers, which indented lists by a pixel amount. (Block quotes are indented by the same distance.) An alternate value might be something like 2.5em
, which would scale the indentation along with changes in the text size.
For authors who want to change the indentation distance of lists, I strongly recommend that you specify both padding and margins to ensure cross-browser compatibility. For example, if you want to use padding to indent a list, use this rule:
ul
{
margin-left
:
0
;
padding-left
:
1em
;}
If you prefer margins, write something like this instead:
ul
{
margin-left
:
1em
;
padding-left
:
0
;}
In either case, remember that the markers will be placed relative to the contents of the list items, and may therefore “hang” outside the main text of a document, or even beyond the edge of the browser window. This is most easily observed if very large images, or long text strings, are used for the list markers, as shown in Figure 15-14.
CSS defines methods to create what’s called generated content. This is content inserted via CSS, but not represented either by markup or content.
For example, list markers are generated content. There is nothing in the markup of a list item that directly represents the markers, and you, the author, do not have to write the markers into your document’s content. The browser simply generates the appropriate marker automatically. For unordered lists, the marker will a symbol of some kind, such as a circle, disc, or square. In ordered lists, the marker is by default a counter that increments by one for each successive list item. (Or, as we saw in previous sections, you may replace either kind with an image or symbol.)
To understand how you can affect list markers and customize the counting of ordered lists (or anything else!), you must first look at more basic generated content.
To insert generated content into the document, use the ::before
and ::after
pseudo-elements. These place generated content before or after the content of an element by way of the content
property (described in the next section).
For example, you might want to precede every hyperlink with the text “(link)” to mark them for printing. This is accomplished with a rule like the following, which has the effect shown in Figure 15-15:
a
[
href
]
::before
{
content
:
"(link)"
;}
Note that there isn’t a space between the generated content and the element content. This is because the value of content in the previous example doesn’t include a space. You could modify the declaration as follows to make sure there’s a space between generated and actual content:
a
[
href
]
::before
{
content
:
"(link) "
;}
It’s a small difference but an important one.
In a similar manner, you might choose to insert a small icon at the end of links to PDF documents. The rule to accomplish this would look something like:
a
.pdf-doc
::after
{
content
:
url(pdf-doc-icon.gif)
;}
Suppose you want to further style such links by placing a border around them. This is done with a second rule:
a
.pdf-doc
{
border
:
1px
solid
gray
;}
The result of these two rules is illustrated in Figure 15-16.
Notice how the link border extends around the generated content, just as the link underline extended under the “(link)” text in Figure 15-15. This happens because by default, generated content is placed inside the element box of the element (unless the generated content is a list marker).
You can float or position generated content outside its parent element’s box. CSS2 did not permit this, but advances in CSS and browser support removed this restriction. Similarly, the old restrictions about what kinds of display
values could be given to generated content have been erased, so that you apply block formatting to the generated content of an inline box, and vice versa. For example, consider:
em
::after
{
content
:
" (!) "
;
display
:
block
;}
Even though em
is an inline element, the generated content will generate a block box. Similarly, given the following code, the generated content is made block-level:
h1
::before
{
content
:
"New Section"
;
display
:
block
;
color
:
gray
;}
The result is illustrated in Figure 15-17.
One interesting aspect of generated content is that it inherits values from the element to which it’s been attached. Thus, given the following rules, the generated text will be green, the same as the content of the paragraphs:
p
{
color
:
green
;}
p
::before
{
content
:
"::: "
;}
If you want the generated text to be purple instead, a simple declaration will suffice:
p
::before
{
content
:
"::: "
;
color
:
purple
;}
Such value inheritance happens only with inherited properties, of course. This is worth noting because it influences how certain effects must be approached. Consider:
h1
{
border-top
:
3px
solid
black
;
padding-top
:
0.25em
;}
h1
::before
{
content
:
"New Section"
;
display
:
block
;
color
:
gray
;
border-bottom
:
1px
dotted
black
;
margin-bottom
:
0.5em
;}
Since the generated content is placed inside the element box of the h1
, it will be placed under the top border of the element. It would also be placed within any padding, as shown in Figure 15-18.
The bottom margin of the generated content, which has been made block-level, pushes the actual content of the element downward by half an em. In every sense, the effect of the generated content in this example is to break up the h1
element into two pieces: the generated-content box and the actual content box. This happens because the generated content has display: block
. If you were to change it to display: inline
, the effect would be as shown in Figure 15-19:
h1
{
border-top
:
3px
solid
black
;
padding-top
:
0.25em
;}
h1
::before
{
content
:
"New Section"
;
display
:
inline
;
color
:
gray
;
border-bottom
:
1px
dotted
black
;
margin-bottom
:
0.5em
;}
Note how the borders are placed and how the top padding is still honored. So is the bottom margin on the generated content, but since the generated content is now inline and margins don’t affect line height, the margin has no visible effect.
With the basics of generating content established, let’s take a closer look at the way the actual generated content is specified.
If you’re going to generate content, you need a way to describe the content to be generated. As you’ve already seen, this is handled with the content
property, but there’s a great deal more to this property than you’ve seen thus far.
You’ve already seen string and URI values in action, and counters will be covered later in this chapter. Let’s talk about strings and URIs in a little more detail before we take a look at the attr( )
and quote values.
String values are presented literally, even if they contain what would otherwise be markup of some kind. Therefore, the following rule would be inserted verbatim into the document, as shown in Figure 15-20:
h2
::before
{
content
:
"<em>¶</em> "
;
color
:
gray
;}
This means that if you want a newline (return) as part of your generated content, you can’t use <br>
. Instead, you use the string A
, which is the CSS way of representing a newline (based on the Unicode line-feed character, which is hexadecimal position A
). Conversely, if you have a long string value and need to break it up over multiple lines, you escape out the line feeds with the character. These are both demonstrated by the following rule and illustrated in Figure 15-21:
h2
::before
{
content
:
"We insert this text before all H2 elements because
it is a good idea to show how these things work. It may be a bit long
but the point should be clearly made. "
;
color
:
gray
;}
You can also use escapes to refer to hexadecimal Unicode values, such as