Skip to content

Table ​

Data-driven table component. Pass columns and data as JSON, customize cells with scoped slots.

Basic ​

NameEmailRoleStatusJoined
OMOlivia Martin
olivia.martin@email.comAdmin ActiveJan 4, 2024
JLJackson Lee
jackson.lee@email.comEditor ActiveFeb 12, 2024
INIsabella Nguyen
isabella.nguyen@email.comViewer InactiveMar 8, 2024
WKWilliam Kim
will@email.comEditor ActiveApr 22, 2024
SDSofia Davis
sofia.davis@email.comAdmin PendingMay 15, 2024
LJLiam Johnson
liam.j@email.comViewer BannedJun 1, 2024
vue
<script setup>
import { VxTable } from 'veloxui'

// Define table columns — each column needs a unique "key" matching a field in the data
const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
  { key: 'joined', label: 'Joined' },
]

// Row data — each object should have keys matching the column "key" values
const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active', joined: 'Jan 4, 2024' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active', joined: 'Feb 12, 2024' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive', joined: 'Mar 8, 2024' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor', status: 'Active', joined: 'Apr 22, 2024' },
  { name: 'Sofia Davis', email: 'sofia.davis@email.com', role: 'Admin', status: 'Pending', joined: 'May 15, 2024' },
]
</script>

<template>
  <!-- The simplest usage: just pass columns and data -->
  <VxTable :columns="columns" :data="data" />

  <!-- You can customize any cell using a slot named after the column key -->
  <VxTable :columns="columns" :data="data">
    <!-- Slot props: row = full row object, value = cell value, index = row index -->
    <template #name="{ row }">
      <strong>{{ row.name }}</strong>
    </template>
    <template #email="{ value }">
      <span style="font-family: monospace; font-size: 13px;">{{ value }}</span>
    </template>
    <template #status="{ value }">
      <span :style="{ color: value === 'Active' ? '#22c55e' : '#a1a1aa' }">
        {{ value }}
      </span>
    </template>
  </VxTable>
</template>

Render Function ​

Use the render property on a column to return HTML directly — no slots needed. Great for quick formatting without template boilerplate.

NameEmailRoleStatusJoined
OM Olivia Martin
olivia.martin@email.comAdmin Active Jan 4, 2024
JL Jackson Lee
jackson.lee@email.comEditor Active Feb 12, 2024
IN Isabella Nguyen
isabella.nguyen@email.comViewer Inactive Mar 8, 2024
WK William Kim
will@email.comEditor Active Apr 22, 2024
SD Sofia Davis
sofia.davis@email.comAdmin Pending May 15, 2024
LJ Liam Johnson
liam.j@email.comViewer Banned Jun 1, 2024
vue
<script setup>
import { VxTable } from 'veloxui'

// The "render" property returns an HTML string — no slot needed
const columns = [
  {
    key: 'name',
    label: 'Name',
    render: (row) => `<strong>${row.name}</strong>`,
  },
  {
    key: 'status',
    label: 'Status',
    render: (row) => {
      const colors = { Active: '#22c55e', Inactive: '#a1a1aa', Pending: '#f59e0b' }
      const color = colors[row.status] || '#a1a1aa'
      return `<span style="color:${color}">${row.status}</span>`
    },
  },
  { key: 'email', label: 'Email' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', status: 'Active' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', status: 'Active' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', status: 'Inactive' },
  { name: 'William Kim', email: 'will@email.com', status: 'Pending' },
]
</script>

<template>
  <VxTable :columns="columns" :data="data" bordered />
</template>

Bordered ​

Add the bordered prop for outer border, rounded corners, and column dividers.

NameEmailRoleStatusJoined
OMOlivia Martin
olivia.martin@email.comAdmin ActiveJan 4, 2024
JLJackson Lee
jackson.lee@email.comEditor ActiveFeb 12, 2024
INIsabella Nguyen
isabella.nguyen@email.comViewer InactiveMar 8, 2024
WKWilliam Kim
will@email.comEditor ActiveApr 22, 2024
SDSofia Davis
sofia.davis@email.comAdmin PendingMay 15, 2024
LJLiam Johnson
liam.j@email.comViewer BannedJun 1, 2024
vue
<script setup>
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive' },
]
</script>

<template>
  <!-- "bordered" adds outer border, rounded corners, and column dividers -->
  <VxTable :columns="columns" :data="data" bordered />
</template>

Striped ​

Alternate row backgrounds with the striped prop.

NameEmailRoleStatusJoined
OMOlivia Martin
olivia.martin@email.comAdmin ActiveJan 4, 2024
JLJackson Lee
jackson.lee@email.comEditor ActiveFeb 12, 2024
INIsabella Nguyen
isabella.nguyen@email.comViewer InactiveMar 8, 2024
WKWilliam Kim
will@email.comEditor ActiveApr 22, 2024
SDSofia Davis
sofia.davis@email.comAdmin PendingMay 15, 2024
LJLiam Johnson
liam.j@email.comViewer BannedJun 1, 2024
vue
<script setup>
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor', status: 'Active' },
  { name: 'Sofia Davis', email: 'sofia.davis@email.com', role: 'Admin', status: 'Pending' },
]
</script>

<template>
  <!-- "striped" alternates row background colors for readability -->
  <VxTable :columns="columns" :data="data" striped bordered />
</template>

Fixed Header ​

Set max-height to make the body scroll while the header stays fixed automatically.

NameEmailRoleStatusJoined
OMOlivia Martin
olivia.martin@email.comAdmin ActiveJan 4, 2024
JLJackson Lee
jackson.lee@email.comEditor ActiveFeb 12, 2024
INIsabella Nguyen
isabella.nguyen@email.comViewer InactiveMar 8, 2024
WKWilliam Kim
will@email.comEditor ActiveApr 22, 2024
SDSofia Davis
sofia.davis@email.comAdmin PendingMay 15, 2024
LJLiam Johnson
liam.j@email.comViewer BannedJun 1, 2024
EWEmma Wilson
emma.wilson@email.comEditor ActiveJul 10, 2024
NBNoah Brown
noah.brown@email.comViewer InactiveAug 3, 2024
AGAva Garcia
ava.garcia@email.comAdmin ActiveSep 18, 2024
EMEthan Miller
ethan.m@email.comEditor PendingOct 5, 2024
MAMia Anderson
mia.anderson@email.comViewer ActiveNov 22, 2024
LTLucas Taylor
lucas.t@email.comAdmin ActiveDec 14, 2024
CTCharlotte Thomas
charlotte.t@email.comEditor BannedJan 8, 2025
JMJames Moore
james.moore@email.comViewer ActiveFeb 19, 2025
vue
<script setup>
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
]

// When there are many rows, use max-height to make the body scrollable
const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor', status: 'Active' },
  { name: 'Sofia Davis', email: 'sofia.davis@email.com', role: 'Admin', status: 'Pending' },
  { name: 'Emma Wilson', email: 'emma.wilson@email.com', role: 'Editor', status: 'Active' },
  { name: 'Noah Brown', email: 'noah.brown@email.com', role: 'Viewer', status: 'Inactive' },
  { name: 'Ava Garcia', email: 'ava.garcia@email.com', role: 'Admin', status: 'Active' },
  { name: 'Ethan Miller', email: 'ethan.m@email.com', role: 'Editor', status: 'Pending' },
  { name: 'Mia Anderson', email: 'mia.anderson@email.com', role: 'Viewer', status: 'Active' },
  { name: 'Lucas Taylor', email: 'lucas.t@email.com', role: 'Admin', status: 'Active' },
  { name: 'Charlotte Thomas', email: 'charlotte.t@email.com', role: 'Editor', status: 'Inactive' },
]
</script>

<template>
  <!-- max-height keeps the header fixed while the body scrolls -->
  <VxTable :columns="columns" :data="data" :max-height="400" bordered />
</template>

Fixed Columns ​

Set fixed: 'left' or fixed: 'right' on a column to pin it while the rest scrolls horizontally. Requires width on fixed columns.

NameEmailRoleDepartmentLocationStatusJoined
OMOlivia Martin
olivia.martin@email.comAdminEngineeringSan Francisco ActiveJan 4, 2024
JLJackson Lee
jackson.lee@email.comEditorMarketingNew York ActiveFeb 12, 2024
INIsabella Nguyen
isabella.nguyen@email.comViewerDesignLondon InactiveMar 8, 2024
WKWilliam Kim
will@email.comEditorEngineeringTokyo ActiveApr 22, 2024
SDSofia Davis
sofia.davis@email.comAdminProductBerlin PendingMay 15, 2024
EWEmma Wilson
emma.wilson@email.comEditorEngineeringToronto ActiveJun 1, 2024
NBNoah Brown
noah.brown@email.comViewerMarketingSydney InactiveJul 10, 2024
AGAva Garcia
ava.garcia@email.comAdminDesignParis ActiveAug 3, 2024
EMEthan Miller
ethan.m@email.comEditorProductSeoul BannedSep 18, 2024
vue
<script setup>
import { VxTable } from 'veloxui'

// Use "fixed" to pin columns. Requires "width" on fixed columns.
const columns = [
  { key: 'name', label: 'Name', fixed: 'left', width: 180 },
  { key: 'email', label: 'Email', width: 250 },
  { key: 'role', label: 'Role', width: 150 },
  { key: 'department', label: 'Department', width: 180 },
  { key: 'location', label: 'Location', width: 180 },
  { key: 'status', label: 'Status', width: 150 },
  { key: 'joined', label: 'Joined', width: 150 },
  { key: 'actions', label: '', fixed: 'right', width: 80 },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', department: 'Engineering', location: 'San Francisco', status: 'Active', joined: 'Jan 4, 2024' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', department: 'Marketing', location: 'New York', status: 'Active', joined: 'Feb 12, 2024' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', department: 'Design', location: 'London', status: 'Inactive', joined: 'Mar 8, 2024' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor', department: 'Engineering', location: 'Tokyo', status: 'Active', joined: 'Apr 22, 2024' },
]
</script>

<template>
  <!-- "Name" stays pinned on the left, "actions" on the right, rest scrolls -->
  <VxTable :columns="columns" :data="data" bordered :max-height="350">
    <template #actions>
      <button style="background: none; border: none; cursor: pointer;">···</button>
    </template>
  </VxTable>
</template>

Selection ​

Add selectable to show checkboxes. Use v-model:selected to bind selected values. By default it uses the id field — set selection-key to change it.

Selected: none
NameEmailRoleStatusJoined
OMOlivia Martin
olivia.martin@email.comAdmin ActiveJan 4, 2024
JLJackson Lee
jackson.lee@email.comEditor ActiveFeb 12, 2024
INIsabella Nguyen
isabella.nguyen@email.comViewer InactiveMar 8, 2024
WKWilliam Kim
will@email.comEditor ActiveApr 22, 2024
SDSofia Davis
sofia.davis@email.comAdmin PendingMay 15, 2024
LJLiam Johnson
liam.j@email.comViewer BannedJun 1, 2024
vue
<script setup>
import { ref } from 'vue'
import { VxTable } from 'veloxui'

const selected = ref([])

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor' },
]
</script>

<template>
  <!-- Show which rows are selected -->
  <p>Selected: {{ selected.length ? selected.join(', ') : 'none' }}</p>

  <!-- "selectable" adds checkboxes, "selection-key" sets which field is used as the value -->
  <VxTable
    :columns="columns"
    :data="data"
    selectable
    v-model:selected="selected"
    selection-key="name"
    bordered
  />
  <!-- selected = ['Olivia Martin', 'Jackson Lee'] when those rows are checked -->
</template>

Drag & Drop ​

Add draggable to enable row reordering. Use v-model:data to get the updated order.

NameEmailRoleStatus
OMOlivia Martin
olivia.martin@email.comAdmin Active
JLJackson Lee
jackson.lee@email.comEditor Active
INIsabella Nguyen
isabella.nguyen@email.comViewer Inactive
WKWilliam Kim
will@email.comEditor Active
SDSofia Davis
sofia.davis@email.comAdmin Pending
LJLiam Johnson
liam.j@email.comViewer Banned
vue
<script setup>
import { ref } from 'vue'
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
]

// Data must be a ref so the table can update the order after drag
const data = ref([
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor', status: 'Active' },
  { name: 'Sofia Davis', email: 'sofia.davis@email.com', role: 'Admin', status: 'Pending' },
])
</script>

<template>
  <!-- "draggable" enables row reordering by drag & drop -->
  <!-- v-model:data syncs the new order back to your data ref -->
  <VxTable :columns="columns" v-model:data="data" draggable bordered />
</template>

Resizable Columns ​

Add the resizable prop to allow users to drag column borders to resize. You can also set resizable per column in the column definition.

NameEmailRoleStatusJoined
OMOlivia Martin
olivia.martin@email.comAdmin ActiveJan 4, 2024
JLJackson Lee
jackson.lee@email.comEditor ActiveFeb 12, 2024
INIsabella Nguyen
isabella.nguyen@email.comViewer InactiveMar 8, 2024
WKWilliam Kim
will@email.comEditor ActiveApr 22, 2024
SDSofia Davis
sofia.davis@email.comAdmin PendingMay 15, 2024
LJLiam Johnson
liam.j@email.comViewer BannedJun 1, 2024
vue
<script setup>
import { VxTable } from 'veloxui'

// Set "width" on columns to define initial widths for resizing
const columns = [
  { key: 'name', label: 'Name', width: 200 },
  { key: 'email', label: 'Email', width: 260 },
  { key: 'role', label: 'Role', width: 140 },
  { key: 'status', label: 'Status', width: 140 },
  { key: 'joined', label: 'Joined', width: 160 },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active', joined: 'Jan 4, 2024' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active', joined: 'Feb 12, 2024' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive', joined: 'Mar 8, 2024' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor', status: 'Active', joined: 'Apr 22, 2024' },
]
</script>

<template>
  <!-- Object form: set min/max width limits for all columns -->
  <VxTable :columns="columns" :data="data" :resizable="{ minWidth: 80, maxWidth: 500 }" bordered />

  <!-- Boolean form: enable resizing with default limits (minWidth: 50) -->
  <VxTable :columns="columns" :data="data" resizable bordered />
</template>

Sorting ​

Add sortable to enable clickable column headers. Click cycles through ascending, descending, and unsorted. Use v-model:sort for controlled sorting, or let the table manage it internally.

NameEmailRoleStatusJoined
Olivia Martinolivia.martin@email.comAdminActiveJan 4, 2024
Jackson Leejackson.lee@email.comEditorActiveFeb 12, 2024
Isabella Nguyenisabella.nguyen@email.comViewerInactiveMar 8, 2024
William Kimwill@email.comEditorActiveApr 22, 2024
Sofia Davissofia.davis@email.comAdminPendingMay 15, 2024
Liam Johnsonliam.j@email.comViewerBannedJun 1, 2024
vue
<script setup>
import { ref } from 'vue'
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor', status: 'Active' },
]

// Optional: control sort state externally
const sort = ref({ key: 'name', order: 'asc' })
</script>

<template>
  <!-- "sortable" makes all column headers clickable to sort -->
  <VxTable :columns="columns" :data="data" sortable bordered />

  <!-- Controlled: bind sort state with v-model:sort -->
  <VxTable :columns="columns" :data="data" sortable v-model:sort="sort" bordered />

  <!-- Per-column: only make specific columns sortable -->
  <VxTable
    :columns="[
      { key: 'name', label: 'Name', sortable: true },
      { key: 'email', label: 'Email' },
    ]"
    :data="data"
    bordered
  />
</template>

Loading ​

Add the loading prop to show a spinner overlay.

NameEmailRoleStatusJoined
Olivia Martinolivia.martin@email.comAdminActiveJan 4, 2024
Jackson Leejackson.lee@email.comEditorActiveFeb 12, 2024
Isabella Nguyenisabella.nguyen@email.comViewerInactiveMar 8, 2024
William Kimwill@email.comEditorActiveApr 22, 2024
Sofia Davissofia.davis@email.comAdminPendingMay 15, 2024
Liam Johnsonliam.j@email.comViewerBannedJun 1, 2024
vue
<script setup>
import { ref } from 'vue'
import { VxTable, VxButton } from 'veloxui'

const loading = ref(false)

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor' },
]

// Simulate a loading state
function toggleLoading() {
  loading.value = true
  setTimeout(() => { loading.value = false }, 2000)
}
</script>

<template>
  <VxButton size="sm" variant="outline" @click="toggleLoading">Toggle loading</VxButton>

  <!-- "loading" shows a spinner overlay on top of the table -->
  <VxTable :columns="columns" :data="data" :loading="loading" bordered />
</template>

Expandable Rows ​

Add expandable to show an expand toggle on each row. Provide the expand slot for the detail content. Uses selection-key (default id) to track expanded rows.

NameEmailRoleStatusJoined
Olivia Martinolivia.martin@email.comAdminActiveJan 4, 2024
Jackson Leejackson.lee@email.comEditorActiveFeb 12, 2024
Isabella Nguyenisabella.nguyen@email.comViewerInactiveMar 8, 2024
William Kimwill@email.comEditorActiveApr 22, 2024
Sofia Davissofia.davis@email.comAdminPendingMay 15, 2024
Liam Johnsonliam.j@email.comViewerBannedJun 1, 2024
vue
<script setup>
import { ref } from 'vue'
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', joined: 'Jan 4, 2024' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', joined: 'Feb 12, 2024' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', joined: 'Mar 8, 2024' },
]

// Optional: track which rows are expanded externally
const expanded = ref([])
</script>

<template>
  <!-- "expandable" adds an expand toggle to each row -->
  <!-- "selection-key" determines which field uniquely identifies each row -->
  <VxTable :columns="columns" :data="data" expandable selection-key="name" bordered>
    <!-- The "expand" slot renders content below the row when expanded -->
    <template #expand="{ row }">
      <div>
        <strong>{{ row.name }}</strong> joined on {{ row.joined }} as {{ row.role }}.
        <br />Contact: {{ row.email }}
      </div>
    </template>
  </VxTable>

  <!-- Controlled: track expanded rows with v-model:expanded -->
  <VxTable
    :columns="columns"
    :data="data"
    expandable
    v-model:expanded="expanded"
    selection-key="name"
    bordered
  >
    <template #expand="{ row }">
      <div>Details for {{ row.name }}</div>
    </template>
  </VxTable>
</template>

Pagination ​

Add pagination to show page controls. Pass true for built-in state, or an object for controlled pagination.

NameEmailRoleStatusJoined
Olivia Martinolivia.martin@email.comAdminActiveJan 4, 2024
Jackson Leejackson.lee@email.comEditorActiveFeb 12, 2024
Isabella Nguyenisabella.nguyen@email.comViewerInactiveMar 8, 2024
William Kimwill@email.comEditorActiveApr 22, 2024
Sofia Davissofia.davis@email.comAdminPendingMay 15, 2024
Liam Johnsonliam.j@email.comViewerBannedJun 1, 2024
Emma Wilsonemma.wilson@email.comEditorActiveJul 10, 2024
Noah Brownnoah.brown@email.comViewerInactiveAug 3, 2024
Ava Garciaava.garcia@email.comAdminActiveSep 18, 2024
Ethan Millerethan.m@email.comEditorPendingOct 5, 2024
10 / page
1–10 of 14
1 / 2
vue
<script setup>
import { ref } from 'vue'
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor', status: 'Active' },
  { name: 'Sofia Davis', email: 'sofia.davis@email.com', role: 'Admin', status: 'Pending' },
  { name: 'Emma Wilson', email: 'emma.wilson@email.com', role: 'Editor', status: 'Active' },
  { name: 'Noah Brown', email: 'noah.brown@email.com', role: 'Viewer', status: 'Inactive' },
  { name: 'Ava Garcia', email: 'ava.garcia@email.com', role: 'Admin', status: 'Active' },
]

// Optional: control pagination state (page, pageSize, available sizes)
const page = ref({ page: 1, pageSize: 5, pageSizes: [5, 10, 25] })
</script>

<template>
  <!-- "pagination" as boolean: uses built-in state, defaults to 10 per page -->
  <VxTable :columns="columns" :data="data" pagination bordered />

  <!-- Controlled: bind pagination state with v-model:pagination -->
  <VxTable :columns="columns" :data="data" v-model:pagination="page" bordered />
</template>

Global Filter ​

Pass a filter string to search across all visible columns.

NameEmailRoleStatusJoined
Olivia Martinolivia.martin@email.comAdminActiveJan 4, 2024
Jackson Leejackson.lee@email.comEditorActiveFeb 12, 2024
Isabella Nguyenisabella.nguyen@email.comViewerInactiveMar 8, 2024
William Kimwill@email.comEditorActiveApr 22, 2024
Sofia Davissofia.davis@email.comAdminPendingMay 15, 2024
Liam Johnsonliam.j@email.comViewerBannedJun 1, 2024
vue
<script setup>
import { ref } from 'vue'
import { VxTable, VxInput } from 'veloxui'

const filterText = ref('')

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor', status: 'Active' },
]
</script>

<template>
  <!-- Use VxInput to capture the search text -->
  <VxInput v-model="filterText" placeholder="Search..." size="sm" style="max-width: 260px;" />

  <!-- "filter" searches across all visible columns and hides non-matching rows -->
  <VxTable :columns="columns" :data="data" :filter="filterText" bordered />
</template>

Row Grouping ​

Set group-by to a column key to group rows with collapsible headers.

NameEmailRoleStatusJoined
Admin(2)
Olivia Martinolivia.martin@email.comAdminActiveJan 4, 2024
Sofia Davissofia.davis@email.comAdminPendingMay 15, 2024
Editor(2)
Jackson Leejackson.lee@email.comEditorActiveFeb 12, 2024
William Kimwill@email.comEditorActiveApr 22, 2024
Viewer(2)
Isabella Nguyenisabella.nguyen@email.comViewerInactiveMar 8, 2024
Liam Johnsonliam.j@email.comViewerBannedJun 1, 2024
vue
<script setup>
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive' },
  { name: 'William Kim', email: 'will@email.com', role: 'Editor', status: 'Active' },
  { name: 'Sofia Davis', email: 'sofia.davis@email.com', role: 'Admin', status: 'Pending' },
]
</script>

<template>
  <!-- "group-by" groups rows by the value of the specified column key -->
  <!-- Rows with the same "role" will be grouped under collapsible headers -->
  <VxTable :columns="columns" :data="data" group-by="role" bordered />
</template>

Column Visibility ​

Add the column-toggle prop to show a built-in column visibility dropdown. The dropdown is auto-populated from the columns definition using a multi-select.

Name +4
NameEmailRoleStatusJoined
Olivia Martinolivia.martin@email.comAdminActiveJan 4, 2024
Jackson Leejackson.lee@email.comEditorActiveFeb 12, 2024
Isabella Nguyenisabella.nguyen@email.comViewerInactiveMar 8, 2024
William Kimwill@email.comEditorActiveApr 22, 2024
Sofia Davissofia.davis@email.comAdminPendingMay 15, 2024
Liam Johnsonliam.j@email.comViewerBannedJun 1, 2024
vue
<script setup>
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
  { key: 'joined', label: 'Joined' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active', joined: 'Jan 4, 2024' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active', joined: 'Feb 12, 2024' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive', joined: 'Mar 8, 2024' },
]
</script>

<template>
  <!-- "column-toggle" adds a dropdown to show/hide columns -->
  <VxTable :columns="columns" :data="data" column-toggle bordered />
</template>

Column Reorder ​

Pass v-model:column-order to enable drag-and-drop column reordering.

Drag column headers to reorder
NameEmailRoleStatusJoined
Olivia Martinolivia.martin@email.comAdminActiveJan 4, 2024
Jackson Leejackson.lee@email.comEditorActiveFeb 12, 2024
Isabella Nguyenisabella.nguyen@email.comViewerInactiveMar 8, 2024
William Kimwill@email.comEditorActiveApr 22, 2024
Sofia Davissofia.davis@email.comAdminPendingMay 15, 2024
Liam Johnsonliam.j@email.comViewerBannedJun 1, 2024
vue
<script setup>
import { ref } from 'vue'
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'status', label: 'Status' },
  { key: 'joined', label: 'Joined' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active', joined: 'Jan 4, 2024' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active', joined: 'Feb 12, 2024' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive', joined: 'Mar 8, 2024' },
]

// Column order as an array of column keys — drag headers to reorder
const order = ref(['name', 'email', 'role', 'status', 'joined'])
</script>

<template>
  <!-- v-model:column-order enables drag-and-drop column reordering -->
  <VxTable :columns="columns" :data="data" v-model:column-order="order" bordered />
</template>

Inline Editing ​

Set editable: true on a column to enable double-click-to-edit. Listen to @cell-edit to update the data.

Double-click a Name or Role cell to edit
NameEmailRoleStatus
Olivia Martinolivia.martin@email.comAdminActive
Jackson Leejackson.lee@email.comEditorActive
Isabella Nguyenisabella.nguyen@email.comViewerInactive
William Kimwill@email.comEditorActive
vue
<script setup>
import { ref } from 'vue'
import { VxTable } from 'veloxui'

// Set "editable: true" on columns you want to be editable
const columns = [
  { key: 'name', label: 'Name', editable: true },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role', editable: true },
  { key: 'status', label: 'Status' },
]

// Each row needs a unique "id" field (used internally to identify the row)
const data = ref([
  { id: 1, name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin', status: 'Active' },
  { id: 2, name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor', status: 'Active' },
  { id: 3, name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer', status: 'Inactive' },
  { id: 4, name: 'William Kim', email: 'will@email.com', role: 'Editor', status: 'Active' },
])

// Handle cell edits — update the data when a cell value changes
function onCellEdit(key, row, value) {
  const item = data.value.find(r => r.id === row.id)
  if (item) item[key] = value
}
</script>

<template>
  <!-- Double-click an editable cell to edit it -->
  <!-- Press Enter to save, Escape to cancel -->
  <VxTable :columns="columns" :data="data" @cell-edit="onCellEdit" bordered />
</template>

Remote Data ​

Pass a fetch-data function to load data with infinite scroll — just like the select dropdown. The table fetches the first page on mount, then loads more rows as you scroll to the bottom. Re-fetches from page 1 when sort or filter changes.

IDNameEmail
vue
<script setup>
import { VxTable } from 'veloxui'

const columns = [
  { key: 'id', label: 'ID', width: 80 },
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
]

// "fetchData" receives { page, pageSize, sort, filter }
// Must return { data: [...rows], total: totalRowCount }
// The table calls this automatically:
//   - On mount (page 1)
//   - When scrolling near the bottom (next page, appended)
//   - When sort or filter changes (resets to page 1)
async function fetchUsers({ page, pageSize }) {
  const res = await fetch(`/api/users?page=${page}&limit=${pageSize}`)
  const json = await res.json()
  return { data: json.items, total: json.total }
}
</script>

<template>
  <!-- "fetch-data" enables server-side data with infinite scroll -->
  <!-- Set "max-height" to make the table scrollable -->
  <VxTable
    :columns="columns"
    :fetch-data="fetchUsers"
    :max-height="350"
    sortable
    bordered
  />
</template>

Row Click ​

Listen to the @row-click event to handle row clicks.

vue
<script setup>
import { ref } from 'vue'
import { VxTable } from 'veloxui'

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
]

const data = [
  { name: 'Olivia Martin', email: 'olivia.martin@email.com', role: 'Admin' },
  { name: 'Jackson Lee', email: 'jackson.lee@email.com', role: 'Editor' },
  { name: 'Isabella Nguyen', email: 'isabella.nguyen@email.com', role: 'Viewer' },
]

const clickedRow = ref(null)

function handleRowClick(row, index) {
  clickedRow.value = row
  console.log('Clicked row:', row, 'at index:', index)
}
</script>

<template>
  <p v-if="clickedRow">Clicked: {{ clickedRow.name }}</p>

  <!-- @row-click fires when any row is clicked -->
  <VxTable :columns="columns" :data="data" @row-click="handleRowClick" bordered />
</template>

Summary Row ​

Use the footer prop to add a summary/totals row. Footer cells support the same footer-[key] slots for custom content.

ProductQtyPriceTotal
MacBook Pro 14"2$1,999.00$3,998.00
AirPods Pro5$249.00$1,245.00
Magic Keyboard3$299.00$897.00
Studio Display1$1,599.00$1,599.00
iPhone 15 Pro4$999.00$3,996.00
Total (5 items)15$11,735.00
vue
<script setup>
import { VxTable } from 'veloxui'

// Use "align" to right-align numeric columns
const columns = [
  { key: 'product', label: 'Product' },
  { key: 'qty', label: 'Qty', align: 'right' },
  { key: 'price', label: 'Price', align: 'right' },
  { key: 'total', label: 'Total', align: 'right' },
]

const data = [
  { product: 'MacBook Pro 14"', qty: 2, price: '$1,999.00', total: '$3,998.00' },
  { product: 'AirPods Pro', qty: 5, price: '$249.00', total: '$1,245.00' },
  { product: 'Magic Keyboard', qty: 3, price: '$299.00', total: '$897.00' },
  { product: 'Studio Display', qty: 1, price: '$1,599.00', total: '$1,599.00' },
  { product: 'iPhone 15 Pro', qty: 4, price: '$999.00', total: '$3,996.00' },
]

// "footer" has the same shape as data — it renders as a sticky bottom row
const footer = [
  { product: 'Total (5 items)', qty: 15, price: '', total: '$11,735.00' },
]
</script>

<template>
  <VxTable :columns="columns" :data="data" :footer="footer" bordered>
    <!-- Customize footer cells with "footer-[key]" slots -->
    <template #footer-product="{ value }">
      <span style="font-weight: 600;">{{ value }}</span>
    </template>
    <template #footer-total="{ value }">
      <span style="font-weight: 700;">{{ value }}</span>
    </template>
  </VxTable>
</template>

API ​

Props ​

PropTypeDefaultDescription
columnsTableColumn[]—Column definitions (required)
dataRecord<string, any>[][]Row data array
footerRecord<string, any>[]—Footer row data (same shape as data)
borderedbooleanfalseOuter border, rounded corners, column dividers
stripedbooleanfalseAlternate row backgrounds
heightstring | number—Fixed height with sticky header
maxHeightstring | number—Max height with sticky header
captionstring—Table caption text
scrollbar'auto' | 'hover' | 'always' | 'hidden''hover'Scrollbar visibility
selectablebooleanfalseShow row checkboxes
selectedany[]—Selected values (v-model)
selectionKeystring'id'Row field used as selection/expand key
draggablebooleanfalseEnable drag & drop row reordering
resizableboolean | TableResizablefalseEnable column resizing
sortablebooleanfalseEnable column sorting
sortTableSort | null—Sort state (v-model). If omitted, uses internal state
loadingbooleanfalseShow loading spinner overlay
expandablebooleanfalseShow expand toggle per row
expandedany[]—Expanded row keys (v-model). If omitted, uses internal state
paginationboolean | TablePaginationfalseEnable pagination. true for built-in, object for controlled
filterstring''Global text filter across all visible columns
groupBystring—Column key to group rows by
columnTogglebooleanfalseShow built-in column visibility dropdown
columnOrderstring[]—Column key order (v-model). Enables drag reorder
fetchData(params: TableFetchParams) => Promise<TableFetchResult>—Server-side data with infinite scroll. Loads more on scroll, re-fetches on sort/filter change
pageCountnumber0Max number of pages to fetch. 0 means unlimited (stops when data.length >= total)
scrollDistancenumber50Distance from bottom (px) to trigger loading more

TableColumn ​

PropertyTypeDefaultDescription
keystring—Data field key (required)
labelstring—Header text (required)
widthstring | number—Column width
align'left' | 'center' | 'right''left'Text alignment
fixed'left' | 'right'—Pin column to left or right edge
resizableboolean | TableResizable—Override table-level resizable per column
sortableboolean—Override table-level sortable per column
editableboolean—Enable double-click inline editing
render(row, index) => string—HTML render function for cell content

TableResizable ​

PropertyTypeDefaultDescription
minWidthnumber50Minimum width (px)
maxWidthnumberInfinityMaximum width (px)

TableSort ​

PropertyTypeDescription
keystringColumn key being sorted
order'asc' | 'desc'Sort direction

TablePagination ​

PropertyTypeDefaultDescription
pagenumber—Current page (1-based)
pageSizenumber—Rows per page
totalnumber—Total item count (for server-side pagination)
pageSizesnumber[][10, 20, 50, 100]Page size options

TableFetchParams ​

PropertyTypeDescription
pagenumberCurrent page (1-based)
pageSizenumberRows per page
sortTableSort | nullCurrent sort state
filterstringCurrent filter text

TableFetchResult ​

PropertyTypeDescription
dataRecord<string, any>[]Rows for the current page
totalnumberTotal item count (all pages)

Slots ​

SlotPropsDescription
[column.key]{ row, value, index }Custom cell content
header-[column.key]{ column }Custom header cell
footer-[column.key]{ row, value, index }Custom footer cell
expand{ row, index }Expandable row detail content
expand-icon{ row, expanded }Custom expand toggle icon
group{ group, count }Custom group header content
caption—Custom caption
empty—Custom empty state

Events ​

EventPayloadDescription
update:selectedany[]Selection changed
update:dataRecord<string, any>[]Row order changed (drag)
update:sortTableSort | nullSort state changed
update:expandedany[]Expanded rows changed
update:paginationTablePaginationPage/pageSize changed
update:columnOrderstring[]Column order changed (drag)
row-click(row, index)Row clicked
cell-edit(key, row, value)Cell value edited
column-resize(key, width)Column resized