@bhplugin/vue3-datatable
A zero-dependency Vue 3 datatable component with client/server-side pagination, sorting, filtering, row selection, and custom cell rendering.
Example
Document & Demos
Install
NPM
npm install @bhplugin/vue3-datatable --save
Yarn
yarn add @bhplugin/vue3-datatable
Usage
<template>
<vue3-datatable :rows="rows" :columns="cols" :sortable="true" :columnFilter="true"> </vue3-datatable>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Vue3Datatable from "@bhplugin/vue3-datatable";
import type { IColumnDefinition } from "@bhplugin/vue3-datatable";
import "@bhplugin/vue3-datatable/dist/style.css";
const cols = ref<IColumnDefinition[]>([
{ field: "id", title: "ID", isUnique: true, type: "number", filter: false },
{ field: "name", title: "Name" },
{ field: "email", title: "Email" },
{ field: "age", title: "Age", type: "number" },
{ field: "dob", title: "Date of Birth", type: "date" },
{ field: "isActive", title: "Active", type: "bool" },
{ field: "address.city", title: "City" },
]);
const rows = ref<Array<Record<string, unknown>>>([
{ id: 1, name: "John Doe", email: "[email protected]", age: 28, dob: "1997-05-15", isActive: true, address: { city: "New York" } },
{ id: 2, name: "Jane Smith", email: "[email protected]", age: 34, dob: "1991-11-22", isActive: false, address: { city: "London" } },
// ...
]);
</script>
Props
| Prop |
Type |
Default |
Description |
| columns (required) |
IColumnDefinition[] |
[] |
Table column definitions |
| rows (required) |
Record[] |
[] |
Table row data |
| isServerMode |
boolean |
false |
Enable server-side mode (disables client-side sort/filter/pagination) |
| totalRows |
number |
0 |
Total row count (required in server mode) |
| skin |
string |
"bh-table-striped bh-table-hover" |
Table skin classes: bh-table-striped, bh-table-hover, bh-table-bordered, bh-table-compact |
| loading |
boolean |
false |
Show loading overlay/skeleton |
| hasCheckbox |
boolean |
false |
Show checkbox column for row selection |
| search |
string |
"" |
Global search string |
| page |
number |
1 |
Current page (1-based) |
| pageSize |
number |
10 |
Rows per page |
| pageSizeOptions |
number[] |
[10, 20, 30, 50, 100] |
Page size dropdown options |
| showPageSize |
boolean |
true |
Show page size dropdown |
| rowClass |
string \| Function |
"" |
Custom row class (string or (row) => string) |
| cellClass |
string \| Function |
"" |
Custom cell class (string or (row) => string) |
| sortable |
boolean |
false |
Enable column sorting |
| sortColumn |
string |
"" |
Initial sort column field |
| sortDirection |
'asc' \| 'desc' |
"asc" |
Initial sort direction |
| columnFilter |
boolean |
false |
Enable per-column filter inputs |
| columnFilterLang |
Record<string, string> |
null |
i18n labels for filter conditions (see below) |
| pagination |
boolean |
true |
Show pagination |
| showNumbers |
boolean |
true |
Show page number buttons |
| showNumbersCount |
number |
5 |
Max visible page number buttons |
| showFirstPage |
boolean |
true |
Show first page button |
| showLastPage |
boolean |
true |
Show last page button |
| paginationInfo |
string |
"Showing {0} to {1} of {2} entries" |
Pagination info template ({0} = start, {1} = end, {2} = total) |
| noDataContent |
string |
"No data available" |
Empty state text |
| skeletonRowCount |
number |
10 |
Number of skeleton rows while loading |
| stickyHeader |
boolean |
false |
Sticky table header |
| height |
string |
"500px" |
Scrollable height (only with stickyHeader) |
| stickyFirstColumn |
boolean |
false |
Sticky first column |
| cloneHeaderInFooter |
boolean |
false |
Clone header as footer row |
| selectRowOnClick |
boolean |
false |
Toggle row selection on row click |
columnFilterLang keys
{
no_filter: 'No filter',
contain: 'Contain', not_contain: 'Not contain',
equal: 'Equal', not_equal: 'Not equal',
start_with: 'Starts with', end_with: 'Ends with',
greater_than: 'Greater than', greater_than_equal: 'Greater than or equal',
less_than: 'Less than', less_than_equal: 'Less than or equal',
is_null: 'Is null', is_not_null: 'Not null',
all: 'All', true: 'True', false: 'False' // boolean column filter labels
}
Column Definition
| Prop |
Type |
Default |
Description |
| isUnique |
boolean |
false |
Mark as unique row identifier (used for selection keys) |
| field |
string |
"" |
Data field path (supports dot notation: address.city) |
| title |
string |
"" |
Column header text |
| value |
string |
"" |
Initial filter value |
| condition |
IFilterCondition |
"" |
Default filter condition (contain for string, equal for number/date, empty for bool) |
| type |
string |
"string" |
Column type: string, number, date, bool |
| width |
string |
"" |
Column width (e.g., "200px") |
| minWidth |
string |
"" |
Minimum column width |
| maxWidth |
string |
"" |
Maximum column width |
| hide |
boolean |
false |
Hide column from rendering |
| filter |
boolean |
true |
Enable column filter |
| search |
boolean |
true |
Include in global search |
| sort |
boolean |
true |
Enable sorting |
| html |
boolean |
false |
Column contains raw HTML |
| cellRenderer |
Function |
- |
Custom cell renderer: (row) => '<strong>html</strong>' (sanitized automatically) |
| headerClass |
string |
"" |
Custom header cell class |
| cellClass |
string |
"" |
Custom body cell class |
Events
| Event |
Payload |
Description |
| changeServer |
IServerChangeResponse |
Server mode only — aggregate event with full state (page, sort, search, filters, change_type) |
| pageChange |
number |
User clicked pagination button (silent on programmatic resets) |
| pageSizeChange |
number |
Page size dropdown changed |
| sortChange |
ISortChangeResponse |
Column sort applied (does NOT reset page) |
| filterChange |
IColumnDefinition[] |
Column filter value or condition changed |
| searchChange |
string |
Global search prop changed |
| rowSelect |
Record[] |
Row selection changed (user interaction only) |
| rowClick |
Record |
Row single click |
| rowDBClick |
Record |
Row double click |
| reset |
- |
reset() called — only event during reset, all others suppressed |
Event rules
- Sort does not reset page
- Filter/search/pagesize resets to page 1 but only emits the specific event (not
pageChange)
pageChange fires only from explicit user pagination clicks
- During reset, only
reset (+ changeServer in server mode) emits — all other events suppressed
Methods
| Method |
Description |
| reset() |
Reset all state (selection, filters, search, sort, page) |
| getFilteredRows() |
Returns all filtered rows |
| getVisibleRows() |
Returns current page visible rows |
| getColumnFilters() |
Returns column definitions with current filter state |
| getSelectedRows() |
Returns all selected rows |
| clearSelectedRows() |
Deselect all rows |
| selectRow(index) |
Select row by index |
| unselectRow(index) |
Deselect row by index |
| isRowSelected(index) |
Check if row is selected |
Slots
Custom cell rendering (named by column field)
<vue3-datatable :rows="rows" :columns="cols">
<template #name="{ value }">
<strong>{{ value.name }}</strong>
</template>
</vue3-datatable>
Pagination arrow slots
<template #firstArrow> « </template>
<template #previousArrow> ‹ </template>
<template #nextArrow> › </template>
<template #lastArrow> » </template>
Empty state slot
<template #noData>
<div>No records found.</div>
</template>
Server Mode Example
<vue3-datatable
:rows="rows"
:columns="cols"
:loading="loading"
:totalRows="totalRows"
:isServerMode="true"
:pageSize="10"
:sortable="true"
:columnFilter="true"
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
@sort-change="onSortChange"
@filter-change="onFilterChange"
@search-change="onSearchChange"
/>
Changelogs
Changelogs
License
@bhplugin/vue3-datatable is open-sourced software licensed under the MIT license.
Our other plugins
Support