Skip to content
Style Guide
v0.1.0

Components

Tables

Real HTML tables for real tabular data. Header cells are scoped, numbers align to the inline end, and wide tables scroll inside their own container rather than breaking the page.

The default table

Wrap every table in .tmt-table-wrap. The wrapper draws the border and the rounded corners, and — critically — gives the table somewhere to scroll when it is wider than its column.

Devices in the living room
Device Status Last seen Power
Ceiling light Online 14:20 8.5 W
Air conditioner Online 14:19 1,240 W
Window sensor Low battery 13:02 0.1 W
Smart plug Offline Yesterday
Data table

Structure carries the meaning

A table that looks right and is marked up wrong is unusable with a screen reader, because the reader announces each cell with its headers. Four things do that work:

  • <caption> — names the table. It is the first thing announced, and it is visible, so it doubles as the table's title. Do not replace it with a heading above the table.
  • scope="col" on every column header.
  • scope="row" on the cell that identifies each row — and that cell should be a <th>, not a <td>. It is the row's name.
  • <thead> and <tbody> — required for sticky headers to work and for the header styling to apply.
Tables are for data, not layout. If the content is not a grid of related values, use .tmt-grid or .tmt-stack instead.

Alignment

  • Text aligns to the inline start — left in English, right in Arabic. The system uses text-align: start, so this happens on its own.
  • Numbers align to the inline end so decimal points line up down the column. Add .tmt-numeric to the header and every cell in the column; it also switches on tabular figures so the digits occupy equal width.
  • Cells align to the top. When one cell wraps to three lines, the rest of the row should not float in the middle.
  • Dates and times are text, not numbers. Left-align them.

Variants

ClassEffectWhen
.tmt-table--stripedAlternate rows get a sunken fill.Wide tables where the eye loses the row. Not needed under about five columns.
.tmt-table--hoverHighlights the row under the pointer.Rows that are clickable or have row actions.
.tmt-table--compactHalves the vertical padding.Internal tools where an operator scans many rows.
.tmt-table--stickyPins the header while the body scrolls.Long tables. Needs a fixed height on the wrapper.
Energy readings
TimeRoomkWh
14:00Living room1.24
13:00Living room0.98
12:00Kitchen2.10
11:00Kitchen0.45
10:00Bedroom0.12
09:00Bedroom0.08
Striped, compact, sticky, hover

Row actions

Put actions in the last column, as small ghost buttons. Each one needs an accessible name that identifies its row — "Edit" repeated down forty rows is useless to anyone navigating by a list of controls.

Schedules
NameRunsActions
Morning lights Weekdays, 06:30
Away mode Manual
Row actions

The actions column header still needs a <th> — give it a visually hidden label rather than leaving it empty, so the column is announced.

On small screens

The default is horizontal scrolling inside the wrapper, and that is usually the right answer: it keeps the data as a table, keeps the semantics intact, and keeps the relationship between columns visible.

Make the scroll discoverable and reachable:

<div class="tmt-table-wrap" tabindex="0" role="region"
     aria-label="Devices in the living room">
  <table class="tmt-table">…</table>
</div>
Keyboard-scrollable wrapper

A scrollable region that is not focusable cannot be scrolled by keyboard at all, so add tabindex="0" and a label whenever the table can overflow.

For a table with only two or three columns, stacking each row into a small card can read better than scrolling. Do that with a separate mobile layout, not by changing the table's display — overriding a table's display strips its semantics from assistive technology.

Empty and loading

  • Empty: keep the header row so the columns are still explained, and put one full-width cell underneath saying what would be here and how to add it.
  • Loading: keep the table's height stable so the page does not jump when the rows arrive.
  • A missing value is an em dash (—), not an empty cell, so it is clear the value is absent rather than the layout broken.