Tableau

A Different Kind of Table Formatter for Markdown

Tableau lets you create great-looking tables from plain-text descriptions. It renders them as HTML, and is usually run as a preprocessor for Markdown documents, though it can also be used standalone.

Tableau separates the data content of your table from its presentation.

You can try any of the examples here in the playground, which renders your table as you type.

For example, here's a multiplication table:

|  x |  1 |  2 |  3 |  4 |  5 |
|  1 |  1 |  2 |  3 |  4 |  5 |
|  2 |  2 |  4 |  6 |  8 | 10 |
|  3 |  3 |  6 |  9 | 12 | 15 |
|  4 |  4 |  8 | 12 | 16 | 20 |
|  5 |  5 | 10 | 15 | 20 | 25 |

x

1

2

3

4

5

1

1

2

3

4

5

2

2

4

6

8

10

3

3

6

9

12

15

4

4

8

12

16

20

5

5

10

15

20

25

This table has no presentation specifications, so tableau uses its default layout.

Presentation is specified after the table data, and starts with a line containing three equals signs.

|  x |  1 |  2 |  3 |  4 |  5 |
|  1 |  1 |  2 |  3 |  4 |  5 |
|  2 |  2 |  4 |  6 |  8 | 10 |
|  3 |  3 |  6 |  9 | 12 | 15 |
|  4 |  4 |  8 | 12 | 16 | 20 |
|  5 |  5 | 10 | 15 | 20 | 25 |
===
# Times table 
[r1;c1] header

Times table

x

1

2

3

4

5

1

1

2

3

4

5

2

2

4

6

8

10

3

3

6

9

12

15

4

4

8

12

16

20

5

5

10

15

20

25

We told it to use "Times Table" as the table's caption (the # is like a markdown header). The next line formats cells. Each cell format line is a cell selector between [ and ], followed by format instructions. Here the selector is [r1;c1]. This selects all cells in row 1 and also all cells in column 1. The head format turns these cells into table headings.

Cell selectors can be more powerful:

|  x |  1 |  2 |  3 |  4 |  5 |
|  1 |  1 |  2 |  3 |  4 |  5 |
|  2 |  2 |  4 |  6 |  8 | 10 |
|  3 |  3 |  6 |  9 | 12 | 15 |
|  4 |  4 |  8 | 12 | 16 | 20 |
|  5 |  5 | 10 | 15 | 20 | 25 |
===
# Times table 
[r1;c1] header
[r2-$r:c$tr] line(lb)
[r2-$r:c2-$tr~1] small bg(shade6)

Times table

x

1

2

3

4

5

1

1

2

3

4

5

2

2

4

6

8

10

3

3

6

9

12

15

4

4

8

12

16

20

5

5

10

15

20

25

[r2-$r:c$tr] line(lb) selects the cells in rows two through the last row in the table whose column number is the same as the row number and draw a line on its left and below it. line(lb) draws a line on the cell's left and bottom sides.

[r2-$r:c2-$tr~1] small bg(shade6) selects the cells in rows two through six, columns two through the column one less than the row number. Make the text small, and shade the background.

We'll explain all this in detail later. First, a few more examples:

More Examples

APA-style Table

Results of Curve-Fitting Analysis Examining the Time Course of Fixations to the Target

Logistic parameter

9-year-olds

16-year-olds

t(40)

p

d

M

SD

M

SD

Maximum asymptote, proportion

.843

.135

.877

.082

0.951

.347

0.302

Crossover, in ms

759

87

694

42

2.877

.006

0.840

Slope, as change in proportion per ms

.001

.0002

.002

.0002

2.635

.012

2.078

Nutrition Label

Nutrition Facts for Yummy Treats

Nutrition Information

Serving Size 1/2 cup (about 82g)

Servings Per Container 8

Amount Per Serving:

Calories 200 • Calories from Fat 130

% Daily Value

Total Fat

22%

Saturated Fat 9g

22%

Trans Fat 9g

0%

Cholesterol 55mg

18%

Sodium 40mg

2%

Total Carbohydrate 17g

6%

Dietary Fiber 1g

4%

Sugars 14g

0%

Protein 3g

Funky backgrounds and Flexible Spans

ant

bee

cat

dog

elk

fox

gnu

hen

idk

1

2

3

4

5

7

9

10

14

16

19

20

21

22

23

24

25

A Gentle Guide to Tableau

Table Data

| a | b | c |
| d | e | f |

a

b

c

d

e

f

Leading and Trailing Pipes
a | b | c
d | e | f

a

b

c

d

e

f

Blank Lines
a | b | c
d | e | f

g | h | i

a

b

c

d

e

f

g

h

i

Inline Cell Formatting
Pole star | _Alpha Ursae_ | $323–433 ly$
Dog star  | _Sirius_ | $8.60 \pm 0.04 ly$ 

Pole star

Alpha Ursae

323–433ly323–433 ly

Dog star

Sirius

8.60±0.04ly8.60 \pm 0.04 ly

Table Layout

Table layout and styling is placed in a separate block that follows the table data, separated from it by three equals signs.

~~~ tableau
data
data
===
layout
layout
~~~

Layout can be applied to the table as a whole, or to one or more cells.

Add a Caption
Animal   | Lifespan
|        | Average  | Max
Badger   |  8       | 14
Elephant | 40       | 70 
Hare     | 6        | 10
Mouse    | 1.5      |  4
===
# Fake Animal Lifespan Data

Fake Animal Lifespan Data

Animal

Lifespan

Average

Max

Badger

8

14

Elephant

40

70

Hare

6

10

Mouse

1.5

4

Add Lines
Animal   | Lifespan
|        | Average  | Max
Badger   |  8       | 14
Elephant | 40       | 70 
Hare     | 6        | 10
Mouse    | 1.5      |  4
===
# Fake Animal Lifespan Data
hlines vlines

Fake Animal Lifespan Data

Animal

Lifespan

Average

Max

Badger

8

14

Elephant

40

70

Hare

6

10

Mouse

1.5

4

Add Lines to Particular Rows (the hard way)
Animal   | Lifespan
|        | Average  | Max
Badger   |  8       | 14
Elephant | 40       | 70 
Hare     | 6        | 10
Mouse    | 1.5      |  4
===
# Fake Animal Lifespan Data
[r3] lines(t)
[r4] lines(t)
[r5] lines(t)
[r6] lines(t)

Fake Animal Lifespan Data

Animal

Lifespan

Average

Max

Badger

8

14

Elephant

40

70

Hare

6

10

Mouse

1.5

4

Add Lines to Particular Rows (an easier way)
Animal   | Lifespan
|        | Average  | Max
Badger   |  8       | 14
Elephant | 40       | 70 
Hare     | 6        | 10
Mouse    | 1.5      |  4
===
# Fake Animal Lifespan Data
[r3,4,5,6] lines(t)

Fake Animal Lifespan Data

Animal

Lifespan

Average

Max

Badger

8

14

Elephant

40

70

Hare

6

10

Mouse

1.5

4

Add Lines to Particular Rows (the easiest way)
Animal   | Lifespan
|        | Average  | Max
Badger   |  8       | 14
Elephant | 40       | 70 
Hare     | 6        | 10
Mouse    | 1.5      |  4
===
# Fake Animal Lifespan Data
[r3-6] lines(t)

Fake Animal Lifespan Data

Animal

Lifespan

Average

Max

Badger

8

14

Elephant

40

70

Hare

6

10

Mouse

1.5

4

Add a Header
Animal   | Lifespan
|        | Average  | Max
Badger   |  8       | 14
Elephant | 40       | 70 
Hare     | 6        | 10
Mouse    | 1.5      |  4
===
# Fake Animal Lifespan Data
[r1-2] header
[r3-6] lines(t) 

Fake Animal Lifespan Data

Animal

Lifespan

Average

Max

Badger

8

14

Elephant

40

70

Hare

6

10

Mouse

1.5

4

Span Some Cells
Animal   | Lifespan
|        | Average  | Max
Badger   |  8       | 14
Elephant | 40       | 70 
Hare     | 6        | 10
Mouse    | 1.5      |  4
===
# Fake Animal Lifespan Data
[r1-2] header
[r1:c2-3] span
[r3-6] lines(t) 

Fake Animal Lifespan Data

Animal

Lifespan

Average

Max

Badger

8

14

Elephant

40

70

Hare

6

10

Mouse

1.5

4

Align Ranges of Cells
Animal   | Lifespan
|        | Average  | Max
Badger   |  8       | 14
Elephant | 40       | 70 
Hare     | 6        | 10
Mouse    | 1.5      |  4
===
# Fake Animal Lifespan Data
[r1-2] header
[r1:c2-3] span
[r3-6] lines(t) 
[r3-$r:c1] align(l)
[r3-$r:c2-$c] align(r)

Fake Animal Lifespan Data

Animal

Lifespan

Average

Max

Badger

8

14

Elephant

40

70

Hare

6

10

Mouse

1.5

4

Shade the Background of Some Cells
Animal   | Lifespan
|        | Average  | Max
Badger   |  8       | 14
Elephant | 40       | 70 
Hare     | 6        | 10
Mouse    | 1.5      |  4
===
# Fake Animal Lifespan Data
[r1-2] header
[r1:c2-3] span
[r3-6] lines(t) 
[r3-$r:c1] align(l)
[r3-$r:c2-$c] align(r)
[r5:c2;r6:c2] bg(shade)
[r3:c3;r4:c3] bg(shade8)

Fake Animal Lifespan Data

Animal

Lifespan

Average

Max

Badger

8

14

Elephant

40

70

Hare

6

10

Mouse

1.5

4

Longer column content

(Because this example is longer, I'm formatting it with the output below the markup.)

~~~ tableau
Cicero | | |
col 2 {{
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis
}}
col 3 {{
atque corrupti quos dolores et quas molestias
}}

| | | |
col 2 {{
:::callout-note
On the other hand, we denounce with righteous indignation and dislike men who
are so beguiled and demoralized by the charms of pleasure of the moment, so
blinded by desire...
:::
}}

lorem | | |
col 2 {{
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
}}
col 3 {{
incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
}}
===
align(l)
[r2:c2-3] span
~~~

This renders as:

Cicero

At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis

atque corrupti quos dolores et quas molestias

Note

On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire...

lorem

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor

incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet,

Matching the Current Row
|  x |  1 |  2 |  3 |  4 |  5 |
|  1 |  1 |  2 |  3 |  4 |  5 |
|  2 |  2 |  4 |  6 |  8 | 10 |
|  3 |  3 |  6 |  9 | 12 | 15 |
|  4 |  4 |  8 | 12 | 16 | 20 |
|  5 |  5 | 10 | 15 | 20 | 25 |
===
[r1;c1] header
[r2-6:c$tr] line(lb)

x

1

2

3

4

5

1

1

2

3

4

5

2

2

4

6

8

10

3

3

6

9

12

15

4

4

8

12

16

20

5

5

10

15

20

25

Matching a Range Based on the Current Row
|  x |  1 |  2 |  3 |  4 |  5 |
|  1 |  1 |  2 |  3 |  4 |  5 |
|  2 |  2 |  4 |  6 |  8 | 10 |
|  3 |  3 |  6 |  9 | 12 | 15 |
|  4 |  4 |  8 | 12 | 16 | 20 |
|  5 |  5 | 10 | 15 | 20 | 25 |
===
[r1;c1] header
[r2-6:c$tr] line(lb)
[r2-6:c2-$tr~1] small bg(shade6)

x

1

2

3

4

5

1

1

2

3

4

5

2

2

4

6

8

10

3

3

6

9

12

15

4

4

8

12

16

20

5

5

10

15

20

25

Selecting Periodic Values in a Range
|  x |  1 |  2 |  3 |  4 |  5 |
|  1 |  1 |  2 |  3 |  4 |  5 |
|  2 |  2 |  4 |  6 |  8 | 10 |
|  3 |  3 |  6 |  9 | 12 | 15 |
|  4 |  4 |  8 | 12 | 16 | 20 |
|  5 |  5 | 10 | 15 | 20 | 25 |
===
[r1;c1] header
[r2-6%even:c2-$c] bg(shade1)

x

1

2

3

4

5

1

1

2

3

4

5

2

2

4

6

8

10

3

3

6

9

12

15

4

4

8

12

16

20

5

5

10

15

20

25

Selecting Periodic Rows And Columns
| ♜ | ♞ | ♝ | ♛ | ♚ | ♝ | ♞ | ♜ |
| ♟ | ♟︎ | ♟︎ | ♟︎ | ♟︎ | ♟︎ | ♟︎ | ♟︎ |
=empty
=empty
=empty
=empty
| ♙	| ♙ | ♙ | ♙ | ♙ | ♙ | ♙ | ♙ |
| ♖	| ♘ | ♗ | ♕ | ♔ | ♗ | ♘ | ♖ |
===
xlarge
[r1-8%even:c1-8%odd] bg(shade6)
[r1-8%odd:c1-8%even] bg(shade6)

♜

♞

♝

♛

♚

♝

♞

♜

♟

♟︎

♟︎

♟︎

♟︎

♟︎

♟︎

♟︎

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

♙

♙

♙

♙

♙

♙

♙

♙

♖

♘

♗

♕

♔

♗

♘

♖

Selecting Rows or Column Numbers That Are a Multiple Of a Value
County Number | County Name | FIPS Code | Public Health Region | Health Service Region
1 | Anderson | 48001 | 4 | 4/5N
2 | Andrews | 48003 | 9 | 9/10
3 | Angelina | 48005 | 5 | 4/5N
4 | Aransas | 48007 | 11 | 11
5 | Archer | 48009 | 2 | 2/3
6 | Armstrong | 48011 | 1 | 1
7 | Atascosa | 48013 | 8 | 8
8 | Austin | 48015 | 6 | 6/5S
9 | Bailey | 48017 | 1 | 1
10 | Bandera | 48019 | 8 | 8
11 | Bastrop | 48021 | 7 | 7
12 | Baylor | 48023 | 2 | 2/3
13 | Bee | 48025 | 11 | 11
14 | Bell | 48027 | 7 | 7
15 | Bexar | 48029 | 8 | 8
===
[r1] header
[r1-$r%%4] line(b)

County Number

County Name

FIPS Code

Public Health Region

Health Service Region

1

Anderson

48001

4

4/5N

2

Andrews

48003

9

9/10

3

Angelina

48005

5

4/5N

4

Aransas

48007

11

11

5

Archer

48009

2

2/3

6

Armstrong

48011

1

1

7

Atascosa

48013

8

8

8

Austin

48015

6

6/5S

9

Bailey

48017

1

1

10

Bandera

48019

8

8

11

Bastrop

48021

7

7

12

Baylor

48023

2

2/3

13

Bee

48025

11

11

14

Bell

48027

7

7

15

Bexar

48029

8

8

Tableau Reference

Create a Table

Tableau tables are created inside code blocks with a language of tableau.

~~~ tableau
...
~~~

The table contains a data section and an optional layout section, separated by a line containing just three equals signs:

~~~ tableau
«data»
===
«layout»
~~~

Both the data and layout sections are normally interpreted line-by-line. One or more lines in the input may end with a backslash character, in which case they will be concatenated with the line that follows before being processed.

~~~ tableau
a | \
b | \
c
d | e | f
g \
| h | \
i
~~~

is interpreted as if it were:

~~~ tableau
a | b | c
d | e | f
g | h | i
~~~

When we talk about lines in the following descriptions, we mean these concatenated lines.

Tableau Data Section

The data section is normally interpreted line by line. There are four line types. The first three each correspond to a row in the resulting table:

The fourth type lets you enter paragraph data to fill a column.

Column Data for a Row

Tableau follows the standard Markdown convention of using pipe characters to separate columns in a row.

~~~ tableau
| a | b | c |
| d | e | f |
| g | h | i |
~~~

Opening pipe characters are optional unless the cell they are adjacent to is blank.

  a | b | c
    | d | e
| f | h

In this example, the first row has values in columns 1, 2, and 3. In the second row, tableau interprets the pipe character as opening the first column, and so this row has data in columns 1 and 2, and not columns 2 and 3.

Include a literal pipe character in a column by escaping it with a backslash. A backslash escapes whatever character follows it, so use \\\\ for a literal backslash.

No Format Information in the Data Section

Unlike most Markdown tables, Tableau does not interpret lines containing minus signs between pipe characters as a heading separator and column alignment specification. If you write

~~~ tableau
| a | b | c |
|---|---|---|
| d | e | f |
| g | h | i |
~~~

you'll just see a table whose second row contains minus signs.

In Tableau, we specify layout in a separate section.

Blank Lines

A line consisting of zero or more spaces will create a half-height empty row in the displayed table.

Empty Lines

A line containing just the text =empty creates a row containing empty cells, where each cell will have the same height as a filled table row.

Column Paragraphs

Any column data row may be followed by one or more column paragraph entries. These look like:

col «n» {{
    paragraph text
    .. .. .. .. ..
}}

«n» is either a column number (col2) or a letter (colb, which is the same column). column may be written in full or abbreviated to col. A block naming a column the previous row does not have is ignored.

The text between the opening {{ and closing }} will be interpreted at the Markdown block level, and so may contain paragraphs, code blocks, divs, and so on. This text will be formatted, and the result will be used as the value of column «n» of the previous row. See Longer column content for an example.

Table Layout Section

Blank lines, and lines starting with two minus signs are ignored in the layout section.

All other lines have the form

[«cell selector(s)»] «format specification(s)»

or

«format specification(s)»

The cell selectors specify one or more cells. The format specifications are applied to these cells.

In the second form (with no cell selectors), the format specifications apply to all cells: they are table-level formats.

Cell Selectors

Here's the raw syntax of cell selectors. This is followed by an annotated version.

<cell selector>   := <cell range> ( ";" <cell range> )*

<cell range>      := <row> ( ":" <col> )?
                  |  <col>

<row>             := "r" <numbers>
<col>             := "c" <numbers>

<numbers>         := <number range> ( "," <number range> )*

<number range>    := <adjusted number> | <adjusted number> "-" <adjusted number> <skip>?

<adjusted number> := <number> ( [~+] <integer> )?

<number>          := <integer>
                  | '$c' | '$lastcol'
                  | '$r' | '$lastrow'
                  | '$tr' | '$thisrow'

<skip>          := ( "%" | "%%" ) ( "odd" | "even" | <integer> )

Notes

We're going to look at the remaining syntax in a different order, starting with the definition of a number

  |  x |  1 |  2 |  3 |  4 |  5 |
  |  1 |  1 |  2 |  3 |  4 |  5 |
  |  2 |  2 |  4 |  6 |  8 | 10 |
  |  3 |  3 |  6 |  9 | 12 | 15 |
  |  4 |  4 |  8 | 12 | 16 | 20 |
  |  5 |  5 | 10 | 15 | 20 | 25 |
  ===
  # Times table 
  [r1;c1] header
  [r2-$r:c$tr] line(lb)
  [r2-$r:c2-$tr~1] small bg(shade6)

Times table

x

1

2

3

4

5

1

1

2

3

4

5

2

2

4

6

8

10

3

3

6

9

12

15

4

4

8

12

16

20

5

5

10

15

20

25

The selector r2-$r:c$tr selects cells in rows 2 through the end of the table whose column number is the same as the row number.

r2-$r:c2-$tr~1 looks at rows 2 through the last. In each row, it selects the cells starting at column 2 and ending at the current row number minus 1.

Format Specifiers

Apart from captions, comments and blank lines, all layout lines look like

[«cell selector(s)»] «format specification(s)»

or

«format specification(s)»

Lines that contain cell selectors apply their formats to the selected cells.

Lines that do not contain cell selectors apply to the table as a whole.

There are some format selectors that only make sense when applied to the whole table, and others only apply to cells.

Tableau Format Specifiers

Format

Table

Cell

Format

Table

Cell

align

Y

Y

large

Y

Y

bg

Y

Y

lines

Y

boxed

Y

normal

Y

Y

caption

Y

small

Y

Y

fg

Y

Y

span

Y

footer

Y

style

Y

Y

header

Y

vlines

Y

hlines

Y

width

Y

Y

Many formats have both explicit and implicit forms. The explicit form is the name of the format, with options between parentheses. The implicit form uses just the options. For example, these two lines are the same:

[r1:c2] align(lt) bg(shade2)
[r1:c2] lt bg(shade2)

The descriptions that follow show whether a particular format has an implicit form, and when it can be used.

align: Horizontal and vertical alignment

context:

table and cell

explicit:

align( [lcrj] [tmb] )

implicit:

[lcrj] [tmb]

Set horizontal cell alignment (left, center, right, justified) and/or vertical cell alignment (top, middle, bottom)

| lt | ct | rt | Now is the time for all
| lb | cm | rt | Now is the time for all
| b  | rm | l  | Now is the time for all
===
vlines hlines
[r1:c1] lt
[r1:c2] ct
[r1:c3] rt
[r1:c4] j
[r2:c1] lt
[r2:c2] cm
[r2:c3] rb
[r2:c4] lt
[r3:c1] b 
[r3:c2] rm 
[r3:c3] l
[r3:c4] r

lt

ct

rt

Now is the time for all

lb

cm

rt

Now is the time for all

b

rm

l

Now is the time for all

bg: Background color

context:

table and cell

explicit:

bg( color )

implicit:

n/a

Set the background color of the selected cells. A color is one of:

Anything else is an error: there is no bare-color shorthand, so a color always appears inside bg(...).

[r2-$r] bg(shade$tr) gives each row its own shade, and [r1] bg(shade$tc) each column of the first row. Row numbers count blank spacer rows too.

For backgrounds Tableau cannot express directly -- gradients, images, animations -- attach a class to the cells instead and write the CSS yourself. See .style below.

shade1 | shade2 | shade3 | shade4
shade5 | shade6 | shade7 | shade8
shade9 | #cdf   | #94a8cb | nothing
===
[r1:c1] bg(shade1)
[r1:c2] bg(shade2)
[r1:c3] bg(shade3)
[r1:c4] bg(shade4)
[r2:c1] bg(shade5)
[r2:c2] bg(shade6)
[r2:c3] bg(shade7)
[r2:c4] bg(shade8)
[r3:c1] bg(shade9)
[r3:c2] bg(#cdf)
[r3:c3] bg(#94a8cb)

shade1

shade2

shade3

shade4

shade5

shade6

shade7

shade8

shade9

#cdf

#94a8cb

nothing

boxed: Box table

context:

table only

explicit:

boxed | box | boxed( line style )

implicit:

n/a

Draw a box around the entire table. boxed(thick, shade6) styles the box; see lines for the line style arguments.

cat  | kitten
dog  | puppy
deer | fawn
===
boxed

cat

kitten

dog

puppy

deer

fawn

caption: Give the table a caption

context:

table only

explicit:

# text | : text

implicit:

n/a

A layout line whose first non-blank character is # or :, followed by a space, sets the table's caption. Any number of leading # characters works the same way, so #, ## and ### are equivalent -- the markdown habit of picking a heading level does no harm.

Unlike the other specifiers, a caption line carries no cell selector and takes the rest of the line as its text. If several layout lines set a caption, the last one wins.

cat  | kitten
deer | fawn
===
: Animals and their young

Animals and their young

cat

kitten

deer

fawn

The caption renders below the table by default. See Styling for how to move it above.

fg: Foreground color

context:

table and cells

explicit:

fg( color )

implicit:

n/a

Set the foreground color of the selected cells. See the description of bg for notes on the color parameter.

cat  | kitten | 27 | medium
dog  | puppy  | 42 | large
deer | fawn   | 68 | larger
===
fg(#a00)
[r2:c2-3] fg(shade4)

cat

kitten

27

medium

dog

puppy

42

large

deer

fawn

68

larger

context:

cells only

explicit:

footer | foot

implicit:

n/a

The selected cells are made into a table footer. Normally applied to whole rows or columns. See the description of header for an example.

header: Specify table header cells

context:

cells only

explicit:

header | head

implicit:

n/a

The selected cells are made into a table header. Normally applied to whole rows or columns.

adult | child  | beta | concern
cat   | kitten | 27 | medium
dog   | puppy  | 42 | large
deer  | fawn   | 68 | larger
adult | child  | beta | concern
===
[r1] header
[r$r] footer

adult

child

beta

concern

cat

kitten

27

medium

dog

puppy

42

large

deer

fawn

68

larger

adult

child

beta

concern

hlines: Draw lines between rows

context:

table only

explicit:

hlines | hline | hlines( line style )

implicit:

n/a

Draw lines between table rows. hlines(dotted, shade6) styles them; see lines for the line style arguments. Unstyled, these lines are thin.

cat   | kitten | 27 | medium
dog   | puppy  | 42 | large
deer  | fawn   | 68 | larger
===
hlines

cat

kitten

27

medium

dog

puppy

42

large

deer

fawn

68

larger

large: Enlarge text

context:

table and cells

explicit:

large | xlarge | xxlarge

short:

lg | xlg | xxlg

implicit:

n/a

Increase font size by a small, medium, or large amount. Also see normal and small.

  small  | xsmall | xxsmall
  normal | normal | normal
  xxlarge | xlarge | large
===
[r1;r3] align(m)
[r1:c1] small
[r1:c2] xsmall
[r1:c3] xxsmall
[r3:c1] xxlarge
[r3:c2] xlarge
[r3:c3] large

small

xsmall

xxsmall

normal

normal

normal

xxlarge

xlarge

large

lines: Draw lines around cells

context:

cells only

explicit:

lines( sides, line style ) | line( sides, line style )

implicit:

n/a

Draw lines on the (t)op, (b)ottom, (l)eft, or (r)ight of the selected cells. The bo(x) attribute draws a box around the cell. Multiple sides may be given in a single lines specifier.

 1  | 2  | 3  | 4
 5  | 6  | 7  | 8
 9  | 10 | 11 | 12
 13 | 14 | 15 | 16
 ===
 align(c)
[r1:c1-2] lines(x)
[r1:c4] lines(tbr)
[r4] lines(l)
[r4:c1-$c%even] lines(t)
[r4:c1-$c%odd] lines(b)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Note that the lines specifier applies to each selected cell individually.

Line styles

Along with the sides, lines takes up to three style arguments, in any order, separated by commas or spaces:

weight

thin default thick xthick

style

solid dashed dotted double groove ridge inset outset

color

shade1 ... shade9, shade$tr, shade$tc -- the background shades

Anything left out keeps its default: a default weight, solid, in the table's line color. double needs at least a thick line to show both rules.

A lines with no sides draws nothing itself: it restyles whatever lines the cell gets from its other lines specifiers, wherever they are in the layout. A style given with sides beats one given without.

Item     | Amount
Coffee   | 4.50
Cake     | 3.25
Total    | 7.75
===
[c2] align(r)
[r1] line(b)
[r$r] line(t) line(b, double, xthick)
[r1-$r] line(shade6)

Item

Amount

Coffee

4.50

Cake

3.25

Total

7.75

hlines, vlines, and boxed take the same style arguments, but no sides.

normal: Normal-sized font

context:

table and cells

explicit:

normal

implicit:

n/a

Makes the text in selected cells normal size. Typically only used to override a more encompassing small or large specifier. See large for an example.

small: Reduce text

context:

table and cells

explicit:

small | xsmall | xxsmall

short:

sm | xsm | xxsm

implicit:

n/a

Decrease font size by a small, medium, or large amount. Also see large for an example.

span: Span cells horizontally and/or vertically

context:

cells only

explicit:

span

implicit:

n/a

Merges the selected cells into single logical cells, whose content and style are taken from the top-left–most cell of each block.

The selector produces one block per combination of a row range and a column range, so [r1-3:c1;r4:c2-4] span makes two blocks. Each range must be contiguous -- a skip such as %even has no rectangle to draw -- and two blocks may not overlap. Either is an error rather than a silently mangled table.

 1  | 2  | 3  | 4
 5  | 6  | 7  | 8
 9  | 10 | 11 | 12
 13 | 14 | 15 | 16
 ===
[r1-3:c1] span
[r1:c1] bg(shade1)

[r1-3:c2-3] span
[r1:c2] bg(shade3)

[r4:c2-4] span
[r4:c2] bg(shade6)

1

2

4

8

12

13

14

A cell-selector row or column spec that's written as a dash range (r1-3, c2-4) is treated as a single contiguous block, but one written as a comma list (r1,2,3, c2,3,4) is treated as several independent, single-value selectors -- each combination of a row group and a column group produces its own rectangle. This matters for span: a range merges its cells into one block, while a comma list keeps each value's cells as a separate block. So [r1-2:c2-4] span spans rows 1-2 and columns 2-4 into a single 2×3 cell, but [r1-2:c2,3,4] span spans rows 1-2 within each of columns 2, 3, and 4 independently, giving three separate two-row-tall cells side by side:

 1  | 2  | 3  | 4
 5  | 6  | 7  | 8
 9  | 10 | 11 | 12
 ===
[r1-2:c2,3,4] span

1

2

3

4

5

9

10

11

12

.style: Attach a CSS class

context:

table and cells

explicit:

style( name ) | class( name )

implicit:

.name

Associates the given style with the selected cells. The interpretation of the style depends on the output format used. For HTML output, the style becomes a CSS class.

The name may be written with or without a leading dot inside style(...) or class(...), so style(warning), style(.warning), class(warning) and .warning are all the same thing.

Styles accumulate. A cell (or the table) keeps every class applied to it, in the order the layout section applies them, so

[r1:c1] .boxed-in
[r1:c1] .warning

gives that cell class="boxed-in warning". Applying the same name twice is harmless -- it is only emitted once. This is how you attach a background Tableau cannot express itself, such as a gradient or an image: write the CSS yourself and name the class here.

 ant | bee | cat
 dog | elk | fox
 gnu | hen | idk
===
.gradient
[r2:c2] .glow

ant

bee

cat

dog

elk

fox

gnu

hen

idk

Expand to see the CSS for the previous table
table.tableau.gradient {
  background: linear-gradient(0.1turn, #3f87a6, #ebf8e1, #f69d3c);
}

table.tableau td.glow {
  box-shadow: 0px 0px 20px 14px rgba(255,46,46,0.9);
}

vlines: Draw lines between columns

context:

table only

explicit:

vlines | vline | vlines( line style )

implicit:

n/a

Draw lines between table columns. See hlines for the horizontal equivalent; the two are frequently used together.

cat   | kitten | 27
dog   | puppy  | 42
deer  | fawn   | 68
===
vlines

cat

kitten

27

dog

puppy

42

deer

fawn

68

width: Set table or column width

context:

table and cells

explicit:

width( number ) | w( number )

implicit:

n/a

The width specifier takes a number, and how it reads that number depends on how you write it. Written with a decimal point -- anything from .0001 up to 1.0 -- it is a ratio (multiply it by 100 in your head if you prefer working with percentages). Written as a plain integer of 1 or more, it is the approximate width of that many characters in the default font.

So width(1) is one character wide and width(1.0) is full width. A ratio above 1.0, or anything that is not a number, is an error.

When applied at the table level, width specifies the width of the table as a whole. When a ratio is given, the width is that ratio of the line length.

When applied to a cell, it represents the width of that cell. Column widths are calculated from the widths of cells in that column using a heuristic which attempts to maintain proportional widths depending on the widths of the cells in each column.

If you want to know how this works, expand this
  • if no widths are given for a particular column, then the width is approximated by the width of the widest cell in the column.

  • if a character width is specified for one or more cells, then the width is set to the largest of these.

  • if one or more cells has a ratio as a width, the column width is set to the largest of these.

At this point we have a list of columns. Some may have ratio widths, others may have character widths. Our goal is to convert them all into ratios, such that the ratios add up to 1.0.

We first remove the columns with ratio widths, adding up those ratios. We subtract the total from 1.0, giving the ratio remaining. Call it rri{rr}i.

We than add up the character widths of the remaining columns. Call this total is cwtot{cw}_{tot}. Then we set the ratio width of these columns to their character widths times rrcwtot\frac{rr}{{cw}_{tot}}.

one  | two  | three
four | five | six
===
bg(#f0f9ff)

one

two

three

four

five

six

one  | two  | three
four | five | six
===
bg(#f0f9ff)
[c1] width(20)
[c2] width(4)

one

two

three

four

five

six

one  | two  | three
four | five | six
===
bg(#f0f9ff)
width(.75)
[c1] width(20)
[c2] width(4)

one

two

three

four

five

six

one  | two  | three
four | five | six
===
bg(#f0f9ff)
width(.4)
[c1] width(.2)
[c2] width(.4)

one

two

three

four

five

six

Styling

HTML Output

Tableau uses CSS for all styling. Generated tables have the class tableau, so scope any changes you make with table.tableau (or, for a single cell's custom class, table.tableau.yourclass) in order to get the correct specificity -- table.tableau beats a bare .yourclass rule, since tableau.css itself uses table.tableau selectors to reset background/border/box-shadow back to neutral values (so that a host page's own table theme, or an earlier bg()/lines()/shading rule, doesn't leak through). See the .gradient example above.

You can override colors using CSS variables. These are the defaults:

:root {
  --tb-hue: 240deg;
  --tb-sat: 29%;

  --tb-border-color: light-dark(hsl(var(--tb-hue) var(--tb-sat) 55%), hsl(var(--tb-hue) var(--tb-sat) 50%));
  --tb-cell-border-color: light-dark(hsl(var(--tb-hue) var(--tb-sat) 55%), hsl(var(--tb-hue) var(--tb-sat) 50%));
  --tb-line-color: light-dark(hsl(var(--tb-hue) var(--tb-sat) 80%), hsl(var(--tb-hue) var(--tb-sat) 50%));
  --tb-header-bg: light-dark(hsl(var(--tb-hue) var(--tb-sat) 60%), hsl(var(--tb-hue) var(--tb-sat) 30%));
  --tb-header-fg: #fff;
  --tb-footer-bg: var(--tb-header-bg);
  --tb-footer-fg: var(--tb-header-fg);
  --tb-caption-bg: light-dark(hsl(var(--tb-hue) var(--tb-sat) 98%), hsl(var(--tb-hue) var(--tb-sat) 23%));
  --tb-caption-line-color: hsl(var(--tb-hue) var(--tb-sat) 50%);

  --tb-shade1-bg: light-dark(oklch(0.83 0.110 55), oklch(0.490 0.20 55));
  --tb-shade2-bg: light-dark(oklch(0.83 0.120 90), oklch(0.532 0.17 90));
  --tb-shade3-bg: light-dark(oklch(0.83 0.120 125), oklch(0.525 0.19 125));
  --tb-shade4-bg: light-dark(oklch(0.83 0.120 165), oklch(0.497 0.17 165));
  --tb-shade5-bg: light-dark(oklch(0.83 0.120 210), oklch(0.504 0.15 210));
  --tb-shade6-bg: light-dark(oklch(0.83 0.086 255), oklch(0.476 0.18 255));
  --tb-shade7-bg: light-dark(oklch(0.83 0.099 300), oklch(0.469 0.21 300));
  --tb-shade8-bg: light-dark(oklch(0.83 0.119 345), oklch(0.476 0.22 345));
  --tb-shade9-bg: light-dark(oklch(0.83 0.095 15), oklch(0.455 0.22 15));
  --tb-shade-bg:  var(--tb-shade1-bg);

  --tb-shade1-fg: light-dark(oklch(0.53 0.131 55), oklch(0.70 0.20 55));
  --tb-shade2-fg: light-dark(oklch(0.53 0.108 90), oklch(0.76 0.17 90));
  --tb-shade3-fg: light-dark(oklch(0.53 0.133 125), oklch(0.75 0.19 125));
  --tb-shade4-fg: light-dark(oklch(0.53 0.111 165), oklch(0.71 0.17 165));
  --tb-shade5-fg: light-dark(oklch(0.53 0.091 210), oklch(0.72 0.15 210));
  --tb-shade6-fg: light-dark(oklch(0.53 0.174 255), oklch(0.68 0.18 255));
  --tb-shade7-fg: light-dark(oklch(0.53 0.210 300), oklch(0.67 0.21 300));
  --tb-shade8-fg: light-dark(oklch(0.53 0.220 345), oklch(0.68 0.22 345));
  --tb-shade9-fg: light-dark(oklch(0.53 0.211 15), oklch(0.67 0.22 15));
  --tb-shade-fg:  var(--tb-shade1-fg);

  --tb-line-width-thin: 0.5px;
  --tb-line-width-default: 1px;
  --tb-line-width-thick: 2px;
  --tb-line-width-xthick: 4px;
}

--tb-hue/--tb-sat set the single hue the header, footer, caption banner, and grid lines are all derived from, so changing just those two re-themes the whole table consistently. --tb-shade1-bg through --tb-shade9-bg (and their -fg counterparts) are the named colors bg(shade1)...bg(shade9) resolve to. The --tb-line-width-* variables are the four line weights.

Each color is a light-dark(light, dark) pair, so it follows the page's color-scheme. By default that's the reader's system setting; a page with its own light/dark toggle only needs to set color-scheme on <html> -- no extra class or data attribute:

html.dark { color-scheme: dark; }

The shades are written in oklch(): the same nine hues in both modes, spread round the color wheel so neighboring shades stay distinct -- pastel in light mode, deeper in dark. A cell shaded with bg(shadeN) gets black text in light mode and white text in dark mode, which reads at 5

or better on every shade; an explicit fg() still wins, and header and footer cells keep their own colors. Backgrounds given as hex or CSS color names don't change between modes, so their text is left alone.

An override with a single value applies in both modes; give it both to keep them apart:

:root {
  --tb-header-bg: light-dark(hsl(200deg 40% 60%), hsl(200deg 40% 25%));
}

The table caption renders below the table by default (caption-side: bottom is already set on table.tableau). To put it back above the table:

table.tableau {
  caption-side: top;
}