RFC 1942

HTML Tables

๐Ÿ“… published May 1996, from ietf

RFC 1942: HTML Tables ๐ŸŽ€

  • Author: D. Raggett, W3C ๐Ÿ’…
  • Category: Experimental ๐Ÿงช
  • Date: May 1996 ๐Ÿ“…

Part 1 of 7 ๐Ÿฆ‹

Status of this Memo โœจ

okay bestie, this memo defines an Experimental Protocol for the Internet community ๐Ÿงช. It is NOT an Internet standard of any kind, so don't treat it like one ๐Ÿ™ˆ. The authors are literally asking for discussion and suggestions for improvement, so feedback is welcome ๐Ÿ’œ. Distribution of this memo is unlimited ๐ŸŒˆ.

Abstract ๐Ÿ“–

HTML is a simple markup language for making hypertext documents that are portable from one platform to another ๐Ÿงš. HTML documents are SGML documents with generic semantics, which means they're flexible enough to represent information from a wide range of applications ๐ŸŒธ.

This spec extends HTML to support a wide variety of tables ๐Ÿ“. The model is designed to play nicely with associated style sheets, but it doesn't need them ๐Ÿ’–. It also supports rendering to braille or speech, and exchanging tabular data with databases and spreadsheets ๐Ÿ”ฅ. The HTML table model borrows some ideas from the CALS table model, like grouping table rows into thead, tbody and tfoot sections, plus specifying cell alignment compactly for sets of cells based on the context โœจ.

Table of Contents ๐Ÿ—‚๏ธ

Recent Changes  ................................................. 1
Brief Introduction  ............................................. 2
Design Rationale  ............................................... 5
Walkthrough of the Table DTD  ................................... 8
Recommended Layout Algorithms  ................................. 23
HTML Table DTD  ................................................ 26
References  .................................................... 29
Security Considerations  ....................................... 30
Author's Address  .............................................. 30

Recent Changes ๐Ÿ†•

okay so this spec extends HTML to support tables ๐Ÿ’…. The table model grew out of early work on HTML+ and the first draft of HTML3 ๐ŸŒฑ. Information providers asked for more control over how tabular info gets presented, so the earlier model got some upgrades ๐Ÿฆ‹:

  • alignment on designated characters such as "." and ":" e.g. aligning a column of numbers on the decimal point ๐Ÿ”ข
  • more flexibility in specifying table frames and rules ๐Ÿ–ผ๏ธ
  • incremental display for large tables as data is received โญ
  • the ability to support scrollable tables with fixed headers, plus better support for breaking tables across pages for printing ๐Ÿ–จ๏ธ
  • optional column based defaults for alignment properties ๐ŸŽ€

Another big goal was backwards compatibility with the widely deployed Netscape implementation of tables ๐Ÿซถ. A smaller goal was making it simpler to import tables that follow the SGML CALS model ๐Ÿง. The latest draft makes the ALIGN attribute compatible with the latest Netscape and Microsoft browsers ๐Ÿ‘‘. There are also some clarifications about the role of the DIR attribute, and the recommended behaviour when absolute and relative column widths are mixed ๐Ÿ’œ.

A new element, COLGROUP, lets you group sets of columns with different width and alignment properties, specified by one or more COL elements ๐Ÿงš. Its semantics got clarified over previous drafts, and RULES=BASIC was replaced by RULES=GROUPS ๐ŸŒˆ.

The FRAME and RULES attributes were changed so they don't cause SGML name clashes with each other or with ALIGN and VALIGN ๐Ÿ’…. This was also motivated by wanting to avoid future problems if FRAME and RULES get extended to other table elements ๐Ÿ”ฎ.

A Brief Introduction to HTML Tables ๐Ÿ“

Tables start with an optional caption, followed by one or more rows ๐ŸŽ€. Each row is made of one or more cells, which are either header cells or data cells ๐Ÿ’–. Cells can be merged across rows and columns, and they can include attributes that help with rendering to speech and braille, or with exporting table data into databases ๐Ÿ”Š. The model gives you limited control over appearance, like horizontal and vertical alignment of cell contents, border styles, and cell margins ๐Ÿฆ‹. You can also group rows and columns together to affect this even more โœจ. Tables can hold a wild range of content, including headers, lists, paragraphs, forms, figures, preformatted text, and even nested tables ๐Ÿคฏ.

Example ๐Ÿงš

Here's a tiny table with merged cells ๐ŸŒธ:

<TABLE BORDER>
  <CAPTION>A test table with merged cells</CAPTION>
  <TR><TH ROWSPAN=2><TH COLSPAN=2>Average
      <TH ROWSPAN=2>other<BR>category<TH>Misc
  <TR><TH>height<TH>weight
  <TR><TH ALIGN=LEFT>males<TD>1.9<TD>0.003
  <TR><TH ALIGN=LEFT ROWSPAN=2>females<TD>1.7<TD>0.002
</TABLE>

On a dumb terminal, it would look something like this ๐Ÿ’€:

              A test table with merged cells
    /--------------------------------------------------\
    |          |      Average      |  other   |  Misc  |
    |          |-------------------| category |--------|
    |          |  height |  weight |          |        |
    |-----------------------------------------|--------|
    | males    | 1.9     | 0.003   |          |        |
    |-----------------------------------------|--------|
    | females  | 1.7     | 0.002   |          |        |
    \--------------------------------------------------/

Next up is a richer example with grouped rows and columns ๐Ÿ‘‘. It's adapted from "Developing International Software" by Nadine Kano ๐Ÿ“š. Here's what the table looks like on paper first ๐Ÿ–จ๏ธ:

                 CODE-PAGE SUPPORT IN MICROSOFT WINDOWS
========================================================================
Code-Page| Name                      |ACP OEMCP| Windows Windows Windows
    ID   |                           |         |  NT 3.1 NT 3.51    95
------------------------------------------------------------------------
   1200  |Unicode (BMP of ISO 10646) |         |     X       X       *
   1250  |Windows 3.1 East. Europe   |  X      |     X       X       X
   1251  |Windows 3.1 Cyrillic       |  X      |     X       X       X
   1252  |Windows 3.1 US (ANSI)      |  X      |     X       X       X
   1253  |Windows 3.1 Greek          |  X      |     X       X       X
   1254  |Windows 3.1 Turkish        |  X      |     X       X       X
   1255  |Hebrew                     |  X      |                     X
   1256  |Arabic                     |  X      |                     X
   1257  |Baltic                     |  X      |                     X
   1361  |Korean (Johab)             |  X      |             **      X
------------------------------------------------------------------------
    437  |MS-DOS United States       |     X   |     X       X       X
    708  |Arabic (ASMO 708)          |     X   |                     X
    709  |Arabic (ASMO 449+, BCON V4)|     X   |                     X
    710  |Arabic (Transparent Arabic)|     X   |                     X
    720  |Arabic (Transparent ASMO)  |     X   |                     X
========================================================================

The markup for this uses COLGROUP elements to group columns and set default column alignment ๐Ÿง. TBODY elements group the rows ๐Ÿซถ. The FRAME and RULES attributes pick which borders get rendered ๐Ÿ–ผ๏ธ:

<table border=2 frame=hsides rules=groups>
<caption>CODE-PAGE SUPPORT IN MICROSOFT WINDOWS</caption>
<colgroup align=center>
<colgroup align=left>
<colgroup align=center span=2>
<colgroup align=center span=3>
<thead valign=top>
<tr>
<th>Code-Page<br>ID
<th>Name
<th>ACP
<th>OEMCP
<th>Windows<br>NT 3.1
<th>Windows<br>NT 3.51
<th>Windows<br>95
<tbody>
<tr><td>1200<td>Unicode (BMP of ISO 10646)<td><td><td>X<td>X<TD>*
<tr><td>1250<td>Windows 3.1 Eastern European<td>X<td><td>X<td>X<TD>X
<tr><td>1251<td>Windows 3.1 Cyrillic<td>X<td><td>X<td>X<TD>X
<tr><td>1252<td>Windows 3.1 US (ANSI)<td>X<td><td>X<td>X<TD>X
<tr><td>1253<td>Windows 3.1 Greek<td>X<td><td>X<td>X<TD>X
<tr><td>1254<td>Windows 3.1 Turkish<td>X<td><td>X<td>X<TD>X
<tr><td>1255<td>Hebrew<td>X<td><td><td><td>X
<tr><td>1256<td>Arabic<td>X<td><td><td><td>X
<tr><td>1257<td>Baltic<td>X<td><td><td><td>X
<tr><td>1361<td>Korean (Johab)<td>X<td><td><td>**<td>X
<tbody>
<tr><td>437<td>MS-DOS United States<td><td>X<td>X<td>X<TD>X
<tr><td>708<td>Arabic (ASMO 708)<td><td>X<td><td><td>X
<tr><td>709<td>Arabic (ASMO 449+, BCON V4)<td><td>X<td><td><td>X
<tr><td>710<td>Arabic (Transparent Arabic)<td><td>X<td><td><td>X
<tr><td>720<td>Arabic (Transparent ASMO)<td><td>X<td><td><td>X
</table>

the table model is SO well thought out, bestie ๐ŸŒˆ๐Ÿ˜ญ

Design Rationale ๐Ÿ’…

okay so the HTML table model came from studying existing SGML table models, how word processing packages handle tables, and a whole bunch of tabular layouts in magazines, books, and other paper docs ๐Ÿ“šโœจ The goal was simple: simple tables should be easy to write, and extra complexity should only show up when you actually need it ๐ŸŽ€ This means you can make HTML table markup with everyday text editors, so starting out is way less scary ๐Ÿซถ Honestly this has been a huge reason HTML has been so successful, no cap ๐Ÿ‘‘

People are increasingly using filters from other document formats, or direct WYSIWYG editors, to make HTML ๐ŸŒˆ It's important that the table model fits nicely with those workflows. This affects how the markup represents cells that span multiple rows or columns, and how alignment and other presentation properties get attached to groups of cells ๐Ÿ’–

Here's a big one: the fonts and window sizes that browsers use are not under the author's control ๐Ÿ˜ญ So relying on column widths in absolute units like picas or pixels is risky ๐Ÿ™ˆ Instead, tables can be sized dynamically to match the current window size and fonts ๐Ÿฆ‹ Authors can give guidance on the relative widths of columns, but user agents should to ensure that columns are wide enough to render the width of the largest single element in the cell's content ๐Ÿ’… If the author's specification has to be overridden, it's preferred that the relative widths of individual columns don't change drastically โœจ

For big tables or slow connections, it's really nice to start showing the table before all the data has arrived ๐Ÿš€ Most user agents have a default window width of about 80 characters, and lots of HTML page graphics are designed with those defaults in mind ๐Ÿ“ Authors can drop a hint to user agents to turn on incremental display of table contents ๐Ÿงš This feature requires the author to specify the number of columns, and it includes a way to control table width and the widths of different columns, either in relative or absolute terms ๐ŸŒธ

For incremental display, the browser needs the number of columns and their widths ๐ŸŽ€ By default the table's width is the current window size (width="100%"), and you can change that by adding a WIDTH attribute to the TABLE start tag ๐Ÿ’œ By default every column is the same width, but you can set column widths with one or more COL elements before the table data starts ๐Ÿฆ‹

The last piece is the number of columns. Some people suggested waiting until the first row has arrived, but that could take forever if the cells have tons of content ๐Ÿ˜ญ Overall it makes more sense, when you want incremental display, to have authors explicitly state the number of columns in the TABLE start tag ๐Ÿ‘‘

Authors still need a way to tell the browser whether to use incremental display or to auto-size the table to fit the cell contents ๐ŸŒˆ For the two pass auto sizing mode, the number of columns gets figured out in the first pass, but for incremental mode the number of columns has to be stated up front ๐Ÿซถ So it seems to that COLS=_nn_ would be better for this job than a LAYOUT attribute like LAYOUT=FIXED or LAYOUT=AUTO ๐Ÿ’–

People generally find it useful to look at documents from two angles: structural idioms like headers, paragraphs, lists, tables, and figures, and rendering idioms like margins, leading, and font names and sizes ๐Ÿ“š Past experience tells us to keep structural information separate from rendering information ๐Ÿง  Mixing them together raises the cost of maintaining documents and lowers portability between applications and media ๐Ÿ’…

For tables, text alignment inside cells and the borders between cells are, from a purist's point of view, rendering information ๐Ÿค“ In practice, though, it's useful to group them with the structural information, because these features move really well from one application to the next โœจ The HTML table model leaves most rendering information to associated style sheets ๐ŸŽจ The model is designed to take advantage of style sheets, but it doesn't require them ๐ŸŒธ

This specification gives you a superset of the simpler model from earlier HTML+ work ๐Ÿฆ‹ Tables are treated as an optional caption plus a sequence of rows, and each row is a sequence of table cells ๐Ÿ’œ The model also tells header cells apart from data cells, and lets cells span multiple rows and columns ๐ŸŽ€

Following the CALS table model, this specification lets table rows be grouped into head, body, and foot sections ๐Ÿงš This simplifies the representation of rendering information, and it can be used to repeat head and foot rows when a table breaks across page boundaries, or to give you fixed headers above a scrollable body panel ๐Ÿ“– In the markup, the foot section goes before the body sections ๐Ÿ‘€ This is an optimization shared with CALS for handling really long tables, because it lets the foot get rendered without waiting for the entire table to be processed ๐Ÿ”ฅ

For the visually impaired, HTML offers a chance to fix the damage caused by the rise of windows-based graphical user interfaces ๐Ÿฅบ The HTML table model includes attributes for labeling each cell, which supports high-quality text to speech conversion ๐Ÿ”Š The same attributes can also support automated import and export of table data to databases or spreadsheets ๐Ÿ“Š

Current desktop publishing packages have super rich control over how tables are rendered, and reproducing that in HTML would be impractical unless HTML turned into a bulky rich text format like RTF or MIF ๐Ÿ˜ต This specification does, however, let authors choose from a set of commonly used classes of border styles ๐ŸŽ€ The FRAME attribute controls the appearence of the border frame around the table, while the RULES attribute decides which rulings appear inside the table ๐Ÿ–ผ๏ธ

While this specification was being developed, a bunch of ideas were explored for specifying ruling patterns for tables ๐Ÿงฉ One problem was the kinds of statements that could be made. Including support for edge subtraction as well as edge addition led to really complicated algorithms ๐ŸŒ€ For example, when the team explored letting the full set of table elements include the FRAME and RULES attributes, it led to an algorithm with some 24 steps just to decide whether a particular edge of a cell should be ruled or not ๐Ÿ˜ฉ Even with all that complexity, it still didn't give enough rendering control to meet every need for tables ๐Ÿ™ƒ So the current specification deliberately sticks to a simple, intuitive model that's enough for most purposes โœจ More experimental work is needed before a more complex approach gets standardized ๐Ÿงช


A walk through the table DTD ๐Ÿงš

The table document type definition (DTD) is where the formal definition of allowed HTML table syntax lives ๐Ÿ“œ What follows is an annotated listing of the DTD ๐Ÿ“ The complete listing is at the end of this document ๐Ÿ”š

Heads up: the TABLE element is a block-like element, not a character-level element ๐Ÿšจ That means it's a peer of other HTML block-like elements, like paragraphs, lists, and headers ๐Ÿงฑ

Common Attributes ๐Ÿ’–

These attributes show up in several elements, so they're defined here to save space ๐Ÿซถ In general, all attribute names and values in this specification are case insensitive, unless noted otherwise ๐Ÿ”ค The ID, CLASS and attributes are required for use with style sheets, while LANG and DIR are needed for internationalization ๐ŸŒ

<!ENTITY % attrs
       "id      ID       #IMPLIED  -- element identifier --
        class   NAMES    #IMPLIED  -- for subclassing elements --
        lang    NAME     #IMPLIED  -- as per RFC 1766 --
        dir   (ltr|rtl)  #IMPLIED  -- I18N text direction --">

ID ๐Ÿชช

Used to define a document-wide identifier ๐Ÿ“ This can name positions within a document so they can be the destination of a hypertext link ๐Ÿ”— Style sheets can also use it to render one element in a unique style ๐ŸŽจ An ID attribute value is an SGML NAME token ๐Ÿงฉ NAME tokens are formed by an initial letter followed by letters, digits, "-" and "." characters ๐Ÿ”ก The letters are restricted to A-Z and a-z ๐Ÿ” 

CLASS ๐Ÿท๏ธ

A space separated list of SGML NAME tokens ๐Ÿซง CLASS names say that the element belongs to the corresponding named classes ๐ŸŽŸ๏ธ It lets authors tell apart the different roles played by the same tag ๐ŸŽญ Style sheets can use the classes to provide different renderings depending on those roles ๐ŸŒˆ

LANG ๐Ÿ—ฃ๏ธ

A LANG attribute identifies the natural language used by the content of the associated element. The syntax and registry of language values are defined by RFC 1766 ๐Ÿ“š Summed up: the language is a primary tag, followed by zero or more subtags separated by "-" โœ‚๏ธ White space isn't allowed, and all tags are case insensitive ๐Ÿ™…โ€โ™€๏ธ The name space of tags is administered by IANA ๐Ÿ›๏ธ The two letter primary tag is an ISO 639 language abbreviation, and the initial subtag is a two letter ISO 3166 country code ๐Ÿ—บ๏ธ Example values for LANG include:

en, en-US, en-uk, i-cherokee, x-pig-latin.

DIR โžก๏ธโฌ…๏ธ

Human writing systems are grouped into scripts, and scripts determine, among other things, which direction the characters are written ๐Ÿ”„ Elements of the Latin script are nominally left to right, while elements of the Arabic script are nominally right to left ๐ŸŒ™ These characters have what's called strong directionality ๐Ÿ’ช Other characters can be directionally neutral (spaces) or weak (punctuation) ๐Ÿซง

The DIR attribute specifies an encapsulation boundary that governs how neutral and weakly directional characters are interpreted ๐Ÿง  It does not override the directionality of strongly directional characters ๐Ÿ›‘ The DIR attribute value is either LTR for left to right, or RTL for right to left, e.g. DIR=RTL ๐ŸŽ€

When applied to TABLE, it indicates the geometric layout of rows (i.e. row 1 is on the right if DIR=RTL, but on the left if DIR=LTR) ๐Ÿ“ It also indicates a default base directionality for any text in the table's content, if no other DIR attribute applies to that text ๐ŸŒŸ

Horizontal and Vertical Alignment Attributes โœจ

okay so you can set how cell contents line up on a cell by cell basis, or let them inherit from the stuff around them, like the row, the column, or the table itself ๐ŸŽ€

ALIGN

this one handles the horizontal alignment of cell contents ๐Ÿ’…

<!-- horizontal alignment attributes for cell contents -->
<!ENTITY % cell.halign
        "align  (left|center|right|justify|char) #IMPLIED
         char    CDATA   #IMPLIED -- alignment char, e.g. char=':' --
         charoff CDATA   #IMPLIED -- offset for alignment char --"
        >

the attribute value should be one of LEFT, CENTER, RIGHT, JUSTIFY and CHAR ๐Ÿซถ user agents may treat JUSTIFY as left alignment if they lack support for text justification, so don't panic if it looks left-aligned ๐Ÿ˜ญ and ALIGN=CHAR is what you use when you want everything lined up on one particular character ๐Ÿ”ฅ

for cells that span multiple rows or columns, where the alignment is inherited from the row or column, the initial row and column for the cell is what decides which alignment property gets used โœจ

also, an alignment attribute on something inside the cell, like a P element, overrides the normal alignment for the cell. main character energy, the inner thing wins ๐Ÿ‘‘

CHAR

this is used to pick the alignment character for use with align=char, e.g. char=":" ๐Ÿงš the default character is the decimal point for the current language, as set by the LANG attribute. and the CHAR attribute value is case sensitive, so be careful bestie ๐Ÿ™ˆ

CHAROFF

this specifies the offset to the first occurrence of the alignment character on each line. if a line doesn't have the alignment character at all, it should be shifted horizontally so it ends at the alignment position ๐Ÿฆ‹

the direction of the cell (the resolved DIR, which comes from inheritance) decides whether the offset is measured from the left or the right margin. for Latin scripts it's from the left margin, and for Arabic scripts it's from the right margin ๐ŸŒˆ

in addition to standard units, you can use the "%" sign to say the value is a percentage offset of the current cell. so CHAROFF="30%" means the alignment character sits 30% of the way through the cell ๐Ÿ’–

when you're using the two pass layout algorithm, the default alignment position (if there's no explicit or inherited CHAROFF) can be found by picking the position that would center lines where the width before and after the alignment character is at its max across the column's ALIGN=CHAR lines. for incremental table layout, the suggested default is CHAROFF="50%" ๐Ÿ“

if several cells in different rows of the same column use character alignment, then by default they should all line up, no matter which character each one uses ๐Ÿซถ and the rules for objects that are too large for the column kick in whenever the explicit or implied alignment means the data is wider than the column's assigned width ๐Ÿ’€

VALIGN

this defines whether the cell contents are aligned with the top, middle or bottom of the cell. it's the vertical glow-up ๐Ÿฆ‹

<!-- vertical alignment attributes for cell contents -->
<!ENTITY % cell.valign
        "valign  (top|middle|bottom|baseline)  #IMPLIED"
        >

if it's present, the value should be one of: TOP, MIDDLE, BOTTOM or BASELINE โœจ all cells in the same row with valign=baseline should be vertically positioned so the first text line in each of them lands on a common baseline. this constraint only applies to the first line though, not the subsequent text lines in those cells ๐ŸŒธ


Inheritance Order

alignment properties can be included with most of the table elements: COL, THEAD, TBODY, TFOOT, TR, TH and TD ๐Ÿ’… when rendering cells, horizontal alignment is decided by columns first (over rows), while for vertical alignment the rows matter more than the columns. it's giving priority drama ๐Ÿ‘‘

here's the detailed precedence order for each attribute, where X > Y means X takes precedence over Y ๐ŸŽ€

ALIGN, CHAR and CHAROFF:

cells > columns > column groups > rows > row groups > default

VALIGN, LANG, and DIR:

cells > rows > row groups > columns > column groups > table > default

so cells are the TH and TD elements, rows are TR elements, row groups are THEAD, TBODY and TFOOT, columns are COL elements, and column groups are COLGROUP and COL elements ๐Ÿงš and yes, there is no inheritance mechanism for the CLASS attribute, so that one stays put ๐Ÿ™ˆ

properties defined on cells take precedence over inherited properties, but they get over-ridden by alignment properties on elements inside the cells ๐Ÿ’– if there's no ALIGN attribute anywhere along the inheritance path, the recommended default alignment is ALIGN=LEFT for table data and ALIGN=CENTER for table headers. the recommended default for vertical alignment is VALIGN=MIDDLE โœจ these defaults were picked to match the behaviour of the widely deployed Netscape implementation, so they're basically iconic history ๐Ÿฆ‹


Standard Units for Widths

several attributes specify widths as a number followed by an optional suffix ๐ŸŒˆ the suffix tells you the unit: pt is points, pi is picas, in is inches, cm is centimeters, mm is millimeters, em is em units (equal to the height of the default font), and px is screen pixels ๐Ÿ’… the default units are screen pixels, which were chosen for backwards compatibility ๐Ÿซถ

the number can be an integer or a real valued number like "2.5". exponents, as in "1.2e2", are not allowed ๐Ÿ’€ and there's no white space allowed between the number and the suffix, so 2 px is a no-go ๐Ÿ™ˆ

the set of suffixes is augmented for certain elements, though ๐Ÿ‘‘ "%" is used for the WIDTH attribute on the TABLE element. it means the attribute gives the percentage width of the space between the current left and right margins, e.g. width="50%" โœจ and for the COL element, "*" is used with the WIDTH attribute to specify relative column widths, e.g. width="3*", using the same representation as the CALS table model ๐Ÿ“


The TABLE element

the next stop is the TABLE element, and we're starting with its entity definition ๐Ÿงš

<!ENTITY % Where "(left|center|right)">

this one's just the set of allowed values for placement, and it's about to get a lot more interesting ๐ŸŒธ๐Ÿ”ฅ

<!ELEMENT table - - (caption?, (col*|colgroup*), thead?, tfoot?, tbody+)>

<!ATTLIST table                    -- table element --
        %attrs;                    -- id, lang, dir and class --
        align   %Where;  #IMPLIED  -- table position relative to --
                                   -- window --
        width   CDATA    #IMPLIED  -- table width relative to window --
        cols    NUMBER   #IMPLIED  -- used for immediate display mode --
        border  CDATA    #IMPLIED  -- controls frame width around --
                                   -- table --
        frame   %Frame;  #IMPLIED  -- which parts of table frame to --
                                   -- include --
        rules   %Rules;  #IMPLIED  -- controls rules between cells --
        cellspacing CDATA #IMPLIED -- spacing between cells --
        cellpadding CDATA #IMPLIED -- spacing within cells --
        >

okay so the TABLE element needs both a start tag and an end tag, no skipping the closing one ๐Ÿ’€. The order is the fun part ๐ŸŽ€: it can start with an optional CAPTION, then either one or more COL elements or one or more COLGROUP elements, then an optional THEAD, an optional TFOOT, and finally one or more TBODY elements โœจ.

ID, CLASS, LANG and DIR

Go back to the common attributes we covered earlier ๐Ÿซถ

ALIGN

ALIGN is all about where the table sits between your left and right margins ๐Ÿฆ‹. ALIGN=CENTER puts it smack in the middle ๐Ÿ’–. ALIGN=LEFT parks it on the left margin, and ALIGN=RIGHT parks it on the right margin. User agents may flow text around the right-hand side of the table for ALIGN=LEFT, or the left-hand side for ALIGN=RIGHT ๐ŸŒธ.

Note you can use <BR CLEAR=LEFT> after the table element if you want to avoid text flowing along side the table when you have specified ALIGN=LEFT, or <BR CLEAR=RIGHT> for a right aligned table. To prevent a right aligned table flowing around something else, use <BR CLEAR=RIGHT> before the table etc. Greater control over textflow is possible using style sheets ๐Ÿ’….

WIDTH

WIDTH sets the width you want for the table ๐Ÿ“. Besides the standard units, you can use a "%" sign so the width is a percentage of the space between the current left and right margins, like width="50%" ๐Ÿ’œ. If you leave this attribute out, the layout algorithm we'll get to later decides the table width ๐Ÿงš.

It's recommended that the table width grow past whatever WIDTH says when it has to, so the cell contents don't overflow ๐ŸŒˆ. Those increases should try to avoid big changes to the relative column widths the author set. To avoid needing tons of horizontal scrolling, or when scrolling is impractical or unwanted, it may be fine to split words across lines ๐Ÿ™ˆ.

COLS

COLS tells everyone how many columns the table has ๐Ÿ‘‘. If it's there, the user agent may render the table on the fly as data comes in from the network, so you don't have to wait for the whole thing ๐Ÿ”ฅ. If WIDTH is missing, a default of "100%" may be assumed for this purpose โœจ. If COLS is absent, the user agent needs a prepass through the table's contents to figure out the number of columns, plus good widths for each column ๐Ÿ˜ญ.

BORDER

BORDER sets the width of the border framing the table. Check standard units for the details ๐ŸŽ€.

FRAME

FRAME says which sides of the frame get drawn ๐Ÿฆ‹.

<!ENTITY % Frame
   "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
  • VOID: Don't render any sides of the frame.
  • ABOVE: The top side of the frame.
  • BELOW: The bottom side of the frame.
  • HSIDES: The top and bottom sides of the frame.
  • LHS: The left hand side of the frame.
  • RHS: The right hand side of the frame.
  • VSIDES: The left and right sides of the frame.
  • BOX: All four sides of the frame.
  • BORDER: All four sides of the frame.

The value "Border" is here for backwards compatibility with browsers already out in the wild ๐Ÿ•ฐ๏ธ. If a document includes <TABLE BORDER>, the user agent will see FRAME=BORDER and BORDER=_implied_. If the document includes <TABLE BORDER=_n_>, the user agent should treat that as FRAME=BORDER, except when _n=0_, where FRAME=VOID is the right call ๐Ÿ’–.

Note: it would have been nicer to pick FRAME values that match the RULES attribute and the alignment values. For example: none, top, bottom, topbot, left, right, leftright, all. Sadly, SGML requires enumerated attribute values to be unique for each element, no matter the attribute name ๐Ÿ˜ฉ. That causes immediate problems for "none", "left", "right" and "all". So the FRAME values were picked to avoid clashing with RULES, ALIGN and VALIGN. That gives a bit of future proofing, since FRAME and RULES are expected to get added to other table elements in later revisions of this spec ๐Ÿ”ฎ. The other option would have been making FRAME a CDATA attribute. The HTML-WG agreed that being able to use SGML validation tools on enumerated values was worth more than having consistent names ๐Ÿซถ.

RULES

RULES says where to draw lines inside the table ๐Ÿ“.

<!ENTITY % Rules "(none | groups | rows | cols | all)">
  • NONE: Suppresses internal rulings.
  • GROUPS: The THEAD, TFOOT and TBODY elements split the table into row groups, and COLGROUP elements split it into column groups. This puts a horizontal rule between each row group and a vertical rule between each column group. Every table has at least one row and one column group, so you'll always see something ๐Ÿ‘‘.
  • ROWS: Same as RULES=GROUPS, plus horizontal rules between all rows. User agents may choose a heavier rule between groups of rows and columns for emphasis ๐Ÿ’ช.
  • COLS: Same as RULES=GROUPS, plus vertical rules between all columns. User agents may choose a heavier rule between groups of rows and columns for emphasis ๐ŸŒˆ.
  • ALL: Rules between all rows and all columns. User agents may choose a heavier rule between groups of rows and columns for emphasis โญ.

If a document includes <TABLE BORDER> or <TABLE BORDER=_n_>, the default for the table element is RULES=ALL, except when _n=0_, where RULES=NONE is right ๐Ÿ“.

CELLSPACING

This one is here for backwards compatibility with browsers already out there ๐Ÿ•ฐ๏ธ. It sets the space between the table frame and the first or last cell border for each row or column, and also the space between other cells in the table. Check standard units. Greater control will come with style sheet languages ๐Ÿ’….

CELLPADDING

Also for backwards compatibility ๐Ÿซถ. It sets the space between a cell's border and its contents, both above/below and left/right. Check standard units. Again, style sheet languages will give you more control ๐Ÿงš.

If a fixed width is set for the table or a column, CELLSPACING and CELLPADDING may need more space than was assigned ๐Ÿ˜ญ. Current practice is for the latter to win over WIDTH attributes when they clash, although this spec doesn't require that ๐Ÿ’€.

Table Captions โœจ

<!ELEMENT caption - - (%text;)+>

<!ENTITY % Caption "(top|bottom|left|right)">

<!ATTLIST caption                  -- table caption --
        %attrs;                    -- id, lang, dir and class --
        align   %Caption; #IMPLIED -- relative to table --
        >

The optional CAPTION element gives the table a caption ๐ŸŽ€. Both start and end tags are required ๐Ÿ’–.

ID, CLASS, LANG and DIR

Same deal as before: see the common attributes we already covered ๐Ÿซถ.

ALIGN

ALIGN controls where the caption goes relative to the table ๐Ÿฆ‹. When it's there, ALIGN should have one of these values: TOP, BOTTOM, LEFT and RIGHT. It's recommended that the caption fits within the width or height of the table, as appropriate ๐ŸŒธ. The default position of the caption is deliberately left unspecified ๐Ÿคทโ€โ™€๏ธ.

Note the ALIGN attribute is overused in HTML, but it's kept here for compatibility with browsers that are already deployed ๐Ÿ’….

The COLGROUP Element ๐Ÿ’œ

<!ELEMENT colgroup - O (col*)>

<!ATTLIST colgroup
        %attrs;                    -- id, lang, dir and class --
        span    NUMBER   1         -- default number of columns in --
                                   -- group --
        width   CDATA    #IMPLIED  -- default width for enclosed --
                                   -- COLs --
        %cell.halign;              -- horizontal alignment in --
                                   -- cells --

        %cell.valign;              -- vertical alignment in cells --
        >

COLGROUP is a container for a group of columns, and it lets you set default properties for them ๐Ÿงš. If there's no COLGROUP element, every column in the table counts as belonging to one single column group ๐ŸŒˆ. Each COLGROUP can hold zero or more COL elements. COLGROUP needs a start tag, but the end tag can be left out, which is handy when you're defining a run of COLGROUPs ๐Ÿซถ. Here's an example:

    <TABLE FRAME=BOX RULES=COLS>
      <COLGROUP>
        <COL WIDTH="1*">
        <COL WIDTH="2*">
      <COLGROUP>
        <COL WIDTH="1*">
        <COL WIDTH="3*">
      <THEAD>
        <TR> ...
    </TABLE>

COLGROUP elements can use these attributes:

ID, CLASS, LANG and DIR

Back to the common attributes we covered earlier ๐Ÿ’–.

SPAN

SPAN is a positive integer that sets a default for how many columns are in this group. It should be ignored if the COLGROUP element has one or more COL elements inside it. It's a handy shortcut for grouping columns without writing out COL elements ๐Ÿ“.

WIDTH

WIDTH sets a default width for each grouped column. Check standard units for the details. The "*" suffix means relative widths too ๐ŸŒธ:

            width=64        width in screen pixels
            width=0.5*      a relative width of 0.5

Relative widths act as constraints on how the widths of different columns relate to each other ๐Ÿฆ‹. If a COLGROUP says its relative width is zero, all of the columns in that group should be set to their minimum widths, unless they're tied to a COL element with its own overriding WIDTH attribute ๐Ÿ˜ญ. When widths are given in absolute units, the user agent can use them to constrain the width of the table ๐Ÿ‘‘. The "*" suffix exists to make importing tables from the CALS representation easier ๐Ÿ’….

ALIGN, CHAR, CHAROFF and VALIGN

These set values for horizontal and vertical alignment inside table cells. Check the inheritance order of alignment properties to see how they stack up ๐ŸŒˆโœจ.

The COL Element ๐ŸŽ€

okay so COL is the element that lets you set column-based defaults for table properties ๐Ÿ’… It's EMPTY, which means no content, and you should never give it an end tag (bestie, don't do it ๐Ÿ™ˆ). You can stack several COL elements in a row, and their attributes override the ones on the parent COLGROUP. Iconic behaviour โœจ

   <!ELEMENT col - O EMPTY>

   <!ATTLIST col                      -- column groups and --
                                      -- properties --
           %attrs;                    -- id, lang, dir and class --
           span    NUMBER   1         -- number of columns spanned --
                                      -- by group --
           width   CDATA    #IMPLIED  -- column width specification --
           %cell.halign;              -- horizontal alignment in --
                                      -- cells --
           %cell.valign;              -- vertical alignment in cells --
           >

ID, CLASS, LANG and DIR ๐Ÿ†”

See the earlier description of the common attributes. Nothing new here, just the usual squad ๐Ÿซถ

SPAN ๐ŸŒˆ

SPAN is a positive integer that says how many columns this element applies to, and it defaults to one. If you don't use SPAN at all, the first COL hits the first column, the second COL hits the second column, and so on. Now watch this: if the second COL has SPAN=2, it covers the second AND third columns ๐Ÿ˜ญ Then the next COL picks up at the fourth column, and the story keeps going from there.

SPAN=0 is the special one ๐Ÿ‘‘ It means the COL spans every column from the current one all the way to the last column. And here's the tea: a COL SPAN does NOT make a group. It's just a sneaky way to share attribute definitions around ๐Ÿ’œ

WIDTH ๐Ÿ“

WIDTH sets the width of the columns (see the standard units section). If one element covers several columns, WIDTH is the width for EACH individual column, not the width of the whole span. Important, don't mix that up ๐Ÿ™ˆ

The * suffix means relative widths, like this:

            width=64        width in screen pixels
            width=0.5*      a relative width of 0.5

Relative widths act as constraints on each other. If a COL gives a relative width of zero, that column should always be set to its minimum width ๐Ÿ’€ When widths are in absolute units, the user agent can use them to constrain the width of the whole table. And the * suffix exists mainly to make it easier to import tables from the CALS representation, so it's giving practicality ๐Ÿงš

ALIGN, CHAR, CHAROFF and VALIGN ๐Ÿ“

These set values for horizontal and vertical alignment inside table cells. Check out the inheritance order of alignment properties for the full lore.


Table Head, Foot and Body Elements ๐Ÿง

Okay the DTD for these three is giving structure ๐Ÿ’–

   <!ELEMENT thead - O tr+>
   <!ELEMENT tfoot - O tr+>
   <!ELEMENT tbody O O tr+>

   <!ATTLIST (thead|tbody|tfoot)      -- table section --
           %attrs;                    -- id, lang, dir and class --
           %cell.halign;              -- horizontal alignment in --
                                      -- cells --
           %cell.valign;              -- vertical alignment in cells --
           >

So tables can be split into head and body sections. THEAD and TFOOT are optional, but you always need one or more TBODY elements, no exceptions โœจ If your table is only a TBODY, you can skip its start and end tags, since the parser can figure them out. If you have a THEAD, its start tag is required, but the end tag can be left off as long as a TFOOT or TBODY start tag comes right after. Same deal for TFOOT ๐Ÿซถ

Note: This definition keeps compatibility with tables made for the older model, and it also lets you drop the end tags for THEAD, TFOOT and TBODY. Literally so convenient ๐Ÿ’…

These three elements are basically a control panel for rendering ๐ŸŽ›๏ธ If there are tons of rows in the body, user agents might use a scrolling region for the table body sections. When you're printing to a paged device, tables often have to break across pages, and that's where THEAD, TFOOT and TBODY shine. They let the user agent repeat the table foot at the bottom of the current page, then repeat the table head at the top of the new page, and then keep going with the body ๐Ÿฆ‹

TFOOT goes before TBODY in the markup, and that's on purpose ๐Ÿ”ฅ It means browsers can render the foot before they've received all of the table data. This is super useful for really long tables with scrolling body sections, or for paged output where the table gets broken over lots of pages.

Each THEAD, TFOOT and TBODY element must contain one or more TR elements ๐ŸŒธ

ID, CLASS, LANG and DIR ๐Ÿ†”

See the earlier description of the common attributes.

ALIGN, CHAR, CHAROFF and VALIGN ๐Ÿ“

These specify values for horizontal and vertical alignment inside table cells. See the inheritance order of alignment properties.


Table Row (TR) elements ๐Ÿฆฉ

Here's the DTD for TR, and it's giving very clear vibes:

   <!ELEMENT tr - O (th|td)+>

   <!ATTLIST tr                       -- table row --
           %attrs;                    -- id, lang, dir and class --
           %cell.halign;              -- horizontal alignment in --
                                      -- cells --
           %cell.valign;              -- vertical alignment in cells --
           >

TR (table row) is basically a container that holds one row of table cells ๐Ÿ“ฆ The end tag is optional, so you can leave it off if you want to live dangerously ๐Ÿ˜Œ

ID, CLASS, LANG and DIR ๐Ÿ†”

See the earlier description of the common attributes.

ALIGN, CHAR, CHAROFF and VALIGN ๐Ÿ“

These specify values for horizontal and vertical alignment inside table cells. See the inheritance order of alignment properties.


Table Cells: TH and TD ๐Ÿฐ

The DTD for cells is the main event ๐ŸŽ€

   <!ELEMENT (th|td) - O %body.content>

   <!ATTLIST (th|td)                  -- header or data cell --
           %attrs;                    -- id, lang, dir and class --
           axis    CDATA    #IMPLIED  -- defaults to cell content --
           axes    CDATA    #IMPLIED  -- list of axis names --
           nowrap (nowrap)  #IMPLIED  -- suppress word wrap --
           rowspan NUMBER   1         -- number of rows spanned by --
                                      -- cell --
           colspan NUMBER   1         -- number of cols spanned by --
                                      -- cell --
           %cell.halign;              -- horizontal alignment in --
                                      -- cells --
           %cell.valign;              -- vertical alignment in cells --
           >

TH is for header cells and TD is for data cells ๐Ÿ’œ This lets user agents render them differently even when there are no style sheets, so the headers can stand out and not blend in with the data.

Cells can span multiple rows and columns, and they're allowed to be empty too. A cell that spans rows adds to the column count on each row it covers, but it only shows up in the markup once, in the first row it spans. The row count comes from the number of TR elements. Any extra rows implied by row-spanning cells past that count should be ignored ๐Ÿ™ˆ

If the table's column count is bigger than the number of cells in a row (after counting cells from spanned rows), the missing cells are treated as sitting on the right hand side of the table and rendered as empty cells. But if the language context says the writing order is right to left, the missing cells should go on the left hand side instead ๐ŸŒˆ

Tables can also have overlapping cells, which is a whole mood ๐Ÿ˜ญ Here's an example:

       <table border>
       <tr><td rowspan=2>1<td>2<td>3
       <tr><td rowspan=2>4
       <tr><td colspan=2>5<td>6
       </table>

And it might look something like this:

       /-----------\
       | 1 | 2 | 3 |
       |   |-------|
       |   | 4 |   |
       |---|...|---|
       | 5 :   | 6 |
       \-----------/

In this example, the cells labelled 4 and 5 overlap. When that happens, how it's rendered is implementation dependent ๐Ÿ’€

The AXIS and AXES attributes give you a way to define concise labels for cells. When rendering to speech, these can supply abbreviated names for the headers that go with each cell. They're also handy if you want to process the table contents later and load them into a database, since then they become the database field names. The table's class attribute should be used to let the software recognise which tables can be treated that way ๐Ÿงš

ID, CLASS, LANG and DIR ๐Ÿ†”

See the earlier description of the common attributes.

AXIS ๐Ÿท๏ธ

AXIS defines an abbreviated name for a header cell, for example one that can be used when rendering to speech. It defaults to the cell's content.

AXES ๐Ÿ—บ๏ธ

AXES is a comma separated list of axis names which together identify the row and column headers that pertain to this cell. It's used for example when rendering to speech, to figure out where the cell sits in the table. If it's missing, the user agent can try to follow up columns and left along rows (right for some languages) to find the matching header cells.

NOWRAP, e.g. <TD NOWRAP> ๐Ÿ™…

If this attribute is present, automatic wrapping of text lines gets turned off for this cell. Be careful with it, because if you use it uncautiously it can make cells excessively wide ๐Ÿ˜ฌ It's only defined for backwards compatibility with user agents that are already out there. You get more control with the associated style sheet languages (for example, for overflow handling).

ROWSPAN, e.g. <TD ROWSPAN=2> ๐Ÿ“

ROWSPAN is a positive integer that defines how many rows this cell spans. The default ROWSPAN is 1. ROWSPAN=0 is special and means the cell spans all rows from the current row up to the last row of the table ๐Ÿ‘‘

COLSPAN, e.g. <TD COLSPAN=2> ๐Ÿ“

COLSPAN is a positive integer that defines how many columns this cell spans. The default COLSPAN is 1. COLSPAN=0 is special too, and means the cell spans all columns from the current column up to the last column of the table ๐Ÿ‘‘

ALIGN, CHAR, CHAROFF and VALIGN ๐Ÿ“

These specify values for horizontal and vertical alignment inside table cells. See the inheritance order of alignment properties.

Note: It is recommended that implementors provide support for the Netscape 1.1 WIDTH attribute for TH and TD, although this isn't part of the current specification. Document authors are advised to use the width attribute for the COL element instead. Bestie, use COL for widths, not the old one ๐Ÿซถ

Here's the layout tea โ˜• If the COLS attribute on the TABLE element specifies the number of columns, then the table may be rendered using a fixed layout. Otherwise, the autolayout algorithm described below should be used ๐Ÿ’–

Fixed Layout Algorithm

okay so the fixed layout algorithm is for when you already know how many columns the table has ๐Ÿ’… The default move is to make every column the same width, but authors can override that with relative or absolute widths using the COLGROUP or COL elements ๐ŸŽ€ The default table width is the space between the current left and right margins, unless the WIDTH attribute on the TABLE element overrides it or the absolute column widths set it ๐Ÿ“ When you're mixing absolute and relative widths, you first hand out space to the absolute columns, and whatever's left gets split between the relative ones โœจ

the table syntax by itself can't guarantee that attribute values agree with each other ๐Ÿ˜ญ for example, the number of columns in the COLS attribute might not match the number implied by the COL elements, and that might not match the number implied by the table cells either ๐Ÿ™ˆ overflow is another mess, because if the columns are too narrow, cell contents can spill out ๐Ÿ’€ user agents are recommended to recover gracefully here, for instance by hyphenating words and falling back to splitting words when there's no known hyphenation point ๐Ÿซถ

if an indivisible element causes cell overflow, the user agent may consider adjusting the column widths and re-rendering the table ๐Ÿ”ฅ in the worst case, clipping may be considered if adjusting widths and/or scrollable cell content isn't feasible ๐Ÿ˜ฌ whatever happens, if cell content gets split or clipped, that should be shown to the user in an appropriate way ๐ŸŒธ


Autolayout Algorithm

if the COLS attribute is missing from the table start tag, the user agent should use this autolayout algorithm ๐Ÿงš it makes two passes over the table data and scales linearly with the table's size, which is honestly so efficient ๐Ÿ˜ญ

in the first pass, line wrapping gets turned off, and the user agent tracks the minimum and maximum width of each cell ๐Ÿ“ the maximum width is the widest line, and since wrapping is off, paragraphs count as long lines unless <BR> elements break them up ๐Ÿ“ the minimum width is the widest word or image (and so on), counting leading indents and list bullets too ๐Ÿฆ‹ basically, imagine formatting the cell's content in its own window, and figure out how narrow that window can get before the cell starts overflowing โœจ letting user agents split words cuts down on horizontal scrolling and on clipping in the worst case ๐Ÿ’œ

nested tables inside cell content get the same treatment ๐Ÿช† their minimum and maximum widths feed into the minimum and maximum widths of the nested table, and from there into the parent table cell itself ๐ŸŒˆ the algorithm stays linear with the total cell content, and it's pretty much independent of how deeply things are nested ๐Ÿ™Œ

to handle character alignment in cell contents, the algorithm keeps three running min/max totals per column: left of the align char, right of the align char, and un-aligned ๐ŸŽ€ the minimum width for a column is then max(min_left + min_right, min_non-aligned) ๐Ÿ”ข

the minimum and maximum cell widths get used to work out the matching minimum and maximum column widths, which then give the minimum and maximum width for the whole table ๐Ÿ“Š cells can hold nested tables, but that doesn't make the code much harder ๐Ÿซถ the next step is assigning column widths based on the space available, which is the space between the current left and right margins ๐Ÿฉท

for cells spanning multiple columns, one simple approach (used by Arena) is to split the min/max widths evenly across the columns involved ๐Ÿฐ a slightly fancier approach is to use the min/max widths of unspanned cells to weight how the spanned widths get split ๐Ÿง experiments showed that a blend of the two works well for a wide range of tables ๐Ÿ’–

the table borders and intercell margins have to be counted when assigning column widths, and there are three cases ๐ŸŽ€

  1. the minimum table width is equal to or wider than the available space. in this case, assign the minimum widths and let the user scroll horizontally. for conversion to braille, the cells have to be replaced by references to notes holding their full content, and by convention those notes come before the table ๐Ÿ“
  2. the maximum table width fits inside the available space. in this case, set the columns to their maximum widths ๐Ÿ™Œ
  3. the maximum width of the table is bigger than the available space, but the minimum table width is smaller. in this case, find the difference between the available space and the minimum table width, and call that W. also call D the difference between the maximum and minimum width of the table ๐Ÿงฎ

for each column, let d be the difference between its maximum and minimum width. then set the column's width to the minimum width plus d times W over D. this means columns with big gaps between their minimum and maximum widths end up wider than columns with smaller gaps ๐Ÿ‘‘

this assignment step gets repeated for nested tables, using the minimum and maximum widths worked out for all of them in the first pass ๐Ÿช† in that case, the width of the parent (enclosing) table cell plays the role of the current window size ๐Ÿ–ผ๏ธ this repeats recursively for every nested table ๐Ÿ’… the topmost table then gets rendered with the assigned widths, and nested tables are rendered as part of the parent table's cell contents ๐Ÿฆ‹

if the table width is set with the WIDTH attribute, the user agent tries to match the column widths to it ๐ŸŽฏ the WIDTH attribute isn't binding if that would leave any column narrower than its minimum (indivisible) width ๐Ÿ™…โ€โ™€๏ธ

if relative widths are set with the COL element, the algorithm changes so columns can grow past their minimum width to meet the relative width constraints ๐Ÿ“ˆ COL elements should be treated as hints only, so no column should go below its minimum width ๐ŸŒท columns also shouldn't get so wide that the table sticks out well past the window ๐ŸชŸ if a COL element gives a relative width of zero, that column should always be set to its minimum width ๐Ÿซ 


HTML Table DTD

the DTD (document type definition) is the formal definition of which syntax is allowed for HTML tables ๐Ÿ“œ here it is, in all its glory ๐Ÿ’œ

<!-- Content model entities imported from parent DTD:

  %body.content; allows table cells to contain headers, paras,
  lists, form elements and even arbitrarily nested tables.

  %text; is text characters, including character entities and
  character emphasis elements, IMG and anchors
-->

<!ENTITY % attrs
       "id      ID       #IMPLIED  -- element identifier --
        class   NAMES    #IMPLIED  -- for subclassing elements --
        lang    NAME     #IMPLIED  -- as per RFC 1766 --
        dir   (ltr|rtl)  #IMPLIED  -- I18N text direction --">

<!--
 The BORDER attribute sets the thickness of the frame around the
 table. The default units are screen pixels.

 The FRAME attribute specifies which parts of the frame around
 the table should be rendered. The values are not the same as
 CALS to avoid a name clash with the VALIGN attribute.

 The value "border" is included for backwards compatibility with
  <TABLE BORDER> which yields frame=border and border=implied
  For <TABLE BORDER=1> you get border=1 and frame=implied. In this
 case, its appropriate to treat this as frame=border for backwards
 compatibility with deployed browsers.
-->

<!ENTITY % Frame "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
<!--
 The RULES attribute defines which rules to draw between cells:

 If RULES is absent then assume:
     "none" if BORDER is absent or BORDER=0 otherwise "all"
-->

<!ENTITY % Rules "(none | groups | rows | cols | all)">

<!-- horizontal placement of table relative to window -->
<!ENTITY % Where "(left|center|right)">

<!-- horizontal alignment attributes for cell contents -->
<!ENTITY % cell.halign
        "align  (left|center|right|justify|char) #IMPLIED
         char    CDATA   #IMPLIED -- alignment char, e.g. char=':' --
         charoff CDATA   #IMPLIED -- offset for alignment char --"
        >

<!-- vertical alignment attributes for cell contents -->
<!ENTITY % cell.valign
        "valign  (top|middle|bottom|baseline)  #IMPLIED"
        >

<!ELEMENT table - - (caption?, (col*|colgroup*), thead?, tfoot?, t
                    body+)>
<!ELEMENT caption - - (%text;)+>
<!ELEMENT thead - O (tr+)>
<!ELEMENT tfoot - O (tr+)>
<!ELEMENT tbody O O (tr+)>
<!ELEMENT colgroup - O (col*)>
<!ELEMENT col - O EMPTY>
<!ELEMENT tr - O (th|td)+>
<!ELEMENT (th|td) - O %body.content>

<!ATTLIST table                    -- table element --
        %attrs;                    -- id, lang, dir and class --
        align   %Where;  #IMPLIED  -- table position relative to --
                                   -- window --
        width   CDATA    #IMPLIED  -- table width relative to window --
        cols    NUMBER   #IMPLIED  -- used for immediate display mode --
        border  CDATA    #IMPLIED  -- controls frame width around --
                                   -- table --
        frame   %Frame;  #IMPLIED  -- which parts of table frame to --
                                   -- include --
        rules   %Rules;  #IMPLIED  -- rulings between rows and cols --
        cellspacing CDATA #IMPLIED -- spacing between cells --
        cellpadding CDATA #IMPLIED -- spacing within cells --
        >

<!-- ALIGN is used here for compatibility with deployed browsers -->
<!ENTITY % Caption "(top|bottom|left|right)">

<!ATTLIST caption                  -- table caption --
        %attrs;                    -- id, lang, dir and class --
        align  %Caption; #IMPLIED  -- relative to table --
        >

<!--
COLGROUP groups a set of COL elements. It allows you to group
several columns together.
-->
<!ATTLIST colgroup
        %attrs;                    -- id, lang, dir and class --
        span    NUMBER   1         -- default number of columns in --
                                   -- group --
        width   CDATA    #IMPLIED  -- default width for enclosed COLs --
        %cell.halign;              -- horizontal alignment in cells --
        %cell.valign;              -- vertical alignment in cells --
        >

<!--
 COL elements define the alignment properties for cells in a given
 column or spanned columns. The WIDTH attribute specifies the
 width of the columns, e.g.

     width=64        width in screen pixels
     width=0.5*      relative width of 0.5
-->

<!ATTLIST col                      -- column groups and properties --
        %attrs;                    -- id, lang, dir and class --
        span    NUMBER   1         -- number of columns spanned by --
                                   -- group --
        width   CDATA    #IMPLIED  -- column width specification --
        %cell.halign;              -- horizontal alignment in cells --
        %cell.valign;              -- vertical alignment in cells --
        >

Okay bestie, the table stuff is where the magic really happens ๐Ÿ’… Here's how the header, footer, and body sections work together ๐ŸŒธ

<!--
    Use THEAD to duplicate headers when breaking table
    across page boundaries, or for static headers when
    body sections are rendered in scrolling panel.

    Use TFOOT to duplicate footers when breaking table
    across page boundaries, or for static footers when
    body sections are rendered in scrolling panel.

    Use multiple TBODY sections when rules are needed
    between groups of table rows.
-->
<!ATTLIST (thead|tbody|tfoot)      -- table section --
        %attrs;                    -- id, lang, dir and class --
        %cell.halign;              -- horizontal alignment in cells --
        %cell.valign;              -- vertical alignment in cells --
        >

<!ATTLIST tr                       -- table row --
        %attrs;                    -- id, lang, dir and class --
        %cell.halign;              -- horizontal alignment in cells --
        %cell.valign;              -- vertical alignment in cells --
        >

<!ATTLIST (th|td)                  -- header or data cell --
        %attrs;                    -- id, lang, dir and class --
        axis    CDATA    #IMPLIED  -- defaults to cell content --
        axes    CDATA    #IMPLIED  -- list of axis names --
        nowrap (nowrap)  #IMPLIED  -- suppress word wrap --
        rowspan NUMBER   1         -- number of rows spanned by cell --
        colspan NUMBER   1         -- number of cols spanned by cell --
        %cell.halign;              -- horizontal alignment in cells --
        %cell.valign;              -- vertical alignment in cells --
        >

So the big idea ๐Ÿงš is that THEAD repeats your headers when a table gets split across pages, or keeps them pinned when the body scrolls in its own panel ๐Ÿ“„ TFOOT does the same job for footers ๐Ÿฆ‹ And TBODY is how you add rules between groups of rows, so you can section off your table like a pro ๐ŸŽ€

Every row lives in a TR ๐Ÿ’– and every cell is either a TH (header) or a TD (data) ๐ŸŒˆ Both cells take axis, axes, nowrap, rowspan, and colspan, plus the usual id, lang, dir, and class attributes from %attrs; ๐Ÿ“ The rowspan and colspan values default to 1, which means "just one, thank you" ๐Ÿ’…

References

Arena W3C's HTML3 browser, see http://www.w3.org/pub/WWW/Arena/. Arena started life as a proof of concept demo for ideas in the HTML+ specification that came before HTML3 ๐ŸŒธ It's now being re-implemented as a reference implementation of HTML3, with support for style sheets and client-side scripting โœจ iconic behaviour, honestly ๐Ÿ˜ญ

CALS Continuous Acquisition and Life-Cycle Support (formerly Computer-aided Acquisition and Logistics Support) (CALS) is a Department of Defense (DoD) strategy for achieving effective creation, exchange, and use of digital data for weapon systems and equipment ๐Ÿซถ More information is on the US Navy CALS home page at http://navysgml.dt.navy.mil/cals.html ๐Ÿ”ฅ

HTML 2.0 (RFC1866) Hypertext Markup Language Specification Version 2.0 by T. Berners-Lee and D. Connolly, November 1995 ๐ŸŒˆ Further information can be found at http://www.w3.org/pub/WWW/MarkUp/ or at ftp://ds.internic.net/rfc/rfc1866.txt ๐Ÿ’œ

HTML 3.0 Hypertext Markup Language Specification Version 3.0 ๐Ÿงš The initial draft specification was published in March 1995 โญ Work on refining HTML3 is proceeding piecemeal, with the new table specification as one of the pieces ๐Ÿ’… For W3C related work on HTML, see http://www.w3.org/pub/WWW/MarkUp/.

RFC 1766 "Tags for the Identification of Languages", by H. Alvestrand, UNINETT, March 1995 ๐Ÿฆ‹ This document can be downloaded from ftp://ds.internic.net/rfc/rfc1766.txt.

Security Considerations

Security issues are not discussed in this memo ๐Ÿ™ˆ Nothing to see here, bestie, this one's all about tables ๐Ÿ’–

Author's Address

Dave Raggett W3C

EMail: dsr@w3.org

The World Wide Web Consortium: http://www.w3.org/ ๐Ÿ‘‘

๐Ÿ“ฆ where this page comes from