The table and what can be built on it: sortable columns, a filter toolbar, row selection and bulk actions. All of it is shared by the public site and internal tools; internal tools change only the colours.
One table, .ds-table, on the public site and in internal tools. It
has row dividers and a row hover. The header row comes from the markup: a table with a
<thead> has one, in condensed uppercase body-colour text on the warm tint, and a
table without one does not.
With a header row
The Type column uses rectangular tags at the default level, documented on Tags.
| Date | Title | Submitter | Type |
|---|---|---|---|
| 2026-03-24 | Asymptotic Expansions of Schrödinger Operators | Esther McGill-Henry | New |
| 2025-07-09 | Symmetry Breaking in Non-Abelian Gauge Theories | Esther McGill-Henry | Rep |
| 2024-07-08 | Spectral Properties of Discrete Schrödinger Operators | Esther McGill-Henry | Wdr |
<div class="ds-table-scroll" role="region" aria-label="Submissions" tabindex="0">
<table class="ds-table">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Title</th>
<th scope="col">Submitter</th>
<th scope="col">Type</th>
</tr>
</thead>
<tbody>
<tr>
<td>2026-03-24</td>
<td>Asymptotic Expansions of Schrödinger Operators</td>
<td>Esther McGill-Henry</td>
<td><span class="ds-tag ds-tag--rectangle">New</span></td>
</tr>
<tr>
<td>2025-07-09</td>
<td>Symmetry Breaking in Non-Abelian Gauge Theories</td>
<td>Esther McGill-Henry</td>
<td><span class="ds-tag ds-tag--rectangle">Rep</span></td>
</tr>
<!-- … one row per record -->
</tbody>
</table>
</div>
<table> element: the frame, the row
dividers and the row hover. Directly inside a card it has no frame of its own; the
card is the frame.<table> when fixed column widths are needed.
The table defaults to auto layout.<div> below the table for row counts or pagination.<th> in the header row, so a screen reader
reads the column name with each cell.role="region", an aria-label that
names the table, and tabindex="0" so a keyboard user can reach it to
scroll.With a header column
| Primary category | math.SP |
|---|---|
| Submitted | 2026-03-24 |
| License | CC BY 4.0 |
<table class="ds-table">
<tbody>
<tr><th scope="row">Primary category</th><td>math.SP</td></tr>
<tr><th scope="row">Submitted</th><td>2026-03-24</td></tr>
<tr><th scope="row">License</th><td>CC BY 4.0</td></tr>
</tbody>
</table>
<th> in the body is a row header: it names its row, in the
cell type at full weight on a tinted ground, and a screen reader reads it with each cell
in the row. The tint comes from the element, so no class is needed.A table has a border all the way round, except directly inside a card, where the card is the frame and the table has none.
Directly inside a card
| Date | Title |
|---|---|
| 2026-03-24 | Asymptotic Expansions of Schrödinger Operators |
| 2025-07-09 | Symmetry Breaking in Non-Abelian Gauge Theories |
Not directly inside a card
| Date | Title |
|---|---|
| 2026-03-24 | Asymptotic Expansions of Schrödinger Operators |
| 2025-07-09 | Symmetry Breaking in Non-Abelian Gauge Theories |
A sortable column has a <button class="sortable"> inside its <th>, holding the label and a <span class="sort-arrow"> for the direction. It looks like the plain header and it can be reached and pressed from the keyboard, which a <th> on its own cannot. .sort-asc or .sort-desc and aria-sort go on the <th> of the sorted column.
Sortable headers
Sort logic is application-specific; the CSS only handles visual states.
| Title | |||
|---|---|---|---|
| 2026-03-24 | Asymptotic Expansions | New | On Hold |
| 2025-07-09 | Symmetry Breaking | Rep | Removed |
| 2024-07-08 | Spectral Properties | New | Removed |
| 2023-04-18 | Geometric Phases | Wdr | Removed |
<table class="ds-table">
<thead>
<tr>
<th scope="col" class="sort-desc" aria-sort="descending">
<button type="button" class="sortable">Date <span class="sort-arrow">↓</span></button>
</th>
<th scope="col">Title</th>
<th scope="col"><button type="button" class="sortable">Type <span class="sort-arrow">⇅</span></button></th>
<th scope="col"><button type="button" class="sortable">Status <span class="sort-arrow">⇅</span></button></th>
</tr>
</thead>
<tbody>
<tr>
<td>2026-03-24</td>
<td>Asymptotic Expansions</td>
<td><span class="ds-tag ds-tag--rectangle">New</span></td>
<td>On Hold</td>
</tr>
<tr>
<td>2025-07-09</td>
<td>Symmetry Breaking</td>
<td><span class="ds-tag ds-tag--rectangle">Rep</span></td>
<td>Removed</td>
</tr>
<!-- … one row per record -->
</tbody>
</table>
<div class="ds-table-footer">1–4 of 4</div>
<button type="button"> inside the <th>. The
button is styled to look like the plain header text, so nothing changes visually; what it
adds is a keyboard stop, so a reader who cannot use a mouse can sort. Pointer cursor, and
the label darkens on hover.<span> inside the header. The glyph in the markup is
replaced by an icon: two arrows in --ds-text-muted when the column is unsorted.<th> of the sorted column. The arrow shows ↑ in
--ds-text.<th> of the sorted column. The arrow shows ↓ in
--ds-text.<th>, matching the class. Remove it from the column that is no
longer sorted. The arrow is a glyph, so this is what tells a screen reader which
column orders the table.The .ds-filter component provides a minimal underline-only <select> for filtering data. Typically placed in a toolbar row above or alongside a table. The custom chevron replaces the native dropdown arrow for visual consistency. To narrow a whole view, use the filter bar.
Filter select, standalone
<div class="ds-filter">
<label for="filter-status">Show:</label>
<select id="filter-status">
<option>All</option>
<option selected>Removed</option>
<option>On Hold</option>
</select>
</div>
<label> and one
<select>. The select gets an underline-only border, the custom
chevron, and a focus ring in --ds-focus-ring. Always include an
“All” option as the inclusive default.for attribute
points at the select’s id.Filter above a table
| Name | Category | Status |
|---|---|---|
| Alice Chen | cs.AI | Active |
| Bob Singh | math.AP | Archived |
<div class="ds-btn-group ds-btn-group--end">
<div class="ds-filter">
<label for="filter-users">Show:</label>
<select id="filter-users">
<option selected>All</option>
<option>Active</option>
<option>Archived</option>
</select>
</div>
</div>
<div class="ds-table-scroll" role="region" aria-label="Users" tabindex="0">
<table class="ds-table">
…
</table>
</div>
A row of fields that narrows everything below it, with one Apply
button. It is a <form method="get">, so the choices go in the page address and a
filtered view can be linked.
| Category | New submissions | Open holds |
|---|---|---|
| cond-mat.str-el | 214 | 6 |
| cond-mat.soft | 132 | 2 |
<form class="ds-filter-bar" method="get" action="tables.html#filter-bar" aria-label="Filters">
<div class="ds-filter-bar-group">
<div class="ds-field">
<label class="ds-label" for="fb-group">Group</label>
<select class="ds-input" id="fb-group" name="group">
<option>Physics</option>
<option>Mathematics</option>
<option>Computer Science</option>
</select>
</div>
<div class="ds-field">
<label class="ds-label" for="fb-archive">Archive</label>
<select class="ds-input" id="fb-archive" name="archive">
<option>All archives</option>
<option>cond-mat</option>
<option>hep-th</option>
</select>
</div>
<div class="ds-field">
<label class="ds-label" for="fb-category">Category</label>
<select class="ds-input" id="fb-category" name="category">
<option>All categories</option>
<option>cond-mat.str-el</option>
<option>cond-mat.soft</option>
</select>
</div>
<div class="ds-field">
<label class="ds-label" for="fb-from">From</label>
<input class="ds-input" id="fb-from" name="from" type="date" value="2026-09-01">
</div>
<div class="ds-field">
<label class="ds-label" for="fb-to">To</label>
<input class="ds-input" id="fb-to" name="to" type="date" value="2026-09-30">
</div>
</div>
<fieldset class="ds-filter-bar-group ds-filter-bar-group--settings">
<legend class="is-sr-only">Settings</legend>
<div class="ds-field">
<label class="ds-label" for="fb-threshold">Threshold days</label>
<input class="ds-input" id="fb-threshold" name="threshold" type="number" min="1" value="7">
</div>
</fieldset>
<div class="ds-filter-bar-actions">
<a href="tables.html#filter-bar">Clear</a>
<button class="ds-btn ds-btn-primary" type="submit">Apply</button>
</div>
</form>
<form method="get">, on the warm tint, with an
aria-label such as "Filters". It narrows everything below it. One per view.
The fields sit in equal columns, as many as fit the width..ds-field, .ds-label and .ds-input, with labels
above.<fieldset> whose <legend> is
.is-sr-only, so it is read out and takes no space. It starts its own row, for
a setting: a number that changes a calculation, such as threshold days. A setting is not
a filter..ds-filter narrows one table from inside its toolbar and applies
as soon as it changes.Compact: one row
<form class="ds-filter-bar" method="get" action="tables.html#filter-bar" aria-label="Subject">
<div class="ds-filter"><label for="cfb-group">Group</label><select id="cfb-group" name="group"><option>cs</option><option>math</option></select></div>
<div class="ds-filter"><label for="cfb-archive">Archive</label><select id="cfb-archive" name="archive"><option>cs</option></select></div>
<div class="ds-filter"><label for="cfb-cat">Category</label><select id="cfb-cat" name="category"><option>All</option></select></div>
<button class="ds-btn" type="submit">Apply</button>
</form>
.ds-filter controls lays them out on one row, with less padding. Use it where a full filter bar would push the content down, such as beside navigation. Apply is a secondary .ds-btn, sized to the row.Compact: a date range
<form class="ds-filter-bar" method="get" action="tables.html#filter-bar" aria-label="Dates">
<div class="ds-filter"><label for="cfb-start">Start</label><input id="cfb-start" name="start" type="date" value="2026-09-16"></div>
<div class="ds-filter"><label for="cfb-end">End (inclusive)</label><input id="cfb-end" name="end" type="date" value="2026-10-06"></div>
<button class="ds-btn" type="submit">Apply</button>
</form>
Rows a reader can choose, one checkbox per row, so an action can apply to several at once.
| Select | Date | Title | Type |
|---|---|---|---|
| 2026-03-24 | Asymptotic Expansions of Schrödinger Operators | New | |
| 2025-07-09 | Symmetry Breaking in Non-Abelian Gauge Theories | Rep | |
| 2024-07-08 | Spectral Properties of Discrete Schrödinger Operators | Wdr |
<div class="ds-table-scroll" role="region" aria-label="Owned papers" tabindex="0">
<table class="ds-table">
<thead>
<tr>
<th scope="col"><span class="is-sr-only">Select</span></th>
<th scope="col">Date</th>
<th scope="col">Title</th>
<th scope="col">Type</th>
</tr>
</thead>
<tbody>
<tr class="is-selected">
<td>
<input type="checkbox" id="sel-1" checked>
<label for="sel-1" class="is-sr-only">Select Asymptotic Expansions of Schrödinger Operators</label>
</td>
<td>2026-03-24</td>
<td>Asymptotic Expansions of Schrödinger Operators</td>
<td><span class="ds-tag ds-tag--rectangle">New</span></td>
</tr>
<tr class="is-selected">
<td>
<input type="checkbox" id="sel-2" checked>
<label for="sel-2" class="is-sr-only">Select Symmetry Breaking in Non-Abelian Gauge Theories</label>
</td>
<td>2025-07-09</td>
<td>Symmetry Breaking in Non-Abelian Gauge Theories</td>
<td><span class="ds-tag ds-tag--rectangle">Rep</span></td>
</tr>
<!-- … one row per record -->
</tbody>
</table>
</div>
<tr>. Tints the row with --ds-accent-wash;
the tint aids scanability of the selection set. Selection state must not rely on the
tint alone: the row’s checkbox shows the state (WCAG 1.4.1).<td>, which
gives it a 24px target and this surface’s accent. Each one has a
<label class="is-sr-only"> that names its row.Actions that apply to every selected row at once, as in moderation queues and ownership lists. The bar sits above a table with row selection.
Selection active
| Select | Date | Title | Type |
|---|---|---|---|
| 2026-03-24 | Asymptotic Expansions of Schrödinger Operators | New | |
| 2025-07-09 | Symmetry Breaking in Non-Abelian Gauge Theories | Rep | |
| 2024-07-08 | Spectral Properties of Discrete Schrödinger Operators | Wdr |
<div class="ds-btn-group">
<button class="ds-btn ds-btn-primary" type="button">Approve</button>
<button class="ds-btn" type="button">Hold</button>
<button class="ds-btn ds-btn-destructive" type="button">Remove…</button>
<span class="ds-bulk-count" role="status">2 selected · <a href="#">Clear selection</a></span>
<div class="ds-filter">
<label for="filter-owned">Show</label>
<select id="filter-owned">…</select>
</div>
</div>
<div class="ds-table-scroll" role="region" aria-label="Owned papers" tabindex="0">
<table class="ds-table">…</table>
</div>
role="status" so a change in the count is announced.Nothing selected
<div class="ds-btn-group">
<button class="ds-btn ds-btn-primary" type="button" disabled>Approve</button>
<button class="ds-btn" type="button" disabled>Hold</button>
<button class="ds-btn ds-btn-destructive" type="button" disabled>Remove…</button>
<span class="ds-bulk-count" role="status">0 selected</span>
<div class="ds-filter">
<label for="filter-owned">Show</label>
<select id="filter-owned">…</select>
</div>
</div>
disabled attribute on every action button while nothing is selected. The buttons stay in place; the
filter stays live, because it does not depend on a selection.scope="col" on every
<th> in the header row, so a screen reader reads the column name with
each cell instead of a bare value.<caption>, or an
aria-label on the scrolling region around the table, as the examples on this
page do. Without one, a screen reader user lands in “table, 4 columns” with no idea
which table it is.aria-sort="ascending" or
aria-sort="descending" on the sorted <th> and remove it from
the others when the sort changes. The arrow is a glyph, and the class is invisible to a
screen reader.<label class="is-sr-only"> that names its row, such as “Select
Asymptotic Expansions”. Without it the choice reads as “checkbox, not checked” and
nothing more. Prefer the label to aria-label, because page translation tools
skip attributes.role="status", so a screen reader user hears “2 selected” when it changes
without leaving the checkbox they just ticked.Every other row takes a tint, which helps the eye follow a long row across a wide table.
| Date | Title | Type |
|---|---|---|
| 2026-03-24 | Asymptotic Expansions of Schrödinger Operators | New |
| 2025-07-09 | Symmetry Breaking in Non-Abelian Gauge Theories | Rep |
| 2024-07-08 | Spectral Properties of Discrete Schrödinger Operators | Wdr |
| 2023-04-18 | Geometric Phases in Adiabatic Transport | New |
<table class="ds-table ds-table--striped">
…
</table>
Numbers align on the right, and their digits line up from row to row.
| Category | New submissions | Open holds |
|---|---|---|
| cs.AI | 3,961 | 48 |
| cs.LG | 2,370 | 112 |
| cs.CL | 1,321 | 7 |
<th scope="col" class="ds-num">New submissions</th>
…
<td class="ds-num">3,961</td>
Pressing a sortable header, by mouse or keyboard, toggles through: descending → ascending → descending. Clicking a different column resets the previous column to unsorted. Sort logic is application-specific; the CSS only handles visual states.
Action buttons come first, then their descriptive text (selection count, “Clear selection”), with any filter at the far end of the row. No background tint behind the action buttons — the bar itself stays neutral. Destructive actions (Remove, Delete) come last among the actions and always require a confirmation step.