@phila/phila-ui-table
v0.1.0-beta.2
Published
Table component
Readme
Table Components
Component Status
| Component | Status |
| --------------- | --------------------------------------------------------------------- |
| BasicTable | |
| ClientTable |
|
| ServerTable |
|
| TablePagination |
|
A set of Vue 3 table components for displaying tabular data with support for sorting, pagination, row selection, and server-side data fetching.
Installation
pnpm add @phila/phila-ui-tableComponents
BasicTable— low-level table with sorting and row selectionClientTable— adds client-side pagination and sorting on top ofBasicTableServerTable— async fetcher-driven table with loading/error statesTablePagination— standalone pagination control
Column Definition
All table components accept a columns prop of type ColumnDef[]:
interface ColumnDef {
headerLabel: string;
key: string;
type?: "text" | "link"; // defaults to "text"
alignment?: "left" | "center" | "right";
sortable?: boolean;
icon?: IconDefinition; // FontAwesome icon shown in the header
}BasicTable
The foundational table component. Handles rendering, sorting, and row selection but does not manage pagination or data fetching itself.
Props
| Prop | Type | Default | Description |
| ------------ | --------------------------- | ------- | ---------------------------------------- |
| columns | ColumnDef[] | [] | Column definitions |
| data | Record<string, unknown>[] | [] | Row data |
| selectable | boolean | false | Adds a checkbox column for row selection |
| striped | boolean | true | Alternates row background colors |
| className | string | — | Additional CSS class on the root element |
Events
| Event | Payload | Description |
| ----------------- | --------------------------------------------- | ------------------------------------------------------------------------------- |
| update:selected | number[] | Emitted when row selection changes; payload is an array of selected row indices |
| sort | { key: string; direction: "asc" \| "desc" } | Emitted when a sortable column header is clicked |
Slots
| Slot | Scope | Description |
| ----------- | ------------------------ | ------------------------------------------------------ |
| cell(key) | { row, col, rowIndex } | Custom cell renderer for the column with the given key |
Usage
<script setup lang="ts">
import { ref } from "vue";
import { BasicTable } from "@phila/phila-ui-table";
import type { ColumnDef } from "@phila/phila-ui-table";
const columns: ColumnDef[] = [
{ headerLabel: "Name", key: "name", sortable: true },
{ headerLabel: "Department", key: "dept" },
{ headerLabel: "Status", key: "status" },
];
const data = [
{ name: "Alice", dept: "Engineering", status: "Active" },
{ name: "Bob", dept: "Design", status: "Inactive" },
];
const selected = ref<number[]>([]);
</script>
<template>
<BasicTable
:columns="columns"
:data="data"
:selectable="true"
@update:selected="selected = $event"
@sort="console.log($event)"
/>
</template>Custom cell rendering
Use the cell(key) slot to override how a specific column's cells are rendered:
<template>
<BasicTable :columns="columns" :data="data">
<template #cell(status)="{ row }">
<span :class="row.status === 'Active' ? 'text-green' : 'text-red'">
{{ row.status }}
</span>
</template>
</BasicTable>
</template>ClientTable
Wraps BasicTable with client-side sorting and pagination. Use this when all data is available upfront.
Props
| Prop | Type | Default | Description |
| ------------- | --------------------------- | ------- | ---------------------------------------- |
| columns | ColumnDef[] | [] | Column definitions |
| data | Record<string, unknown>[] | [] | Full dataset |
| selectable | boolean | false | Adds a checkbox column for row selection |
| striped | boolean | true | Alternates row background colors |
| pageSize | number | 10 | Number of rows per page |
| defaultSort | TableSort | — | Initial sort state { key, direction } |
| className | string | — | Additional CSS class on the root element |
Events
| Event | Payload | Description |
| ----------------- | ---------- | ---------------------------------- |
| update:selected | number[] | Emitted when row selection changes |
Pagination is shown automatically when the data exceeds pageSize.
Usage
<script setup lang="ts">
import { ClientTable } from "@phila/phila-ui-table";
import type { ColumnDef } from "@phila/phila-ui-table";
const columns: ColumnDef[] = [
{ headerLabel: "Event", key: "event", sortable: true },
{ headerLabel: "Date", key: "date", sortable: true },
{ headerLabel: "Location", key: "location" },
];
const data = [
{ event: "Spring Fair", date: "2025-04-12", location: "Rittenhouse Square" },
{ event: "Jazz in the Park", date: "2025-06-07", location: "Clark Park" },
// ...more rows
];
</script>
<template>
<ClientTable :columns="columns" :data="data" :page-size="5" :default-sort="{ key: 'date', direction: 'asc' }" />
</template>ServerTable
Wraps BasicTable with server-side data fetching. Accepts a fetcher function that is called automatically when the page or sort changes.
Props
| Prop | Type | Default | Description |
| ------------- | -------------------------------------------------------------- | ------- | -------------------------------------------------------- |
| columns | ColumnDef[] | [] | Column definitions |
| fetcher | (params: ServerFetchParams) => Promise<ServerFetchResult<T>> | — | Required. Async function that returns a page of data |
| selectable | boolean | false | Adds a checkbox column for row selection |
| striped | boolean | true | Alternates row background colors |
| pageSize | number | 10 | Number of rows per page |
| defaultSort | TableSort | — | Initial sort state { key, direction } |
| className | string | — | Additional CSS class on the root element |
Events
| Event | Payload | Description |
| ----------------- | ---------- | ---------------------------------- |
| update:selected | number[] | Emitted when row selection changes |
While loading, a "Loading…" overlay is shown over the table. If the fetcher throws, an error message is displayed in its place. Pagination is shown automatically when there is more than one page.
Fetcher interface
interface ServerFetchParams {
page: number;
pageSize: number;
sort: { key: string; direction: "asc" | "desc" } | null;
}
interface ServerFetchResult<T> {
data: T[];
total: number; // total number of records (across all pages)
}Usage
<script setup lang="ts">
import { ServerTable } from "@phila/phila-ui-table";
import type { ColumnDef, ServerFetchParams, ServerFetchResult } from "@phila/phila-ui-table";
type Employee = { name: string; dept: string; hired: string };
const columns: ColumnDef[] = [
{ headerLabel: "Name", key: "name", sortable: true },
{ headerLabel: "Department", key: "dept", sortable: true },
{ headerLabel: "Hire Date", key: "hired" },
];
async function fetcher(params: ServerFetchParams): Promise<ServerFetchResult<Employee>> {
const res = await fetch(
`/api/employees?page=${params.page}&pageSize=${params.pageSize}` +
(params.sort ? `&sortKey=${params.sort.key}&sortDir=${params.sort.direction}` : ""),
);
return res.json(); // { data: Employee[], total: number }
}
</script>
<template>
<ServerTable :columns="columns" :fetcher="fetcher" />
</template>TablePagination
A standalone pagination control. Used internally by ClientTable and ServerTable, but can be wired up manually alongside BasicTable when using the composables directly.
Props
| Prop | Type | Default | Description |
| ------------ | -------- | ------- | ---------------------------------------- |
| totalPages | number | — | Required. Total number of pages |
| modelValue | number | 1 | Current page (use with v-model) |
| className | string | — | Additional CSS class on the root element |
Events
| Event | Payload | Description |
| ------------------- | -------- | --------------------------------------------------- |
| update:modelValue | number | Emitted when the user navigates to a different page |
Usage
<template>
<TablePagination v-model="page" :total-pages="totalPages" />
</template>Composables
Use the composables directly when you need more control than the wrapped components provide — for example, to display selected rows outside the table, or to share pagination state with other parts of the page.
useClientTable
import { useClientTable } from "@phila/phila-ui-table";
const {
displayData, // ComputedRef<T[]> — the current page of (sorted) data
sort, // Ref<TableSort | null>
page, // Ref<number>
totalPages, // ComputedRef<number>
totalItems, // ComputedRef<number>
onSort, // (payload: TableSort) => void
onPageChange, // (page: number) => void
} = useClientTable({ data, pageSize: 10, initialSort: { key: "name", direction: "asc" } });Options
| Option | Type | Default | Description |
| ------------- | --------------- | ------- | ------------------------------------------- |
| data | MaybeRef<T[]> | — | Required. Source dataset (can be a ref) |
| pageSize | number | 10 | Rows per page |
| initialSort | TableSort | — | Starting sort state |
useServerTable
import { useServerTable } from "@phila/phila-ui-table";
const {
data, // Ref<T[]> — current page of rows
loading, // Ref<boolean>
error, // Ref<string | null>
sort, // Ref<TableSort | null>
page, // Ref<number>
totalPages, // ComputedRef<number>
totalItems, // Ref<number>
onSort, // (payload: TableSort) => void
onPageChange, // (page: number) => void
refresh, // () => Promise<void> — re-run the current fetch
} = useServerTable({ fetcher, pageSize: 10 });Options
| Option | Type | Default | Description |
| ------------- | -------------------------------------------------------------- | ------- | ------------------------------- |
| fetcher | (params: ServerFetchParams) => Promise<ServerFetchResult<T>> | — | Required. Async data source |
| pageSize | number | 10 | Rows per page |
| initialSort | TableSort | — | Starting sort state |
Manual wiring example
This pattern is useful when you need to access loading state or selected rows outside the table:
<script setup lang="ts">
import { ref, computed } from "vue";
import { BasicTable, TablePagination, useServerTable } from "@phila/phila-ui-table";
import type { ColumnDef, ServerFetchParams, ServerFetchResult } from "@phila/phila-ui-table";
type EventRow = { event: string; location: string; date: string };
const columns: ColumnDef[] = [
{ headerLabel: "Event", key: "event", sortable: true },
{ headerLabel: "Location", key: "location" },
{ headerLabel: "Date", key: "date", sortable: true },
];
async function fetcher(params: ServerFetchParams): Promise<ServerFetchResult<EventRow>> {
const res = await fetch(`/api/events?page=${params.page}&pageSize=${params.pageSize}`);
return res.json();
}
const { data, loading, page, totalPages, onSort, onPageChange } = useServerTable<EventRow>({
fetcher,
pageSize: 10,
});
const selected = ref<number[]>([]);
const selectedEvents = computed(() => selected.value.map(i => data.value[i]?.event ?? ""));
</script>
<template>
<div>
<div v-if="loading">Loading…</div>
<template v-else>
<BasicTable
:columns="columns"
:data="data"
:selectable="true"
@sort="onSort"
@update:selected="selected = $event"
/>
<TablePagination
v-if="totalPages > 1"
:model-value="page"
:total-pages="totalPages"
@update:model-value="onPageChange"
/>
</template>
<p>Selected: {{ selectedEvents.join(", ") || "none" }}</p>
</div>
</template>