MeldUI

Data Table: Basic Usage

Setting up a basic data table with sorting, pagination, and search.

Basic Table

A minimal DataTable with column-header sorting, a <Filters> row in the toolbar, and a <DataPagination> footer. The parent owns sorting / filters / pagination state via v-model and fetches data when the merged state changes.

The recommended path uses useDataTableController to bundle the three refs and encapsulate the page-reset rule.

Core Props

PropTypeDefaultDescription
columnsColumnDef<TData>[]requiredColumn definitions from createColumnHelper
dataTData[]requiredThe current page of data
pageCountnumber-Required when enablePagination is true
totalRowsnumber-Optional. Enables “X of Y” displays
enableSortingbooleanfalseRender column-header sort dropdown. Requires v-model:sorting
enableFilterbooleanfalseRender <Filters> row in toolbar. Requires v-model:filters
enablePaginationbooleanfalseRender <DataPagination> footer. Requires v-model:pagination + pageCount
sortingSortingState-v-model:sorting target
filtersDataTableFilterState-v-model:filters target
paginationPaginationState-v-model:pagination target — { pageIndex, pageSize }
getRowId(row, index) => string-Stable row identifier. Strongly recommended when enable-row-selection is on with server-side pagination; without it, selection state follows the row index across pages
filterSearch{ id, placeholder?, debounceMs? }-Search input config (search travels in filters under filterSearch.id)
pageSizeOptionsnumber[][10,20,30,40,50]Available page size options
loadingbooleanfalseShow loading state
errorstring | Error-Show error message with retry button
density'compact' | 'comfortable' | 'spacious''comfortable'Row density
maxHeightstring'600px'Max height with vertical scroll

Emits

EventPayloadDescription
update:sortingSortingStateFires on sort change (v-model)
update:filtersDataTableFilterStateFires on filter change (v-model)
update:paginationPaginationStateFires on page or page-size change (v-model)
retryUser clicked Retry in the error state
refreshUser clicked the toolbar refresh button
rowActivateRow<TData>Keyboard activation (Enter/Space)

Pagination Position

<DataTable pagination-position="both" />
<DataTable pagination-position="top" />

Exposed Methods

Access via a template ref. Methods cover visual concerns only — sorting / filter / pagination are owned by the parent.

<script setup>
const tableRef = ref()

// Reset visual state (selection, pinning, sizing, expansion, column visibility)
tableRef.value?.resetAll()
</script>

<template>
  <DataTable ref="tableRef" ... />
</template>
MethodDescription
resetSelection()Deselect all rows
resetPinning()Restore default column pinning
resetColumnSizing()Restore default column widths
resetExpanded()Collapse all expanded rows
resetAll()Reset all visual concerns
pinColumn(id, side)Pin a column left or right
unpinColumn(id)Unpin a column

For sorting / filter / pagination reset, call useDataTableController().reset() in the parent.

Manual wiring (without the controller)

The controller is sugar over three refs plus the page-reset rule. Skip it when you want full control — but you must replicate the flush: 'sync' watchers yourself, otherwise every filter or sort change triggers two fetches (once with the new filter and old pageIndex, then again after the reset).

<script setup lang="ts">
import { ref, watch } from 'vue'
import {
  DataTable,
  createColumnHelper,
  type DataTableFilterState,
  type PaginationState,
  type SortingState,
} from '@meldui/vue'

const sorting = ref<SortingState>([])
const filters = ref<DataTableFilterState>({})
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 20 })

// Page-reset rule — flush:'sync' so the parent observes one coherent state per user action.
watch(
  filters,
  () => {
    pagination.value = { ...pagination.value, pageIndex: 0 }
  },
  {
    deep: true,
    flush: 'sync',
  },
)
watch(
  sorting,
  () => {
    pagination.value = { ...pagination.value, pageIndex: 0 }
  },
  {
    deep: true,
    flush: 'sync',
  },
)

// Fetch when any of the three change.
watch([sorting, filters, pagination], () => fetchPage(), { deep: true })
</script>

<template>
  <DataTable
    :columns="columns"
    :data="data"
    :page-count="pageCount"
    enable-sorting
    enable-filter
    enable-pagination
    v-model:sorting="sorting"
    v-model:filters="filters"
    v-model:pagination="pagination"
  />
</template>

See useDataTableController for the recommended path, and Recipes for both styles compared side-by-side.