Acid2: the guided tour

Hello World!
Acidman smiley face

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.

What are we testing?

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:

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.

What should appear

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 text

Hello World!

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:

ElementDeclarationEffect
<h2 id="top">margin: 100em 3em 0Sets 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 .5emSets 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: leftOverrides any default style sheet.
font: 2em/24px sans-serifSets 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: navySelects a 'navy' color for the string.
white-space: preIndicates 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 containing block

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:

ElementDeclarationEffect
<div class="picture">position: relativeTurns the element into a containing block.
border: 1em solid transparentAdds 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 3emSets 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.

Row 1

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.

ElementDeclarationEffect
<p>position: fixedPositions the element relative to the viewport.
margin: 0Overrides any default style sheet.
padding: 0
border: 0
top: 9emSets the vertical position of the element relative to the window.
left: 11emSets 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: 8pxSets the height of the element. This is later overridden by settings on max-height and, finally, min-height.
min-height: 1emWhen 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: blackThe 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.

Row 2

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:

ElementDeclarationEffect
<blockquote ...position: absoluteMakes the element an absolutely positioned element.
top: 0Aligns the margin edge of the box to the top edge of the box's containing block.
margin: 36px 0 0 60pxSets 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: 0Overrides any default style sheet.
border: black 2emThe border makes up the black squares in this row. This declaration sets the color and width of the border.
border-style: none solidTurns 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:

ElementDeclarationEffect
<address ...float: rightMakes the element floating. However, this has no visible effect as the position of the element is determined by the shrink-to-fit algorithm.
width: 48pxSets the width of the element to 4 squares.
height: 12pxSets the height of the element to 1 square.
background: yellowSets the yellow color of the face.
margin: 0Overrides any default style sheet.
padding: 0Overrides any default style sheet.

The resulting face fragment can be seen on the left.

Row 3

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>&nbsp;&nbsp;....</div>
  </div>
		

Note that the only content in the inner element is &nbsp; 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:

ElementDeclarationEffect
<div class="forehead">margin: 4emSets 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: 8emSets the width of the outer element. The inner element is constrained by this width.
border-left: solid black 1emGenerates the left black square.
border-right: solid black 1emGenerates 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:

ElementDeclarationEffect
<div>width: 12emThe 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: 1emSets the height of the row to 12px.

The resulting face fragment can be seen on the left.

Row 4-5

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:

ElementDeclarationEffect
<div class="eyes">position: absolutePositions the element absolutely on row 4.
top: 5em
left: 3em
margin: 0Overrides any default style sheet.
padding: 0
background: redOne purpose of the child elements is to hide this color.
<div id="eyes-a">height: 0The 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: 2emEnsures the right vertical size for inline content.
text-align: rightCauses 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: inlineInline content should be painted on top.
vertical-align: bottomSet to avoid baseline alignment issues.
<object ...>width: 7.5emShould have no effect since the object is inline ('height'/'width' don't apply to inlines).
height: 2.5em
<object ...>border-right: solid 1em blackSets 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 11pxSets 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: leftTests 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: 10emSets 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: blockBlock elements are painted first when elements are overlapping.
background: redThe red background should not be visible, as floating and inline elements are painted on top.
border-left: 2em solid yellowProvide the familiar yellow for the left edge of the row.
width: 10emSets the dimensions.
height: 2em

The resulting face fragment can be seen on the left.

Row 6-9

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:

ElementDeclarationEffect
<div class="nose">float: leftFloats 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 -1emThe 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 blackSets 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: 0Resets 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: 0Overrides any default style sheet.
width: 12emSets the width to 12 squares. It is inside this area the descendants will roam.
divpadding: 1em 1em 3emThe 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: yellowA familiar color.
divwidth: 2emThe 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: redShould not be visible.
margin: autoThis 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:

DeclarationEffect
display: blockThis 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 solidThe 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: 0Prevents 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

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.

elementforehead
margin-bottom4em
margin-top6.25em0
elementemptydiv
margin-bottom6.25em-6em
margin-top5em
elementsmile

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.

Row 10-11

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:

ElementDeclarationEffect
<div class="smile">margin: 5em 3emThe 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: bothEnsures that the element is moved down from any floating elements (i.e., the nose element).
<div>margin-top: 1emThis has no effect as it collapses with the top margin of the smile element.
background: blackThe back background of this element makes up the facial outline of the face.
width: 12emThe smile is 12 squares wide, including the facial outline.
height: 2emThe smile covers 2 rows.
position: relativeMakes this element a containing block.
<div>position: absolute
top: 0Aligns the top of this element with the top of the containing block.
right: 1emPositions the element one square from the right edge of the containing block.
width: autoThe width of the element is determined by its descendants.
height: 0Sets the height to zero. The borders of the element will still be visible, though.
margin: 0Overrides any default style sheet.
border: yellow solid 1emThis border constitutes the yellow area of these two rows.
<span>display: inline
border: solid 1em transparentThere is a 1em transparent left and right border on this element. There is no top or bottom border.
border-style: none solid
float: rightThe element floats to the right, and is pulled 1 square up by a negative margin.
margin: -1em 0 0 0
background: blackThe 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 1emThis border forms the base of the smile. The width comes from the child element.
<strong>width: 6emSets the width of the smile in row 11.
display: blockSo that 'margin-bottom' will apply.
margin-bottom: -1emThis 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.

Row 12

This row tests the line-height property. In the markup, the row is represented by two nested div elements:

  <div class="chin">
    <div>&nbsp;</div>
  </div>
		

These declarations apply to the outer div element:

DeclarationEffect
margin: -5em 4em 0Sets 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: 8emSets the width of the chin, not including the borders, to 8 squares.
line-height: 1emMakes 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 blackMakes the left border a square black box.
border-right: solid 1em blackMakes 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:

DeclarationEffect
display: inline
font: 2px/4px serifMakes 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.

Row 13

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:

DeclarationEffect
color: maroonSets the color and implies a border-color, which is later overridden.
border: solidSets a solid border of 'medium' width. The border color is set later.
color: orangeSets 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: blackOverrides (implied) border-color on previous line.
border-width: 0 2emOverrides (implied) declaration on earlier line.
margin: 0 5em 1emPositions the element.
padding: 0 1emThese two declarations make the yellow area 4 squares wide.
width: 2em
height: 1emSets the height to 1 square.
background: yellowSets 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 codeWhat should be ignored and why
.parser { /* ... \*/ }The backslash should have no effect.
.parser { error: \}; background: yellow }error is 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; }error is 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.

Row 14

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:

ElementDeclarationEffect
<ul>display: tablePresents the list using table formatting.
padding: 0Overrides any default style sheet.
margin: -1em 7em 0Positions the element horizontally and vertically.
background: redThe background of this element should be fully covered by its children.
<li class="first-part">padding: 0Overrides any default style sheet.
margin: 0
display: table-cellTurns the element explicitly into a table cell.
height: 1em1 square.
width: 1em
background: blackThe color we want to see.
<li class="second-part">padding: 0Overrides any default style sheet.
margin: 0
display: tableThis 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: 1em1 square.
width: 1em
background: blackIndeed.
<li class="third-part">padding: 0Overrides any default style sheet.
margin: 0
display: table-cellMakes the element a separate table cell.
height: 0.5emAll cells in this row have the same height, so this cell will also be 1 square.
width: 1em
background: blackBlack.
<li class="fourth-part">padding: 0Overrides any default style sheet.
margin: 0
list-style: noneremoves the list-style marker
height: 1em1 square.
width: 1em
background: blackThe last square of the face is black.

The resulting face fragment can be seen on the left.

After row 14

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: 10pxSets the height of the element to a miniscule 10px.
overflow: hiddenHides overflow content, including the image.
font: 20em serifSets 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.

Revision history

  1. Version 1.1

    • Changed top margin and added relative position value for bottom: to .smile div to compensate for margin collapsing
    • Changed top margin on .chin to compensate for margin collapsing
  2. Version 1.0

    • Original test

Contact

Every 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.