Working together for standards The Web Standards Project

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.

Note: When taking the test, you should use the default settings of the browser you are testing. Changing the zoom level, minimum font size, applying a fit-to-width algorithm, or making other changes may alter the rendition of the Acid2 page without this constituting a failure in compliance. (Added 21 July 2006)

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:

  • Transparent PNGs — The eyes are encoded as transparent PNGs.
  • The 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.
  • Absolute, relative and fixed positioning — Being able to position elements accurately is important for advanced page layouts.
  • Box model — The original Acid test focused on the CSS box model. Acid2 continues in this fine tradition by testing ‘height’, ‘width’, ‘max-width’, ‘min-width’, ‘max-height’ and ‘min-height’.
  • CSS tables — There is nothing wrong with table layouts. It is a powerful layout model which makes sense on bigger screens. However, the table markup is troublesome as it ties the content to these screens. Therefore, being able to specify table layouts in CSS is important.
  • Margins — CSS defines accurate algorithms for how margins around elements should be calculated.
  • Generated content — The ability to add decorations and annotations to Web pages without modifying the markup has long been requested by authors.
  • CSS parsing — Acid2 includes a number of illegal CSS statements that should be ignored by a compliant browser.
  • Paint order — We test that overlapping content is painted in the right order. This is not a feature in itself, but a requirement for other features to work correctly.
  • Line heights — The Acid2 test checks a few key parts of the CSS inline box model, upon which any standards-compliant Web page depends.
  • Hovering effects — One of the elements in the face changes color when you hover over 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.

What should appear

Acid2, when rendered correctly, consists of one line of text (Hello World) and a 14×14 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:

In this guide, elements are referred to by their type (e.g., 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 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:

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.

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.

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.

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>

Each of the two elements have class attributes with two values in them. The first element (blockquote) is matched by this selector:


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:

The square is not an official length unit, but is convenient to use in this document as the face is laid out as squares in a 14×14 grid. One square is 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.

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">

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:

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

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">
    <div id="eyes-b"></div>
    <div id="eyes-c"></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.
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
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.

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:

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

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.

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

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.

Row 12

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

  <div class="chin">

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

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>

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 } 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:

    <li class="first-part"></li>
    <li class="second-part"></li>
    <li class="third-part"></li>
    <li class="fourth-part"></li>

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

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">
          <img src="data:image/png;base64,..." alt="">

The image referenced from the img element is a 64×64 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.

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


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 .


