summaryrefslogtreecommitdiff
path: root/docs/guides
diff options
context:
space:
mode:
authorMartin Haug <mhaug@live.de>2024-03-15 15:02:56 +0100
committerGitHub <noreply@github.com>2024-03-15 14:02:56 +0000
commit9bd14514520684955570bc87ef79b7b08b35f7c4 (patch)
tree04debccb6d28e83474248433b464fd0431740170 /docs/guides
parent1084bce4e8107f8371f2dde013986674659de8a6 (diff)
Add a guide for the new tables (#3655)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
Diffstat (limited to 'docs/guides')
-rw-r--r--docs/guides/guide-for-latex-users.md2
-rw-r--r--docs/guides/tables.md1343
-rw-r--r--docs/guides/welcome.md1
3 files changed, 1344 insertions, 2 deletions
diff --git a/docs/guides/guide-for-latex-users.md b/docs/guides/guide-for-latex-users.md
index 1e35f6b3..8b2c267f 100644
--- a/docs/guides/guide-for-latex-users.md
+++ b/docs/guides/guide-for-latex-users.md
@@ -10,8 +10,6 @@ out Typst. We will explore the main differences between these two systems from a
user perspective. Although Typst is not built upon LaTeX and has a different
syntax, you will learn how to use your LaTeX skills to get a head start.
-<!-- Mention that Typst is not built upon LaTeX -->
-
Just like LaTeX, Typst is a markup-based typesetting system: You compose your
document in a text file and mark it up with commands and other syntax. Then, you
use a compiler to typeset the source file into a PDF. However, Typst also
diff --git a/docs/guides/tables.md b/docs/guides/tables.md
new file mode 100644
index 00000000..df6f0ea7
--- /dev/null
+++ b/docs/guides/tables.md
@@ -0,0 +1,1343 @@
+---
+description: |
+ Not sure how to change table strokes? Need to rotate a table? This guide
+ explains all you need to know about tables in Typst.
+---
+
+# Table guide
+Tables are a great way to present data to your readers in an easily readable,
+compact, and organized manner. They are not only used for numerical values, but
+also survey responses, task planning, schedules, and more. Because of this wide
+set of possible applications, there is no single best way to lay out a table.
+Instead, think about the data you want to highlight, your document's overarching
+design, and ultimately how your table can best serve your readers.
+
+Typst can help you with your tables by automating styling, importing data from
+other applications, and more! This guide takes you through a few of the most
+common questions you may have when adding a table to your document with Typst.
+Feel free to skip to the section most relevant to you – we designed this guide
+to be read out of order.
+
+If you want to look up a detail of how tables work, you should also [check out
+their reference page]($table). And if you are looking for a table of contents
+rather than a normal table, the reference page of the [`outline`
+function]($outline) is the right place to learn more.
+
+## How to create a basic table? { #basic-tables }
+In order to create a table in Typst, use the [`table` function]($table). For a
+basic table, you need to tell the table function two things:
+
+- The number of columns
+- The content for each of the table cells
+
+So, let's say you want to create a table with two columns describing the
+ingredients for a cookie recipe:
+
+```example
+#table(
+ columns: 2,
+ [*Amount*], [*Ingredient*],
+ [360g], [Baking flour],
+ [250g], [Butter (room temp.)],
+ [150g], [Brown sugar],
+ [100g], [Cane sugar],
+ [100g], [70% cocoa chocolate],
+ [100g], [35-40% cocoa chocolate],
+ [2], [Eggs],
+ [Pinch], [Salt],
+ [Drizzle], [Vanilla extract],
+)
+```
+
+This example shows how to call, configure, and populate a table. Both the column
+count and cell contents are passed to the table as arguments. The [argument
+list]($function) is surrounded by round parentheses. In it, we first pass the
+column count as a named argument. Then, we pass multiple [content
+blocks]($content) as positional arguments. Each content block contains the
+contents for a single cell.
+
+To make the example more legible, we have placed two content block arguments on
+each line, mimicking how they would appear in the table. You could also write
+each cell on its own line. Typst does not care on which line you place the
+arguments. Instead, Typst will place the content cells from left to right (or
+right to left, if that is the writing direction of your language) and then from
+top to bottom. It will automatically add enough rows to your table so that it
+fits all of your content.
+
+It is best to wrap the header row of your table in the [`table.header`
+function]($table.header). This clarifies your intent and will also allow future
+versions of Typst to make the output more accessible to users with a screen
+reader:
+
+```example
+#table(
+ columns: 2,
+ table.header[*Amount*][*Ingredient*],
+ [360g], [Baking flour],
+<<< // ... the remaining cells
+>>> [250g], [Butter (room temp.)],
+>>> [150g], [Brown sugar],
+>>> [100g], [Cane sugar],
+>>> [100g], [70% cocoa chocolate],
+>>> [100g], [35-40% cocoa chocolate],
+>>> [2], [Eggs],
+>>> [Pinch], [Salt],
+>>> [Drizzle], [Vanilla extract],
+)
+```
+
+You could also write a show rule that automatically [strongly
+emphasizes]($strong) the contents of the first cells for all tables. This
+quickly becomes useful if your document contains multiple tables!
+
+```example
+#show table.cell.where(y: 0): strong
+
+#table(
+ columns: 2,
+ table.header[Amount][Ingredient],
+ [360g], [Baking flour],
+<<< // ... the remaining cells
+>>> [250g], [Butter (room temp.)],
+>>> [150g], [Brown sugar],
+>>> [100g], [Cane sugar],
+>>> [100g], [70% cocoa chocolate],
+>>> [100g], [35-40% cocoa chocolate],
+>>> [2], [Eggs],
+>>> [Pinch], [Salt],
+>>> [Drizzle], [Vanilla extract],
+)
+```
+
+We are using a show rule with a selector for cell coordinates here instead of
+applying our styles directly to `table.header`. This is due to a current
+limitation of Typst that will be fixed in a future release.
+
+Congratulations, you have created your first table! Now you can proceed to
+[change column sizes](#column-sizes), [adjust the strokes](#strokes), [add
+striped rows](#striped-rows-and-columns), and more!
+
+## How to change the column sizes? { #column-sizes }
+If you create a table and specify the number of columns, Typst will make each
+column large enough to fit its largest cell. Often, you want something
+different, for example, to make a table span the whole width of the page. You
+can provide a list, specifying how wide you want each column to be, through the
+`columns` argument. There are a few different ways to specify column widths:
+
+- First, there is `{auto}`. This is the default behavior and tells Typst to grow
+ the column to fit its contents. If there is not enough space, Typst will try
+ its best to distribute the space among the `{auto}`-sized columns.
+- [Lengths]($length) like `{6cm}`, `{0.7in}`, or `{120pt}`. As usual, you can
+ also use the font-dependent `em` unit. This is a multiple of your current font
+ size. It's useful if you want to size your table so that it always fits
+ about the same amount of text, independent of font size.
+- A [ratio in percent]($ratio) such as `{40%}`. This will make the column take
+ up 40% of the total horizontal space available to the table, so either the
+ inner width of the page or the table's container. You can also mix ratios and
+ lengths into [relative lengths]($relative). Be mindful that even if you
+ specify a list of column widths that sum up to 100%, your table could still
+ become larger than its container. This is because there can be
+ [gutter]($table.gutter) between columns that is not included in the column
+ widths. If you want to make a table fill the page, the next option is often
+ very useful.
+- A [fractional part of the free space]($fraction) using the `fr` unit, such as
+ `1fr`. This unit allows you to distribute the available space to columns. It
+ works as follows: First, Typst sums up the lengths of all columns that do not
+ use `fr`s. Then, it determines how much horizontal space is left. This
+ horizontal space then gets distributed to all columns denominated in `fr`s.
+ During this process, a `2fr` column will become twice as wide as a `1fr`
+ column. This is where the name comes from: The width of the column is its
+ fraction of the total fractionally sized columns.
+
+Let's put this to use with a table that contains the dates, numbers, and
+descriptions of some routine checks. The first two columns are `auto`-sized and
+the last column is `1fr` wide as to fill the whole page.
+
+```example
+#table(
+ columns: (auto, auto, 1fr),
+ table.header[Date][°No][Description],
+ [24/01/03], [813], [Filtered participant pool],
+ [24/01/03], [477], [Transitioned to sec. regimen],
+ [24/01/11], [051], [Cycled treatment substrate],
+)
+```
+
+Here, we have passed our list of column lengths as an [array], enclosed in round
+parentheses, with its elements separated by commas. The first two columns are
+automatically sized, so that they take on the size of their content and the
+third column is sized as `{1fr}` so that it fills up the remainder of the space
+on the page. If you wanted to instead change the second column to be a bit more
+spacious, you could replace its entry in the `columns` array with a value like
+`{6em}`.
+
+## How to caption and reference my table? { #captions-and-references }
+A table is just as valuable as the information your readers draw from it. You
+can enhance the effectiveness of both your prose and your table by making a
+clear connection between the two with a cross-reference. Typst can help you with
+automatic [references]($ref) and the [`figure` function]($figure).
+
+Just like with images, wrapping a table in the `figure` function allows you to
+add a caption and a label, so you can reference the figure elsewhere. Wrapping
+your table in a figure also lets you use the figure's `placement` parameter to
+float it to the top or bottom of a page.
+
+Let's take a look at a captioned table and how to reference it in prose:
+
+```example
+>>> #set page(width: 14cm)
+#show table.cell.where(y: 0): set text(weight: "bold")
+
+#figure(
+ table(
+ columns: 4,
+ stroke: none,
+
+ table.header[Test Item][Specification][Test Result][Compliance],
+ [Voltage], [220V ± 5%], [218V], [Pass],
+ [Current], [5A ± 0.5A], [4.2A], [Fail],
+ ),
+ caption: [Probe results for design A],
+) <probe-a>
+
+The results from @probe-a show that the design is not yet optimal.
+We will show how its performance can be improved in this section.
+```
+
+The example shows how to wrap a table in a figure, set a caption and a label,
+and how to reference that label. We start by using the `figure` function. It
+expects the contents of the figure as a positional argument. We just put the
+table function call in its argument list, omitting the `#` character because it
+is only needed when calling a function in markup mode. We also add the caption
+as a named argument (above or below) the table.
+
+After the figure call, we put a label in angle brackets (`[<probe-a>]`). This
+tells Typst to remember this element and make it referenceable under this name
+throughout your document. We can then reference it in prose by using the at sign
+and the label name `[@probe-a]`. Typst will print a nicely formatted reference
+and automatically update the label if the table's number changes.
+
+## How to get a striped table? { #fills }
+Many tables use striped rows or columns instead of strokes to differentiate
+between rows and columns. This effect is often called _zebra stripes._ Tables
+with zebra stripes are popular in Business and commercial Data Analytics
+applications, while academic applications tend to use strokes instead.
+
+To add zebra stripes to a table, we use the `table` function's `fill` argument.
+It can take three kinds of arguments:
+
+- A single color (this can also be a gradient or a pattern) to fill all cells
+ with. Because we want some cells to have another color, this is not useful if
+ we want to build zebra tables.
+- An array with colors which Typst cycles through for each column. We can use an
+ array with two elements to get striped columns.
+- A function that takes the horizontal coordinate `x` and the vertical
+ coordinate `y` of a cell and returns its fill. We can use this to create
+ horizontal stripes or [checkerboard patterns]($grid.cell).
+
+Let's start with an example of a horizontally striped table:
+
+```example
+>>> #set page(width: 16cm)
+#set text(font: "IBM Plex Sans")
+
+// Medium bold table header.
+#show table.cell.where(x: 1): set text(weight: "medium")
+
+// Bold titles.
+#show table.cell.where(y: 0): set text(weight: "bold")
+
+// See the strokes section for details on this!
+#let frame(stroke) = (x, y) => (
+ left: if x > 0 { 0pt } else { stroke },
+ right: stroke,
+ top: if y < 2 { stroke } else { 0pt },
+ bottom: stroke,
+)
+
+#set table(
+ fill: (rgb("EAF2F5"), none),
+ stroke: frame(rgb("21222C")),
+)
+
+#table(
+ columns: (0.4fr, 1fr, 1fr, 1fr),
+
+ table.header[Month][Title][Author][Genre],
+ [January], [The Great Gatsby], [F. Scott Fitzgerald], [Classic],
+ [February], [To Kill a Mockingbird], [Harper Lee], [Drama],
+ [March], [1984], [George Orwell], [Dystopian],
+ [April], [The Catcher in the Rye], [J.D. Salinger], [Coming-of-Age],
+)
+```
+
+This example shows a book club reading list. The line `{fill: (rgb("EAF2F5"),
+ none)}` in `table`'s set rule is all that is needed to add striped columns. It
+tells Typst to alternate between coloring columns with a light blue (in the
+[`rgb`]($color.rgb) function call) and nothing (`{none}`). Note that we
+extracted all of our styling from the `table` function call itself into set and
+show rules, so that we can automatically reuse it for multiple tables.
+
+Because setting the stripes itself is easy we also added some other styles to
+make it look nice. The other code in the example provides a dark blue
+[stroke](#stroke-functions) around the table and below the first line and
+emboldens the first row and the column with the book title. See the
+[strokes](#strokes) section for details on how we achieved this stroke
+configuration.
+
+Let's next take a look at how we can change only the set rule to achieve
+horizontal stripes instead:
+
+```example
+>>> #set page(width: 16cm)
+>>> #set text(font: "IBM Plex Sans")
+>>> #show table.cell.where(x: 1): set text(weight: "medium")
+>>> #show table.cell.where(y: 0): set text(weight: "bold")
+>>>
+>>> #let frame(stroke) = (x, y) => (
+>>> left: if x > 0 { 0pt } else { stroke },
+>>> right: stroke,
+>>> top: if y < 2 { stroke } else { 0pt },
+>>> bottom: stroke,
+>>> )
+>>>
+#set table(
+ fill: (_, y) => if calc.odd(y) { rgb("EAF2F5") },
+ stroke: frame(rgb("21222C")),
+)
+>>>
+>>> #table(
+>>> columns: (0.4fr, 1fr, 1fr, 1fr),
+>>>
+>>> table.header[Month][Title][Author][Genre],
+>>> [January], [The Great Gatsby],
+>>> [F. Scott Fitzgerald], [Classic],
+>>> [February], [To Kill a Mockingbird],
+>>> [Harper Lee], [Drama],
+>>> [March], [1984],
+>>> [George Orwell], [Dystopian],
+>>> [April], [The Catcher in the Rye],
+>>> [J.D. Salinger], [Coming-of-Age],
+>>> )
+```
+
+We just need to replace the set rule from the previous example with this one and
+get horizontal stripes instead. Here, we are passing a function to `fill`. It
+discards the horizontal coordinate with an underscore and then checks if the
+vertical coordinate `y` of the cell is odd. If so, the cell gets a light blue
+fill, otherwise, no fill is returned.
+
+Of course, you can make this function arbitrarily complex. For example, if you
+want to stripe the rows with a light and darker shade of blue, you could do
+something like this:
+
+```example
+>>> #set page(width: 16cm)
+>>> #set text(font: "IBM Plex Sans")
+>>> #show table.cell.where(x: 1): set text(weight: "medium")
+>>> #show table.cell.where(y: 0): set text(weight: "bold")
+>>>
+>>> #let frame(stroke) = (x, y) => (
+>>> left: if x > 0 { 0pt } else { stroke },
+>>> right: stroke,
+>>> top: if y < 2 { stroke } else { 0pt },
+>>> bottom: stroke,
+>>> )
+>>>
+#set table(
+ fill: (_, y) => (none, rgb("EAF2F5"), rgb("DDEAEF")).at(calc.rem(y, 3)),
+ stroke: frame(rgb("21222C")),
+)
+>>>
+>>> #table(
+>>> columns: (0.4fr, 1fr, 1fr, 1fr),
+>>>
+>>> table.header[Month][Title][Author][Genre],
+>>> [January], [The Great Gatsby],
+>>> [F. Scott Fitzgerald], [Classic],
+>>> [February], [To Kill a Mockingbird],
+>>> [Harper Lee], [Drama],
+>>> [March], [1984],
+>>> [George Orwell], [Dystopian],
+>>> [April], [The Catcher in the Rye],
+>>> [J.D. Salinger], [Coming-of-Age],
+>>> )
+```
+
+This example shows an alternative approach to write our fill function. The
+function uses an array with three colors and then cycles between its values for
+each row by indexing the array with the remainder of `y` divided by 3.
+
+Finally, here is a bonus example that uses the _stroke_ to achieve striped rows:
+
+```example
+>>> #set page(width: 16cm)
+>>> #set text(font: "IBM Plex Sans")
+>>> #show table.cell.where(x: 1): set text(weight: "medium")
+>>> #show table.cell.where(y: 0): set text(weight: "bold")
+>>>
+>>> #let frame(stroke) = (x, y) => (
+>>> left: if x > 0 { 0pt } else { stroke },
+>>> right: stroke,
+>>> top: if y < 2 { stroke } else { 0pt },
+>>> bottom: stroke,
+>>> )
+>>>
+#set table(
+ stroke: (x, y) => (
+ y: 1pt,
+ left: if x > 0 { 0pt } else if calc.even(y) { 1pt },
+ right: if calc.even(y) { 1pt },
+ ),
+)
+>>>
+>>> #table(
+>>> columns: (0.4fr, 1fr, 1fr, 1fr),
+>>>
+>>> table.header[Month][Title][Author][Genre],
+>>> [January], [The Great Gatsby],
+>>> [F. Scott Fitzgerald], [Classic],
+>>> [February], [To Kill a Mockingbird],
+>>> [Harper Lee], [Drama],
+>>> [March], [1984],
+>>> [George Orwell], [Dystopian],
+>>> [April], [The Catcher in the Rye],
+>>> [J.D. Salinger], [Coming-of-Age],
+>>> )
+```
+
+### Manually overriding a cell's fill color { #fill-override }
+Sometimes, the fill of a cell needs not to vary based on its position in the
+table, but rather based on its contents. We can use the [`table.cell`
+element]($table.cell) in the `table`'s parameter list to wrap a cell's content
+and override its fill.
+
+For example, here is a list of all German presidents, with the cell borders
+colored in the color of their party.
+
+```example
+>>> #set page(width: 10cm)
+#set text(font: "Roboto")
+
+#let cdu(name) = ([CDU], table.cell(fill: black, text(fill: white, name)))
+#let spd(name) = ([SPD], table.cell(fill: red, text(fill: white, name)))
+#let fdp(name) = ([FDP], table.cell(fill: yellow, name))
+
+#table(
+ columns: (auto, auto, 1fr),
+ stroke: (x: none),
+
+ table.header[Tenure][Party][President],
+ [1949-1959], ..fdp[Theodor Heuss],
+ [1959-1969], ..cdu[Heinrich Lübke],
+ [1969-1974], ..spd[Gustav Heinemann],
+ [1974-1979], ..fdp[Walter Scheel],
+ [1979-1984], ..cdu[Karl Carstens],
+ [1984-1994], ..cdu[Richard von Weizsäcker],
+ [1994-1999], ..cdu[Roman Herzog],
+ [1999-2004], ..spd[Johannes Rau],
+ [2004-2010], ..cdu[Horst Köhler],
+ [2010-2012], ..cdu[Christian Wulff],
+ [2012-2017], [n/a], [Joachim Gauck],
+ [2017-], ..spd[Frank-Walter-Steinmeier],
+)
+```
+
+In this example, we make use of variables because there only have been a total
+of three parties whose members have become president (and one unaffiliated
+president). Their colors will repeat multiple times, so we store a function that
+produces an array with their party's name and a table cell with that party's
+color and the president's name (`cdu`, `spd`, and `fdp`). We then use these
+functions in the `table` argument list instead of directly adding the name. We
+use the [spread operator]($arguments/#spreading) `..` to turn the items of the
+arrays into single cells. We could also write something like
+`{[FDP], table.cell(fill: yellow)[Theodor Heuss]}` for each cell directly in the
+`table`'s argument list, but that becomes unreadable, especially for the parties
+whose colors are dark so that they require white text. We also delete vertical
+strokes and set the font to Roboto.
+
+The party column and the cell color in this example communicate redundant
+information on purpose: Communicating important data using color only is a bad
+accessibility practice. It disadvantages users with vision impairment and is in
+violation of universal access standards, such as the
+[WCAG 2.1 Success Criterion 1.4.1](https://www.w3.org/WAI/WCAG21/Understanding/use-of-color.html).
+To improve this table, we added a column printing the party name. Alternatively,
+you could have made sure to choose a color-blindness friendly palette and mark
+up your cells with an additional label that screen readers can read out loud.
+The latter feature is not currently supported by Typst, but will be added in a
+future release. You can check how colors look for color-blind readers with
+[this Chrome extension](https://chromewebstore.google.com/detail/colorblindly/floniaahmccleoclneebhhmnjgdfijgg),
+[Photoshop](https://helpx.adobe.com/photoshop/using/proofing-colors.html), or
+[GIMP](https://docs.gimp.org/2.10/en/gimp-display-filter-dialog.html).
+
+## How to adjust the lines in a table? { #strokes }
+By default, Typst adds strokes between each row and column of a table. You can
+adjust these strokes in a variety of ways. Which one is the most practical,
+depends on the modification you want to make and your intent:
+
+- Do you want to style all tables in your document, irrespective of their size
+ and content? Use the `table` function's [stroke]($table.stroke) argument in a
+ set rule.
+- Do you want to customize all lines in a single table? Use the `table`
+ function's [stroke]($table.stroke) argument when calling the table function.
+- Do you want to change, add, or remove the stroke around a single cell? Use the
+ `table.cell` element in the argument list of your table call.
+- Do you want to change, add, or remove a single horizontal or vertical stroke
+ in a single table? Use the [`table.hline`] and [`table.vline`] elements in the
+ argument list of your table call.
+
+We will go over all of these options with examples next! First, we will tackle
+the `table` function's [stroke]($table.stroke) argument. Here, you can adjust
+both how the table's lines get drawn and configure which lines are drawn at all.
+
+Let's start by modifying the color and thickness of the stroke:
+
+```example
+#table(
+ columns: 4,
+ stroke: 0.5pt + rgb("666675"),
+ [*Monday*], [11.5], [13.0], [4.0],
+ [*Tuesday*], [8.0], [14.5], [5.0],
+ [*Wednesday*], [9.0], [18.5], [13.0],
+)
+```
+
+This makes the table lines a bit less wide and uses a bluish gray. You can see
+that we added a width in point to a color to achieve our customized stroke. This
+addition yields a value of the [stroke type]($stroke). Alternatively, you can
+use the dictionary representation for strokes which allows you to access
+advanced features such as dashed lines.
+
+The previous example showed how to use the stroke argument in the table
+function's invocation. Alternatively, you can specify the stroke argument in the
+`table`'s set rule. This will have exactly the same effect on all subsequent
+`table` calls as if the stroke argument was specified in the argument list. This
+is useful if you are writing a template or want to style your whole document.
+
+```typ
+// Renders the exact same as the last example
+#set table(stroke: 0.5pt + rgb("666675"))
+
+#table(
+ columns: 4,
+ [*Monday*], [11.5], [13.0], [4.0],
+ [*Tuesday*], [8.0], [14.5], [5.0],
+ [*Wednesday*], [9.0], [18.5], [13.0],
+)
+```
+
+For small tables, you sometimes want to suppress all strokes because they add
+too much visual noise. To do this, just set the stroke argument to `none`:
+
+```example
+#table(
+ columns: 4,
+ stroke: none,
+ [*Monday*], [11.5], [13.0], [4.0],
+ [*Tuesday*], [8.0], [14.5], [5.0],
+ [*Wednesday*], [9.0], [18.5], [13.0],
+)
+```
+
+If you want more fine-grained control of where lines get placed in your table,
+you can also pass a dictionary with the keys `top`, `left`, `right`, `bottom`
+(controlling the respective cell sides), `x`, `y` (controlling vertical and
+horizontal strokes), and `rest` (covers all strokes not styled by other
+dictionary entries). All keys are optional; omitted keys will be treated as if
+their value was the default value. For example, to get a table with only
+horizontal lines, you can do this:
+
+```example
+#table(
+ columns: 2,
+ stroke: (x: none),
+ align: horizon,
+ [☒], [Close cabin door],
+ [☐], [Start engines],
+ [☐], [Radio tower],
+ [☐], [Push back],
+)
+```
+
+This turns off all vertical strokes and leaves the horizontal strokes in place.
+To achieve the reverse effect (only horizontal strokes), set the stroke argument
+to `{(y: none)}` instead.
+
+[Further down in the guide](#stroke-functions), we cover how to use a function
+in the stroke argument to customize all strokes individually. This is how you
+achieve more complex stroking patterns.
+
+### Adding individual lines in the table { #individual-lines }
+If you want to add a single horizontal or vertical line in your table, for
+example to separate a group of rows, you can use the [`table.hline`] and
+[`table.vline`] elements for horizontal and vertical lines, respectively. Add
+them to the argument list of the `table` function just like you would add
+individual cells and a header.
+
+Let's take a look at the following example from the reference:
+
+```example
+#set table.hline(stroke: 0.6pt)
+
+#table(
+ stroke: none,
+ columns: (auto, 1fr),
+ // Morning schedule abridged.
+ [14:00], [Talk: Tracked Layout],
+ [15:00], [Talk: Automations],
+ [16:00], [Workshop: Tables],
+ table.hline(),
+ [19:00], [Day 1 Attendee Mixer],
+)
+```
+
+In this example, you can see that we have placed a call to `table.hline` between
+the cells, producing a horizontal line at that spot. We also used a set rule on
+the element to reduce its stroke width to make it fit better with the weight of
+the font.
+
+By default, Typst places horizontal and vertical lines after the current row or
+column, depending on their position in the argument list. You can also manually
+move them to a different position by adding the `y` (for `hline`) or `x` (for
+`vline`) argument. For example, the code below would produce the same result:
+
+```typ
+#set table.hline(stroke: 0.6pt)
+
+#table(
+ stroke: none,
+ columns: (auto, 1fr),
+ // Morning schedule abridged.
+ table.hline(y: 3),
+ [14:00], [Talk: Tracked Layout],
+ [15:00], [Talk: Automations],
+ [16:00], [Workshop: Tables],
+ [19:00], [Day 1 Attendee Mixer],
+)
+```
+
+Let's imagine you are working with a template that shows none of the table
+strokes except for one between the first and second row. Now, since you have one
+table that also has labels in the first column, you want to add an extra
+vertical line to it. However, you do not want this vertical line to cross into
+the top row. You can achieve this with the `start` argument:
+
+```example
+>>> #set page(width: 12cm)
+>>> #show table.cell.where(y: 0): strong
+>>> #set table(stroke: (_, y) => if y == 0 { (bottom: 1pt) })
+// Base template already configured tables, but we need some
+// extra configuration for this table.
+#{
+ set table(align: (x, _) => if x == 0 { left } else { right })
+ show table.cell.where(x: 0): smallcaps
+ table(
+ columns: (auto, 1fr, 1fr, 1fr),
+ table.vline(x: 1, start: 1),
+ table.header[Trainset][Top Speed][Length][Weight],
+ [TGV Réseau], [320 km/h], [200m], [383t],
+ [ICE 403], [330 km/h], [201m], [409t],
+ [Shinkansen N700], [300 km/h], [405m], [700t],
+ )
+}
+```
+
+In this example, we have added `table.vline` at the start of our positional
+argument list. But because the line is not supposed to go to the left of the
+first column, we specified the `x` argument as `{1}`. We also set the `start`
+argument to `{1}` so that the line does only start after the first row.
+
+The example also contains two more things: We use the align argument with a
+function to right-align the data in all but the first column and use a show rule
+to make the first column of table cells appear in small capitals. Because these
+styles are specific to this one table, we put everything into a [code
+block]($scripting/#blocks), so that the styling does not affect any further
+tables.
+
+### Overriding the strokes of a single cell { #stroke-override }
+Imagine you want to change the stroke around a single cell. Maybe your cell is
+very important and needs highlighting! For this scenario, there is the
+[`table.cell` function]($table.cell). Instead of adding your content directly in
+the argument list of the table, you wrap it in a `table.cell` call. Now, you can
+use `table.cell`'s argument list to override the table properties, such as the
+stroke, for this cell only.
+
+Here's an example with a matrix of two of the Big Five personality factors, with
+one intersection highlighted.
+
+```example
+>>> #set page(width: 16cm)
+#table(
+ columns: 3,
+ stroke: (x: none),
+
+ [], [*High Neuroticism*], [*Low Neuroticism*],
+
+ [*High Agreeableness*],
+ table.cell(stroke: orange + 2pt)[
+ _Sensitive_ \ Prone to emotional distress but very empathetic.
+ ],
+ [_Compassionate_ \ Caring and stable, often seen as a supportive figure.],
+
+ [*Low Agreeableness*],
+ [_Contentious_ \ Competitive and easily agitated.],
+ [_Detached_ \ Independent and calm, may appear aloof.],
+)
+```
+
+Above, you can see that we used the `table.cell` element in the table's argument
+list and passed the cell content to it. We have used its `stroke` argument to
+set a wider orange stroke. Despite the fact that we disabled vertical strokes on
+the table, the orange stroke appeared on all sides of the modified cell, showing
+that the table's stroke configuration is overwritten.
+
+### Complex document-wide stroke customization { #stroke-functions }
+This section explains how to customize all lines at once in one or multiple
+tables. This allows you to draw only the first horizontal line or omit the outer
+lines, without knowing how many cells the table has. This is achieved by
+providing a function to the table's `stroke` parameter. The function should
+return a stroke given the zero-indexed x and y position of the current cell. You
+should only need these functions if you are a template author, do not use a
+template, or need to heavily customize your tables. Otherwise, your template
+should set appropriate default table strokes.
+
+For example, this is a set rule that draws all horizontal lines except for the
+very first and last line.
+
+```example
+#show table.cell.where(x: 0): set text(style: "italic")
+#show table.cell.where(y: 0): set text(style: "normal", weight: "bold")
+#set table(stroke: (_, y) => if y > 0 { (top: 0.8pt) })
+
+#table(
+ columns: 3,
+ align: center + horizon,
+ table.header[Technique][Advantage][Drawback],
+ [Diegetic], [Immersive], [May be contrived],
+ [Extradiegetic], [Breaks immersion], [Obstrusive],
+ [Omitted], [Fosters engagement], [May fracture audience],
+)
+```
+
+In the set rule, we pass a function that receives two arguments, assigning the
+vertical coordinate to `y` and discarding the horizontal coordinate. It then
+returns a stroke dictionary with a `{0.8pt}` top stroke for all but the first
+line. The cells in the first line instead implicitly receive `{none}` as the
+return value. You can easily modify this function to just draw the inner
+vertical lines instead as `{(x, _) => if x > 0 { (left: 0.8pt) }}`.
+
+Let's try a few more stroking functions. The next function will only draw a line
+below the first row:
+
+```example
+>>> #show table.cell: it => if it.x == 0 and it.y > 0 {
+>>> set text(style: "italic")
+>>> it
+>>> } else {
+>>> it
+>>> }
+>>>
+>>> #show table.cell.where(y: 0): strong
+#set table(stroke: (_, y) => if y == 0 { (bottom: 1pt) })
+
+<<< // Table as seen above
+>>> #table(
+>>> columns: 3,
+>>> align: center + horizon,
+>>> table.header[Technique][Advantage][Drawback],
+>>> [Diegetic], [Immersive], [May be contrived],
+>>> [Extradiegetic], [Breaks immersion], [Obstrusive],
+>>> [Omitted], [Fosters engagement], [May fracture audience],
+>>> )
+```
+
+If you understood the first example, it becomes obvious what happens here. We
+check if we are in the first row. If so, we return a bottom stroke. Otherwise,
+we'll return `{none}` implicitly.
+
+The next example shows how to draw all but the outer lines:
+
+```example
+>>> #show table.cell: it => if it.x == 0 and it.y > 0 {
+>>> set text(style: "italic")
+>>> it
+>>> } else {
+>>> it
+>>> }
+>>>
+>>> #show table.cell.where(y: 0): strong
+#set table(stroke: (x, y) => (
+ left: if x > 0 { 0.8pt },
+ top: if y > 0 { 0.8pt },
+))
+
+<<< // Table as seen above
+>>> #table(
+>>> columns: 3,
+>>> align: center + horizon,
+>>> table.header[Technique][Advantage][Drawback],
+>>> [Diegetic], [Immersive], [May be contrived],
+>>> [Extradiegetic], [Breaks immersion], [Obstrusive],
+>>> [Omitted], [Fosters engagement], [May fracture audience],
+>>> )
+```
+
+This example uses both the `x` and `y` coordinates. It omits the left stroke in
+the first column and the top stroke in the first row. The right and bottom lines
+are not drawn.
+
+Finally, here is a table that draws all lines except for the vertical lines in
+the first row and horizontal lines in the table body. It looks a bit like a
+calendar.
+
+```example
+>>> #show table.cell: it => if it.x == 0 and it.y > 0 {
+>>> set text(style: "italic")
+>>> it
+>>> } else {
+>>> it
+>>> }
+>>>
+>>> #show table.cell.where(y: 0): strong
+#set table(stroke: (x, y) => (
+ left: if x == 0 or y > 0 { 1pt } else { 0pt },
+ right: 1pt,
+ top: if y <= 1 { 1pt } else { 0pt },
+ bottom: 1pt,
+))
+
+<<< // Table as seen above
+>>> #table(
+>>> columns: 3,
+>>> align: center + horizon,
+>>> table.header[Technique][Advantage][Drawback],
+>>> [Diegetic], [Immersive], [May be contrived],
+>>> [Extradiegetic], [Breaks immersion], [Obstrusive],
+>>> [Omitted], [Fosters engagement], [May fracture audience],
+>>> )
+```
+
+This example is a bit more complex. We start by drawing all the strokes on the
+right of the cells. But this means that we have drawn strokes in the top row,
+too, and we don't need those! We use the fact that `left` will override `right`
+and only draw the left line if we are not in the first row or if we are in the
+first column. In all other cases, we explicitly remove the left line. Finally,
+we draw the horizontal lines by first setting the bottom line and then for the
+first two rows with the `top` key, suppressing all other top lines. The last
+line appears because there is no `top` line that could suppress it.
+
+### How to achieve a double line? { #double-stroke }
+Typst does not yet have a native way to draw double strokes, but there are
+multiple ways to emulate them, for example with [patterns]($pattern). We will
+show a different workaround in this section: Table gutters.
+
+Tables can space their cells apart using the `gutter` argument. When a gutter is
+applied, a stroke is drawn on each of the now separated cells. We can
+selectively add gutter between the rows or columns for which we want to draw a
+double line. The `row-gutter` and `column-gutter` arguments allow us to do this.
+They accept arrays of gutter values. Let's take a look at an example:
+
+```example
+#table(
+ columns: 3,
+ stroke: (x: none),
+ row-gutter: (2.2pt, auto),
+ table.header[Date][Exercise Type][Calories Burned],
+ [2023-03-15], [Swimming], [400],
+ [2023-03-17], [Weightlifting], [250],
+ [2023-03-18], [Yoga], [200],
+)
+```
+
+We can see that we used an array for `row-gutter` that specifies a `{2.2pt}` gap
+between the first and second row. It then continues with `auto` (which is the
+default, in this case `{0pt}` gutter) which will be the gutter between all other
+rows, since it is the last entry in the array.
+
+## How to align the contents of the cells in my table? { #alignment }
+You can use multiple mechanisms to align the content in your table. You can
+either use the `table` function's `align` argument to set the alignment for your
+whole table (or use it in a set rule to set the alignment for tables throughout
+your document) or the [`align`] function (or `table.cell`'s `align` argument) to
+override the alignment of a single cell.
+
+When using the `table` function's align argument, you can choose between three
+methods to specify an [alignment]:
+
+- Just specify a single alignment like `right` (aligns in the top-right corner)
+ or `center + horizon` (centers all cell content). This changes the alignment
+ of all cells.
+- Provide an array. Typst will cycle through this array for each column.
+- Provide a function that is passed the horizontal `x` and vertical `y`
+ coordinate of a cell and returns an alignment.
+
+For example, this travel itinerary right-aligns the day column and left-aligns
+everything else by providing an array in the `align` argument:
+
+```example
+>>> #set page(width: 12cm)
+#set text(font: "IBM Plex Sans")
+#show table.cell.where(y: 0): set text(weight: "bold")
+
+#table(
+ columns: 4,
+ align: (right, left, left, left),
+ fill: (_, y) => if calc.odd(y) { green.lighten(90%) },
+ stroke: none,
+
+ table.header[Day][Location][Hotel or Apartment][Activities],
+ [1], [Paris, France], [Hotel de L'Europe], [Arrival, Evening River Cruise],
+ [2], [Paris, France], [Hotel de L'Europe], [Louvre Museum, Eiffel Tower],
+ [3], [Lyon, France], [Lyon City Hotel], [City Tour, Local Cuisine Tasting],
+ [4], [Geneva, Switzerland], [Lakeview Inn], [Lake Geneva, Red Cross Museum],
+ [5], [Zermatt, Switzerland], [Alpine Lodge], [Visit Matterhorn, Skiing],
+)
+```
+
+However, this example does not yet look perfect — the header cells should be
+bottom-aligned. Let's use a function instead to do so:
+
+```example
+>>> #set page(width: 12cm)
+#set text(font: "IBM Plex Sans")
+#show table.cell.where(y: 0): set text(weight: "bold")
+
+#table(
+ columns: 4,
+ align: (x, y) =>
+ if x == 0 { right } else { left } +
+ if y == 0 { bottom } else { top },
+ fill: (_, y) => if calc.odd(y) { green.lighten(90%) },
+ stroke: none,
+
+ table.header[Day][Location][Hotel or Apartment][Activities],
+ [1], [Paris, France], [Hotel de L'Europe], [Arrival, Evening River Cruise],
+ [2], [Paris, France], [Hotel de L'Europe], [Louvre Museum, Eiffel Tower],
+<<< // ... remaining days omitted
+>>> [3], [Lyon, France], [Lyon City Hotel], [City Tour, Local Cuisine Tasting],
+>>> [4], [Geneva, Switzerland], [Lakeview Inn], [Lake Geneva, Red Cross Museum],
+>>> [5], [Zermatt, Switzerland], [Alpine Lodge], [Visit Matterhorn, Skiing],
+)
+```
+
+In the function, we calculate a horizontal and vertical alignment based on
+whether we are in the first column (`{x == 0}`) or the first row (`{y == 0}`).
+We then make use of the fact that we can add horizontal and vertical alignments
+with `+` to receive a single, two-dimensional alignment.
+
+You can find an example of using `table.cell` to change a single cell's
+alignment on [its reference page]($table.cell).
+
+## How to merge cells? { #merge-cells }
+When a table contains logical groupings or the same data in multiple adjacent
+cells, merging multiple cells into a single, larger cell can be advantageous.
+Another use case for cell groups are table headers with multiple rows: That way,
+you can group for example a sales data table by quarter in the first row and by
+months in the second row.
+
+A merged cell spans multiple rows and/or columns. You can achieve it with the
+[`table.cell`] function's `rowspan` and `colspan` arguments: Just specify how
+many rows or columns you want your cell to span.
+
+The example below contains an attendance calendar for an office with in-person
+and remote days for each team member. To make the table more glanceable, we
+merge adjacent cells with the same value:
+
+```example
+>>> #set page(width: 22cm)
+#let ofi = [Office]
+#let rem = [_Remote_]
+#let lea = [*On leave*]
+
+#show table.cell.where(y: 0): set text(
+ fill: white,
+ weight: "bold",
+)
+
+#table(
+ columns: 6 * (1fr,),
+ align: (x, y) => if x == 0 or y == 0 { left } else { center },
+ stroke: (x, y) => (
+ // Separate black cells with white strokes.
+ left: if y == 0 and x > 0 { white } else { black },
+ rest: black,
+ ),
+ fill: (_, y) => if y == 0 { black },
+
+ table.header(
+ [Team member],
+ [Monday],
+ [Tuesday],
+ [Wednesday],
+ [Thursday],
+ [Friday]
+ ),
+ [Evelyn Archer],
+ table.cell(colspan: 2, ofi),
+ table.cell(colspan: 2, rem),
+ ofi,
+ [Lila Montgomery],
+ table.cell(colspan: 5, lea),
+ [Nolan Pearce],
+ rem,
+ table.cell(colspan: 2, ofi),
+ rem,
+ ofi,
+)
+```
+
+In the example, we first define variables with "Office", "Remote", and "On
+leave" so we don't have to write these labels out every time. We can then use
+these variables in the table body either directly or in a `table.cell` call if
+the team member spends multiple consecutive days in office, remote, or on leave.
+
+The example also contains a black header (created with `table`'s `fill`
+argument) with white strokes (`table`'s `stroke` argument) and white text (set
+by the `table.cell` set rule). Finally, we align all the content of all table
+cells in the body in the center. If you want to know more about the functions
+passed to `align`, `stroke`, and `fill`, you can check out the sections on
+[alignment], [strokes](#stroke-functions), and [striped
+tables](#striped-rows-and-columns).
+
+This table would be a great candidate for fully automated generation from an
+external data source! Check out the [section about importing
+data](#importing-data) to learn more about that.
+
+## How to rotate a table? { #rotate-table }
+When tables have many columns, a portrait paper orientation can quickly get
+cramped. Hence, you'll sometimes want to switch your tables to landscape
+orientation. There are two ways to accomplish this in Typst:
+
+- If you want to rotate only the table but not the other content of the page and
+ the page itself, use the [`rotate` function]($rotate) with the `reflow`
+ argument set to `{true}`.
+- If you want to rotate the whole page the table is on, you can use the [`page`
+ function]($page) with its `flipped` argument set to `{true}`. The header,
+ footer, and page number will now also appear on the long edge of the page.
+ This has the advantage that the table will appear right side up when read on a
+ computer, but it also means that a page in your document has different
+ dimensions than all the others, which can be jarring to your readers.
+
+Below, we will demonstrate both techniques with a student grade book table.
+
+First, we will rotate the table on the page. The example also places some text
+on the right of the table.
+
+```example
+#set page("a5", columns: 2, numbering: "— 1 —")
+>>> #set page(margin: auto)
+#show table.cell.where(y: 0): set text(weight: "bold")
+
+#rotate(
+ -90deg,
+ reflow: true,
+
+ table(
+ columns: (1fr,) + 5 * (auto,),
+ inset: (x: 0.6em,),
+ stroke: (_, y) => (
+ x: 1pt,
+ top: if y <= 1 { 1pt } else { 0pt },
+ bottom: 1pt,
+ ),
+ align: (left, right, right, right, right, left),
+
+ table.header(
+ [Student Name],
+ [Assignment 1], [Assignment 2],
+ [Mid-term], [Final Exam],
+ [Total Grade],
+ ),
+ [Jane Smith], [78%], [82%], [75%], [80%], [B],
+ [Alex Johnson], [90%], [95%], [94%], [96%], [A+],
+ [John Doe], [85%], [90%], [88%], [92%], [A],
+ [Maria Garcia], [88%], [84%], [89%], [85%], [B+],
+ [Zhang Wei], [93%], [89%], [90%], [91%], [A-],
+ [Marina Musterfrau], [96%], [91%], [74%], [69%], [B-],
+ ),
+)
+
+#lorem(80)
+```
+
+
+What we have here is a two-column document on ISO A5 paper with page numbers on
+the bottom. The table has six columns and contains a few customizations to
+[stroke](#strokes), alignment and spacing. But the most important part is that
+the table is wrapped in a call to the `rotate` function with the `reflow`
+argument being `{true}`. This will make the table rotate 90 degrees
+counterclockwise. The reflow argument is needed so that the table's rotation
+affects the layout. If it was omitted, Typst would lay out the page as if the
+table was not rotated (`{true}` might become the default in the future).
+
+The example also shows how to produce many columns of the same size: To the
+initial `{1fr}` column, we add an array with five `{auto}` items that we
+create by multiplying an array with one `{auto}` item by five. Note that arrays
+with just one item need a trailing comma to distinguish them from merely
+parenthesized expressions.
+
+The second example shows how to rotate the whole page, so that the table stays
+upright:
+
+```example
+#set page("a5", numbering: "— 1 —")
+>>> #set page(margin: auto)
+#show table.cell.where(y: 0): set text(weight: "bold")
+
+#page(flipped: true)[
+ #table(
+ columns: (1fr,) + 5 * (auto,),
+ inset: (x: 0.6em,),
+ stroke: (_, y) => (
+ x: 1pt,
+ top: if y <= 1 { 1pt } else { 0pt },
+ bottom: 1pt,
+ ),
+ align: (left, right, right, right, right, left),
+
+ table.header(
+ [Student Name],
+ [Assignment 1], [Assignment 2],
+ [Mid-term], [Final Exam],
+ [Total Grade],
+ ),
+ [Jane Smith], [78%], [82%], [75%], [80%], [B],
+ [Alex Johnson], [90%], [95%], [94%], [96%], [A+],
+ [John Doe], [85%], [90%], [88%], [92%], [A],
+ [Maria Garcia], [88%], [84%], [89%], [85%], [B+],
+ [Zhang Wei], [93%], [89%], [90%], [91%], [A-],
+ [Marina Musterfrau], [96%], [91%], [74%], [69%], [B-],
+ )
+
+ #pad(x: 15%, top: 1.5em)[
+ = Winter 2023/24 results
+ #lorem(80)
+ ]
+]
+```
+
+Here, we take the same table and the other content we want to set with it and
+put it into a call to the [`page`] function while supplying `{true}` to the
+`flipped` argument. This will instruct Typst to create new pages with width and
+height swapped and place the contents of the function call onto a new page.
+Notice how the page number is also on the long edge of the paper now. At the
+bottom of the page, we use the [`pad`] function to constrain the width of the
+paragraph to achieve a nice and legible line length.
+
+## How to break a table across pages? { #table-across-pages }
+It is best to contain a table on a single page. However, some tables just have
+many rows, so breaking them across pages becomes unavoidable. Fortunately, Typst
+supports breaking tables across pages out of the box. If you are using the
+[`table.header`] and [`table.footer`] functions, their contents will be repeated
+on each page as the first and last rows, respectively. If you want to disable
+this behavior, you can set `repeat` to `{false}` on either of them.
+
+If you have placed your table inside of a [figure], it becomes unable to break
+across pages by default. However, you can change this behavior. Let's take a
+look:
+
+```example
+#set page(width: 9cm, height: 6cm)
+#show table.cell.where(y: 0): set text(weight: "bold")
+#show figure: set block(breakable: true)
+
+#figure(
+ caption: [Training regimen for Marathon],
+ table(
+ columns: 3,
+ fill: (_, y) => if y == 0 { gray.lighten(75%) },
+
+ table.header[Week][Distance (km)][Time (hh:mm:ss)],
+ [1], [5], [00:30:00],
+ [2], [7], [00:45:00],
+ [3], [10], [01:00:00],
+ [4], [12], [01:10:00],
+ [5], [15], [01:25:00],
+ [6], [18], [01:40:00],
+ [7], [20], [01:50:00],
+ [8], [22], [02:00:00],
+ [...], [...], [...],
+ table.footer[_Goal_][_42.195_][_02:45:00_],
+ )
+)
+```
+
+A figure automatically produces a [block] which cannot break by default.
+However, we can reconfigure the block of the figure using a show rule to make it
+`breakable`. Now, the figure spans multiple pages with the headers and footers
+repeating.
+
+## How to import data into a table? { #importing-data }
+Often, you need to put data that you obtained elsewhere into a table. Sometimes,
+this is from Microsoft Excel or Google Sheets, sometimes it is from a dataset
+on the web or from your experiment. Fortunately, Typst can load many [common
+file formats]($category/data-loading), so you can use scripting to include their
+data in a table.
+
+The most common file format for tabular data is CSV. You can obtain a CSV file
+from Excel by choosing "Save as" in the _File_ menu and choosing the file format
+"CSV UTF-8 (Comma-delimited) (.csv)". Save the file and, if you are using the
+web app, upload it to your project.
+
+In our case, we will be building a table about Moore's Law. For this purpose, we
+are using a statistic with [how many transistors the average microprocessor
+consists of per year from Our World in
+Data](https://ourworldindata.org/grapher/transistors-per-microprocessor). Let's
+start by pressing the "Download" button to get a CSV file with the raw data.
+
+Be sure to move the file to your project or somewhere Typst can see it, if you
+are using the CLI. Once you did that, we can open the file to see how it is
+structured:
+
+```csv
+Entity,Code,Year,Transistors per microprocessor
+World,OWID_WRL,1971,2308.2417
+World,OWID_WRL,1972,3554.5222
+World,OWID_WRL,1974,6097.5625
+```
+
+The file starts with a header and contains four columns: Entity (which is to
+whom the metric applies), Code, the year, and the number of transistors per
+microprocessor. Only the last two columns change between each row, so we can
+disregard "Entity" and "Code".
+
+First, let's start by loading this file with the [`csv`] function. It accepts
+the file name of the file we want to load as a string argument:
+
+```typ
+#let moore = csv("moore.csv")
+```
+
+We have loaded our file (assuming we named it `moore.csv`) and [bound
+it]($scripting/#bindings) to the new variable `moore`. This will not produce any
+output, so there's nothing to see yet. If we want to examine what Typst loaded,
+we can either hover the name of the variable in the web app or print some items
+from the array:
+
+```example
+#let moore = csv("moore.csv")
+
+#moore.slice(0, 3)
+```
+
+With the arguments `{(0, 3)}`, the [`slice`]($array.slice) method returns the
+first three items in the array (with the indices 0, 1, and 2). We can see that
+each row is its own array with one item per cell.
+
+Now, let's write a loop that will transform this data into an array of cells
+that we can use with the table function.
+
+```example
+#let moore = csv("moore.csv")
+
+#table(
+ columns: 2,
+ ..for (.., year, count) in moore {
+ (year, count)
+ }
+)
+```
+
+The example above uses a for loop that iterates over the rows in our CSV file
+and returns an array for each iteration. We use the for loop's
+[destructuring]($scripting/#bindings) capability to discard all but the last two
+items of each row. We then create a new array with just these two. Because Typst
+will concatenate the array results of all the loop iterations, we get a
+one-dimensional array in which the year column and the number of transistors
+alternate. We can then insert the array as cells. For this we use the [spread
+operator]($arguments/#spreading) (`..`). By prefixing an array, or, in our case
+an expression that yields an array, with two dots, we tell Typst that the
+array's items should be used as positional arguments.
+
+Alternatively, we can also use the [`map`]($array.map), [`slice`]($array.slice),
+and [`flatten`]($array.flatten) array methods to write this in a more functional
+style:
+
+```typ
+#let moore = csv("moore.csv")
+
+#table(
+ columns: moore.first().len(),
+ ..moore.map(m => m.slice(2)).flatten(),
+)
+```
+
+This example renders the same as the previous one, but first uses the `map`
+function to change each row of the data. We pass a function to map that gets run
+on each row of the CSV and returns a new value to replace that row with. We use
+it to discard the first two columns with `slice`. Then, we spread the data into
+the `table` function. However, we need to pass a one-dimensional array and
+`moore`'s value is two-dimensional (that means that each of its row values
+contains an array with the cell data). That's why we call `flatten` which
+converts it to a one-dimensional array. We also extract the number of columns
+from the data itself.
+
+Now that we have nice code for our table, we should try to also make the table
+itself nice! The transistor counts go from millions in 1995 to trillions in 2021
+and changes are difficult to see with so many digits. We could try to present
+our data logarithmically to make it more digestible:
+
+```example
+#let moore = csv("moore.csv")
+#let moore-log = moore.slice(1).map(m => {
+ let (.., year, count) = m
+ let log = calc.log(float(count))
+ let rounded = str(calc.round(log, digits: 2))
+ (year, rounded)
+})
+
+#show table.cell.where(x: 0): strong
+
+#table(
+ columns: moore-log.first().len(),
+ align: right,
+ fill: (_, y) => if calc.odd(y) { rgb("D7D9E0") },
+ stroke: none,
+
+ table.header[Year][Transistor count ($log_10$)],
+ table.hline(stroke: rgb("4D4C5B")),
+ ..moore-log.flatten(),
+)
+```
+
+In this example, we first drop the header row from the data since we are adding
+our own. Then, we discard all but the last two columns as above. We do this by
+[destructuring]($scripting/#bindings) the array `m`, discarding all but the two
+last items. We then convert the string in `count` to a floating point number,
+calculate its logarithm and store it in the variable `log`. Finally, we round it
+to two digits, convert it to a string, and store it in the variable `rounded`.
+Then, we return an array with `year` and `rounded` that replaces the original
+row. In our table, we have added our custom header that tells the reader that
+we've applied a logarithm to the values. Then, we spread the flattened data as
+above.
+
+We also styled the table with [stripes](#striped-rows-and-columns), a
+[horizontal line](#individual-lines) below the first row, [aligned](#alignment)
+everything to the right, and emboldened the first column. Click on the links to
+go to the relevant guide sections and see how it's done!
+
+## What if I need the table function for something that isn't a table? { #table-and-grid }
+Tabular layouts of content can be useful not only for matrices of closely
+related data, like shown in the examples throughout this guide, but also for
+presentational purposes. Typst differentiates between grids that are for layout
+and presentational purposes only and tables, in which the arrangement of the
+cells itself conveys information.
+
+To make this difference clear to other software and allow templates to heavily
+style tables, Typst has two functions for grid and table layout:
+
+- The [`table`] function explained throughout this guide which is intended for
+ tabular data.
+- The [`grid`] function which is intended for presentational purposes and page
+ layout.
+
+Both elements work the same way and have the same arguments. You can apply
+everything you have learned about tables in this guide to grids. There are only
+three differences:
+
+- You'll need to use the [`grid.cell`], [`grid.vline`], and [`grid.hline`]
+ elements instead of [`table.cell`], [`table.vline`], and [`table.hline`].
+- The grid has different defaults: It draws no strokes by default and has no
+ spacing (`inset`) inside of its cells.
+- Elements like `figure` do not react to grids since they are supposed to have
+ no semantical bearing on the document structure.
diff --git a/docs/guides/welcome.md b/docs/guides/welcome.md
index 9714518b..c5c92782 100644
--- a/docs/guides/welcome.md
+++ b/docs/guides/welcome.md
@@ -11,3 +11,4 @@ propose other topics for guides!
## List of Guides
- [Guide for LaTeX users]($guides/guide-for-latex-users)
- [Page setup guide]($guides/page-setup-guide)
+- [Table guide]($guides/table-guide)