Skip to content
TEI & XML Encoding

To encode a table in TEI, wrap it in table, give each line a row, and each box a cell; mark headers with role="label" and span with @cols/@rows. For an illustration, use figure as the logical block, put the image in a graphic @url inside it, add a visible caption in head, and describe the picture in figDesc. Both elements come from the figures module, which you must enable in your schema first.

Step 1: enable the right module

table and figure are not in the bare TEI core — they belong to the figures module. If your customisation excludes it, both elements fail validation with a confusing "element not allowed" message. Either start from tei_all or add the module in your ODD:

xml
<moduleRef key="figures"/>

Confirm this before encoding anything, or you will waste time debugging valid-looking markup.

Step 2: build a basic table

A table is rows of cells. Header cells carry role="label":

xml
<table>
  <row role="label">
    <cell>Year</cell>
    <cell>Baptisms</cell>
    <cell>Burials</cell>
  </row>
  <row>
    <cell>1841</cell>
    <cell>112</cell>
    <cell>87</cell>
  </row>
</table>

Every data row should have the same number of cells as the header — unless you are spanning, which is the next step and the most error-prone one.

How do you span columns and rows without breaking the table?

Use @cols to merge horizontally and @rows to merge vertically, and then omit the cells the span swallows:

xml
<row role="label">
  <cell cols="2">Parish register totals</cell>
</row>
<row role="label">
  <cell>Baptisms</cell>
  <cell>Burials</cell>
</row>

The trap: after a span, the row has fewer literal cell elements than the table's column count, which is correct but easy to miscount. After every span, tally the effective width of each row and confirm they all match. A Schematron rule summing cell/@cols per row catches mismatches automatically.

Step 3: encode a figure

A figure separates the logical block from the image file and the description:

xml
<figure xml:id="fig-map1">
  <graphic url="images/parish-map-1841.jpg"
           width="1600px" height="1100px"/>
  <head>Map of the parish, surveyed 1841.</head>
  <figDesc>Hand-drawn map showing the church, three farms and the river.</figDesc>
</figure>

head is the caption readers see; figDesc is a text description for accessibility, search and anyone reading without the image. Recording image dimensions in @width/@height helps downstream rendering and IIIF integration.

Figure versus graphic: which is which?

ElementRoleHolds
figurelogical blockgraphic, head, figDesc
graphicimage reference@url, @width, @height
headvisible captioncaption text
figDescdescription for accessibilityprose describing the image

Beginners often put the caption text directly in figure as loose text — always wrap it in head so it is queryable and renders correctly.

Should you reproduce a complex source table exactly?

Capture the logical structure faithfully: the rows, columns, header cells and spans that carry meaning. You do not need to reproduce every ruled line, shaded cell or font choice. Presentation you genuinely must preserve goes in @rend (for example rend="bold") or your stylesheet — never by distorting the table model to fake a visual effect. A clean logical table converts to HTML, CSV and print far more reliably than a visually-faithful but structurally-mangled one.

Common pitfalls to avoid

  • Forgetting the figures module — table/figure then look broken but the schema is the problem.
  • Miscounting cells after a column span (the number-one validity failure).
  • Putting captions as bare text instead of in head.
  • Skipping figDesc, which costs you accessibility and discoverability.
  • Encoding presentation (borders, colours) as structure rather than @rend.

Key Takeaways

  • Enable the figures module first; table and figure live there, not in the core.
  • A table is row elements of cell elements; mark headers with role="label".
  • Span with @cols/@rows and omit absorbed cells — then recount each row's width.
  • figure is the logical block; graphic @url holds the image; head is the caption.
  • Always add figDesc for accessibility and search; never leave captions as loose text.
  • Encode logical structure faithfully and push pure presentation into @rend/stylesheets.

Frequently Asked Questions

Which TEI elements do I use for a table?

Use table containing row elements, each holding cell elements. Mark header rows or cells with role="label", and use @rows and @cols on a cell to span. The table module must be enabled in your schema.

How do I handle a cell that spans multiple columns?

Put cols="2" (or more) on the cell and omit the cells it absorbs. Use rows="2" for vertical spans. Spanning is the most common source of malformed TEI tables, so count cells per row carefully after every span.

What is the difference between figure and graphic in TEI?

figure is the logical block — an illustration, diagram or chart with its caption. graphic is the actual image reference (with @url) that usually sits inside figure. A figure can also hold a figDesc describing the image for accessibility and search.

How do I add a caption to a figure?

Use head for the visible caption and figDesc for a prose description of the image content. head is what readers see; figDesc supports accessibility and is not normally rendered.

Should I reproduce a complex source table exactly or simplify it?

Capture the logical structure (rows, columns, spans, headers) faithfully, but you do not need to mirror every visual rule or colour. Record presentation you must keep in @rend or your stylesheet, not by distorting the table model.

Do I need to enable a module for tables and figures?

Yes. table lives in the figures module; enable it in your ODD or use a TEI customisation that already includes it (like tei_all). Without the module, table and figure will fail validation.