Patterns · Data
Table
A data grid — results, leaderboard, participant/admin list — rendered through the shared DataTable.
Examples
<!-- Paste-and-run: save as .html and open in a browser. No build step, no bundler.
Table is a COMPOSITE (antd, no framework-free element), so its HTML form is a
CDN-React page — React + antd load from a CDN (esm.sh) and mount on open. It
consumes the SAME shared `tableTheme` the React/Vue wrappers use, so it renders
the DS V3 look (white header, grey labels, dividers-only, radius 8, brand hover).
No JSX here (JSX needs a compiler) — we use React.createElement via the `h` alias. -->
<div id="root"></div>
<script type="module">
import React from 'https://esm.sh/react@18';
import { createRoot } from 'https://esm.sh/react-dom@18/client';
import { ConfigProvider, Table } from 'https://esm.sh/antd@6?deps=react@18,react-dom@18';
import { tableTheme } from 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/table-theme.js';
const h = React.createElement;
const columns = [
{ key: 'name', title: 'Name', dataIndex: 'name', sorter: (a, b) => a.name.localeCompare(b.name) },
{ key: 'team', title: 'Team', dataIndex: 'team' },
{ key: 'responses', title: 'Responses', dataIndex: 'responses' },
{ key: 'score', title: 'Score', dataIndex: 'score', sorter: (a, b) => a.score - b.score },
];
const rows = [
{ key: '1', name: 'Alex Rivera', team: 'Growth', responses: 128, score: 94 },
{ key: '2', name: 'Priya Nair', team: 'Product', responses: 112, score: 88 },
{ key: '3', name: 'Sam Okafor', team: 'Growth', responses: 97, score: 81 },
];
// Same shared theme the React/Vue DataTable wrappers pass to ConfigProvider.
createRoot(document.getElementById('root')).render(
h(ConfigProvider, { theme: tableTheme },
h(Table, { rowKey: 'key', columns, dataSource: rows, pagination: { pageSize: 5, showSizeChanger: true } })
)
);
// In a real app use the shared DataTable (DS V3 single-arrow sort, filter, freeze, reorder) —
// never a raw <Table>. This page shows the paste-and-run form of the same themed grid.
</script>
import { DataTable, Button } from '@aha/ui-react'; // wraps antd v6 <Table> + components.Table theme
const columns = [
{ key: 'name', title: 'Name', dataIndex: 'name', sorter: (a, b) => a.name.localeCompare(b.name) },
{ key: 'team', title: 'Team', dataIndex: 'team', filter: { type: 'checklist', options: teams } },
{ key: 'responses', title: 'Responses', dataIndex: 'responses' },
{ key: 'score', title: 'Score', dataIndex: 'score', sorter: (a, b) => a.score - b.score },
];
<DataTable
rowKey="key"
columns={columns}
dataSource={rows}
reorderable
freezable
pagination={{ pageSize: 5, showSizeChanger: true }}
/>
// Never a raw <Table> — DataTable applies the DS V3 look (white header, single-arrow sort, Phosphor icons).
<script setup>
import { DataTable } from '@aha/ui-vue'; // wraps ant-design-vue v4 <a-table> + the SAME components.Table theme
const columns = [
{ key: 'name', title: 'Name', dataIndex: 'name', sorter: (a, b) => a.name.localeCompare(b.name) },
{ key: 'team', title: 'Team', dataIndex: 'team', filter: { type: 'checklist', options: teams } },
{ key: 'responses', title: 'Responses', dataIndex: 'responses' },
{ key: 'score', title: 'Score', dataIndex: 'score', sorter: (a, b) => a.score - b.score },
];
</script>
<template>
<DataTable
row-key="key"
:columns="columns"
:data-source="rows"
reorderable
freezable
:pagination="{ pageSize: 5, showSizeChanger: true }"
/>
</template>
// theme/index.ts — declared ONCE, mirrored in variables.css. Both tiers read it.
export const ahaSlidesTheme = {
token: { colorPrimary: '#6A1EBB', borderRadius: 8, fontFamily: '"Plus Jakarta Sans", sans-serif',
colorText: '#1A1A1A', colorTextSecondary: '#8A8A8A', colorBorderSecondary: '#E3E3E3' },
components: {
Table: {
headerBg: '#ffffff', // white header — NOT antd's default #fafafa
headerColor: '#8A8A8A', // secondary-grey labels
headerSplitColor: 'transparent', // no vertical separators
borderColor: '#E3E3E3', // horizontal dividers only
rowHoverBg: '#F1F1F1', // gray hover — DS V3 bg-fill-secondary-hover (gray-30)
cellPaddingBlock: 16, // literal 16 (never token.padding → undefined → 0)
cellPaddingInline: 16,
headerBorderRadius: 0,
},
},
};
API
| Prop | Type | Default | Notes |
|---|---|---|---|
columns | Column[] | [] | dataIndex / key / title / sorter / filter / fixed / width |
dataSource | object[] | [] | row data |
rowKey | string | fn | — | unique row id |
pagination | object | false | — | brand-outlined page + N/page size changer |
reorderable | boolean | false | drag headers to reorder |
freezable | boolean | false | right-click header to freeze one column |
Install
# .npmrc — once: point the @ahaslides-product scope at GitHub Packages
@ahaslides-product:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} # a GitHub token with read:packages
npm i @ahaslides-product/design
import '@ahaslides-product/design/tokens.css'; // once, at the app root
import '@ahaslides-product/design/table-theme'; // registers <table>
Agent feed for this component (absolute, fetchable anywhere): table.agent.json · table.md · table.llms.txt
When to use
When to use
- DataTable — any rows×columns grid — results, leaderboard, admin list
- never a raw <Table> — a bare antd Table restyled per call site is the drift this replaces
White header (not antd grey #fafafa), single-arrow sort (not dual caret), Phosphor icons, tokenised colour. The look is theme-driven; call sites pass data + columns only.
Surfaces
editor dashboard admin
Spec
Container white surface, 1px #E3E3E3 border, radius 8 — wraps the whole grid · Header white #fff + secondary-grey #8A8A8A labels (row 60px) · Body horizontal dividers #E3E3E3 only, no zebra; row 60px · Radius 8 · Cell padding 16 (literal) · Row hover gray-30 #F1F1F1 · Customisations (DS V3) sorting (single arrow) · filtering (checklist/range) · reorder · resize · rows-per-page · freeze · add/remove columns