Skip to content

Table

Placeable in Canvas

A responsive data table that stacks into labelled rows on narrow viewports.

Usage
Loading…

Setup required before this component works

Drupal Canvas's built-in text format strips every table tag. Its allowlist is <strong> <em> <u> <a href> <p> <br> <ul> <ol> <li> — no <table>, <tr> or <td>. Paste a table into Body without the setup in the Reference tab below and it renders as run-together text:

Team rosterNameRoleJane DoeEngineer

Canvas locks that format against editing, so this cannot be fixed from the Canvas UI.

Props

Prop Required Type Description Example
body Optional string The table content. <table class="table tablesaw" data-tablesaw-mode="stack"><caption class="visually-hidden">Team roster</caption><thead><tr><th scope="col">Name</th><th scope="col">Role</th></tr></thead><tbody><tr><td>Jane Doe</td><td>Engineer</td></tr><tr><td>John Smith</td><td>Designer</td></tr></tbody></table>
color_scheme Optional enum Dark puts this on a dark background with light text. Defaults to light. light, dark

Give every table a header row: scope on its cells and Tablesaw's stacked view on phones both depend on it. → Full accessibility guidelines

Setup

Recommended — Canvas Full HTML. canvas_full_html swaps Canvas's restricted formats for an unfiltered one, and unlike Canvas's own formats it is an ordinary editable text format.

composer require drupal/canvas_full_html
drush en canvas_full_html

Then, in this order:

  1. Turn it on at /admin/config/content/canvas-full-html — it ships off.
  2. Add a table button. The module's toolbar has no insertTable by default: go to /admin/config/content/formats/manage/canvas_full_html and drag Table into the toolbar. Add Source too if authors need it.
  3. Enable Apply IXM table markup in that same format's filter list. This is what adds scope attributes and the Tablesaw classes (see below). Without it, tables render but are not screen-reader labelled and will not stack on mobile.
  4. drush cr, then create a new Table component — existing instances stay pinned to the component version they were saved with.

Note the format is granted to the administrator role on install. Give use text format canvas_full_html to any other role that authors tables.

Alternative — widen a format yourself. If you would rather not add a contrib dependency, create a text format that permits table markup, enable Apply IXM table markup on it, and point the Table component's body prop at it with hook_canvas_storable_prop_shape_alter(). The allowlist needs at least:

<table class data-tablesaw-mode> <thead> <tbody> <tfoot> <tr>
<th scope rowspan colspan> <td rowspan colspan> <caption class>

Do not edit filter.format.canvas_html_block to achieve this. Canvas owns that config and forbids modification, so the change is a per-site override that will drift, and it would put a table button on every rich-text prop in the site.

Authoring a table

Once set up, drop the Table component onto the page and open Body. The toolbar's table button inserts a grid; click any cell to type in it.

With a table selected, a toolbar appears above it:

Control What it does
Column Insert a column left/right, delete a column, or make the first column a header
Row Insert a row above/below, delete a row, or make the first row a header
Merge cells Merge the selection, or split a merged cell
Toggle caption Add a caption above the table

To delete the whole table, select it by its top-left handle and press Backspace, or delete the Table component itself.

Give the table a header row. A new table has no header row until you add one — use Row → Header row (or Column → Header column). Both mobile stacking and screen-reader labelling depend on it. Without one the table still renders and scrolls horizontally, but it will not stack, and Tablesaw is deliberately not engaged (pointing it at a headerless table makes it log a <thead> is required and abort).

What the markup filter applies

With Apply IXM table markup enabled you never type attributes. On render it adds:

  • scope="col" to header cells in <thead>, and scope="row" to a header cell that leads a body row — this is what lets a screen reader announce which column and row a cell belongs to
  • class="table" always, plus class="tablesaw" data-tablesaw-mode="stack" when the table has a header row, so it stacks into labelled rows on narrow viewports

It is idempotent and leaves alone any scope or data-tablesaw-mode the author set deliberately.

Tokens used by this component

Token Default
--ixm-table-border-color var(--theme-color-border, var(--bs-border-color, #dee2e6))
--ixm-table-border-width 0
--ixm-table-focus-outline-color var(--theme-color-primary, var(--bs-primary, #0d6efd))
--ixm-table-header-accent-color var(--theme-color-border-strong, var(--theme-color-primary, var(--bs-primary, #0d6efd)))
--ixm-table-header-accent-width 1.5px
--ixm-table-radius var(--theme-radius-lg, var(--bs-border-radius-lg, 0.5rem))
--ixm-table-row-border-color var(--theme-color-border, var(--bs-border-color, #dee2e6))
--ixm-table-stripe-color transparent

→ Full token reference