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
Tableitself. - 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.
<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>