4.9 Tabular data
4.9.1
The table element
- Categories:
- Flow content.
- Palpable content.
- Contexts in which this element can be used:
- Where flow content is expected.
- Content model:
- In this order: optionally a
captionelement, followed by zero or morecolgroupelements, followed optionally by atheadelement, followed optionally by atfootelement, followed by either zero or moretbodyelements or one or moretrelements, followed optionally by atfootelement (but there can only be onetfootelement child in total). - Content attributes:
- Global attributes
- DOM interface:
-
interface HTMLTableElement : HTMLElement { attribute HTMLTableCaptionElement? caption; HTMLElement createCaption(); void deleteCaption(); attribute HTMLTableSectionElement? tHead; HTMLElement createTHead(); void deleteTHead(); attribute HTMLTableSectionElement? tFoot; HTMLElement createTFoot(); void deleteTFoot(); readonly attribute HTMLCollection tBodies; HTMLElement createTBody(); readonly attribute HTMLCollection rows; HTMLElement insertRow(optional long index); void deleteRow(long index); };
The table element represents data with
more than one dimension, in the form of a table.
Tables have rows, columns, and cells given by their descendants. The rows and columns form a grid; a table's cells must completely cover that grid without overlap.
Authors are encouraged to provide information describing how to interpret complex tables. Guidance on how to provide such information is given below.
Tables must not be used as layout aids. Historically, some Web authors have misused tables in HTML as a way to control their page layout. This usage is non-conforming, because tools attempting to extract tabular data from such documents would obtain very confusing results. In particular, users of accessibility tools like screen readers are likely to find it very difficult to navigate pages with tables used for layout.
There are a variety of alternatives to using HTML tables for layout, primarily using CSS positioning and the CSS table model. [CSS]
Authors are encouraged to consider using some of the table design techniques described below to make tables easier to navigate for users.
-
table .
caption[ = value ] -
Returns the table's
captionelement.Can be set, to replace the
captionelement. If the new value is not acaptionelement, throws aHierarchyRequestErrorexception. -
caption = table .
createCaption() -
Ensures the table has a
captionelement, and returns it. -
table .
deleteCaption() -
Ensures the table does not have a
captionelement. -
table .
tHead[ = value ] -
Returns the table's
theadelement.Can be set, to replace the
theadelement. If the new value is not atheadelement, throws aHierarchyRequestErrorexception. -
thead = table .
createTHead() -
Ensures the table has a
theadelement, and returns it. -
table .
deleteTHead() -
Ensures the table does not have a
theadelement. -
table .
tFoot[ = value ] -
Returns the table's
tfootelement.Can be set, to replace the
tfootelement. If the new value is not atfootelement, throws aHierarchyRequestErrorexception. -
tfoot = table .
createTFoot() -
Ensures the table has a
tfootelement, and returns it. -
table .
deleteTFoot() -
Ensures the table does not have a
tfootelement. -
table .
tBodies -
Returns an
HTMLCollectionof thetbodyelements of the table. -
tbody = table .
createTBody() -
Creates a
tbodyelement, inserts it into the table, and returns it. -
table .
rows -
Returns an
HTMLCollectionof thetrelements of the table. -
tr = table .
insertRow(index) -
Creates a
trelement, along with atbodyif required, inserts them into the table at the position given by the argument, and returns thetr.The position is relative to the rows in the table. The index −1 is equivalent to inserting at the end of the table.
If the given position is less than −1 or greater than the number of rows, throws an
IndexSizeErrorexception. -
table .
deleteRow(index) -
Removes the
trelement with the given position in the table.The position is relative to the rows in the table. The index −1 is equivalent to deleting the last row of the table.
If the given position is less than −1 or greater than the index of the last row, or if there are no rows, throws an
IndexSizeErrorexception.
Here is an example of a table being used to mark up a Sudoku puzzle. Observe the lack of headers, which are not necessary in such a table.
<section>
<style scoped>
table { border-collapse: collapse; border: solid thick; }
colgroup, tbody { border: solid medium; }
td { border: solid thin; height: 1.4em; width: 1.4em; text-align: center; padding: 0; }
</style>
<h1>Today's Sudoku</h1>
<table>
<colgroup><col><col><col>
<colgroup><col><col><col>
<colgroup><col><col><col>
<tbody>
<tr> <td> 1 <td> <td> 3 <td> 6 <td> <td> 4 <td> 7 <td> <td> 9
<tr> <td> <td> 2 <td> <td> <td> 9 <td> <td> <td> 1 <td>
<tr> <td> 7 <td> <td> <td> <td> <td> <td> <td> <td> 6
<tbody>
<tr> <td> 2 <td> <td> 4 <td> <td> 3 <td> <td> 9 <td> <td> 8
<tr> <td> <td> <td> <td> <td> <td> <td> <td> <td>
<tr> <td> 5 <td> <td> <td> 9 <td> <td> 7 <td> <td> <td> 1
<tbody>
<tr> <td> 6 <td> <td> <td> <td> 5 <td> <td> <td> <td> 2
<tr> <td> <td> <td> <td> <td> 7 <td> <td> <td> <td>
<tr> <td> 9 <td> <td> <td> 8 <td> <td> 2 <td> <td> <td> 5
</table>
</section>
4.9.1.1 Techniques for describing tables
For tables that consist of more than just a grid of cells with headers in the first row and headers in the first column, and for any table in general where the reader might have difficulty understanding the content, authors should include explanatory information introducing the table. This information is useful for all users, but is especially useful for users who cannot see the table, e.g. users of screen readers.
Such explanatory information should introduce the purpose of the table, outline its basic cell structure, highlight any trends or patterns, and generally teach the user how to use the table.
For instance, the following table:
| Negative | Characteristic | Positive |
|---|---|---|
| Sad | Mood | Happy |
| Failing | Grade | Passing |
...might benefit from a description explaining the way the table is laid out, something like "Characteristics are given in the second column, with the negative side in the left column and the positive side in the right column".
There are a variety of ways to include this information, such as:
- In prose, surrounding the table
-
<p>In the following table, characteristics are given in the second column, with the negative side in the left column and the positive side in the right column.</p> <table> <caption>Characteristics with positive and negative sides</caption> <thead> <tr> <th id="n"> Negative <th> Characteristic <th> Positive <tbody> <tr> <td headers="n r1"> Sad <th id="r1"> Mood <td> Happy <tr> <td headers="n r2"> Failing <th id="r2"> Grade <td> Passing </table>
- In the table's
caption -
<table> <caption> <strong>Characteristics with positive and negative sides.</strong> <p>Characteristics are given in the second column, with the negative side in the left column and the positive side in the right column.</p> </caption> <thead> <tr> <th id="n"> Negative <th> Characteristic <th> Positive <tbody> <tr> <td headers="n r1"> Sad <th id="r1"> Mood <td> Happy <tr> <td headers="n r2"> Failing <th id="r2"> Grade <td> Passing </table>
- In the table's
caption, in adetailselement -
<table> <caption> <strong>Characteristics with positive and negative sides.</strong> <details> <summary>Help</summary> <p>Characteristics are given in the second column, with the negative side in the left column and the positive side in the right column.</p> </details> </caption> <thead> <tr> <th id="n"> Negative <th> Characteristic <th> Positive <tbody> <tr> <td headers="n r1"> Sad <th id="r1"> Mood <td> Happy <tr> <td headers="n r2"> Failing <th id="r2"> Grade <td> Passing </table>
- Next to the table, in the same
figure -
<figure> <figcaption>Characteristics with positive and negative sides</figcaption> <p>Characteristics are given in the second column, with the negative side in the left column and the positive side in the right column.</p> <table> <thead> <tr> <th id="n"> Negative <th> Characteristic <th> Positive <tbody> <tr> <td headers="n r1"> Sad <th id="r1"> Mood <td> Happy <tr> <td headers="n r2"> Failing <th id="r2"> Grade <td> Passing </table> </figure> - Next to the table, in a
figure'sfigcaption -
<figure> <figcaption> <strong>Characteristics with positive and negative sides</strong> <p>Characteristics are given in the second column, with the negative side in the left column and the positive side in the right column.</p> </figcaption> <table> <thead> <tr> <th id="n"> Negative <th> Characteristic <th> Positive <tbody> <tr> <td headers="n r1"> Sad <th id="r1"> Mood <td> Happy <tr> <td headers="n r2"> Failing <th id="r2"> Grade <td> Passing </table> </figure>
Authors may also use other techniques, or combinations of the above techniques, as appropriate.
The best option, of course, rather than writing a description explaining the way the table is laid out, is to adjust the table such that no explanation is needed.
In the case of the table used in the examples above, a simple
rearrangement of the table so that the headers are on the top and
left sides removes the need for an explanation as well as removing
the need for the use of headers attributes:
<table> <caption>Characteristics with positive and negative sides</caption> <thead> <tr> <th> Characteristic <th> Negative <th> Positive <tbody> <tr> <th> Mood <td> Sad <td> Happy <tr> <th> Grade <td> Failing <td> Passing </table>
4.9.1.2 Techniques for table design
Good table design is key to making tables more readable and usable.
In visual media, providing column and row borders and alternating row backgrounds can be very effective to make complicated tables more readable.
For tables with large volumes of numeric content, using monospaced fonts can help users see patterns, especially in situations where a user agent does not render the borders. (Unfortunately, for historical reasons, not rendering borders on tables is a common default.)
In speech media, table cells can be distinguished by reporting the corresponding headers before reading the cell's contents, and by allowing users to navigate the table in a grid fashion, rather than serializing the entire contents of the table in source order.
Authors are encouraged to use CSS to achieve these effects.
4.9.2
The caption element
- Categories:
- None.
- Contexts in which this element can be used:
- As the first element child of a
tableelement. - Content model:
-
Flow content, but with no descendant
tableelements. - Content attributes:
- Global attributes
- DOM interface:
-
interface HTMLTableCaptionElement : HTMLElement {};
The caption element represents the title of the
table that is its parent, if it has a parent and that
is a table element.
When a table element is the only content in a
figure element other than the figcaption,
the caption element should be omitted in favor of the
figcaption.
A caption can introduce context for a table, making it significantly easier to understand.
Consider, for instance, the following table:
| 1 | 2 | 3 | 4 | 5 | 6 | |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
In the abstract, this table is not clear. However, with a caption giving the table's number (for reference in the main prose) and explaining its use, it makes more sense:
<caption> <p>Table 1. <p>This table shows the total score obtained from rolling two six-sided dice. The first row represents the value of the first die, the first column the value of the second die. The total is given in the cell that corresponds to the values of the two dice. </caption>
This provides the user with more context:
| 1 | 2 | 3 | 4 | 5 | 6 | |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
4.9.3
The colgroup element
- Categories:
- None.
- Contexts in which this element can be used:
- As a child of a
tableelement, after anycaptionelements and before anythead,tbody,tfoot, andtrelements. - Content model:
- If the
spanattribute is present: Empty. - If the
spanattribute is absent: Zero or morecolelements. - Content attributes:
- Global attributes
span- DOM interface:
-
interface HTMLTableColElement : HTMLElement { attribute unsigned long span; };
The colgroup element represents a group of one or more columns in the table that
is its parent, if it has a parent and that is a table
element.
If the colgroup element contains no col
elements, then the element may have a span content attribute
specified, whose value must be a valid non-negative
integer greater than zero.
4.9.4
The col element
- Categories:
- None.
- Contexts in which this element can be used:
- As a child of a
colgroupelement that doesn't have aspanattribute. - Content model:
- Empty.
- Content attributes:
- Global attributes
span- DOM interface:
-
HTMLTableColElement, same as forcolgroupelements. This interface defines one member,span.
If a col element has a parent and that is a
colgroup element that itself has a parent that is a
table element, then the col element
represents one or more columns in the column group represented by that
colgroup.
The element may have a span content attribute
specified, whose value must be a valid non-negative
integer greater than zero.
4.9.5
The tbody element
- Categories:
- None.
- Contexts in which this element can be used:
- As a child of a
tableelement, after anycaption,colgroup, andtheadelements, but only if there are notrelements that are children of thetableelement. - Content model:
- Zero or more
trelements - Content attributes:
- Global attributes
- DOM interface:
-
interface HTMLTableSectionElement : HTMLElement { readonly attribute HTMLCollection rows; HTMLElement insertRow(optional long index); void deleteRow(long index); };
The
HTMLTableSectionElementinterface is also used fortheadandtfootelements.
The tbody element represents a block of rows that consist of a body of data for
the parent table element, if the tbody
element has a parent and it is a table.
-
tbody .
rows -
Returns an
HTMLCollectionof thetrelements of the table section. -
tr = tbody .
insertRow( [ index ] ) -
Creates a
trelement, inserts it into the table section at the position given by the argument, and returns thetr.The position is relative to the rows in the table section. The index −1, which is the default if the argument is omitted, is equivalent to inserting at the end of the table section.
If the given position is less than −1 or greater than the number of rows, throws an
IndexSizeErrorexception. -
tbody .
deleteRow(index) -
Removes the
trelement with the given position in the table section.The position is relative to the rows in the table section. The index −1 is equivalent to deleting the last row of the table section.
If the given position is less than −1 or greater than the index of the last row, or if there are no rows, throws an
IndexSizeErrorexception.
4.9.6
The thead element
- Categories:
- None.
- Contexts in which this element can be used:
- As a child of a
tableelement, after anycaption, andcolgroupelements and before anytbody,tfoot, andtrelements, but only if there are no othertheadelements that are children of thetableelement. - Content model:
- Zero or more
trelements - Content attributes:
- Global attributes
- DOM interface:
-
HTMLTableSectionElement, as defined fortbodyelements.
The thead element represents the block of rows that consist of the column labels
(headers) for the parent table element, if the
thead element has a parent and it is a
table.
This example shows a thead element being used.
Notice the use of both th and td elements
in the thead element: the first row is the headers,
and the second row is an explanation of how to fill in the
table.
<table> <caption> School auction sign-up sheet </caption> <thead> <tr> <th><label for=e1>Name</label> <th><label for=e2>Product</label> <th><label for=e3>Picture</label> <th><label for=e4>Price</label> <tr> <td>Your name here <td>What are you selling? <td>Link to a picture <td>Your reserve price <tbody> <tr> <td>Ms Danus <td>Doughnuts <td><img src="http://example.com/mydoughnuts.png" title="Doughnuts from Ms Danus"> <td>$45 <tr> <td><input id=e1 type=text name=who required form=f> <td><input id=e2 type=text name=what required form=f> <td><input id=e3 type=url name=pic form=f> <td><input id=e4 type=number step=0.01 min=0 value=0 required form=f> </table> <form id=f action="/auction.cgi"> <input type=button name=add value="Submit"> </form>
4.9.7
The tfoot element
- Categories:
- None.
- Contexts in which this element can be used:
- As a child of a
tableelement, after anycaption,colgroup, andtheadelements and before anytbodyandtrelements, but only if there are no othertfootelements that are children of thetableelement. - As a child of a
tableelement, after anycaption,colgroup,thead,tbody, andtrelements, but only if there are no othertfootelements that are children of thetableelement. - Content model:
- Zero or more
trelements - Content attributes:
- Global attributes
- DOM interface:
-
HTMLTableSectionElement, as defined fortbodyelements.
The tfoot element represents the block of rows that consist of the column summaries
(footers) for the parent table element, if the
tfoot element has a parent and it is a
table.
4.9.8
The tr element
- Categories:
- None.
- Contexts in which this element can be used:
- As a child of a
theadelement. - As a child of a
tbodyelement. - As a child of a
tfootelement. - As a child of a
tableelement, after anycaption,colgroup, andtheadelements, but only if there are notbodyelements that are children of thetableelement. - Content model:
- Zero or more
tdorthelements - Content attributes:
- Global attributes
- DOM interface:
-
interface HTMLTableRowElement : HTMLElement { readonly attribute long rowIndex; readonly attribute long sectionRowIndex; readonly attribute HTMLCollection cells; HTMLElement insertCell(optional long index); void deleteCell(long index); };
The tr element represents a row of cells in a table.
-
tr .
rowIndex -
Returns the position of the row in the table's
rowslist.Returns −1 if the element isn't in a table.
-
tr .
sectionRowIndex -
Returns the position of the row in the table section's
rowslist.Returns −1 if the element isn't in a table section.
-
tr .
cells -
Returns an
HTMLCollectionof thetdandthelements of the row. -
cell = tr .
insertCell( [ index ] ) -
Creates a
tdelement, inserts it into the table row at the position given by the argument, and returns thetd.The position is relative to the cells in the row. The index −1, which is the default if the argument is omitted, is equivalent to inserting at the end of the row.
If the given position is less than −1 or greater than the number of cells, throws an
IndexSizeErrorexception. -
tr .
deleteCell(index) -
Removes the
tdorthelement with the given position in the row.The position is relative to the cells in the row. The index −1 is equivalent to deleting the last cell of the row.
If the given position is less than −1 or greater than the index of the last cell, or if there are no cells, throws an
IndexSizeErrorexception.
4.9.9
The td element
- Categories:
- Sectioning root.
- Contexts in which this element can be used:
- As a child of a
trelement. - Content model:
- Flow content.
- Content attributes:
- Global attributes
colspanrowspanheaders- DOM interface:
-
interface HTMLTableDataCellElement : HTMLTableCellElement {};
The td element represents a data cell in a table.
4.9.10
The th element
- Categories:
- None.
- Contexts in which this element can be used:
- As a child of a
trelement. - Content model:
-
Flow content, but with no
header,footer, sectioning content, or heading content descendants. - Content attributes:
- Global attributes
colspanrowspanheadersscopeabbr- DOM interface:
-
interface HTMLTableHeaderCellElement : HTMLTableCellElement { attribute DOMString scope; attribute DOMString abbr; };
The th element represents a header cell in a table.
The th element may have a scope content attribute
specified. The scope attribute is
an enumerated attribute with five states, four of which
have explicit keywords:
- The
rowkeyword, which maps to the row state - The row state means the header cell applies to some of the subsequent cells in the same row(s).
- The
colkeyword, which maps to the column state - The column state means the header cell applies to some of the subsequent cells in the same column(s).
- The
rowgroupkeyword, which maps to the row group state - The row group state means the header cell applies to all
the remaining cells in the row group. A
thelement'sscopeattribute must not be in the row group state if the element is not anchored in a row group. - The
colgroupkeyword, which maps to the column group state - The column group state means the header cell applies to
all the remaining cells in the column group. A
thelement'sscopeattribute must not be in the column group state if the element is not anchored in a column group. - The auto state
- The auto state makes the header cell apply to a set of cells selected based on context.
The scope attribute's
missing value default is the auto state.
The th element may have an abbr
content attribute specified. Its value must be an alternative label for the header cell, to be
used when referencing the cell in other contexts (e.g. when describing the header cells that apply
to a data cell). It is typically an abbreviated form of the full header cell, but can also be an
expansion, or merely a different phrasing.
The following example shows how the scope attribute's rowgroup value affects which
data cells a header cell applies to.
Here is a markup fragment showing a table:
<table> <thead> <tr> <th> ID <th> Measurement <th> Average <th> Maximum <tbody> <tr> <td> <th scope=rowgroup> Cats <td> <td> <tr> <td> 93 <th scope=row> Legs <td> 3.5 <td> 4 <tr> <td> 10 <th scope=row> Tails <td> 1 <td> 1 <tbody> <tr> <td> <th scope=rowgroup> English speakers <td> <td> <tr> <td> 32 <th scope=row> Legs <td> 2.67 <td> 4 <tr> <td> 35 <th scope=row> Tails <td> 0.33 <td> 1 </table>
This would result in the following table:
| ID | Measurement | Average | Maximum |
|---|---|---|---|
| Cats | |||
| 93 | Legs | 3.5 | 4 |
| 10 | Tails | 1 | 1 |
| English speakers | |||
| 32 | Legs | 2.67 | 4 |
| 35 | Tails | 0.33 | 1 |
The headers in the first row all apply directly down to the rows in their column.
The headers with the explicit scope attributes apply to all the
cells in their row group other than the cells in the first column.
The remaining headers apply just to the cells to the right of them.
4.9.11
Attributes common to td and th elements
The td and th elements may have a colspan content
attribute specified, whose value must be a valid non-negative
integer greater than zero.
The td and th elements may also have a
rowspan content
attribute specified, whose value must be a valid non-negative
integer.
These attributes give the number of columns and rows respectively that the cell is to span. These attributes must not be used to overlap cells.
The td and th element may have a headers content
attribute specified. The headers attribute, if specified,
must contain a string consisting of an unordered set of unique
space-separated tokens that are case-sensitive,
each of which must have the value of an ID of a th element taking
part in the same table as the
td or th element.
A th element with ID id is said
to be directly targeted by all td and
th elements in the same table that have headers attributes whose values
include as one of their tokens the ID id. A
th element A is said to be
targeted by a th or td element
B if either A is directly
targeted by B or if there exists an element
C that is itself targeted by the element
B and A is directly
targeted by C.
A th element must not be targeted by
itself.
The td and th elements implement
interfaces that inherit from the HTMLTableCellElement
interface:
interface HTMLTableCellElement : HTMLElement { attribute unsigned long colSpan; attribute unsigned long rowSpan; [PutForwards=value] readonly attribute DOMSettableTokenList headers; readonly attribute long cellIndex; };
-
cell .
cellIndex -
Returns the position of the cell in the row's
cellslist. This does not necessarily correspond to the x-position of the cell in the table, since earlier cells might cover multiple rows or columns.Returns −1 if the element isn't in a row.
4.9.12 Examples
The following shows how might one mark up the bottom part of table 45 of the Smithsonian physical tables, Volume 71:
<table> <caption>Specification values: <b>Steel</b>, <b>Castings</b>, Ann. A.S.T.M. A27-16, Class B;* P max. 0.06; S max. 0.05.</caption> <thead> <tr> <th rowspan=2>Grade.</th> <th rowspan=2>Yield Point.</th> <th colspan=2>Ultimate tensile strength</th> <th rowspan=2>Per cent elong. 50.8mm or 2 in.</th> <th rowspan=2>Per cent reduct. area.</th> </tr> <tr> <th>kg/mm<sup>2</sup></th> <th>lb/in<sup>2</sup></th> </tr> </thead> <tbody> <tr> <td>Hard</td> <td>0.45 ultimate</td> <td>56.2</td> <td>80,000</td> <td>15</td> <td>20</td> </tr> <tr> <td>Medium</td> <td>0.45 ultimate</td> <td>49.2</td> <td>70,000</td> <td>18</td> <td>25</td> </tr> <tr> <td>Soft</td> <td>0.45 ultimate</td> <td>42.2</td> <td>60,000</td> <td>22</td> <td>30</td> </tr> </tbody> </table>
This table could look like this:
| Grade. | Yield Point. | Ultimate tensile strength | Per cent elong. 50.8 mm or 2 in. | Per cent reduct. area. | |
|---|---|---|---|---|---|
| kg/mm2 | lb/in2 | ||||
| Hard | 0.45 ultimate | 56.2 | 80,000 | 15 | 20 |
| Medium | 0.45 ultimate | 49.2 | 70,000 | 18 | 25 |
| Soft | 0.45 ultimate | 42.2 | 60,000 | 22 | 30 |
The following shows how one might mark up the gross margin table on page 46 of Apple, Inc's 10-K filing for fiscal year 2008:
<table> <thead> <tr> <th> <th>2008 <th>2007 <th>2006 <tbody> <tr> <th>Net sales <td>$ 32,479 <td>$ 24,006 <td>$ 19,315 <tr> <th>Cost of sales <td> 21,334 <td> 15,852 <td> 13,717 <tbody> <tr> <th>Gross margin <td>$ 11,145 <td>$ 8,154 <td>$ 5,598 <tfoot> <tr> <th>Gross margin percentage <td>34.3% <td>34.0% <td>29.0% </table>
This table could look like this:
| 2008 | 2007 | 2006 | |
|---|---|---|---|
| Net sales | $ 32,479 | $ 24,006 | $ 19,315 |
| Cost of sales | 21,334 | 15,852 | 13,717 |
| Gross margin | $ 11,145 | $ 8,154 | $ 5,598 |
| Gross margin percentage | 34.3% | 34.0% | 29.0% |
The following shows how one might mark up the operating expenses table from lower on the same page of that document:
<table>
<colgroup> <col>
<colgroup> <col> <col> <col>
<thead>
<tr> <th> <th>2008 <th>2007 <th>2006
<tbody>
<tr> <th scope=rowgroup> Research and development
<td> $ 1,109 <td> $ 782 <td> $ 712
<tr> <th scope=row> Percentage of net sales
<td> 3.4% <td> 3.3% <td> 3.7%
<tbody>
<tr> <th scope=rowgroup> Selling, general, and administrative
<td> $ 3,761 <td> $ 2,963 <td> $ 2,433
<tr> <th scope=row> Percentage of net sales
<td> 11.6% <td> 12.3% <td> 12.6%
</table>
This table could look like this:
| 2008 | 2007 | 2006 | |
|---|---|---|---|
| Research and development | $ 1,109 | $ 782 | $ 712 |
| Percentage of net sales | 3.4% | 3.3% | 3.7% |
| Selling, general, and administrative | $ 3,761 | $ 2,963 | $ 2,433 |
| Percentage of net sales | 11.6% | 12.3% | 12.6% |