Hacktron Design System

Tables

Display structured data in a scannable, organized way.

Composing a table

A real table is never just <Table> on its own — it composes as three pieces, top to bottom: FilterBar (search + filters), Table (the data itself), and a footer row (item count + Pagination , only rendered once there's more than one page) — a plain flex row the caller renders below <Table>, not the same thing as a column's own footer property (a <tfoot> totals row inside the table itself, still available for that separate purpose). Search/filter state and pagination are the caller's own — Table stays a dumb data/columns renderer, the same split it keeps from TanStack Query.

When to use each piece

  • Always compose FilterBar → Table → footer for a real, user-facing data table — never hand-build page controls or fold pagination into Table itself.
  • Skip FilterBar only for a small, fixed, unfilterable dataset (e.g. a settings table with a handful of rows nobody needs to search).
  • Skip the footer's Pagination only when the dataset always fits on one page — the footer's item count still belongs there regardless.

Example

Full composition

FilterBar owns search text and which filter fields are active; this page's own computed turns that into filtered, paginated rows.

OwnerRepositoryVisibilityFindings
s

security-team

repo-1Public0
p

platform-team

repo-2Private1
d

docs-team

repo-3Private2
s

security-team

repo-4Public3
p

platform-team

repo-5Private0

14 repositories

<template>
  <FilterBar v-model="activeFilters" v-model:search="search" :fields="filterFields" />
  <Table :data="paginatedRepos" :columns="columns" />
  <div class="flex items-center justify-between">
    <span>{{ filteredRepos.length }} repositories</span>
    <Pagination
      v-if="pageCount > 1"
      v-model:page="page"
      :total="filteredRepos.length"
      :items-per-page="ITEMS_PER_PAGE"
    />
  </div>
</template>