Tables
A Wit table is one node. Hand it rows and it draws a full <table> — caption, header, body — with no grid of pipes to align.
Rows can be typed in place, come from a list of records, or be written as row / col child nodes when cells need links and
emphasis.
Which form?
| You have | Use | Header comes from |
|---|---|---|
| short rows typed by hand | rows in place | the first row |
| a list of records | rows: @name | the first record's keys |
| a list of records and your own labels | schema plus rows | the schema |
| cells with links or emphasis | row and col nodes | the first row |
Rows in place
Pass a list of rows in the rows parameter, one row per line in
square brackets. The first row is the header. A caption rides
above the table.
@table(caption: Output formats, rows: [ [Format, Extension] [Web page, .html] [Markdown, .md] [Print, .pdf] ])
| Format | Extension |
|---|---|
| Web page | .html |
| Markdown | .md |
Emits:
<table><caption>Output formats</caption><thead><tr><th>Format</th><th>Extension</th></tr></thead><tbody><tr><td>Web page</td><td>.html</td></tr><tr><td>Markdown</td><td>.md</td></tr><tr><td>Print</td><td>.pdf</td></tr></tbody></table>
Short tables fit on one line: @table(rows: [[Client, Hours], [Couple, 0.75]]).
Numbers keep the form you wrote, so 1.50 stays 1.50.
Drop the header with header: false — the first row becomes a
plain body row:
@table(header: false, rows: [[Web page, .html], [Markdown, .md]])
| Web page | .html |
| Markdown | .md |
<table><tbody><tr><td>Web page</td><td>.html</td></tr><tr><td>Markdown</td><td>.md</td></tr></tbody></table>
From a list of records
Point rows at a list of records and each record becomes a row.
With no schema, the columns are the first record's keys.
#refs: [ { author: Weil, S., year: 1952 } { author: Berger, J., year: 1972 } ] !! @table(rows: @refs)
| author | year |
|---|---|
| Weil, S. | 1952 |
| Berger, J. | 1972 |
<table><thead><tr><th>author</th><th>year</th></tr></thead><tbody><tr><td>Weil, S.</td><td>1952</td></tr><tr><td>Berger, J.</td><td>1972</td></tr></tbody></table>
The commas inside Weil, S. are safe: a record value runs until the
next key:.
Pick and label the columns
A schema chooses the columns. As a list, it picks fields in order
and uses the field names as headings. As a record, each key picks a field and its
value is the heading:
#sites: [ { name: Dunmore Head, status: operational, keeper: A. Vane } { name: Carrick Point, status: maintenance, keeper: R. Moss } ] !! @table(schema: [name, status], rows: @sites) @table(schema: { name: Site, status: Status }, rows: @sites)
| name | status |
|---|---|
| Dunmore Head | operational |
| Carrick Point | maintenance |
| Site | Status |
|---|---|
| Dunmore Head | operational |
| Carrick Point | maintenance |
The first table has headings name / status; the
second Site / Status. Both leave out keeper. Loading the records from a file at build time works the
same way; see Data.
Rich cells: row and col nodes
When cells need links, emphasis, or other nodes, leave off rows and
write the table's body: a row per row, a col per
cell.
@table(caption: Modules) (row (col Module col) (col Notes col) row) (row (col Parser col) (col Reads *source*; see (a(href: /docs/reference/syntax/) the syntax a). col) row) table@
| Module | Notes |
|---|---|
| Parser | Reads source; see the syntax. |
<table><caption>Modules</caption><thead><tr><th>Module</th><th>Notes</th></tr></thead><tbody><tr><td>Parser</td><td>Reads <strong>source</strong>; see <a href="/docs/reference/syntax/">the syntax</a>.</td></tr></tbody></table>
The first row is the header unless you pass header: false. A th cell is always a header cell, even in a body row. The HTML names tr / td / th work too.
Rows from data, with rich cells
Wrap the row in (each … as …) to make one row per record. Fields
work in cell text and in parameters such as a link's href. Add an (if …) to filter:
#modules: [ { name: Parser, status: stable, page: /docs/build/parsing-ast/ } { name: Sandbox, status: draft, page: /docs/build/scripting/ } ] !! @table(caption: Stable modules) (row (col Module col) (col Status col) row) (each @modules as m) (if @m.status is stable) (row (col (a(href: @m.page) @m.name a) col) (col (em @m.status em) col) row) (end) (end) table@
| Module | Status |
|---|---|
| Parser | stable |
The draft row drops out:
<table><caption>Stable modules</caption><thead><tr><th>Module</th><th>Status</th></tr></thead><tbody><tr><td><a href="/docs/build/parsing-ast/">Parser</a></td><td><em>stable</em></td></tr></tbody></table>
Header and caption controls
| Parameter | Effect |
|---|---|
| caption: text | adds a caption element |
| header: false | no header row |
| header: N | row N (from 0) becomes the header |
| header: a list of labels | explicit header cells; every row is data |
| (none) | the first row is the header |
Edge cases
| Case | Behaviour |
|---|---|
| empty or missing rows | an empty table element |
| null cell | an empty cell |
| number or boolean cell | its written form |
| comma in a row typed in place | starts a new cell |
Centring is theme, not renderer
The default theme centres tables. --raw ships a reset-only base, so
tables there are not centred — add your own CSS.
See also
- Iteration and Conditionals — the loops and tests behind data-driven rows.
- Data — defining and loading the record lists a table reads.
- Core vocabulary — every table parameter.