Acid2 is a test page for web browsers published by The Web Standards Project (WaSP). It has been written to help browser vendors make sure their products correctly support features that web designers would like to use. These features are part of existing standards but haven't been interoperably supported by major browsers. Acid2 tries to change this by challenging browsers to render Acid2 correctly before shipping.
Acid2 is a complex web page. It uses features that are not in common use yet, because of lack of support, and it crams many tests into one page. The aim has been to make it simple for developers and users to check if a browser passes the test. If it does, the smiley face on the left will appear. If something is wrong, the face will be distorted and/or shown partly in red.
The purpose of this document is to explain how Acid2 works. The markup behind Acid2 is peculiar in that it attempts, on one single page, to test many different features. We do not envision or recommend that normal Web pages should be written this way, but it is appropriate for a test page. At first sight, the source code is hard to understand, but the guided tour offered in this document will explain it in some detail. The guide assumes a technical understanding of HTML, CSS and PNG.
Note: Acid2 uses data URLs extensively. To allow vendors that not yet support data URLs to test with Acid2, a version without data URLs has been published.
Acid2 tests features that web designers have been requesting. Everything that Acid2 tests is specified in a Web standard, but not all Web standards are tested. Acid2 does not guarantee conformance with any specification. After careful consideration, we have selected and are testing the features we consider most important for the future of the web. Although Acid2 was inspired by Microsoft's announcement of IE7, it is not targeted at a specific browser. We believe Acid2 will highlight problems in all current browsers.
Acid2 assumes basic support for HTML4, CSS1, PNG, and Data URLs. The first three items on the list are included for obvious reasons: they form the backbone of web content standards. Data URLs are described in HTML4 but is less used due to lack of support. We believe data URLs are convenient and useful for web designers, and easy to add in browsers. Further, Acid2 assumes that the browser uses a default style sheet with common values. For example, it is assumed that all 'div' elements have 'display: block'. Finally, the test assumes a pixel density of 96 ppi, as per the CSS 2.1 Last Call Working Draft. For user agents that support font zooming, it also assumes a zoom factor of 100%. The list below gives an overview of the most important additional features that are tested:
object
element — The eyes of the face are attached to an object
element. Being able to use object
(which can have alternative content) is one of the oldest requests from web designers.
hoverover it. Which one?
It should be noted that Acid2 is rendered in standards mode. That is, the test page includes a DOCTYPE which signals that the browser should treat the page according to standards. Vendors that are reluctant to make changes in how they render legacy documents can continue their current behavior in what is known as quirks mode.
Acid2, when rendered correctly, consists of one line of text
(Hello World
) and a 14x14 grid of squares inside a containing
block where a smiley face can be seen. Each square is 12px high and
12px wide. The face has a yellow background with a black facial
outline around it. In this section, we will explain how all of this is
created.
The string "Hello World!" marks the start of Acid2. It tests basic CSS1 properties. In the source code, it is marked up as an h2
element:
<h2 id="top">Hello World!</h2>
These CSS declarations apply to the top
element:
h2
), their class (e.g., nose
), or their id (e.g., top
). To simplify the language, it isn't always clear from the prose whether a name is a type, class or id. However, we have chosen class names and id values to avoid ambiguities, and all references to a specific element is given a background color.Element | Declaration | Effect |
---|---|---|
<h2 id="top"> | margin: 100em 3em 0 | Sets the margins around the string. The large top margin separates the test itself from the introductory text on the top of the page. (The sample rendering of the string on the left side does not show the right spacing around the string.) |
padding: 2em 0 0 .5em | Sets some vertical padding around the element so that there is some white space above and below the string. The padding above the element has to be accurately set in order for the scalp of the face to appear in the right position. | |
text-align: left | Overrides any default style sheet. | |
font: 2em/24px sans-serif | Sets the font to be double the size of its parent element. The parent element has a font size of 12px, so the font height of the string is 24px. The line height of the string is also set to '24px'. This will ensure that the distance from the string to the face is the same in all browsers and is required for the scalp to appear in the correct position. Also, a 'sans-serif' font is set in this declaration. | |
color: navy | Selects a 'navy' color for the string. | |
white-space: pre | Indicates that the string should not be split over several lines, even if the window is very narrow. |
These declarations are written in CSS1 and most browsers will have no problems rendering this code.
The picture of a face appears inside a containing block. It tests a basic part of absolute positioning. In the markup, the containing block is started by this tag:
<div class="picture">
And these declarations apply:
Element | Declaration | Effect |
---|---|---|
<div class="picture"> | position: relative | Turns the element into a containing block. |
border: 1em solid transparent | Adds a border around the containing block. However, the border is transparent and therefore not visible, except for the space it takes up. | |
margin: 0 0 100em 3em | Sets the margins around the containing block. The large bottom margin should leave enough room at the bottom of the page for the test to appear at the top of the window. |
This row is the scalp of the face and it tests fixed positioning, minimum and maximum heights, and minimum and maximum widths. In the markup, the row is represented by a p
element which is fixed to the window rather than the scrollable canvas. If the Acid2 page is scrolled, the scalp will stay fixed in place, becoming unstuck from the rest of the face, which will scroll.
Element | Declaration | Effect |
---|---|---|
<p> | position: fixed | Positions the element relative to the viewport. |
margin: 0 | Overrides any default style sheet. | |
padding: 0 | ||
border: 0 | ||
top: 9em | Sets the vertical position of the element relative to the window. | |
left: 11em | Sets the horizontal position of the element relative to the window. | |
width: 140% | Percentage values on width refer to the width of the containing block, which is big. The width would therefore be large, but max-width limits it to 4em. | |
max-width: 4em | ||
height: 8px | Sets the height of the element. This is later overridden by settings on max-height and, finally, min-height. | |
min-height: 1em | When min-height is larger than max-height, min-height will win (CSS 2.1, section 10.7). For most devices, 12px (1em) will be bigger than 2mm, and the height of the element is therefore 1em. | |
max-height: 2mm | ||
background: black | The element has no content and the box will therefore appear black. |
In order for row 1 to be positioned correctly, we assume that fragment identifiers cause scrolling to occur up to the top padding edge (or maybe top border edge, that isn't tested) of the element, and not its top margin edge or top content edge. This is a common convention, but is not described in a specification.
The resulting face fragment can be seen on the left.
This row tests attribute selectors, class selectors, absolute positioning, and floating elements. The second row of the face has two black boxes with a yellow box in
between. The effect is created by a blockquote
element that has an
address
element inside it:
<blockquote class="first one"> <address class="second two"></address> </blockquote>
Each of the two elements have class attributes with two values in them. The first element (blockquote
) is matched by this selector:
[class~=one].first.one
The selector has three parts: one attribute selector ([class~=one]
) and two class selectors (.first
and .one
). All parts of the selector match and the following declarations therefore apply:
Element | Declaration | Effect |
---|---|---|
<blockquote ... | position: absolute | Makes the element an absolutely positioned element. |
top: 0 | Aligns the margin edge of the box to the top edge of the box's containing block. | |
margin: 36px 0 0 60px | Sets the top margin to 36px, which moves the squares down into the second row. Also, it sets the left margin to be 60px which pushes the leftmost black squares into their correct position. | |
padding: 0 | Overrides any default style sheet. | |
border: black 2em | The border makes up the black squares in this row. This declaration sets the color and width of the border. | |
border-style: none solid | Turns off top and bottom borders, and sets the left and right borders to be solid. |
The width of the blockquote element is not set explicitly and is
therefore 'auto'. CSS 2.1 section 10.3.7 (rule 3) specifies that the width of the element should be determined by the shrink-to-fit algorithm. As a result, the width of the element is determined by its content: the
address
element.
The innermost element (address
) is matched by this selector:
[class~=one][class~=first] [class=second\ two][class="second two"]
This selectors has two main part with several sub-parts. The first
main part ([class~=one][class~=first]
matches the
blockquote
element, and the second main part
(class=second\ two][class="second two"]
) matches the
address
element. Therefore, these properties apply to the
address
element:
squareis not an official length unit, but is convenient to use in this document as the face is laid out as squares in a 14x14 grid. One
squareis therefore equal to 12px. On most elements in Acid2, the font size is 12px, so that 1em is equal to one square.
Element | Declaration | Effect |
---|---|---|
<address ... | float: right | Makes the element floating. However, this has no visible effect as the position of the element is determined by the shrink-to-fit algorithm. |
width: 48px | Sets the width of the element to 4 squares. | |
height: 12px | Sets the height of the element to 1 square. | |
background: yellow | Sets the yellow color of the face. | |
margin: 0 | Overrides any default style sheet. | |
padding: 0 | Overrides any default style sheet. |
The resulting face fragment can be seen on the left.
This row tests width, overflow, the universal selector, and data URLs. The row consists of two nested elements. The outer element generates the two black squares of the third line, and the inner element generates the yellow box between the two black squares. The elements are represented by this markup:
<div class="forehead"> <div> ....</div> </div>
Note that the only content in the inner element is
entities (not all are reproduced above) and
the content will therefore be displayed on a single line.
The outer div
element has these declarations attached to it:
Element | Declaration | Effect |
---|---|---|
<div class="forehead"> | margin: 4em | Sets the horizontal and vertical position of the left black square. The dimension is relative to the containing block since this is the first row that has elements in the normal flow. |
width: 8em | Sets the width of the outer element. The inner element is constrained by this width. | |
border-left: solid black 1em | Generates the left black square. | |
border-right: solid black 1em | Generates the right black square. | |
background: red url(data:image/png;base64,...) | The background is set to a color name ('red') and a data url. The data url (not reprinted in full) represents a single-pixel yellow PNG image. The PNG image will always be available, and the red background should therefore not be seen. |
The inner div
element is matched by this selector:
.forehead *
The above selector would match any element within the outer div
, but on this page there is only one element; namely, the inner div
element which has these declarations attached to it:
Element | Declaration | Effect |
---|---|---|
<div> | width: 12em | The width of the element is set to be wider that the parent. However, this should not influence the size of the parent in any way. |
line-height: 1em | Sets the height of the row to 12px. |
The resulting face fragment can be seen on the left.
These rows constitute one unit. They test paint order and fixed backgrounds. The rows depict a pair of eyes along with the familiar black outline of the yellow face.
In the markup, these rows are represented by a set of nested div
and object
elements. Here is an outline of the element structure:
<div class="eyes"> <div id="eyes-a"> <object data="data:application/x-unknown,ERROR"> <object data="http://www.webstandards.org/404/" type="text/html"> <object type="image/png" data="data:image/png;...">ERROR</object> </object> </object> </div> <div id="eyes-b"></div> <div id="eyes-c"></div> </div>
The eyes appear in the inner object
element. The two ancestor object
elements should both display their fallback contents — which is the inner object
element. The fallback content of the inner object
element should not be displayed as the data
attribute returns a valid PNG image (depicting the eyes).
The table below shows which declarations apply the elements in the structure:
Element | Declaration | Effect |
---|---|---|
<div class="eyes"> | position: absolute | Positions the element absolutely on row 4. |
top: 5em | ||
left: 3em | ||
margin: 0 | Overrides any default style sheet. | |
padding: 0 | ||
background: red | One purpose of the child elements is to hide this color. | |
<div id="eyes-a"> | height: 0 | The height is zero so that the element will not take up any space. (Its content will still be visible due to the initial 'visible' value on the 'overflow' property.) As a result, the next sibling element (eyes-b ) will overlap with this element.
|
line-height: 2em | Ensures the right vertical size for inline content. | |
text-align: right | Causes the objects to be aligned to the right of the block, so that they overlap the right hand side of the float (which is red) and not the left hand side (which is the left black bit). | |
<object ...> | display: inline | Inline content should be painted on top. |
vertical-align: bottom | Set to avoid baseline alignment issues. | |
<object ...> | width: 7.5em | Should have no effect since the object is inline ('height'/'width' don't apply to inlines). |
height: 2.5em | ||
<object ...> | border-right: solid 1em black | Sets the right facial outline for these rows. The border will be painted on top since they are set on inline content. |
padding: 0 12px 0 11px | Sets some space to the left and right of the eyes. | |
background: url(data:image/png;base64,...) 1px 0 fixed | Sets the background to a semi-transparent yellow checkerboard image. The background image is fixed and, and offset by one pixel to — along with the element below — form a solid yellow background. | |
<div id="eyes-b"> | float: left | Tests that floats are painted under inlines but over blocks. Also, makes it possible for the next block element (eyes-c ) to be overlapping this element.
|
width: 10em | Sets the height and width of the yellow area on these rows. | |
height: 2em | ||
background: fixed url(data:image/png;base64,...) | This background image is identical to the one on the innermost object element. Also, it is fixed to the element. It is, however, not offset by one pixel so that the two background images together form a solid yellow background.
| |
border-left: solid 1em black | ||
border-right: solid 1em red | ||
<div id="eyes-c"> | display: block | Block elements are painted first when elements are overlapping. |
background: red | The red background should not be visible, as floating and inline elements are painted on top. | |
border-left: 2em solid yellow | Provide the familiar yellow for the left edge of the row. | |
width: 10em | Sets the dimensions. | |
height: 2em |
The resulting face fragment can be seen on the left.
These rows constitute the center part of the face, including the nose. They test generated content and child selectors.
The element structure is quite simple with three div
elements inside each other:
<div class="nose"><div><div></div></div></div>
These properties apply to the nose
element:
Element | Declaration | Effect |
---|---|---|
<div class="nose"> | float: left | Floats the element to the left. This does not change the position of the element itself, but it has effects on subsequent elements. |
margin: -2em 2em -1em | The top margin is set to '-2em', the left and right margin to 2em, and the bottom margin to -1em. The left margin makes sure this element is in its right horizontal position. | |
border: solid 1em black | Sets a solid 1em border on all four sides of the element. The width of the top border is reset in the next declaration, though. | |
border-top: 0 | Resets the width of the top border to zero. | |
min-height: 80% | Percentage values on these properties become 'auto' (as per CSS 2.1 section 10.5 and section 10.7). The intrinsic height is more than 3em, so the height of the element is 3em (a.k.a. 3 squares). | |
height: 60% | ||
max-height: 3em | ||
padding: 0 | Overrides any default style sheet. | |
width: 12em | Sets the width to 12 squares. It is inside this area the descendants will roam. | |
div | padding: 1em 1em 3em | The 3em bottom padding alone ensures that the intrinsic size of the parent element is at least 3em. With the 1em top padding and zero height, the height of the yellow area is 4 squares. |
height: 0 | ||
background: yellow | A familiar color. | |
div | width: 2em | The red background color of this element will not be visible even if the height and width has been set explicitly. This is due to the generated content using up all the available space. |
height: 2em | ||
background: red | Should not be visible. | |
margin: auto | This declaration ensures that the nose is centered within the nose element.
|
The inner div
element has generated content before and after it. The generated content constitutes the diamond-shaped nose of the face.
The generated content before the element constitutes the upper half of the nose. These declarations apply:
Declaration | Effect |
---|---|
display: block | This declaration has two important effects: it makes the 'height' apply to the generated content, and the two pseudo-elements are stacked vertically. |
border-style: none solid solid | The 1em border is solid on the right, bottom and left sides. However, the border color is identical to the surroundings (i.e., it's yellow) on the left and right side, so only a black triangle (the bottom border) can be discerned. The top border is set to 'none' and should therefore not be visible at all. |
border-color: red yellow black yellow | |
border-width: 1em | |
content: '' | Ensures that the pseudo-element is generated, but that no content is shown. |
height: 0 | Prevents the line box generated by the 'content' property from having any effect. |
The declarations set on the :after
pseudo-element are identical, except that the bottom border has been swapped with the top border.
The result of stacking two black triangles, one over the other, is that a diamond-shaped nose occurs in the face.
The resulting face fragment can be seen on the left.
Between row 9 and 10 are several elements with different vertical
margins. When positioning the smile
element of row 10, it is necessary to calculate how the margins collapse.
Vertical margin collapsing takes place between block-level elements in the normal flow. The table below lists the relevant elements along with their vertical margins.
element | forehead | |
margin-bottom | 4em | |
margin-top | 6.25em | 0 |
element | empty | div |
margin-bottom | 6.25em | -6em |
margin-top | 5em | |
element | smile |
The height of the empty
element and it child is zero. Therefore, all values with a shaded background in the above table will collapse. The resulting margin between forehead
to the top of the smile
is 0.25em.
However, there is also a floating element between forehead
to the top of the smile
. Floats do not enter the collapsing
margin calculations, but in this case it will effectively override the
margins; The top border edge of the smile
element is placed flush with the bottom margin edge of the floating
nose
element.
These rows constitute the smile in the face and test collapsing margins. Also, the 'inherit' keyword and nested floating elements are tested. These rows are represented by this structure, along with a short textual description of each element:
<div class="smile"> absolutely positions the two rows <div> paints the black facial outline <div> paints the yellow background <span> paints, in part, the upper half of the smile itself <em> paints, in part, the lower and upper half of the smile <strong> gives the smile its width </strong> </em> </span> </div> </div> </div>
The table below details which CSS declarations apply to which elements:
Element | Declaration | Effect |
---|---|---|
<div class="smile"> | margin: 5em 3em | The vertical margin ('5em') collapses with the earlier margins of the forehead and empty elements. The horizontal margin (3em ) positions the row in the right place.
|
clear: both | Ensures that the element is moved down from any floating elements (i.e., the nose element).
| |
<div> | margin-top: 1em | This has no effect as it collapses with the top margin of the smile element.
|
background: black | The back background of this element makes up the facial outline of the face. | |
width: 12em | The smile is 12 squares wide, including the facial outline. | |
height: 2em | The smile covers 2 rows. | |
position: relative | Makes this element a containing block. | |
<div> | position: absolute | |
top: 0 | Aligns the top of this element with the top of the containing block. | |
right: 1em | Positions the element one square from the right edge of the containing block. | |
width: auto | The width of the element is determined by its descendants. | |
height: 0 | Sets the height to zero. The borders of the element will still be visible, though. | |
margin: 0 | Overrides any default style sheet. | |
border: yellow solid 1em | This border constitutes the yellow area of these two rows. | |
<span> | display: inline | |
border: solid 1em transparent | There is a 1em transparent left and right border on this element. There is no top or bottom border. | |
border-style: none solid | ||
float: right | The element floats to the right, and is pulled 1 square up by a negative margin. | |
margin: -1em 0 0 0 | ||
background: black | The element has a black background and is 1 square high. It forms a black bar of 8 squares, of which the 6 middle squares are overpainted by the top border of the child element. | |
height: 1em | ||
<em> | float: inherit | |
border-top: solid yellow 1em | ||
border-bottom: solid black 1em | This border forms the base of the smile. The width comes from the child element. | |
<strong> | width: 6em | Sets the width of the smile in row 11. |
display: block | So that 'margin-bottom' will apply. | |
margin-bottom: -1em | This declaration has no effect. The parent element has top and bottom borders so the negative margin set here will not collapse with other margins. |
The resulting face fragment can be seen on the left.
This row tests the line-height property. In the markup, the row is represented by two nested div
elements:
<div class="chin"> <div> </div> </div>
These declarations apply to the outer div
element:
Declaration | Effect |
---|---|
margin: -5em 4em 0 | Sets the vertical and horizontal position. The top -5em margin collapses with the bottom 5em margin of the smile element, leaving zero gap between this element and the previous one.
|
width: 8em | Sets the width of the chin, not including the borders, to 8 squares. |
line-height: 1em | Makes the inline box of the element 12px high. This also becomes the height of the row as the only other inline box on the line is fully enclosed by this inline box. Section 10.8 of CSS 2.1 describes this in detail. |
border-left: solid 1em black | Makes the left border a square black box. |
border-right: solid 1em black | Makes the right border a square black box. |
background: yellow url(data:image/png;base64,...) no-repeat fixed | The background of the element is set to 'yellow'. Also, there is a 64px by 64px red box in the background (represented by the PNG image in the data url, not reprinted in full). The red box is not visible as the background-attachment is set to 'fixed' which puts the red box outside of view. |
These declarations apply to the inner div
element:
Declaration | Effect |
---|---|
display: inline | |
font: 2px/4px serif | Makes the font-size and line-height small to ensure that the inline box is fully enclosed by the inline box of the parent element. |
The resulting face fragment can be seen on the left.
This row tests the parser, cascading mechanism and selectors. After removing some dubious comments, the remaining markup should be like:
<div class="parser-container"> <div class="parser"></div> </div>
The challenge for browsers is figuring out which declarations applies to the parser
element. These declarations do:
Declaration | Effect |
---|---|
color: maroon | Sets the color and implies a border-color, which is later overridden. |
border: solid | Sets a solid border of 'medium' width. The border color is set later. |
color: orange | Sets the color and implies a border-color, which is later overridden. Note that 'orange' is a valid color name in CSS 2.1. |
border-color: black | Overrides (implied) border-color on previous line. |
border-width: 0 2em | Overrides (implied) declaration on earlier line. |
margin: 0 5em 1em | Positions the element. |
padding: 0 1em | These two declarations make the yellow area 4 squares wide. |
width: 2em | |
height: 1em | Sets the height to 1 square. |
background: yellow | Sets the background. |
This row also tests that the parser correctly ignores parts of the style sheet according to the specification. The table below describes what should be ignored and why:
CSS code | What should be ignored and why |
---|---|
.parser { /* ... \*/ } | The backslash should have no effect. |
.parser { error: \}; background: yellow } | erroris not a valid property in CSS. It's important that the parser picks up again after the semicolon, though; the background should be yellow. |
* html .parser { background: gray; } | html is the root element, so the selector will not match anything.
|
\.parser { padding: 2m; } | Testing that there is no element of type .parser(as opposed to one with the class parser). |
.parser { m\argin: 2em; } | Testing that there is no property called m(U+000A)rgin(as opposed to one called margin). |
; .parser { height: 3em; } | The selector includes the semicolon from the previous line and therefore does not match anything. |
.parser { width: 200; } | Unit is lacking. |
.parser { border: 5em solid red ! error; } | erroris not allowed after the exclamation mark. |
.parser { background: red pink; } | Only one color name is allowed. |
The resulting face fragment can be seen on the left.
This row tests CSS tables. The structure is a simple list:
<ul> <li class="first-part"></li> <li class="second-part"></li> <li class="third-part"></li> <li class="fourth-part"></li> </ul>
The style sheet associated with these elements turns it into a table with one row and four cells. Each cell is equal to one square. Here are the elements shown alongside their respective declarations:
Element | Declaration | Effect |
---|---|---|
<ul> | display: table | Presents the list using table formatting. |
padding: 0 | Overrides any default style sheet. | |
margin: -1em 7em 0 | Positions the element horizontally and vertically. | |
background: red | The background of this element should be fully covered by its children. | |
<li class="first-part"> | padding: 0 | Overrides any default style sheet. |
margin: 0 | ||
display: table-cell | Turns the element explicitly into a table cell. | |
height: 1em | 1 square. | |
width: 1em | ||
background: black | The color we want to see. | |
<li class="second-part"> | padding: 0 | Overrides any default style sheet. |
margin: 0 | ||
display: table | This element is turned into a table of its own. An anonymous table cell will be created around it. The anonymous table cell should not take up any space, and the width and height of the element should therefore be honored. | |
height: 1em | 1 square. | |
width: 1em | ||
background: black | Indeed. | |
<li class="third-part"> | padding: 0 | Overrides any default style sheet. |
margin: 0 | ||
display: table-cell | Makes the element a separate table cell. | |
height: 0.5em | All cells in this row have the same height, so this cell will also be 1 square. | |
width: 1em | ||
background: black | Black. | |
<li class="fourth-part"> | padding: 0 | Overrides any default style sheet. |
margin: 0 | ||
list-style: none | removes the list-style marker | |
height: 1em | 1 square. | |
width: 1em | ||
background: black | The last square of the face is black. |
The resulting face fragment can be seen on the left.
After row 14 comes some elements that should not cause anything visible to be generated. What is being tested here is that table cells honor the line-height property. Here are the elements:
<div class="image-height-test"> <table> <tr> <td> <img src="data:image/png;base64,..." alt=""> </td> </tr> </table> </div>
The image referenced from the img
element is a 64x64 red PNG image. It should never be visible due to these declarations:
<div class="image-height-test"> | height: 10px | Sets the height of the element to a miniscule 10px. |
overflow: hidden | Hides overflow content, including the image. | |
font: 20em serif | Sets a huge font size which results in an equally big line height. Even if there is no textual content in a cell, it must honor the line height calculations. |
As a result of honoring the big line-height value, the image is pushed far down. As only the top 10px of the element is visible, the image will be hidden.
bottom:
to .smile div
to compensate for margin collapsing.chin
to compensate for margin collapsingEvery effort has been made to ensure that Acid2 is correct and that it tests the features in greatest demand. If you find errors in it, think it unfair for any reason, or have other comments, please let us know.