CSS Books

Living Idea — Last Updated 21 August 2017

This version:
https://books.idea.whatwg.org/
Editor:
Håkon Wium Lie, Opera Software,
Participate:
whatwg@whatwg.org
www-style@w3.org
file a bug (open bugs)

Abstract

This specification defines features often used when printing books or magazines. Using this functionality, documents written in HTML can be presented in a book-like manner, either on screen or on paper. Real books are being published with functionality described in this specification.

Status

This specification is in the process of establishing itself in WHATWG. Most of this specification has been interoperably implemented and is in daily use by publishers. Implementation coverage is documented here.

Table of Contents

  1. Goals
  2. 1 Conformance
  3. 2 Introduction
  4. 3 Running headers and footers
    1. 3.1 Named strings
      1. 3.1.1 Setting named strings: the 'string-set' property
      2. 3.1.2 Using named strings
    2. 3.2 Running elements
  5. 4 Leaders
  6. 5 Cross-references
    1. 5.1 The ''target-counter'' and ''target-counters'' values
    2. 5.2 The ''target-text'' value
  7. 6 Footnotes
    1. 6.1 Turning elements into footnotes
    2. 6.2 The footnote area
    3. 6.3 Footnote calls
    4. 6.4 Footnote markers
    5. 6.5 Counting footnotes
    6. 6.6 Laying out footnotes
  8. 7 Named Areas
    1. 7.1 Creating names areas: @area
    2. 7.2 Flowing content to a named area: 'flow'
    3. 7.3 Placement policy
    4. 7.4 Note calls: ::call
    5. 7.5 Floating elements to margin boxes
    6. 7.6 Pulling content: target-pull()
  9. 8 Bookmarks
    1. 8.1 'bookmark-level'
    2. 8.2 'bookmark-label'
    3. 8.3 'bookmark-state'
  10. 9 Page selectors
    1. 9.1 The nth() page pseudo-class
    2. 9.2 Page groups
  11. 10 Conditional content
  12. 11 Clipping column rules
    1. 11.1 column-rule-clip
  13. 12 Selecting elements within pages
  14. 13 Selecting columns
  15. 14 Baseline grids
    1. 14.1 'baseline-grid'
    2. 14.2 'baseline-block-snap'
  16. 15 Character substitution
  17. 16 Microtypography
    1. 16.1 Minimum, maximum and optimal spacing
  18. 17 Spatial layout of pages; @layout
    1. 17.1 Page shift effects
  19. 18 Appendix A: Default style sheet
  20. References
  21. Acknowledgments

Goals

The main goal of this specification is to enable authors to write books in HTML or other markup languages. Book publishing is a craft with long traditions and certain formatting conventions have developed over time. This specification adds functionality to CSS so that style sheets can express commonly used features in printed books, on-screen books, and page description languages. These features are also often used by magazines, in brochures, and in other publications.

1 Conformance

All diagrams, examples, and notes in this specification are non-normative, as are all sections explicitly marked non-normative. Everything else in this specification is normative.

The key words "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the normative parts of this specification are to be interpreted as described in RFC2119. For readability, these words do not appear in all uppercase letters in this specification. [RFC2119]

Conformance requirements phrased as algorithms or specific steps may be implemented in any manner, so long as the end result is equivalent.

User agents may impose implementation-specific limits on otherwise unconstrained inputs, e.g. to prevent denial of service attacks, to guard against running out of memory, or to work around platform-specific limitations.

2 Introduction

A book is a collection of sheets, bound together within covers. Usually, the sheets are printed paper sheets of a uniform size. The book format, known as the codex was developed around 2000 years ago and books gradually replaced scrolls, clay tablets and other competing formats. With the invention of the printing press, books could be printed and distributed inexpesively.

Each sheet of paper has two sides, and most books are have content on both sides of the sheets. Each side is called a "page". When a book is opened, two pages are visible; these form one "spread". The main problem when producing books from web content is to split content into pages and spreads of certain fixed sizes. This problem is not addressed by this specification. Instead, this specification describes how to achieve many of the features printers have developed over the centuries, e.g, running headers, footnotes, sidenotes, and page groups.

3 Running headers and footers

To aid navigation in printed material, headers and footers are often printed in the page margins. [CSSPAGE] describes how to place headers and footers on a page, but not how to fetch headers and footers from elements in the document. This specification offers two ways to achieve this. The first mechanism is named strings which copies the text (without style, structure, or replaced content) of an element for later reuse in margin boxes. The second mechanism is running elements which moves elements (with style, structure, and replaced content) into a margin box.

3.1 Named strings

html AntennaHouse Prince

A named string can be thought of as a variable that hold a string of text. Named strings are created with the 'string-set' property which copies a string of text into a named string. Only text is copied; not style, structure, or replaced content.

html AntennaHouse Prince

Consider this code:

h1 { string-set: heading content() }

Whenever an h1 element is encountered, its textual content is copied into a named string called heading. The string can later be retrieved in the 'content' property:

@page { @top-center { content: string(heading) }}

3.1.1 Setting named strings: the 'string-set' property

Name: string-set
Value: [[ <identifier> <content-list>] [, <identifier> <content-list>]* ] | none
Initial: none
Applies to: all elements
Inherited: no
Percentages: N/A
Media: all
Computed value: as specified value

The 'string-set' property accepts a comma-separated list of named strings. Each named string is followed by a content list that specifies the text to assign to the named string. Whenever an element with value of 'string-set' different from ''none'' is encountered, the named strings are assigned their respective value. Implementations must keep track of named string assigments on a per-element basis; references to named strings can only be resolved after pagination (see Using named strings below).

<content-list> expands to one or more of these values, in any order:

<string>
a string, e.g. "foo"
<counter>
the counter() or counters() function, as per CSS 2.1 section 4.3.5 [CSS]
content(text)
The textual content of the element, not including the content of its ::before and ::after pseudo-element. The content of the element's descendants, including their respective ::before and ::after pseudo-elements, are included in the returned content. This is the default value, so ''content()'' is equivalent to ''content(text)''.
content(before)
The textual content of the ::before pseudo-element of the element.
content(after)
The textual content of the ::after pseudo-element of the element.
content(first-letter)
The first letter of the textual content of the element. The definition of a letter is the same as for the :first-letter pseudo-element, as per CSS 2.1 section 5.12.2.

The syntax is formally described as:

  <content-list> = [ <string> | <counter> | [ content( [ before | after | first-letter ] ) ] ]+

Named strings can only hold the result of one assignment; whenever a new assignment is made to a named string, its old value is replaced.

User agents, however, must be able to remember the several assignments as the ''string()'' functional value (described below) can refer to different assignments.

The scope of a named string is the page of the element to which the 'string-set' property is attached and subsequent pages.

html AntennaHouse Prince
h2 {
  string-set: header "Chapter " counter(header) ": " content();
  counter-increment: header;
}

<h2>Europa</h2>

Note that the string called "header" is different from the counter with the same name. The above code may result in the string called "header" is set to "Chapter 1: Europa".

The textual content of the named string is processed as if 'white-space: normal' has been set.

html AntennaHouse Prince

In this example, the header will consist of three words ("In the beginning") with normal spacing between the words.

h2 {
  string-set: header "In " ' ' content();
}

<h2>   the    beginning</h2>

In this example, the named string called index will hold the first letter of <dt> elements.

dt { string-set: index content(first-letter) }

One-letter running headers are often used in dictionaries.

The textual content is copied regardless of values on other CSS properties.

html AntennaHouse Prince

In this example, the first four declarations have no effect on the on the named string copied in the last declaration:

title {
  display: none;
  content: "foo";
  text-transform: uppercase;
  text-replace: "foo" "bar";
  string-set: title content();
}

3.1.2 Using named strings

Named strings can be referred to with the ''string()'' value on the 'content' property. The ''string()'' value has one required argument (the name of the string), and one optional argument that indicates which string assignment to use. References to named strings can only be resolved after pagination.

@page { @top-center { content: string(header) }}
@page { @right-middle { content: string(index) }}
@page { @top-left { content: string(entry) }}
h1 { string-set: header "Chapter " counter(chapter) content() }
dt { string-set: index content(first-letter), entry content() }

If the value of the named string is changed by an element on a certain page, the named string may have several values on that page. In order to specify which of these values should be used, an optional second argument is accepted on the ''string()'' value. This argument can have one of four keywords:

The syntax of the ''string()'' value is:

  string( <custom-ident> [ , [ first | start | last | first-except ]]? )

The assignment is considered to take place on the first page where a content box representing the element occurs. If the element does not have any content boxes (e.g., if 'display: none' is set), the assignment is considered to take place on the page where the first content box would have occured if the element had been in the normal flow.

In this example, the first term on the page will be shown in the top left corner and the last term on the page will be shown in the top right corner. In top center of the page, the first letter of first term will be shown.

@page { @top-left { content: string(term, first) }}
@page { @top-right { content: string(term, last) }}
@page { @top-center { content: string(index, first) }}
dt { string-set: index content(first-letter), term content(text) }
html AntennaHouse Prince

Given this CSS code:

h2 { string-set: header content() }

The value of the header string

page#HTML codefirststartlastfirst-except
1
<h1>Continents</h1>
...
<h2>Africa</h2>
...
...
Africa Africa Africa
2
...
<h2>Americas</h2>
...
<h2>Asia</h2>
...
Americas Africa Asia
3
...
...
...
...
Asia Asia Asia Asia
4
<h2>Europe</h2>
...
<h2>Oceania</h2>
..

Europe Europe Oceania

In this example, the term that is being described at the start of the page is shown in the top left header.

@page { @top-left { content: string(term, start) }}
dt { string-set: term content() }
@page { @top-left { content: string(term, first) }}
@page { @top-right { content: string(term, last) }}
@page { @top-center { content: string(index, first) }}
dt { string-set: index content(first-letter), term content(text) }

In this example, the header in the top center will be blank on pages where 'h1' elements appear. On other pages, the string of the previous 'h1' element will be shown.

@page { @top-center { content: string(chapter, first-except) }}
h1 { string-set: chapter content() }

If the named string referred to in a 'string()' value has not been assigned a value, the empty string is used.

3.2 Running elements

html AntennaHouse Prince

Named strings, as described above, can only hold textual content; any style, structure or replaced content associated with the element is ignored. To overcome this limitation, a way of moving elements into running headers and footers is introduced.

Elements that are moved into headers and footers are repeated on several pages; they are said to be running elements. To support running elements, a new value – running() – is introduced on the 'position' property. It has one required argument: the name by which the running element can be referred to. A running element is not shown in its natural place; there it is treated as if 'display: none' had been set. Instead, the running element is displayed when referred to by the ''element()'' value.

Like counters and named strings, the name of a running element is chosen by the style sheet author, and the names have a separate namespace. A running element can hold one element, including its pseudo-elements and its descendants. Whenever a new element is assigned to a running element, the old element is lost.

User agents, however, must be able to remember the result of more than one assignment as the ''element()'' value (described below) can refer to different assignments.

Running elements inherit through their normal place in the structure of the document.

title { position: running(header) }
@page { @top-center {
  content: element(header) }
}

Like the ''string()'' value, the ''element()'' value accepts an optional second argument. The syntax is defined as:

  element( <custom-ident> [ , [ first | start | last | first-except ]]? )

The keywords have the same meaning as for the ''string()'' value.

The ''element()'' value is only allowed on the 'content' property within a margin box. The ''element()'' value cannot be combined with any other values.

In this example, the header is hidden from view in all media types except print. On printed pages, the header is displayed top center on all pages, except where h1 elements appear.

<style>
  div.header { display: none }
  @media print {
  div.header {
    display: block;
    position: running(header);
  }
  @page { @top-center { content: element(header, first-except) }}
</style>
...
<div class="header">Introduction</div>
<h1 class="chapter">An introduction</div>

This code illustrates how to change the running header on one page in the middle of a run of pages:

...
<style>
@page { @top-center {
  content: element(header, first) }}
.header { position: running(header) }
.once { font-weight: bold }
</style>
...
<div class="header">Not now</div>
<p>Da di ha di da di ...
  <span class="header once">NOW!</span>
  <span class="header">Not now</span>
  ... da di ha di hum.</p>
...
The header is "Not now" from the outset, due to the "div" element. The first "span" element changes it to "NOW!" on the page where the "span" element would have appeared. The second "span" element, which would have appeared on the same page as the first is not used because the ''first'' keyword has been specified. However, the second "span" element still sets the exit value for "header" and this value is used on subsequent pages.

4 Leaders

html AntennaHouse Prince

A leader is a visual pattern that guides the eye. Typically, leaders are used to visually connect an entry in a list with a corresponding code. For example, there are often leaders between titles and page numbers in a table of contents (TOC). Another example is the phone book where there are leaders between a name and a telephone number.

In CSS3, a leader is composed of series of glyphs through the ''leader()'' value on the 'content' property. The functional notation accepts two values. The first describes the glyph pattern that makes up the leader. The syntax is:

  leader( [ dotted | solid | space |  <string> ] )

Using the keyword values is equivalent to setting a string value. The table below shows the equivalents:

KeywordStringUnicode characters
leader(dotted)leader('. ')\002E \0020
leader(solid)leader('_')\005F
leader(space)leader(' ')\0020

The string inside the parenthesis is called the leader string. The leader string must be shown in full at least once, thereby pushing other content to the side (in the writing direction of the element to which the leader is attached). The leader string establishes the minimum length of the leader. If there is empty space on the line, the leader is expanded by repeating it as many times as possible. The expansion of a leader from its minimum length never causes line breaks to change. At the end of the leader, a partial leader string may be shown.

Line-breaking characters inside leader strings must be ignored. A list of line-breaking characters are found in [Unicode Technical Report #13]. Other white space characters are collapsed according to the values of white-space properties. A leader consisting only of whitespace characters is called a whitespace leader.

To determine the length of the leaders, user agents must do the following for each line where a leader appears:

  1. Lay out the content with leaders of minimum lengths.
  2. Determine the empty space on the line. The empty space is the difference between the width of the line box (as per CSS 2.1, section 9.4.2) and the formatted content in the line box. For table cells in automatic table layout, leaders may influence the layout by enlarging the "maximum cell width" (as per CSS 2.1 17.5.2.2).
  3. Distribute the empty space between all leaders on the line and fill the empty space with the specified leader patterns. Glyphs must not be shown partially. All leaders on the line should, to the extent possible, have the same length. This may not always be possible as the minimum leader length must be honored.

By adding a leader to the ::after pseudo-element, a leader is added after the content of an element:

h1::after { content: leader(dotted) }

<h1>Dotted news</h1>

The formatted result may be:

|Dotted news...................|

By setting 'white-space: pre', the white space in the leader string is preserved:

h1::after {
  content: leader('  -  ');
  white-space: pre;
}
<h1>Dashing news</h1>

The formatted result may be:

|Dashing news  -    -    -    -|

If a leader is set on an inline element inside a justified paragraph, there will not be any empty space on the line. Therefore, the leader is shown only once.

span::after {
  content: leader('...');
}

<p>A paragraph with a <span>span</span> inside.
would be formatted as:
A paragraph with a span... inside.

The presence of a leader may influence tables. Consider this example with two table cells in one row:

td.leader::after { content: leader('..') }

<table style="width: 30em">
<tr><td class=leader>foo</td> <td>bar</td></tr>
</table>

In UAs that don't do leaders, the two cells will have the same width and the table cells could be rendered like this:

|foo                             |bar                           |

When leaders are supported, the maximum cell width of the first cell grows, and the table cell could be rendered like this:

|foo........................................................|bar|

A table of contents often use leaders to visually connect a chapter title with the corresponding page number. This can be achieved with code like:

ul.toc a::after {
  content: leader(". . . ") target-counter(attr(href url), page);
}

The formatted result could be:

Africa ...........1
America...........9
Antarctica.......56
Asia............134

The appearance of leaders is influenced by the same set of properties that influence the appearance of fixed strings.

The same properties can affect the presentation of the fixed string and the leader.

content: "...";
content: leader("...");
html AntennaHouse Prince

Line numbers sometimes displayed on the side of poetry, for reference purposes. In this example, a counter is dsplayed on every other line and pushed to the right by an invisible leader.

@page { counter-reset: line }
.line { counter-increment: line }
.line:nth-of-type(2n+1)::after { content: leader(space) counter(line) }

The formatted result could be:

Oh! let that eye, which, wild as the gazelle's,    1
Now brightly bold or beautifully shy,
Wins as it wanders, dazzles where it dwells,       3
Glance o'er this page, nor to my verse deny

Note that the formatter doesn't really count lines, but rather elements marked up as lines.

A leader is only present on one line. If the presence of a minimum length leader at the end of a line results in subsequent content being moved to the next line, the leader will also be moved to the next line.

Consider this code:

<style>
.name::after { content: leader("...") }
</style>
<div class="entry">
<span class="name">Douglas Adams</span>
<span class="number">43</span>
</div>

Depending on the width of the containing block, the content may or may not fit on one line. In the rendered examples below, the thrird rendition the leader and its subsequent content is moved to the second line.

|Douglas Adams......42|
|Douglas Adams...42|
|Douglas Adams   |
|..............42|

The purpose of a leader is most often to connect two strings visually. To achieve this, User Agents may chose to move content appearing before the leader onto the same line as the leader.

In this renditon, the User Agen has chosen to move "Adams" to the next line to preserve a visual connection:

|Douglas         |
|Adams.........42|

In cases where there is not enough space for content on both sides of the leader, the leader will remain on the same line as the following content.

|Douglas  |
|Adams    |
|.......42|
|Dou|glas
|Ada|ms
|...|42

In this example, a whitespace leader is placed before a <cite> element:

<style>
cite::before { content: leader('  ') }
</style>
<blockquote>
  For a moment, nothing happend.
  Then, after a second or so,
  nothing continued to happen.
    <cite>Douglas Adams</cite>
</blockquote>

Whitespace leaders do not connect two strings in the same way that visible leaders do. User Agents may therefore chose to not move content appearing before the leader onto the same line as the leader. This may result in a rendition like:

|For a moment, nothing happend.  |
|Then, after a second or so,     |
|nothing continued to happen.    |
|                   Douglas Adams|

Rather than:

|For a moment, nothing happend.  |
|Then, after a second or so,     |
|nothing continued to            |
|happen.            Douglas Adams|

When possible, User Agents should align corresponding glyphs from the leaders of consecutive lines.

In this example, the dotted lines are aligned horizontally so that the dots appear directly above and below each other:

  Alfa ........ 5
  Bravo ...... 41
  Charlie ... 125

5 Cross-references

It is common to refer to other parts of a document by way of a section number (e.g., "See section 3.4.1"), a page number (e.g., "See discussion on page 72"), or a string (e.g., "See the chapter on Europe"). This specification describes how to resolve cross-references automatically by introducing two new values on the 'content' property.

5.1 The ''target-counter'' and ''target-counters'' values

Numerical cross-references are generated by ''target-counter()'' and ''target-counters()'' values on the 'content' property that fetch the value of a counter at the target end of the link. These functions are similar to the ''counter()'' and ''counters()'' functions, except that they fetch counter values from remote elements. ''target-counter()'' has two required arguments: the url of the link, and the name of a counter. ''target-counters()'' has three required arguments: the url of the link, the name of a counter, and a separator string. Both functions accepts an optional argument at the end that describes which list style type to use when presenting the resulting number; ''decimal'' being the default. The syntax is defined as:

  target-counter( <url> , <custom-ident> [ , <counter-style> ]? )
  target-counters( <url> , <custom-ident> , <string> [ , <counter-style> ]? )

This style sheet specifies that a string like " (see page 72)" is added after a link:

a::after { content: "(see page " target-counter(attr(href url), page, decimal) ")" }
html AntennaHouse Prince

This style sheet specifies that a string like " (see section 1.3.5)" is added after a link:

a::after { content: "(see section " target-counters(attr(href url), section, ".", decimal) ")" }

5.2 The ''target-text'' value

Textual cross-references are generated by ''target-text()'' which fetches the textual content from the target end of the link. Only text is copied; not style, structure, or replaced content. ''target-text()'' has one required argument: the url of the link. An optional second argument specifies exactly which content is fetched. There are four possible values: ''content'', ''before'', ''after'', ''first-letter''; these keywords are defined on 'string-set' above. The syntax is:

  target-text( <url> [, [ content | before | after | first-letter ]]? )

To generate this text:

See Chapter 3 ("A better way") on page 31 for an in-depth evaluation.

from this markup:
<p>See <a href="#chx">this chapter</a> for an in-depth evaluation.
...
<h2 id="chx">A better way</h2>
this CSS code can be used:
h2 { counter-increment: chapter }
a { content: "Chapter " target-counter(attr(href url), chapter)
   ' ("'  target-text(attr(href url), content) '") on page '
   target-counter(attr(href url), page);

6 Footnotes

html AntennaHouse Prince

When an element is turned into a footnote, certain things happen: the element is moved to the footnote area, a footnote call is left behind in its place, a footnote marker is displayed before the element, and the footnote counter is incremented.

A footnote is a note typically placed at the bottom of a page that comments on, or cites, a reference. References to footnotes are marked with a footnote call in the main text which corresponds to a footnote marker in the footnote area. The rendering of footnotes is complex. As far as possible, footnotes try to reuse other parts of CSS. However, due to the typographic traditions of footnotes, some new functionality is required.

In its simplest form, making a footnote is simple.

<style>
.footnote { float: footnote }
</style>

<p>A sentence consists of words.<span class="footnote">Most often.</span>.

In this example, the text Most often. will be placed in a footnote. A note-call will be left behind in the main text and a corresponding marker will be shown next to the footnote. Here is one possible rendering:

A sentence consists of words.¹

¹ Most often.

Footnotes make sense in paged media, but in continous media the information is better displayed inline. Here is an example of how to support both paged and contious media:

<style>
@media print {
.footnote { float: footnote }
}
@media screen {
.footnote::before { content: " (" }
.footnote::after { content: ")" }
}
</style>

<p>A sentence consists of words.<span class="footnote">Most often.</span>.

In contious media, the presentaion will be:

A sentence consists of words. (Most often.)

Consider this markup:

<p>Sorry, <span title="This is, of course, a lie.">we're closing for lunch</span>.

The content of the "title" attribute can be turned into a footnote with this code:

span[title]::after {
  content: attr(title);
  float: footnote;
}

6.1 Turning elements into footnotes

An element with ''float: footnote'' (called a footnote element) is moved to the footnote area and a footnote-call pseudo-element is put in its original place.

span.footnote {
  float: footnote;
}

Footnote elements are presented inside the footnote area, but they inherit through their normal place in the structure of the document.

For each new footnote element, the ''footnote'' counter is automatically incremented.

6.2 The footnote area

All elements with ''float: footnote'' are moved to the footnote area. The footnote area is described by an @footnote-rule inside the @page-rule. By default, the footnote area appears at the bottom of the page, but it can be positioned in other places.

These rules place the footnote area at the bottom of the page, spanning all columns of the outermost multicol element:

@page {
  @footnote {
    float: bottom;
    float-reference: page;
  }
}

The code above is part of the default style sheet.

These rules place the footnote area at the bottom of the first column:

@page {
  @footnote {
    float: bottom;
    float-reference: column;
  }
}

These rules place the footnote area at the bottom of the page. Also, the footnote area is split into three columns which the footnotes are flowed into:

@page {
  @footnote {
    float: bottom;
    float-reference: page;
    columns: 3;
  }
}

This code places the footnote area at the bottom of the last column of the outermost multicol element:

@page {
  @footnote {
    float: bottom;
    float-defer-column: last;
  }
}

The content of the footnote area is considered to come before other content which may compete for the same space on the same page.

@page { @footnote { float: bottom; float-reference: page; }}
figure { float: bottom; column-span: all }

If figures and footnotes are on the same page, the footnotes will appear below the figures as they are floated to the bottom before the figures.

Potentially, every page has a footnote area. If there are no footnotes on the page, the footnote area will not take up any space. If there are footnotes on a page, the layout of the footnote area will be determined by the properties/values set on it, and by the footnote elements inside it.

These properties apply to the footnote area: 'float', 'float-reference', 'content', 'border', 'padding', 'margin', 'height', 'width', 'max-height', 'max-width', 'min-height', 'min-width', and the background properties.

This example uses some of the applicable properties on @footnote:

@footnote {
  margin-top: 0.5em;
  border-top: thin solid black;
  border-clip: 4em;
  padding-top: 0.5em;
}

The result of this code is a footnote area separated from other content above it by margin, border and padding. Only 4em of the border is visible due to the 'border-clip' property, which is defined in CSS Backgrounds and Borders Module Level 4.

6.3 Footnote calls

When an element is moved to the footnote area, a footnote-call is left behind. By default, User Agents must behave as if this code is part of the default style sheet:

::footnote-call {
  content: counter(footnote, super-decimal);
}

The resulting note call is a super-script decimal number.

6.4 Footnote markers

A ::footnote-marker pseudo-element is added to each footnote element, before the ::before pseudo-element (if any), and replacing the ::marker pseudo-element (if any). User agents must, by default, show the "footnote" counter in the footnote-marker.

User Agents may display footnote-calls and footnote-markers this way by default:

::footnote-call {
  content: counter(footnote, super-decimal);
}
::footnote-marker {
  content: counter(footnote, super-decimal);
}

Marker elements are discussed in more detail in the CSS Lists module [CSSLIST]. One suggested change to that module is to honor the value of 'list-style-position' on the ::footnote-marker pseudo-element itself rather than the corresponding list-item element. Further, one clarification to the horizontal placement of the marker is suggested: the margin box of the marker box is horizontally aligned with the start of the line box.

6.5 Counting footnotes

The "footnote" counter is automatically incremented each time a footnote is generated. That is, the "footnote" counter is incremented by one each time an element with ''float: footnote'' appears.

This code adds square brackets around footnote calls and

::footnote-call {
  content: " [" counter(footnote) "]";
}

The footnote counter can be reset with the 'counter-reset' property.

This code resets the "footnote" counter on a per-page page basis:

@page { counter-reset: footnote }
html AntennaHouse Prince

Footnotes can also be combined with other counters. In this example, a poem is shown with line numbers on the side. The "line" counter is also used in the footnoter marker.

@page { counter-reset: line }
div.line { counter-increment: line }
div.line:nth-of-type(2n+1)::after { content: leader(space) counter(line) }
.footnote { float: footnote }
.footnote::footnote-call { content: "" }
.footnote::footnote-marker { content: counter(line) }

The formatted result could be:

Come, blue-eyed maid of heaven! - but thou, alas,       1
Didst never yet one mortal song inspire -
Goddess of Wisdom! here thy temple was,                 3
And is, despite of war and wasting fire,

1 From "Childe Harold's Pilgrimage" by Lord Byron
3 The use of uppercase "W" indicates great wisdom

6.6 Laying out footnotes

Footnotes must appear as early as possible under the following constraints:

  1. A footnote marker may not appear on an earlier page than the footnote call.
  2. Footnotes may not appear out of document order.
  3. The footnote area is limited in size by 'max-height', unless the page contains only footnotes. (E.g., if at the end of the document there are still footnotes unprinted, the User Agent can use the whole page to display footnotes.)
  4. If there is a footnote call on a page, the footnote area may not be empty, unless its 'max-height' is too small.

7 Named Areas

The page area and footnote area are predefined areas in CSS; all content is shown in the page area by default, and footnotes can be floated to the page area. This section describes a generic mechanism to create named areas, how to posistion named areas, and how to flow content into named areas.

7.1 Creating names areas: @area

Named areas can be generated by the style sheet to provide space for sidenotes, figures and other content which is presented apart from the main flow. The @area construct is used to create named areas, into which content can be flowed into with the 'flow' property.

Named areas are can be absolutely positioned, or positiond with the 'float' property. When absolutely positioned, measurements are relative to the page box.

In this example, a named area called sidenote is created.

@page {
  margin-left: 10em;
  @area sidenote { position: absolute; left: -8em; top: 0; width: 6em; height: auto }
}

The left edge of the named area is 8em to the left of the left edge of the page box.

Areas with the same name are connected and content can flow from such area to the next, when necessary.

In this example, both left and right pages have a sidenote area:

@page :left {
  margin-left: 10em;
  @area sidenote { position: absolute; left: -8em; top: 0; width: 6em; height: auto }
}
@page :right {
  margin-right: 10em;
  @area sidenote { position: absolute; right: -8em; top: 0; width: 6em; height: auto }
}

The baseline of the sidenotes are aligned with the baseline of their reference point.

To support having several identically named areas on the same page, a second level of @area blocks are used. Only two levels of @area are allowed. The outer @area must always have a name, and the inner @area must never have a name. The named areas are painted as if they appeared in the source file after the footnote area, and before any content.

@page {
  margin-left: 1cm 5cm;  /* wide side margins */
  @area sidenote {
    @area { position: absolute; left: -4cm; top: 0; bottom: 0; width: 3.5cm; } /* left */
    @area { position: absolute; right: -4cm; top: 0; bottom: 0; width: 3.5cm; } /* right */
  }
}

7.2 Flowing content to a named area: 'flow'

The 'flow' property is introduced to flow content into a named area. A functional notation indicates a named area, alignment within the named area, and placement policy.

   area(<ident> [, <alignment> [, <placement-policy> ]]);

Aligmment is one of:

baseline
element requests that its first baseline is aligned with the baseline of its reference point in the closest matching named area. This is the default value.
same-page
element requests to be filled into the first available space in the first matching named area on the same page
same-spread
element requests to be filled into the first available space in the first matching named area on the same spread
fill
element requests to be filled into the first available space in the first matching named area on any page
top-edge
element requests that its top border edge is aligned with the top border edge of the BFC of the reference point in the closest matching named area
bottom-edge
element requests that it bottom border edge is aligned with the bottom border edge of the BFC of the reference point in the closest matching named area

An optional exclamation mark ('!') can be added to the above keywords to express that aligment is essential: if it cannot be honored, the ele,ment will not be shown.

This code moves <aside> elements to the named area called "sidenote" on the same page as the reference point:

aside { flow: area(sidenote, same-page); }

In this example, images are flowed to right pages, where the "photos" area take up all available space:

@page :right {
  @area photos {
    float: top;
    float-reference: bleed-box;
    width: 1vw;
    height: 1vh;
  }
}
img { flow: area(photos) }

The result is a document where content in the normal flow is shown on left pages, and right pages hold images. The images are filled into the named area in sequential order.

@page {
  margin-left: 1cm 5cm;  /* wide side margins */
  @area sidenote {
    @area { position: absolute; left: -4cm; top: 0; bottom: 0; width: 3.5cm; } /* left */
    @area { position: absolute; right: -4cm; top: 0; bottom: 0; width: 3.5cm; } /* right */
  }
}
.sidenote { flow: area(sidenote, baseline) }

One possible rendering is:

            ........ .........
            ........ .........
            ........ ...2..... second
            ........ ......... sidenote
            ........ .........
      first ....1... .........
   sidenote ........ .........
            ........ ....3.... third
            ........ ......... sidenote
            ........ .........

The digits mark the call points.

In this example there are two streams of footnotes, both using named areas. The first stream flowed into the "fn1" area, and the second is flowed into "fn2". The first stream is referenced by numbers, and the second by lower-alpha.

@page {
  @area fn1 { float: bottom; float-reference: page }
  @area fn2 { float: bottom; float-reference: page }
}

.fn1 { flow: area(fn1, same-page); counter-increment: fn1 }
.fn1::call { content: "[" counter(fn1) "]" }
.fn2::marker { content: "[" counter(fn1) "]" }

.fn2 { flow: area(fn2,same-page); counter-increment: fn2 }
.fn2::call { content: "[" counter(fn2, lower-alpha) "]" }
.fn2::marker { content: "[" counter(fn2, lower-alpha) "]" }

In the formatted result, the named areas appear at the bottom of the page; fn1 is floated to the bottom first and therefore appears below fn2. A possible rendering could be:

[a] From "Childe Harold's Pilgrimage" by Lord Byron
[3] The use of uppercase "W" indicates great wisdom

In this example there are two streams of footnotes. The first stream is floated to the predefined "footnote" area, and the second is flowed to a name area called "fn2". The first stream is referenced by line numbers, and the second by lower-alpha.

@page {
  counter-reset: line
  @area fn2 {
    float: bottom;
    float-reference: page;
  }
}
div.line { counter-increment: line }
div.line:nth-of-type(2n+1)::after { content: leader(space) counter(line) }

.footnote { float: footnote }
.footnote::footnote-call { content: "[" counter(footnote, lower-alpha) "]" }
.footnote::footnote-marker { content: "[" counter(footnote, lower-alpha) "]" }

.fn2 { flow: area(fn2, same-page) }
.fn2::call { content: "" }
.fn2::marker { content: "[" counter(line) "]" }

In the formatted result, the footnote area appears below the named area.

Come, blue-eyed maid of heaven! - but thou, alas,[a]       1
Didst never yet one mortal song inspire -
Goddess of Wisdom! here thy temple was,                    3
And is, despite of war and wasting fire,

[3] The use of uppercase "W" indicates great wisdom
[a] From "Childe Harold's Pilgrimage" by Lord Byron

Consider this code:

.s1 { flow: area(sidenote, same-page) }
.s2 { flow: area(sidenote, same-page) }

When there is one sidenote area, to the left of the first column, the rendering may look like:

┌─────┐......1.....  ............
│ s1  │............  ............
└─────┘............  ............
┌─────┐............  ............
│ s2  │............  ............
└─────┘............  ............
       ............  ....2.......
       ............  ............
       ............  ............

This example is similar to the previous example, except that there are two sidenote areas: left of the first column, and right of the second column. The sidenote elements are move to the closest sidenote area:

.s1 { flow: area(sidenote, same-page)
.s2 { flow: area(sidenote, same-page)
┌─────┐......1.....  ............┌─────┐
│ s1  │............  ............│ s2  │
└─────┘............  ............└─────┘
       ............  ............
       ............  ............
       ............  ............
       ............  ....2.......
       ............  ............
       ............  ............

In this example, figures appear in the main flow, while captions are flowed to the closest sidenote areas (there are two: left of the first column, and right of the second column). The captions are aligned with the top of the figure element.

figcaption { flow: area(sidenote, top-edge) }

<figure>
  <figcaption>s1</figcaption>
</figure>
<figure>
  <figcaption>s2</figcaption>
</figure>
       ............  ............
       ............  ............
┌─────┐┌──────────┐  ............
│ s1  ││ fig1     │  ┌──────────┐┌─────┐
└─────┘│          │  │ fig2     ││ s2  │
       │          │  │          │└─────┘
       └──────────┘  │          │
       ............  └──────────┘
       ............  ............
       ............  ............
       ............  ............

In this example, the figure has an image that spans the width of the page and bleeds to the edges. The caption follows underneath, in the sidenote area. The intended rendering is:

 ┌──────────────────────┐
 │ image                │
 │                      │
 │                      │
 └──────────────────────┘
 ┌────────┐  ...........
 │caption │  ...........
 └────────┘  ...........
             ...........
             ...........

The style sheet define two name areas, one for the photograph and one for the sidenote. The "photo" named area is floated to the top, while the "sidenote" area is absolutely positioned. The two named areas overlap. Therefore, the order of the content is significant; the image is first flowed into the "photo" area. The position of the caption will be influcenced by the height of the image.

@page {
  margin: 7mm 7mm 7mm 40mm;
  @area photo { float: top; float-reference: bleed-box }
  @area sidenote { position: absolute; left: -40mm; top: 0; bottom: 0; width: 35mm; }
}
figure img { flow: area(photo, same-page); clear: page }
figcaption { flow: area(sidenote, same-page) }

...

<figure>
  <img>
  <figcaption>caption</figcaption>
</figure>

In this example, photographs are shown on right pages while the corresponding captions are flowed to a sidenote area on left pages.

@page :right {
  margin-right: 10em;
  @area photos {
    position: absolute;
    top: 0; right: -10em; bottom: 0; left: 0;
  }
}
@page :left {
  margin-left: 10em;
  @area sidenote { position: absolute; right: -8em; top: 0; width: 6em; height: auto }
}
img { flow: area(photos) }
caption { flow: area(sidenote) }

When there is one sidenote area, to the left of the first column, the rendering may look like:

┌─────┐ ......1..... ┌─────────────┐
│ s1  │ ............ │photo 1      │
└─────┘ ............ │             │
┌─────┐ ............ └─────────────┘
│ s2  │ ............ ┌──────────┐
└─────┘ ............ │photo 2   │
        .........2.. │          │
        ............ └──────────┘
        ............

7.3 Placement policy

This is fairly sketchy

An optional argument indicates a placement policy:

erase
element erases any other content which may have been flowed to the named area
stick
Element sticks to the named area and is repeated on any subsequent pages which has the same named ares. Also, element erases any other content which may have been flowed to the named area.
copy
rather than being moved, the element is copied to the named area.

In this example, there are two named areas called "sidenote", one on the left side of the first column, and the other on the right side of the second column. Three reference points are marked with 1, 2, and 3. Exact alignment along the baseline is possible for all three notes.

            ........ .........
            ........ .........
            ........ ...2..... second
            ........ ......... sidenote
            ........ .........
      first ....1... .........
   sidenote ........ .........
            ........ ....3.... third
            ........ ......... sidenote
            ........ .........

The code used to achieve this is:

.sidenote { flow: area(sidenote, baseline) }

This example is similar to the previous one, except for the placement of the third reference point: it appears much closer to the second reference point. As a result, the second sidenote pushes down the third sidenote, which is no longer exactly aligned along the baseline.

            ........ .........
            ........ .........
            ........ ...2..... second
            ........ .....3... sidenote
            ........ ......... third
      first ....1... ......... sidenote
   sidenote ........ .........
            ........ .........
            ........ .........
            ........ .........

This example is similar to the previous one, except that 'baseline!' is used:

.sidenote { flow: area(sidenote, baseline!) }

As a result, the third sidnote (which cannot be granted its requested placement) is not shown:

            ........ .........
            ........ .........
            ........ ...2..... second
            ........ .....3... sidenote
            ........ .........
      first ....1... .........
   sidenote ........ .........
            ........ .........
            ........ .........
            ........ .........

First, move an element to a named area:

aside { flow: area(sidenote, same-page) }

Then, make the element stick so that it is shown on succeeding pages:

h1.copy { flow: area(sidenote, same-page, stick) }
aside { flow: area(sidenote, same-page, erase) }

In this example, the h1 element is copied into a running header which is defined with @area. The copied element is restyled. It erases the running header when it enters, and is set to stick (until eased by another element):

@page :left {
  @area running-header {
    position: absolute; top: -2cm; left: 0; width: 3.5cm; height: 1cm
    @inside h1 {
      font: 12pt sans-serif;
      text-align: right;
    }
  }
}
@page :right {
  @area running-header {
    position: absolute; top: -2cm; right: 0; width: 3.5cm; height: 1cm
    @inside h1 {
      font: 12pt sans-serif;
      text-align: right;
    }
  }
}

h1 { flow: area(running-header, same-page, stick) }

In this example, the named area called "running header" is placed in the center top of the page. It is filled with three elements, one being the copy

@page {
  @area running-header {
    position: absolute; top: -2cm; left: 0; right: 0; height: 1cm;
    text-align: center;
    @inside h1 {
      font: 12pt sans-serif;
      display: inline;
    }}}

#pagecounter {
  flow: area(running-header, fill, stick); content: counter(page);  }
#ornament {
  flow: area(running-header, fill, stick); color: red; content: "\2747" /* snow crystal */}
h1 {
  flow: area(running-header, fill, copy stick); font: 12pt serif }

<span id=pagecounter></span>
<span id=ornament></span>
<h1>Erin Hildebrand</h1>

7.4 Note calls: ::call

Elements that are floated to named areas can add a "call", similar to a footnote-call, at the place where they were floated from.

.sidenote {
  flow: area(sidenote, same-page);
  counter-increment: sn;
}
.sidenote::call {
  content: counter(sn, lower-alpha); font-size: 0.6em; vertical-align: top;
}

7.5 Floating elements to margin boxes

Margin boxes can be considered a special type of named areas. Unlike names areas, margin boxes:

The 'margin()' function moves elements to margin areas:

div.running-header { flow: margin(top-left) } /* move to top-left margin area */ h1 { flow: margin(top-right, copy) } /* copy to margin area */

7.6 Pulling content: target-pull()

To support legacy browsers, it is often better to make a link to the note rather than including the text inline. This example shows how to fetch the content of a note and place it in a footnote.

<style>
@media print {
  .footnote {
    float: footnote;
    content: target-pull(attr(href url)) }
  .call { display: none }
}
</style>
...
<p>A sentence consists of words<a class="footnote" href="#words"> [3]</a>.
...
<p id=words><span class="call">[3]</span> Most often.

When shown in a legacy browser, the content of the element will be shown as a clickable link to an endnote. When printed according to this specification, there will be a footnote:

A sentence consists of words¹.

¹ Most often.

When footnotes are long or complex, it may be impractical to keep their content at the point of reference. Instead, the content of the footnote may be stored at some other point in the source file. This example shows how to fetch the content of a note and place it in a footnote.

<style>
@media print {
  .footnote {
    float: footnote;
    content: target-pull(attr(href url)) }
}
</style>
...
<p>A sentence consists of words<a class="footnote" href="#words"></a>.
...
<div id=words>Most often, a sentence consists of words. However, some sentences also
  have <em>numbers</em>. And punctuation, don't forget punctuation.</div>

8 Bookmarks

html AntennaHouse Prince

Some document formats have the capability to represent bookmarks into the document. These bookmarks can e.g. be used to show an outline or an index of the document. Bookmarks are typically shown outside the document itself, often in a tree-structured and clickable table of contents. To generate bookmarks, these properties are defined: 'bookmark-level', 'bookmark-label', and 'bookmark-state'.

8.1 'bookmark-level'

Name: bookmark-level
Value: none | <integer>
Initial: none
Applies to: all elements
Inherited: no
Percentages: N/A
Media: all
Computed value: specified value

This property describes what level a certain bookmark has in a hierarchical bookmark structure. The values are:

none
no bookmark is generated
<integer>
Indicates the level of the bookmark; the highest level is ''1'', then ''2'', ''3'' etc. Zero and negative values are not allowed.

8.2 'bookmark-label'

Name: bookmark-label
Value: <content-list> | none
Initial: none
Applies to: all elements
Inherited: no
Percentages: N/A
Media: all
Computed value: specified value

This property specifies the label of the bookmark, i.e., the text that will represent the bookmark in the bookmark structure. This properly will only be consulted if 'bookmark-level' is different from 'none'. The values are:

<content-list>
as defined on the 'string-set' property
none
no bookmark is generated

This code would generate a simple hierachical outline of a document that uses three heading levels:

h1 { bookmark-level: 1 }
h2 { bookmark-level: 2 }
h3 { bookmark-level: 3 }
h1, h2, h3 { bookmark-label: content() }

This code will make bookmarks from links.

a[href] { bookmark-label: attr(href); bookmark-level: 1 }
a[title] { bookmark-label: attr(title); bookmark-level: 1 }

If a title attribute exisits, its value will be used as the bookmark label. Otherwisee, the URL is used.

This code specififies a string to be used as the bookmark label:

#frog { bookmark-label: "The green frog"; bookmark-level: 1 }

Consider this code:

h1 { bookmark-label: content(before) ": " content(); bookmark-level: 1 }
h1:before { content: "Chapter" }

<h1>Africa</h1>

The resulting bookmark would be: "Chapter: Africa".

8.3 'bookmark-state'

Name: bookmark-state
Value: open | closed
Initial: open
Applies to: block-level elements
Inherited: no
Percentages: N/A
Media: all
Computed value: specified value

A hierarchy of bookmarks may be shown in an open or closed state. The user will typically be able to toggle the state to navigate in the bookmarks. This property describes the initial state of a bookmark.

In this example, h1 and h2 elements are set to have an open initial bookmark stat, all other elements will be closed initially:

* { bookmark-state: closed }
h1, h2 { bookmark-state: open }

9 Page selectors

A document consists of a linear sequence of pages. In simple documents, all pages have the same page area and marginalia. In more complex documents, pages may have different sizes, marginalia and orientation. Being able to style pages differently is therefore important.

In CSS2, first, left and right pages, as well as named pages, can be selected. This specification adds more advanced page selectors. Page selectors are similar to normal selectors in the sense that they query a data set and select parts of it. However, page selectors and normal selector are different in what they query (normal selectors query a document tree; page selectors query a sequence of pages) and what they select (normal selectors find structured elements, page selectors find formatted pages).

9.1 The nth() page pseudo-class

The nth() construct is added to select a numbered page.

These are equivalent, they both select the first page of a document:

@page :first { background: lime }
@page :nth(1) { background: lime }

The grammar of nth-child() pseudo-class can be used with nth():

In this example, pages in a document will cycle through pink, lime and white backgrounds:

@page :nth(3n) { background: pink }
@page :nth(3n+1) { background: lime }
@page :nth(3n+2) { background: white }

9.2 Page groups

A page group is a sequence of pages with the same name. Being able to style the first page of page group different from the other pages in the page group is important. To support this, the named page and the :first and :nth() selector can be combined with the page name in page selectors:

In this example, various background colors will be set on different pages in the "chapter" page group:

@page chapter:first { background: pink }  /* the first page */
@page chapter:nth(1) { background: pink }  /* the first page */
@page chapter:nth(2) { background: lime }  /* the second page */
@page chapter:nth(3n+2) { background: white }  /* every third page */

A new page group is started when an element is set to a named page which is different from the preceding element, or different from the ancestor element.

In this example, the first article starts a new page group due to having a different named page than its ancestor. However, the second article continues in the same page group as the first, due to having the same named page. As a result, there will only be one page with a green background.

@page chapter:first { background: green }
body { page: upright }
article { page: chapter }

<body>
  <article>...</article>
  <article>...</article>
</body>

To enable page groups to be created when sibling elements use the same named page, the 'break-before' and 'break-after' properties are consulted. When a forced page break has been set between the two elements (either 'break-after: page' on the first element, or 'break-before: page' on the second element), the second element will create a new page group.

In this example, both articles start new page groups due to there being an explicit page break between them.

@page chapter:first { background: lime }
body { page: upright }
article { page: chapter; break-before: page }

<body>
  <article>...</article>
  <article>...</article>
</body>

10 Conditional content

Content is sometimes displayed or hidden based on their placement after formatting. This specification proposes the 'condition' property to express conditions that must be met in order for the element to be displayed; if the condition is not met, the effect will be as if 'display: none' had been set.

In this example, thematic breaks within the prose is normally indicated by a few blank lines. However, if the thematic break appears at a page break, it will be indicated by three asterisks.

  .tb:before { content: "***"; condition: at-page-break; margin: 1em 0; text-align: center;  }
  .tb:after  { content: "\a\a\a"; condition: not-at-page-break }

  <div class=tb></div>

11 Clipping column rules

11.1 column-rule-clip

Name: column-rule-clip
Value: none | auto | [ <length> <length>? ]
Initial: none
Applies to: multicol elements
Inherited: no
Percentages: N/A
Media: visual
Computed value: as specified

Values are:

none
no clipping is performed
auto
column rules are clipped so that they align with prominent font features; for latin scripts this means that the top end of the column rule aligns with the ex-height of the dominant font, while the bottom end of the rule aligns with the baseline of the dominiant font.
length
If one value is specified, the top and bottom of all column rules are clipped by specified length. If two values are specified, the first value applies to the top column rules and the second value applies to the bottom column rules.

Spanners split column rules, and each fragment is possibly clipped by this property.

12 Selecting elements within pages

This section sketches several possible solutions.

To select elements depending on which page they start on, these pseudo-classes can be used:

aside:left { background: red }
aside:right { background: green }
aside:first { background: blue }
aside:nth(4n+1) { background: pink }

To select elements within a page, normal selectors can be combined with a page selector.

@page :first p { background: lime }
@page :nth(1) .sidenote { background: lime }
@page :nth(3n+1) p:first-line { background: lime }
@page funky:nth(1) p { background: pink }

The syntax is:

  @page \S+ <page-selector> \S+ <selector>

In an alternative longhand syntax, selectors are placed inside an @inside-block:

These are identical:

@page :left {
  @inside {
    p { text-align: left }
  }
}

@page :left p { text-align: left }

The longhand syntax also allows the styling of elements that appear inside page areas and margin boxes.

In this example, two sidenote areas are declared. Paragraphs inside those areas are given a differnet styling based on which side they are in.

@page {
  margin-left: 1cm 5cm;  /* wide side margins */
  @area sidenote {
    @area {
      position: absolute; left: -4cm; top: 0; bottom: 0; width: 3.5cm; /* left sidenote */
      @inside p {
        text-align: right;   /* paragraphs in left sidenotes are aligned right */
      }
    }
    @area {
      position: absolute; right: -4cm; top: 0; bottom: 0; width: 3.5cm; /* right sidenote */
      @inside p {
        text-align: left;   /* paragraphs in right sidenotes are aligned left */
      }
    }
  }
}
@page :left {
  background: pink;            /* declaration applies to pages */
  @inside {
    p { text-align: left }     /* declaration applies to elements on page */
  }
  @top-center {
    background: orange;        /* declaration applies to margin box */
    @inside {
      p { text-align: left }   /* declaration applies to elements in top-center margin boxes */
    }
  }
}

13 Selecting columns

This section is not fully developed.

div.chapter:column(3)       /* the 3rd column of the element */
div.chapter:column(2n)      /* all even columns of the element */
div.chapter:column(3+)      /* all columns but the 1st and 2nd */
div.chapter:column(2,2)     /* second column on second page */
div.chapter:column(*,2)     /* all columns on the second page */
div.chapter:column(1,*)     /* the first column on all pages */

14 Baseline grids

html AntennaHouse Prince

Text in adjacent columns often use the same baselines. This appears more pleasant to typographers, and reduces show-through, where a printed line shows through to the other side of the sheet. By aligning text to a grid of baselines, a baseline rhythm is established. Subheadings and figures may disturb the baseline rhythm, but subsequent text should find it again. The 'baseline-grid' property is introduced to define, engage and clear baseline grids, while the 'baseline-block-snap' property indicates how to align elements that do not align themselves with a baseline grid.

14.1 'baseline-grid'

Name: baseline-grid
Value: normal | none | new | root | page
Initial: normal
Applies to: block containers
Inherited: no
Percentages: N/A
Media: visual
Computed value: as specified

This property is used to define, engage, and clear baseline grids. When a baseline grid is enganged, it will be in effect on content inside the block container (including child elements) until another baseline grid is engaged, or the baseline grid is cleared. When a baseline grid is in effect, content will be aligned with the baseline grid.

normal
The element does not define, engage, or clear a baseline grid.
none
the element clears the baseline grid
new
the element defines and engages a new baseline grid for its content. The new baseline grid is defined by establishing the first baseline of the element by using the first available font; thereafter the frequency of the baseline grid is equal to the used value of 'line-height'.
root
the element enganges the root baseline grid. The root baseline grid is automatically defined by the root element; the calculation for finding the root baseline grid is the same as for the 'new' value. While the root element defines the root baseline grid, it does not engage it.
page
the element engages the page baseline grid. The page baseline grid is automaticalle defined by the page box on the current page and the font properties of the root element; the calculation for finding the page baseline grid is the same as for the root baseline grid, except that the page box is used to position the baseline grid. In continous media, this value is the same as 'root'.

In this example, the article element defines an enganges a baseline grid. Content inside the article will align with the baseline grid, except for headlines and figures.

article { baseline-grid: new }
h1, h2, figure { baseline-grid: none }

One possible rendition is:

____________________________ ____________________________ ____________________________ ____________________________ ____________________________ ____________________________ ____________________________ ____________________________ ____________________________
Abc def ghi j klmn o pqrst uvw xyz.
Heading
Abc def ghi j klmn o pqrst uvw xyz. Abc de fghij klm
nop q rstuw xyz.
figure
caption
Abc def ghij klm nop.

Notice how the body text aligns with the baseline grid (shown in orange for illustration purposes), while the heading, figure and figure caption do not. After the heading and figure, the main text starts again on the next available baseline.

In this example there are sidenotes in different scripts:

aside { font: 12pt serif; baseline-grid: new }
[lang=ar] { font: 10pt 'Amiri', serif }

<aside><span lang=en>...</span></aside>
...
<aside><span lang=ar>...</span></aside>

The <aside> elements establish a baseline grid from the used font (font: 12pt serif). Content inside the <aside> elements, even in different scripts, fonts and sizes, align with the baseline grid established by the <aside> elements.

The root baseling grid is automatically defined, but is not automatically engaged. In this example, the root baseline grid is enganged by the root element.

:root { baseline-grid: root }

The root baseline grid is handy when only a few elements want to opt-in to use a baseline grid.

p, li, blockquote { baseline-grid: root }

The 'page' baseline grid is automatically defined so that pages with different page margins still can align content to to the same baseline grid. In this example, the 'page' basedline grid is applied to all sidenotes:

aside { baseline-grid: page }

14.2 'baseline-block-snap'

Name: baseline-block-snap
Value: [before | after | center | auto] || [margin-box | border-box]
Initial: auto
Applies to: elements with 'baseline-grid: new' or 'baseline-grid: none'
Inherited: no
Percentages: N/A
Computed value: specified value

This property describes how to align content that is not aligned to a baseline grid. Values are:

auto
same as 'before' on top of column, same as 'after' at bottom of column, otherwise 'center'
before
the element is placed as close to the previous content as possible
after
the element is placed as close to the following content as possible
center
the element is centered between the previous and following content.
margin-box
the margin edge is used to align the block on the baseline grid
border-box
the border edge is used to align the block on the baseline grid

In this example, elements that do not follow the baseline grid will snap upwards towards their older siblings.

h2, h2, figure {
  baseline-block-snap: before;
}
____________________________ ____________________________ ____________________________ ____________________________ ____________________________ ____________________________ ____________________________ ____________________________ ____________________________
Abc def ghi j klmn o pqrst uvw xyz.
Heading
Abc def ghi j klmn o pqrst uvw xyz. Abc de fghij klm
nop q rstuw xyz.
figure
caption
Abc def ghij klm nop.

15 Character substitution

html AntennaHouse Prince

It is sometimes convenient to replace one character or string with another without changing the source document. The ‘text-replace’ property offers a way to perform the replacement in the style sheet.

Name: text-replace
Value: [<string> <string>]+ | none
Initial: none
Applies to: all elements
Inherited: yes
Percentages: N/A
Media: visual
Computed value: as specified value

This property replaces all occurrences of a certain string with another string in the content of the element. The property accepts pairs of strings as value, in addition to the initial 'none' value. For each pair of strings, occurrences of the first string in the content will be replaced with the second string. If 'none' is specified, no replacements will occur.

html AntennaHouse Prince

In this example, three consecutive period characters are replaced with an ellipsis character and the apostrophe character is replaced with a quotation character:

body { text-replace: "..." "\2026" "'" "\2019" }

As a result, the string "It's the beginning...!" will be changed to "It’s the beginning…!".

In this example, a narrow no-break space is added before exclamation marks in French texts. This is known as "quart de cadratin" in French.

html:lang(fr) { text-replace "!" "\202F!" }

As a result, the string "Mais, oui!" is changed to "Mais, oui !".

In this example, a hair space is added before and after em-dashes.

body { text-replace: "—" "\200A—\200A" }

As a result, this string "no—not yet" is changed to "no — not yet".

Text replacements are applied serially; the first pair of strings is applied to the textual content of the element, then the second pair of strings is applied to the outcome etc.

The two rules below yield the same result. In the first rule all 'a' characters are converted to 'b'. Subsequently, all 'b' characters are converted to 'c'. In the second rule, all 'a' and 'b' characters are converted directly to 'c'.

body { text-replace: "a" "b" "b" "c" }
body { text-replace: "a" "c" "b" "c" }

The first string in a pair must have at least one character, while the second may be empty.

In this example, all 'a' characters are removed.

body { text-replace: "a" "" }

If the first string of a pair is empty, or if an odd number of strings has been specified, the whole value is ignored and no text replacements are performed. Text replacements do not occur across element boundaries.

No text replacement will occur in this example:

h1 { text-replace: "foo" "bar" "" "foo" }   /* ignore value */
h2 { text-replace: "foo" "bar" "foobar" }   /* ignore value */
h3 { text-replace: "foobar" "barfoo" }      /* accept value, but ... */

<h3>foo<span>bar</span></h3>    <!-- ... start tag breaks string which would otherwise match -->

This property is evaluated after the content property, and before text-transform.

16 Microtypography

Microtypography is a range of methods for improving the appearance of text. Often, small adjustments in the space between letters and words can achieve a more even presentation with less need for hyphenation and fewer widows and orphans. This specification extends two existing CSS properties — 'letter-spacing' and 'word-spacing' — with minimum and maximum values to guide User Agents when laying out text.

16.1 Minimum, maximum and optimal spacing

The 'letter-spacing' and 'word-spacing' properties are extended to accept up to three values:

  [ normal | <length> | <percentage>]{1,3}

Values are:

normal
The spacing is the normal spacing for the current font. This value allows the user agent to alter the space between characters in order to justify text.
<length>
Specifies extra spacing in addition to the default space between characters and words. Values may be negative, but there may be implementation-specific limits.
<percentage>
Relative to width of space character.

If one value is specified, it describes the optimum value, as well as the minimum and maximum values. If two values are specified, the first denotes the optimum and the minimum, and the second denotes the maximum. If three values are specified, they describe the optimum, minimum and maximum values.

p {
  letter-spacing: -0.04em 0 0.04em;
  word-spacing: -0.2em normal 0.2em;
}

What are min/max values relative to? Are they relative to the normal position (like the opt values are), or to the opt position?

If we add percentage values, what are they relative to? Something other than em? CSS3 Text suggested advance measure/width for word-spacing, which probably means the space character?

letter-spacing: -5% 0% 5%;
letter-spacing: -5% 0% 5%;

word-spacing: 80% 100% 120%;
word-spacing: 80% 100% 120%;

Would it be better to only use positive numbers? Like this:

p {
  letter-spacing: 0.04em 0 0.05em;
  word-spacing: 0.2em normal 0.2em;
}

If so, one could often just use two values::

p {
  letter-spacing: 0 0.05em;
  word-spacing: normal 0.2em;
}

17 Spatial layout of pages; @layout

In commonly used apps, pages are often laid out spatially so that users can nagivate from one page to another by moving up, down, right or left. To support this feature for web content, a new @-rule is proposed: @layout. The purpose of @layout is to describe how pages are laid out spatially relative to the current document.

Four new properties are allowed inside @layout: nav-up, nav-right, nav-bottom, nav-right.

The name of the properties inside @layout are borrowed from CSS3 Basic User Interface Module.

The properties accept these values:

go()
the function takes one argument, which refers to the rel attribute of the link element
<link rel=index href="../index.html">
<link rel=previous href=g3.html>
<link rel=next href=g1.html>
...

@layout {
  nav-up: go(index);
  nav-left: go(previous);
  nav-right: go(next);
}

This functionality relies on semantics in HTML and CSS. Other languages may have other other ways to describe such semantics. One possible solution for other languages is "link[rel=index] { nav-up: attr(href) }"

''back''
The keyword takes the user one step back in the history of browsed pages.
@layout {
  nav-left: back;
}
url()
The funcation takes one argument: a URL. Relative URLs are relative to the style sheet.
@layout {
  nav-up: url(..);
  nav-down: url(a1.html);
}
url-doc()
The function is identical to url(), except that relative URLs are relative to the document, not to the style sheet.
@layout {
  nav-up: url-doc(..);
  nav-down: url-doc(a1.html);
}

Combined with the @document-rule, navigation maps can be described:

@document url("http://example.com/foo") {
  @layout {
    nav-right: link-rel(next);
  }
}

@document url("http://example.com/bar") {
  @layout {
    nav-up: link-rel(next);
  }
}

alternative name: @navigation, @neighborhood, @hood

17.1 Page shift effects

To describe page shift effects, four new properties inside @layout are proposed: nav-up-shift, nav-right-shift, nav-down-shift, nav-left-shift. These properties take one of several keyword values:

pan
pans to the new page; this is the initial value
turn
turns the page, like soft book pages do
flip
flips the page, like stiff cardbord
fold
the old page folds, like an accordion

The proposed keyword values are loosely described. Are there better ways to describe transitions?

@layout {
   nav-up-shift: pan;
   nav-down-shift: flip;
}

18 Appendix A: Default style sheet

@page {
  counter-reset: footnote;
  @footnote {
    counter-increment: footnote;
    float: bottom;
    column-span: all;
    height: auto;
  }
}

::footnote-call {
  counter-increment: footnote;
  content: counter(footnote, super-decimal);
}
::footnote-marker {
  content: counter(footnote, super-decimal);
}

h1 { bookmark-level: 1 }
h2 { bookmark-level: 2 }
h3 { bookmark-level: 3 }
h4 { bookmark-level: 4 }
h5 { bookmark-level: 5 }
h6 { bookmark-level: 6 }

References

[CSS]
CSS, Bert Bos, Tantek Çelik, Ian Hickson et al.. W3C.
[CSSPAGE]
CSS Paged Media Module, Melinda Grant, Håkon Wium Lie, Elika J. Etemad et al.. W3C.
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels, Scott Bradner. IETF.

Acknowledgments

This document has been improved by Bert Bos, Michael Day, Melinda Grant, David Baron, Markus Mielke, Steve Zilles, Ian Hickson, Elika Etemad, Laurens Holst, Mike Bremford, Allan Sandfeld Jensen, Kelly Miller, Werner Donné, Tarquin (Mark) Wilton-Jones, Michel Fortin, Christian Roth, Brady Duga, Del Merritt, Ladd Van Tol, Tab Atkins Jr., Jacob Grundtvig Refstrup, James Elmore, Ian Tindale, Murakami Shinyu, Paul E. Merrell, Philip Taylor, Brad Kemper, Peter Linss, Daniel Glazman, Tantek Çelik, Florian Rivoal, Alex Mogilevsky, Simon Sapin, Cameron McCormack, Liam R E Quin, Peter Moulder, Morten Stenshorne, Rune Lillesveen, Lars Erik Bolstad, Anton Prowse, Michel Onoff, Dave Cramer.