Table
This is a table used to display content with rows and columns.
WARNING
To ensure the row expansion and row selection functions work properly, when these functions are needed, you must ensure that each data item in the data array contains a field with the same name as the rowKey parameter (default is 'key'), and the value of this field is unique across all data.
If backend pagination is needed, please refer to Server-side Pagination.
If cross-page selection is needed, please refer to Cross-page Select all.
Basic Usage
The data prop is the current data of the table, and the columns prop sets the table columns.
Please configure the key property for elements in columns as the unique identifier for rows.
Please configure the key property for elements in data as the unique identifier for rows; this unique identifier field can be controlled by rowKey.
Name | Age | Email |
|---|---|---|
Alice | 25 | alice@example.com |
Bob | 30 | bob@example.com |
Charlie | 35 | charlie@example.com |
<template>
<px-table :data="data" :columns="columns"></px-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{ key: 1, name: 'Alice', age: 25, email: 'alice@example.com' },
{ key: 2, name: 'Bob', age: 30, email: 'bob@example.com' },
{ key: 3, name: 'Charlie', age: 35, email: 'charlie@example.com' }
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name'
},
{
key: 'age',
label: 'Age',
field: 'age'
},
{
key: 'email',
label: 'Email',
field: 'email'
}
]
</script>Alignment
columns[].align configures the text alignment of cells; the default is 'left'.
Name | Age | Email |
|---|---|---|
Olivia | 28 | olivia@example.com |
James | 32 | james@example.com |
Sophia | 24 | sophia@example.com |
William | 29 | william@example.com |
Emma | 31 | emma@example.com |
<template>
<px-table :data="data" :columns="columns"></px-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{ key: 1, name: 'Olivia', age: 28, email: 'olivia@example.com' },
{ key: 2, name: 'James', age: 32, email: 'james@example.com' },
{ key: 3, name: 'Sophia', age: 24, email: 'sophia@example.com' },
{ key: 4, name: 'William', age: 29, email: 'william@example.com' },
{ key: 5, name: 'Emma', age: 31, email: 'emma@example.com' }
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name',
align: 'left'
},
{
key: 'age',
label: 'Age',
field: 'age',
align: 'center'
},
{
key: 'email',
label: 'Email',
field: 'email',
align: 'right'
}
]
</script>Custom Cells
You can customize cells in the following ways:
- Use
columns[].slotNameto configure a cell content slot. - Use
columns[].labelSlotNameto configure a header cell content slot. - Use
columns[].renderto configure a cell content render function. - Use
columns[].labelRenderto configure a header cell render function. - Use
columns[].cellPropsto configure cell properties. - Use
columns[].labelCellPropsto configure header cell properties. - Use
columns[].contentPropsto configure cell content properties. - Use
columns[].labelContentPropsto configure header content properties.
Name | Age | Email | Status Active / Inactive | Actions |
|---|---|---|---|---|
Emma Johnson | 32 | emma.johnson@example.com | active | |
James Miller | 45 | jmiller@test.net | inactive | |
Sophia Chen | 28 | schen@demo.org | active | |
William Brown | 61 | wbrown55@sample.co | inactive | |
Olivia Davis | 39 | odavis@mail.io | active |
<template>
<px-table :data="data" :columns="columns">
<template #status="{ record }">
<px-tag :theme="record.status === 'active' ? 'success' : 'danger'">{{
record.status
}}</px-tag>
</template>
<template #status-label>
Status
<px-tag style="margin-left: 8px" theme="success">Active</px-tag>
/
<px-tag theme="danger">Inactive</px-tag>
</template>
</px-table>
</template>
<script setup lang="ts">
import { Button } from '@pixelium/web-vue'
import { h, ref } from 'vue'
const data = ref([
{
key: 1,
name: 'Emma Johnson',
age: 32,
email: 'emma.johnson@example.com',
status: 'active'
},
{
key: 2,
name: 'James Miller',
age: 45,
email: 'jmiller@test.net',
status: 'inactive'
},
{
key: 3,
name: 'Sophia Chen',
age: 28,
email: 'schen@demo.org',
status: 'active'
},
{
key: 4,
name: 'William Brown',
age: 61,
email: 'wbrown55@sample.co',
status: 'inactive'
},
{
key: 5,
name: 'Olivia Davis',
age: 39,
email: 'odavis@mail.io',
status: 'active'
}
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name'
},
{
key: 'age',
label: 'Age',
field: 'age'
},
{
key: 'email',
label: 'Email',
field: 'email',
cellProps: {
style: {
backgroundColor: 'wheat',
color: 'var(--px-neutral-10)'
}
}
},
{
key: 'status',
labelSlotName: 'status-label',
slotName: 'status'
},
{
key: 'action',
render: () => {
return h(Button, { size: 'small' }, { default: () => 'Detail' })
},
labelRender: () => {
return 'Actions'
}
}
]
</script>Column Width
columns[].width and columns[].minWidth configure cell width.
When setting columns[].width, please leave at least one column unset so the table can adapt to its actual width.
Name | Role | Email | Address |
|---|---|---|---|
Olivia Martinez | Admin | olivia.martinez@company.com | 123 Maple Street, New York, NY 10001 |
James Wilson | Developer | james.wilson@company.com | 456 Oak Avenue, San Francisco, CA 94102 |
Sophia Chen | Designer | sophia.chen@company.com | 789 Pine Road, Chicago, IL 60601 |
William Taylor | Manager | william.taylor@company.com | 321 Elm Boulevard, Austin, TX 73301 |
Emma Johnson | Analyst | emma.johnson@company.com | 654 Cedar Lane, Seattle, WA 98101 |
<template>
<px-table :data="data" :columns="columns"></px-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{
key: 1,
name: 'Olivia Martinez',
role: 'Admin',
email: 'olivia.martinez@company.com',
address: '123 Maple Street, New York, NY 10001'
},
{
key: 2,
name: 'James Wilson',
role: 'Developer',
email: 'james.wilson@company.com',
address: '456 Oak Avenue, San Francisco, CA 94102'
},
{
key: 3,
name: 'Sophia Chen',
role: 'Designer',
email: 'sophia.chen@company.com',
address: '789 Pine Road, Chicago, IL 60601'
},
{
key: 4,
name: 'William Taylor',
role: 'Manager',
email: 'william.taylor@company.com',
address: '321 Elm Boulevard, Austin, TX 73301'
},
{
key: 5,
name: 'Emma Johnson',
role: 'Analyst',
email: 'emma.johnson@company.com',
address: '654 Cedar Lane, Seattle, WA 98101'
}
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name',
minWidth: 250
},
{
key: 'role',
label: 'Role',
field: 'role',
width: 120
},
{
key: 'email',
label: 'Email',
field: 'email',
minWidth: 300
},
{
key: 'address',
label: 'Address',
field: 'address',
minWidth: 300
}
]
</script>Borders and Background
bordered enables table borders; by default all borders are shown. variant sets the table background style variant; the default is a solid background ('normal').
Name | Age | Email |
|---|---|---|
Alice | 25 | alice@example.com |
Bob | 30 | bob@example.com |
Charlie | 35 | charlie@example.com |
<template>
<px-space direction="vertical">
<div style="display: flex; align-items: center">
<px-checkbox v-model="threeLineTable" @change="threeLineTableCheckChangeHandler">
Three-Line Table
</px-checkbox>
</div>
<div style="display: flex; align-items: center">
Bordered:
<px-checkbox-group
style="margin-left: 16px"
v-model="bordered"
:options="borderedOptions"
@change="borderedCheckChangeHandler"
></px-checkbox-group>
</div>
<div style="display: flex; align-items: center">
Variant:
<px-radio-group
style="margin-left: 16px"
v-model="variant"
:options="variantOptions"
></px-radio-group>
</div>
<px-table
:data="data"
:columns="columns"
:variant="variant"
:bordered="borderedConfig"
></px-table>
</px-space>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
const data = ref([
{ key: 1, name: 'Alice', age: 25, email: 'alice@example.com' },
{ key: 2, name: 'Bob', age: 30, email: 'bob@example.com' },
{ key: 3, name: 'Charlie', age: 35, email: 'charlie@example.com' }
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name'
},
{
key: 'age',
label: 'Age',
field: 'age'
},
{
key: 'email',
label: 'Email',
field: 'email'
}
]
const borderedOptions = [
{ label: 'Table', value: 'table' },
{ label: 'Side', value: 'side' },
{ label: 'Head', value: 'head' },
{ label: 'Row', value: 'row' },
{ label: 'Col', value: 'col' }
]
const bordered = ref<string[]>(['table', 'head'])
const threeLineTable = ref(true)
const borderedCheckChangeHandler = (val: string[]) => {
if (val.length === 2 && val.includes('table') && val.includes('head')) {
threeLineTable.value = true
} else {
threeLineTable.value = false
}
}
const threeLineTableCheckChangeHandler = (val: boolean) => {
if (val) {
bordered.value = ['table', 'head']
}
}
const borderedConfig = computed(() => {
return {
table: bordered.value.includes('table'),
side: bordered.value.includes('side'),
head: bordered.value.includes('head'),
row: bordered.value.includes('row'),
col: bordered.value.includes('col')
}
})
const variantOptions = [
{ label: 'Normal', value: 'normal' },
{ label: 'Striped', value: 'striped' },
{ label: 'Checkered', value: 'checkered' }
]
const variant = ref<string>('normal')
</script>Due to the features of display: table, unexpected results may occur when setting a small height or max-height on the outermost element of the Table component. The <table> height will still be expanded by its child elements.
We recommend using the tableAreaProps property to set a fixed or dynamic table height, for example: :table-area-props="{ style: 'max-height: 400px' }".
Name | Role | Level | Salary | Address |
|---|---|---|---|---|
Aric Ironfist | Blacksmith | 42 | 2800 | Iron Forge Street 12 |
Lyra Swiftarrow | Hunter | 36 | 2200 | Forest Edge Cottage |
Borin Stonebeard | Miner | 28 | 1800 | Mountain Hall 7 |
Elara Moonshadow | Alchemist | 51 | 3200 | Apothecary Lane 3 |
Garrick Stormblade | Knight | 65 | 4500 | Castle Barracks |
Mira Whisperwind | Bard | 24 | 1500 | Tavern Square 5 |
Thrain Forgeheart | Engineer | 47 | 2900 | Workshop District 9 |
Sylas Greenleaf | Druid | 58 | 3800 | Ancient Grove |
Kaelen Firehand | Pyromancer | 61 | 4200 | Mage Tower East |
Freya Shieldmaiden | Guard Captain | 54 | 3600 | Guard Post Central |
Jorin Goldfinder | Merchant | 33 | 2500 | Market Square 8 |
Nyssa Lightfoot | Rogue | 39 | 2700 | Shadow Alley 2 |
Torin Boulderbreaker | Stone Mason | 31 | 2100 | Quarry Road 4 |
Liana Starweaver | Astrologer | 45 | 3100 | Observatory Hill |
Finn Riverstride | Fisherman | 22 | 1400 | Dockside Hut |
Rowan Oakenshield | Carpenter | 27 | 1700 | Timber Yard 6 |
Vera Nightshade | Herbalist | 34 | 2300 | Herb Garden Cottage |
Draven Bloodedge | Mercenary | 49 | 3300 | Fighter's Guild |
Mara Swiftcurrent | Sailor | 26 | 1600 | Harbor Watch 3 |
Orin Deepdelver | Archaeologist | 43 | 3000 | Explorer's Lodge |
<template>
<px-table
:pagination="false"
:data="data"
:columns="columns"
row-key="name"
:table-area-props="{ style: 'max-height: 400px' }"
></px-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{
name: 'Aric Ironfist',
role: 'Blacksmith',
level: 42,
salary: 2800,
address: 'Iron Forge Street 12'
},
{
name: 'Lyra Swiftarrow',
role: 'Hunter',
level: 36,
salary: 2200,
address: 'Forest Edge Cottage'
},
{
name: 'Borin Stonebeard',
role: 'Miner',
level: 28,
salary: 1800,
address: 'Mountain Hall 7'
},
{
name: 'Elara Moonshadow',
role: 'Alchemist',
level: 51,
salary: 3200,
address: 'Apothecary Lane 3'
},
{
name: 'Garrick Stormblade',
role: 'Knight',
level: 65,
salary: 4500,
address: 'Castle Barracks'
},
{
name: 'Mira Whisperwind',
role: 'Bard',
level: 24,
salary: 1500,
address: 'Tavern Square 5'
},
{
name: 'Thrain Forgeheart',
role: 'Engineer',
level: 47,
salary: 2900,
address: 'Workshop District 9'
},
{
name: 'Sylas Greenleaf',
role: 'Druid',
level: 58,
salary: 3800,
address: 'Ancient Grove'
},
{
name: 'Kaelen Firehand',
role: 'Pyromancer',
level: 61,
salary: 4200,
address: 'Mage Tower East'
},
{
name: 'Freya Shieldmaiden',
role: 'Guard Captain',
level: 54,
salary: 3600,
address: 'Guard Post Central'
},
{
name: 'Jorin Goldfinder',
role: 'Merchant',
level: 33,
salary: 2500,
address: 'Market Square 8'
},
{
name: 'Nyssa Lightfoot',
role: 'Rogue',
level: 39,
salary: 2700,
address: 'Shadow Alley 2'
},
{
name: 'Torin Boulderbreaker',
role: 'Stone Mason',
level: 31,
salary: 2100,
address: 'Quarry Road 4'
},
{
name: 'Liana Starweaver',
role: 'Astrologer',
level: 45,
salary: 3100,
address: 'Observatory Hill'
},
{
name: 'Finn Riverstride',
role: 'Fisherman',
level: 22,
salary: 1400,
address: 'Dockside Hut'
},
{
name: 'Rowan Oakenshield',
role: 'Carpenter',
level: 27,
salary: 1700,
address: 'Timber Yard 6'
},
{
name: 'Vera Nightshade',
role: 'Herbalist',
level: 34,
salary: 2300,
address: 'Herb Garden Cottage'
},
{
name: 'Draven Bloodedge',
role: 'Mercenary',
level: 49,
salary: 3300,
address: "Fighter's Guild"
},
{
name: 'Mara Swiftcurrent',
role: 'Sailor',
level: 26,
salary: 1600,
address: 'Harbor Watch 3'
},
{
name: 'Orin Deepdelver',
role: 'Archaeologist',
level: 43,
salary: 3000,
address: "Explorer's Lodge"
}
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name'
},
{
key: 'role',
label: 'Role',
field: 'role'
},
{
key: 'level',
label: 'Level',
field: 'level'
},
{
key: 'salary',
label: 'Salary',
field: 'salary'
},
{
label: 'Address',
field: 'address',
key: 'address'
}
]
</script>Scroll Area
scroll.x configures the scroll area's width. The Table component internally computes a minimum scroll width based on column configuration; when the table's actual width is less than the greater of that computed minimum and scroll.x, a horizontal scrollbar will appear.
Name | Age | Email |
|---|---|---|
Alice | 25 | alice@example.com |
Bob | 30 | bob@example.com |
Charlie | 35 | charlie@example.com |
<template>
<px-table :data="data" :columns="columns" :scroll="{ x: 1200 }"></px-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{ key: 1, name: 'Alice', age: 25, email: 'alice@example.com' },
{ key: 2, name: 'Bob', age: 30, email: 'bob@example.com' },
{ key: 3, name: 'Charlie', age: 35, email: 'charlie@example.com' }
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name'
},
{
key: 'age',
label: 'Age',
field: 'age'
},
{
key: 'email',
label: 'Email',
field: 'email'
}
]
</script>Cell Merging
Configure merged cells using the spanMethod property.
BuildingType | Coord | Level | Income | Maintain |
|---|---|---|---|---|
Blacksmith | (23, 18) | 3 | 1800 | 450 |
(25, 20) | 4 | 2400 | 600 | |
Tavern | (34, 27) | 2 | 1600 | 400 |
(36, 25) | 5 | 3200 | 800 | |
Market | (41, 33) | 3 | 2100 | 525 |
(43, 35) | 6 | 4000 | 1000 | |
Farm | (15, 12) | 2 | 900 | 225 |
(18, 14) | 4 | 2000 | 500 | |
Barracks | (9, 39) | 3 | 1500 | 375 |
(11, 37) | 5 | 2800 | 700 |
<template>
<px-table :data="data" :columns="columns" :spanMethod="spanMethod"></px-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TableOptionsArg } from '@pixelium/web-vue'
// If on-demand import
// import type { TableOptionsArg } from '@pixelium/web-vue/es'
const data = ref([
{
buildingType: 'Blacksmith',
coord: '(23, 18)',
level: 3,
income: 1800,
maintain: 450
},
{
buildingType: 'Blacksmith',
coord: '(25, 20)',
level: 4,
income: 2400,
maintain: 600
},
{
buildingType: 'Tavern',
coord: '(34, 27)',
level: 2,
income: 1600,
maintain: 400
},
{
buildingType: 'Tavern',
coord: '(36, 25)',
level: 5,
income: 3200,
maintain: 800
},
{
buildingType: 'Market',
coord: '(41, 33)',
level: 3,
income: 2100,
maintain: 525
},
{
buildingType: 'Market',
coord: '(43, 35)',
level: 6,
income: 4000,
maintain: 1000
},
{
buildingType: 'Farm',
coord: '(15, 12)',
level: 2,
income: 900,
maintain: 225
},
{
buildingType: 'Farm',
coord: '(18, 14)',
level: 4,
income: 2000,
maintain: 500
},
{
buildingType: 'Barracks',
coord: '(9, 39)',
level: 3,
income: 1500,
maintain: 375
},
{
buildingType: 'Barracks',
coord: '(11, 37)',
level: 5,
income: 2800,
maintain: 700
},
{
buildingType: 'Town Hall',
coord: '(12, 45)',
level: 5
}
])
const columns = [
{
key: 'buildingType',
label: 'BuildingType',
field: 'buildingType'
},
{
key: 'coord',
label: 'Coord',
field: 'coord'
},
{
key: 'level',
label: 'Level',
field: 'level'
},
{
key: 'income',
label: 'Income',
field: 'income'
},
{
label: 'Maintain',
field: 'maintain',
key: 'maintain'
}
]
const spanMethod = ({ rowIndex, colIndex, record, column }: TableOptionsArg) => {
const curBuildingType = record.buildingType
if (colIndex === 0) {
if (data.value[rowIndex - 1]?.buildingType === curBuildingType) {
return
}
let rowspan = 0
for (let i = rowIndex; i < data.value.length; i++) {
if (data.value[i].buildingType !== curBuildingType) {
break
}
rowspan++
}
return {
rowspan
}
}
if (curBuildingType === 'Town Hall' && colIndex === 2) {
return { colspan: 3 }
}
}
</script>Fixed Header and Fixed Columns
The table header is fixed by default; it can be configured with the fixedHead prop.
Set fixed: 'left' or fixed: 'right' on child elements of the columns prop to fix columns.
WARNING
Fixed columns, when rendered, will move to the corresponding fixed side if they are in the middle of the table.
Fixed column configuration only applies to root nodes in the case of multi-level headers.
Id | Name | Age | Phone | Email | Address |
|---|---|---|---|---|---|
1 | John Smith | 28 | +1-555-101-2001 | john.smith@example.com | 123 Main St, Springfield, IL 62704 |
2 | Jane Johnson | 34 | +1-555-102-2002 | jane.johnson@example.com | 456 Oak Ave, Shelbyville, IL 62706 |
3 | Bob Williams | 22 | +1-555-103-2003 | bob.williams@example.com | 789 Pine Rd, Capital City, IL 62708 |
4 | Alice Brown | 45 | +1-555-104-2004 | alice.brown@example.com | 101 Maple Dr, Metropolis, IL 62710 |
5 | Charlie Jones | 31 | +1-555-105-2005 | charlie.jones@example.com | 202 Cedar Ln, Smallville, IL 62712 |
6 | Diana Garcia | 29 | +1-555-106-2006 | diana.garcia@example.com | 303 Birch St, Gotham, NY 10001 |
7 | Eve Miller | 38 | +1-555-107-2007 | eve.miller@example.com | 404 Elm Pl, Star City, NY 10003 |
8 | Frank Davis | 26 | +1-555-108-2008 | frank.davis@example.com | 505 Walnut Way, Central City, NY 10005 |
9 | Grace Rodriguez | 41 | +1-555-109-2009 | grace.rodriguez@example.com | 606 Spruce Ct, Coast City, CA 90210 |
10 | Henry Martinez | 33 | +1-555-110-2010 | henry.martinez@example.com | 707 Ash Blvd, Emerald City, CA 90212 |
11 | Ivy Hernandez | 27 | +1-555-111-2011 | ivy.hernandez@example.com | 808 Poplar Ave, Keystone City, CA 90214 |
12 | Jack Lopez | 36 | +1-555-112-2012 | jack.lopez@example.com | 909 Fir St, National City, TX 75001 |
13 | Kate Gonzalez | 24 | +1-555-113-2013 | kate.gonzalez@example.com | 111 Redwood Dr, Opal City, TX 75003 |
14 | Leo Wilson | 39 | +1-555-114-2014 | leo.wilson@example.com | 222 Sequoia Ln, Ivy Town, TX 75005 |
15 | Mia Anderson | 30 | +1-555-115-2015 | mia.anderson@example.com | 333 Magnolia Rd, Hub City, FL 32003 |
16 | Nick Thomas | 42 | +1-555-116-2016 | nick.thomas@example.com | 444 Willow Cir, Blue Valley, FL 32005 |
17 | Olivia Taylor | 35 | +1-555-117-2017 | olivia.taylor@example.com | 555 Cypress St, Starling City, FL 32007 |
18 | Paul Moore | 23 | +1-555-118-2018 | paul.moore@example.com | 666 Juniper Way, Fawcett City, WA 98001 |
19 | Quinn Jackson | 37 | +1-555-119-2019 | quinn.jackson@example.com | 777 Sycamore Pl, Gateway City, WA 98003 |
20 | Ryan Martin | 32 | +1-555-120-2020 | ryan.martin@example.com | 888 Laurel Ave, Coral City, WA 98005 |
<template>
<px-table
:data="data"
:columns="columns"
row-key="id"
:table-area-props="{ style: 'max-height: 400px' }"
:scroll="{ x: 1200 }"
:pagination="false"
></px-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{
id: 1,
name: 'John Smith',
age: 28,
phone: '+1-555-101-2001',
email: 'john.smith@example.com',
address: '123 Main St, Springfield, IL 62704'
},
{
id: 2,
name: 'Jane Johnson',
age: 34,
phone: '+1-555-102-2002',
email: 'jane.johnson@example.com',
address: '456 Oak Ave, Shelbyville, IL 62706'
},
{
id: 3,
name: 'Bob Williams',
age: 22,
phone: '+1-555-103-2003',
email: 'bob.williams@example.com',
address: '789 Pine Rd, Capital City, IL 62708'
},
{
id: 4,
name: 'Alice Brown',
age: 45,
phone: '+1-555-104-2004',
email: 'alice.brown@example.com',
address: '101 Maple Dr, Metropolis, IL 62710'
},
{
id: 5,
name: 'Charlie Jones',
age: 31,
phone: '+1-555-105-2005',
email: 'charlie.jones@example.com',
address: '202 Cedar Ln, Smallville, IL 62712'
},
{
id: 6,
name: 'Diana Garcia',
age: 29,
phone: '+1-555-106-2006',
email: 'diana.garcia@example.com',
address: '303 Birch St, Gotham, NY 10001'
},
{
id: 7,
name: 'Eve Miller',
age: 38,
phone: '+1-555-107-2007',
email: 'eve.miller@example.com',
address: '404 Elm Pl, Star City, NY 10003'
},
{
id: 8,
name: 'Frank Davis',
age: 26,
phone: '+1-555-108-2008',
email: 'frank.davis@example.com',
address: '505 Walnut Way, Central City, NY 10005'
},
{
id: 9,
name: 'Grace Rodriguez',
age: 41,
phone: '+1-555-109-2009',
email: 'grace.rodriguez@example.com',
address: '606 Spruce Ct, Coast City, CA 90210'
},
{
id: 10,
name: 'Henry Martinez',
age: 33,
phone: '+1-555-110-2010',
email: 'henry.martinez@example.com',
address: '707 Ash Blvd, Emerald City, CA 90212'
},
{
id: 11,
name: 'Ivy Hernandez',
age: 27,
phone: '+1-555-111-2011',
email: 'ivy.hernandez@example.com',
address: '808 Poplar Ave, Keystone City, CA 90214'
},
{
id: 12,
name: 'Jack Lopez',
age: 36,
phone: '+1-555-112-2012',
email: 'jack.lopez@example.com',
address: '909 Fir St, National City, TX 75001'
},
{
id: 13,
name: 'Kate Gonzalez',
age: 24,
phone: '+1-555-113-2013',
email: 'kate.gonzalez@example.com',
address: '111 Redwood Dr, Opal City, TX 75003'
},
{
id: 14,
name: 'Leo Wilson',
age: 39,
phone: '+1-555-114-2014',
email: 'leo.wilson@example.com',
address: '222 Sequoia Ln, Ivy Town, TX 75005'
},
{
id: 15,
name: 'Mia Anderson',
age: 30,
phone: '+1-555-115-2015',
email: 'mia.anderson@example.com',
address: '333 Magnolia Rd, Hub City, FL 32003'
},
{
id: 16,
name: 'Nick Thomas',
age: 42,
phone: '+1-555-116-2016',
email: 'nick.thomas@example.com',
address: '444 Willow Cir, Blue Valley, FL 32005'
},
{
id: 17,
name: 'Olivia Taylor',
age: 35,
phone: '+1-555-117-2017',
email: 'olivia.taylor@example.com',
address: '555 Cypress St, Starling City, FL 32007'
},
{
id: 18,
name: 'Paul Moore',
age: 23,
phone: '+1-555-118-2018',
email: 'paul.moore@example.com',
address: '666 Juniper Way, Fawcett City, WA 98001'
},
{
id: 19,
name: 'Quinn Jackson',
age: 37,
phone: '+1-555-119-2019',
email: 'quinn.jackson@example.com',
address: '777 Sycamore Pl, Gateway City, WA 98003'
},
{
id: 20,
name: 'Ryan Martin',
age: 32,
phone: '+1-555-120-2020',
email: 'ryan.martin@example.com',
address: '888 Laurel Ave, Coral City, WA 98005'
}
])
const columns = [
{
key: 'id',
label: 'Id',
field: 'id',
fixed: 'left'
},
{
key: 'name',
label: 'Name',
field: 'name'
},
{
key: 'age',
label: 'Age',
field: 'age'
},
{
key: 'phone',
label: 'Phone',
field: 'phone'
},
{
key: 'email',
label: 'Email',
field: 'email'
},
{
label: 'Address',
field: 'address',
key: 'address',
fixed: 'right',
width: 300
}
]
</script>Multi-level Headers
Set children on child elements of the columns prop to enable multi-level headers.
In multi-level header cases, the header area shows cell borders.
Basic Information | Contact Information | Work Information | Status | ||||||
|---|---|---|---|---|---|---|---|---|---|
ID | Name | Contact Details | Address | Department | Salary Information | ||||
Phone | Email | Base Salary | Bonus | Total | |||||
1 | John Smith | +1 (212) 555-0198 | john.smith@example.com | 123 Main St, New York, NY | Sales | 5000 | 1000 | 6000 | Active |
2 | Emily Johnson | +44 20 7946 0958 | emily.j@example.com | 456 Park Ave, Los Angeles, CA | Marketing | 4500 | 800 | 5300 | Active |
3 | Michael Brown | +61 2 9876 5432 | m.brown@example.com | 789 Broadway, Chicago, IL | Engineering | 7000 | 1500 | 8500 | Active |
4 | Sarah Davis | +49 30 12345678 | sarah.davis@example.com | 101 Oak St, Houston, TX | HR | 4800 | 600 | 5400 | On Leave |
5 | David Wilson | +33 1 2345 6789 | d.wilson@example.com | 202 Pine Rd, Phoenix, AZ | Finance | 6500 | 1200 | 7700 | Active |
6 | Jennifer Taylor | +81 3 1234 5678 | jennifer.t@example.com | 303 Elm St, Philadelphia, PA | Sales | 5200 | 900 | 6100 | Active |
7 | Robert Miller | +1 (415) 555-0134 | robert.m@example.com | 404 Cedar Ave, San Antonio, TX | Engineering | 7200 | 1600 | 8800 | Active |
8 | Lisa Anderson | +44 131 496 0600 | lisa.a@example.com | 505 Maple Dr, San Diego, CA | Marketing | 4700 | 700 | 5400 | Inactive |
9 | William Thomas | +61 8 8123 4567 | william.t@example.com | 606 Birch Ln, Dallas, TX | Finance | 6800 | 1300 | 8100 | Active |
10 | Maria Jackson | +49 89 98765432 | maria.j@example.com | 707 Walnut St, San Jose, CA | HR | 4900 | 650 | 5550 | Active |
<template>
<px-table
:data="data"
:columns="columns"
:scroll="{ x: 1500 }"
:table-area-props="{ style: 'max-height: 400px' }"
></px-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const columns = [
{
label: 'Basic Information',
align: 'center',
key: 'base-info',
children: [
{
label: 'ID',
field: 'id',
width: 80,
align: 'center',
key: 'id'
},
{
label: 'Name',
field: 'name',
width: 120,
key: 'name'
}
]
},
{
label: 'Contact Information',
key: 'contactInformation',
children: [
{
label: 'Contact Details',
key: 'contactDetails',
children: [
{
key: 'phone',
label: 'Phone',
field: 'phone',
minWidth: 120
},
{
key: 'email',
label: 'Email',
field: 'email',
minWidth: 180
}
]
},
{
label: 'Address',
field: 'address',
key: 'address',
minWidth: 200
}
]
},
{
label: 'Work Information',
key: 'workInformation',
children: [
{
label: 'Department',
field: 'department',
key: 'department',
width: 120
},
{
label: 'Salary Information',
key: 'salaryInformation',
children: [
{
label: 'Base Salary',
field: 'baseSalary',
key: 'baseSalary',
width: 100,
align: 'right'
},
{
label: 'Bonus',
field: 'bonus',
key: 'bonus',
width: 100,
align: 'right'
},
{
label: 'Total',
field: 'totalSalary',
key: 'totalSalary',
width: 100,
align: 'right'
}
]
}
]
},
{
label: 'Status',
field: 'status',
width: 80,
fixed: 'right',
key: 'status',
align: 'center'
}
]
const data = ref([
{
id: 1,
name: 'John Smith',
phone: '+1 (212) 555-0198',
email: 'john.smith@example.com',
address: '123 Main St, New York, NY',
department: 'Sales',
baseSalary: 5000,
bonus: 1000,
totalSalary: 6000,
status: 'Active'
},
{
id: 2,
name: 'Emily Johnson',
phone: '+44 20 7946 0958',
email: 'emily.j@example.com',
address: '456 Park Ave, Los Angeles, CA',
department: 'Marketing',
baseSalary: 4500,
bonus: 800,
totalSalary: 5300,
status: 'Active'
},
{
id: 3,
name: 'Michael Brown',
phone: '+61 2 9876 5432',
email: 'm.brown@example.com',
address: '789 Broadway, Chicago, IL',
department: 'Engineering',
baseSalary: 7000,
bonus: 1500,
totalSalary: 8500,
status: 'Active'
},
{
id: 4,
name: 'Sarah Davis',
phone: '+49 30 12345678',
email: 'sarah.davis@example.com',
address: '101 Oak St, Houston, TX',
department: 'HR',
baseSalary: 4800,
bonus: 600,
totalSalary: 5400,
status: 'On Leave'
},
{
id: 5,
name: 'David Wilson',
phone: '+33 1 2345 6789',
email: 'd.wilson@example.com',
address: '202 Pine Rd, Phoenix, AZ',
department: 'Finance',
baseSalary: 6500,
bonus: 1200,
totalSalary: 7700,
status: 'Active'
},
{
id: 6,
name: 'Jennifer Taylor',
phone: '+81 3 1234 5678',
email: 'jennifer.t@example.com',
address: '303 Elm St, Philadelphia, PA',
department: 'Sales',
baseSalary: 5200,
bonus: 900,
totalSalary: 6100,
status: 'Active'
},
{
id: 7,
name: 'Robert Miller',
phone: '+1 (415) 555-0134',
email: 'robert.m@example.com',
address: '404 Cedar Ave, San Antonio, TX',
department: 'Engineering',
baseSalary: 7200,
bonus: 1600,
totalSalary: 8800,
status: 'Active'
},
{
id: 8,
name: 'Lisa Anderson',
phone: '+44 131 496 0600',
email: 'lisa.a@example.com',
address: '505 Maple Dr, San Diego, CA',
department: 'Marketing',
baseSalary: 4700,
bonus: 700,
totalSalary: 5400,
status: 'Inactive'
},
{
id: 9,
name: 'William Thomas',
phone: '+61 8 8123 4567',
email: 'william.t@example.com',
address: '606 Birch Ln, Dallas, TX',
department: 'Finance',
baseSalary: 6800,
bonus: 1300,
totalSalary: 8100,
status: 'Active'
},
{
id: 10,
name: 'Maria Jackson',
phone: '+49 89 98765432',
email: 'maria.j@example.com',
address: '707 Walnut St, San Jose, CA',
department: 'HR',
baseSalary: 4900,
bonus: 650,
totalSalary: 5550,
status: 'Active'
}
])
</script>Row Selection
Configure row selection via the selection prop. selectedKey controls selected items (controlled mode). If omitted or undefined, the component is uncontrolled; use defaultSelectedKey to provide a default.
Set columns[].disabled to disable the selector for a row.
When there are left-fixed columns, the row selection column is also fixed to the left.
ID | Name | Email | City | Address | |
|---|---|---|---|---|---|
1001 | Emma Johnson | emma.johnson@example.com | New York | 123 Main Street, Apt 4B | |
1002 | James Wilson | j.wilson@example.com | Los Angeles | 456 Oak Avenue | |
1003 | Sophia Chen | sophia.chen@example.com | Chicago | 789 Pine Road, Suite 12 | |
1004 | Michael Brown | m.brown@example.com | Miami | 321 Beach Boulevard | |
1005 | Olivia Davis | olivia.davis@example.com | Seattle | 654 Lakeview Drive |
<template>
<div>
<px-switch
style="margin-bottom: 16px"
active-label="Multiple"
v-model="selection.multiple"
></px-switch>
<px-table
:data="data"
:columns="columns"
row-key="id"
:selection="selection"
:scroll="{ x: 1500 }"
v-model:selected-keys="selectedKeys"
></px-table>
<div style="margin-top: 16px">selectedKeys: {{ selectedKeys }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{
id: 1001,
name: 'Emma Johnson',
email: 'emma.johnson@example.com',
city: 'New York',
address: '123 Main Street, Apt 4B'
},
{
id: 1002,
name: 'James Wilson',
email: 'j.wilson@example.com',
city: 'Los Angeles',
address: '456 Oak Avenue',
disabled: true
},
{
id: 1003,
name: 'Sophia Chen',
email: 'sophia.chen@example.com',
city: 'Chicago',
address: '789 Pine Road, Suite 12'
},
{
id: 1004,
name: 'Michael Brown',
email: 'm.brown@example.com',
city: 'Miami',
address: '321 Beach Boulevard'
},
{
id: 1005,
name: 'Olivia Davis',
email: 'olivia.davis@example.com',
city: 'Seattle',
address: '654 Lakeview Drive'
}
])
const columns = [
{
key: 'id',
label: 'ID',
field: 'id',
fixed: 'left'
},
{
key: 'name',
label: 'Name',
field: 'name'
},
{
key: 'email',
label: 'Email',
field: 'email'
},
{
key: 'city',
label: 'City',
field: 'city'
},
{
key: 'address',
label: 'Address',
field: 'address'
}
]
const selection = ref({
multiple: false,
showSelectAll: true
})
const selectedKeys = ref<number[]>([])
</script>Expandable Rows
Configure expandable rows via the expandable prop. expandedKey controls expanded rows (controlled mode). If omitted or undefined, the component is uncontrolled; use defaultExpandedKey to set a default.
Row expansion content is provided via columns[].expand or the expand slot. If empty (or when the expand slot is set, it is considered false), no expand button is shown.
When there are left-fixed columns, the expand button column is also fixed to the left.
ID | Name | |
|---|---|---|
1001 | Emma Johnson | |
1002 | James Wilson | |
1003 | Sophia Chen | |
1004 | Michael Brown | |
1005 | Olivia Davis |
ID | Name | |
|---|---|---|
1001 | Emma Johnson | |
1002 | James Wilson | |
1003 | Sophia Chen | |
1004 | Michael Brown | |
1005 | Olivia Davis |
<template>
<div>
<px-table
:data="data0"
:columns="columns"
row-key="id"
expandable
v-model:expanded-keys="expandedKeys"
></px-table>
<px-table
style="margin-top: 16px"
:data="data1"
:columns="columns"
row-key="id"
expandable
v-model:expanded-keys="expandedKeys"
>
<template #expand="{ record }">
<div>
<div v-for="key in keys" style="display: flex; align-items: center; gap: 8px">
<div style="color: gray; margin-right: 16px">{{ key }}:</div>
<div>{{ record[key] }}</div>
</div>
</div>
</template>
</px-table>
<div style="margin-top: 16px">expandedKeys: {{ expandedKeys }}</div>
</div>
</template>
<script setup lang="ts">
import type { TableData } from '@pixelium/web-vue'
// When On-demand Import
// import type { TableData } from '@pixelium/web-vue/es'
import { h, ref } from 'vue'
const keys = ['email', 'city', 'address']
const expandRender = ({ record }: { record: TableData }) => {
return h(
'div',
{},
keys.map((e) => {
return h('div', { style: 'display: flex; align-items: center; gap: 8px' }, [
h('div', { style: 'color: gray; margin-right: 16px' }, e + ': '),
h('div', {}, record[e])
])
})
)
}
const data0 = ref([
{
id: 1001,
name: 'Emma Johnson',
email: 'emma.johnson@example.com',
city: 'New York',
address: '123 Main Street, Apt 4B',
expand: expandRender
},
{
id: 1002,
name: 'James Wilson',
email: 'j.wilson@example.com',
city: 'Los Angeles',
address: '456 Oak Avenue',
expand: expandRender
},
{
id: 1003,
name: 'Sophia Chen',
email: 'sophia.chen@example.com',
city: 'Chicago',
address: '789 Pine Road, Suite 12'
},
{
id: 1004,
name: 'Michael Brown',
email: 'm.brown@example.com',
city: 'Miami',
address: '321 Beach Boulevard',
expand: expandRender
},
{
id: 1005,
name: 'Olivia Davis',
email: 'olivia.davis@example.com',
city: 'Seattle',
address: '654 Lakeview Drive',
expand: expandRender
}
])
const data1 = ref([
{
id: 1001,
name: 'Emma Johnson',
email: 'emma.johnson@example.com',
city: 'New York',
address: '123 Main Street, Apt 4B'
},
{
id: 1002,
name: 'James Wilson',
email: 'j.wilson@example.com',
city: 'Los Angeles',
address: '456 Oak Avenue',
expand: false
},
{
id: 1003,
name: 'Sophia Chen',
email: 'sophia.chen@example.com',
city: 'Chicago',
address: '789 Pine Road, Suite 12'
},
{
id: 1004,
name: 'Michael Brown',
email: 'm.brown@example.com',
city: 'Miami',
address: '321 Beach Boulevard'
},
{
id: 1005,
name: 'Olivia Davis',
email: 'olivia.davis@example.com',
city: 'Seattle',
address: '654 Lakeview Drive'
}
])
const columns = [
{
key: 'id',
label: 'ID',
field: 'id',
fixed: 'left'
},
{
key: 'name',
label: 'Name',
field: 'name'
}
]
const expandedKeys = ref<number[]>([])
</script>Sorting
Configure sorting using columns[].sortable. sortOrder controls the sorting state of each column (controlled mode). If omitted or undefined, the component is uncontrolled; use defaultSortOrder to provide a default.
The defaultSortOrder property inside columns[].sortable can also set the default for that column.
When sortMethod is set to 'custom', there is no sorting behavior. Backend sorting can be implemented by listening to the sortOrderChange event.
If sortMethod is not provided, the default comparator is used, based on JavaScript's native greater-than/less-than logic.
Name | Age | Email |
|---|---|---|
Emma | 31 | emma@example.com |
James | 32 | james@example.com |
Olivia | 28 | olivia@example.com |
Sophia | 24 | sophia@example.com |
William | 29 | william@example.com |
<template>
<px-table :data="data" :columns="columns" v-model:sort-order="sortOrder"></px-table>
<div style="margin-top: 16px">sortOrder: {{ sortOrder }}</div>
</template>
<script setup lang="ts">
import type { SortOrder, TableData } from '@pixelium/web-vue'
// If On-demand Import
// import type { SortOrder, TableData } from '@pixelium/web-vue/es'
import { ref } from 'vue'
const data = ref([
{ key: 1, name: 'Olivia', age: 28, email: 'olivia@example.com' },
{ key: 2, name: 'James', age: 32, email: 'james@example.com' },
{ key: 3, name: 'Sophia', age: 24, email: 'sophia@example.com' },
{ key: 4, name: 'William', age: 29, email: 'william@example.com' },
{ key: 5, name: 'Emma', age: 31, email: 'emma@example.com' }
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name',
sortable: {
orders: ['asc', 'desc'] as const,
sortMethod: (a: TableData, b: TableData, order: 'asc' | 'desc', field?: string) => {
const res = a.name.length - b.name.length
return order === 'desc' ? -res : res
},
defaultSortOrder: 'asc' as const
}
},
{
key: 'age',
label: 'Age',
field: 'age',
sortable: {
orders: ['asc'] as const
}
},
{
key: 'email',
label: 'Email',
field: 'email',
sortable: {
orders: ['asc', 'desc'] as const
}
}
]
const sortOrder = ref<SortOrder>({})
</script>Multi-column Sorting
Enable multi-column sorting by setting multiple: true in columns[].sortable's sortMethod. Use the priority property to set a column's priority; higher values take precedence.
WARNING
Multi-column sorting and single-column sorting are mutually exclusive. Triggering one will clear the selection of the other.
Name | Age | Email |
|---|---|---|
Emma | 31 | emma@example.com |
James | 32 | james@example.com |
Olivia | 28 | olivia@example.com |
Sophia | 24 | sophia@example.com |
William | 29 | william@example.com |
<template>
<px-table :data="data" :columns="columns" v-model:sort-order="sortOrder"></px-table>
<div style="margin-top: 16px">sortOrder: {{ sortOrder }}</div>
</template>
<script setup lang="ts">
import type { SortOrder, TableData } from '@pixelium/web-vue'
// If On-demand Import
// import type { SortOrder, TableData } from '@pixelium/web-vue/es'
import { ref } from 'vue'
const data = ref([
{ key: 1, name: 'Olivia', age: 28, email: 'olivia@example.com' },
{ key: 2, name: 'James', age: 32, email: 'james@example.com' },
{ key: 3, name: 'Sophia', age: 24, email: 'sophia@example.com' },
{ key: 4, name: 'William', age: 29, email: 'william@example.com' },
{ key: 5, name: 'Emma', age: 31, email: 'emma@example.com' }
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name',
sortable: {
orders: ['asc', 'desc'] as const,
sortMethod: (a: TableData, b: TableData, order: 'asc' | 'desc', field?: string) => {
const res = a.name.length - b.name.length
return order === 'desc' ? -res : res
},
defaultSortOrder: 'asc' as const,
multiple: true,
priority: 2
}
},
{
key: 'age',
label: 'Age',
field: 'age',
sortable: {
orders: ['asc'] as const,
multiple: true
}
},
{
key: 'email',
label: 'Email',
field: 'email',
sortable: {
orders: ['asc', 'desc'] as const,
multiple: true,
priority: 1
}
}
]
const sortOrder = ref<SortOrder>({})
</script>Filtering
Configure filtering with columns[].filterable. filterValue controls the filter state of each column (controlled mode). If omitted or undefined, the component is uncontrolled; use defaultFilterValue to provide a default.
The defaultFilterValue inside columns[].filterable can also set the default for that column.
If filterMethod is not provided, the default comparator is used, based on JavaScript's native === equality.
Name | Base Salary | Bonus | Total | Status |
|---|---|---|---|---|
James Wilson | 8500 | 420 | 8920 | active |
Michael Brown | 11200 | 150 | 11350 | active |
Robert Taylor | 9800 | 920 | 10820 | active |
Jennifer Miller | 6300 | 310 | 6610 | active |
Lisa Martinez | 4500 | 230 | 4730 | active |
Amanda Clark | 2900 | 0 | 2900 | active |
<template>
<px-table :data="data" :columns="columns" v-model:filter-value="filterValue"></px-table>
<div style="margin-top: 16px">filterValue: {{ filterValue }}</div>
</template>
<script setup lang="ts">
import type { FilterValue, TableData } from '@pixelium/web-vue'
// If On-demand Import
// import type { FilterValue, TableData } from '@pixelium/web-vue/es'
import { ref } from 'vue'
const columns = [
{
label: 'Name',
field: 'name',
key: 'name'
},
{
label: 'Base Salary',
field: 'baseSalary',
key: 'baseSalary',
filterable: {
filterOptions: [{ label: '> 7000', value: 7000 }],
filterMethod: (value: number[], record: TableData, field?: string) => {
return value[0] ? record.baseSalary > value[0] : true
}
}
},
{
label: 'Bonus',
field: 'bonus',
key: 'bonus',
filterable: {
filterOptions: [{ label: '> 500', value: 500 }],
filterMethod: (value: number[], record: TableData, field?: string) => {
return value[0] ? record.bonus > value[0] : true
}
}
},
{
label: 'Total',
field: 'total',
key: 'total',
filterable: {
filterOptions: [{ label: '> 10000', value: 500 }],
filterMethod: (value: number[], record: TableData, field?: string) => {
return value[0] ? record.total > value[0] : true
}
}
},
{
label: 'Status',
field: 'status',
key: 'status',
filterable: {
multiple: true,
filterOptions: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' }
],
defaultFilterValue: ['active']
}
}
]
const data = ref([
{
name: 'James Wilson',
baseSalary: 8500,
bonus: 420,
total: 8920,
status: 'active'
},
{
name: 'Sarah Johnson',
baseSalary: 5200,
bonus: 780,
total: 5900,
status: 'inactive'
},
{
name: 'Michael Brown',
baseSalary: 11200,
bonus: 150,
total: 11350,
status: 'active'
},
{
name: 'Emily Davis',
baseSalary: 3400,
bonus: 0,
total: 3400,
status: 'inactive'
},
{
name: 'Robert Taylor',
baseSalary: 9800,
bonus: 920,
total: 10820,
status: 'active'
},
{
name: 'Jennifer Miller',
baseSalary: 6300,
bonus: 310,
total: 6610,
status: 'active'
},
{
name: 'David Anderson',
baseSalary: 7600,
bonus: 650,
total: 8250,
status: 'inactive'
},
{
name: 'Lisa Martinez',
baseSalary: 4500,
bonus: 230,
total: 4730,
status: 'active'
},
{
name: 'William Thomas',
baseSalary: 10500,
bonus: 870,
total: 11370,
status: 'inactive'
},
{
name: 'Amanda Clark',
baseSalary: 2900,
bonus: 0,
total: 2900,
status: 'active'
}
])
const filterValue = ref<FilterValue>({})
</script>Summary Row
Configure summary rows with the summary prop.
summary.datasets the content of the summary row.summary.summaryTextsets the text for the first column of the summary row.summary.placementadjusts the placement of the summary row; choose'start'or'end'(default) to place the summary at the top or bottom of the data area.summary.fixedconfigures whether the summary row is fixed; the summary row is fixed by default.summary.spanMethodconfigures cell merging within the summary row area.
WARNING
When the summary row is placed at the start of the data area, a fixed summary row requires a fixed header to take effect.
Name | Base Salary | Bonus | Total | Status |
|---|---|---|---|---|
Emma Johnson | 8500 | 420 | 8920 | active |
David Smith | 6500 | 780 | 7280 | inactive |
Sophia Williams | 11200 | 150 | 11350 | active |
Michael Brown | 4200 | 0 | 4200 | inactive |
Olivia Davis | 9800 | 920 | 10720 | active |
James Wilson | 7300 | 310 | 7610 | active |
Sarah Miller | 10500 | 850 | 11350 | inactive |
Robert Taylor | 4200 | 190 | 4390 | active |
Jennifer Anderson | 8900 | 0 | 8900 | inactive |
Thomas Clark | 11500 | 970 | 12470 | active |
Sum | 82600 | 4590 | 87190 | |
Average | 8260 | 459 | 8719 | |
<template>
<div style="display: flex; align-items: center">
<px-switch
active-label="Fixed"
inactive-label="Unfixed"
v-model="summaryFixed"
style="margin-right: 16px"
></px-switch>
Placement:
<px-switch
active-label="Start"
inactive-label="End"
v-model="summaryStart"
style="margin-left: 8px"
></px-switch>
</div>
<px-table
:data="data"
:columns="columns"
:summary="summary"
style="margin-top: 16px"
:table-area-props="{ style: 'max-height: 300px' }"
></px-table>
</template>
<script setup lang="ts">
import type { TableOptionsArg } from '@pixelium/web-vue'
// If on-demand import
// import type { TableOptionsArg } from '@pixelium/web-vue/es'
import { computed, ref } from 'vue'
const columns = [
{
label: 'Name',
field: 'name',
key: 'name'
},
{
label: 'Base Salary',
field: 'baseSalary',
key: 'baseSalary'
},
{
label: 'Bonus',
field: 'bonus',
key: 'bonus'
},
{
label: 'Total',
field: 'total',
key: 'total'
},
{
label: 'Status',
field: 'status',
key: 'status'
}
]
const data = ref([
{
name: 'Emma Johnson',
baseSalary: 8500,
bonus: 420,
total: 8920,
status: 'active'
},
{
name: 'David Smith',
baseSalary: 6500,
bonus: 780,
total: 7280,
status: 'inactive'
},
{
name: 'Sophia Williams',
baseSalary: 11200,
bonus: 150,
total: 11350,
status: 'active'
},
{
name: 'Michael Brown',
baseSalary: 4200,
bonus: 0,
total: 4200,
status: 'inactive'
},
{
name: 'Olivia Davis',
baseSalary: 9800,
bonus: 920,
total: 10720,
status: 'active'
},
{
name: 'James Wilson',
baseSalary: 7300,
bonus: 310,
total: 7610,
status: 'active'
},
{
name: 'Sarah Miller',
baseSalary: 10500,
bonus: 850,
total: 11350,
status: 'inactive'
},
{
name: 'Robert Taylor',
baseSalary: 4200,
bonus: 190,
total: 4390,
status: 'active'
},
{
name: 'Jennifer Anderson',
baseSalary: 8900,
bonus: 0,
total: 8900,
status: 'inactive'
},
{
name: 'Thomas Clark',
baseSalary: 11500,
bonus: 970,
total: 12470,
status: 'active'
}
])
const summaryData = [
{
baseSalary: 0,
bonus: 0,
total: 0
},
{
baseSalary: 0,
bonus: 0,
total: 0
}
]
data.value.forEach((record) => {
summaryData[0].baseSalary += record.baseSalary
summaryData[0].bonus += record.bonus
summaryData[0].total += record.total
})
summaryData[1].baseSalary = parseFloat(
(summaryData[0].baseSalary / data.value.length).toFixed(4)
)
summaryData[1].bonus = parseFloat((summaryData[0].bonus / data.value.length).toFixed(4))
summaryData[1].total = parseFloat((summaryData[0].total / data.value.length).toFixed(4))
const summaryFixed = ref(false)
const summaryStart = ref(false)
const summary = computed(() => {
return {
data: summaryData,
summaryText: ['Sum', 'Average'],
fixed: summaryFixed.value,
placement: summaryStart.value ? 'start' : 'end',
spanMethod: ({ rowIndex, colIndex, record, column }: TableOptionsArg) => {
if (colIndex === 3) {
return {
colspan: 2
}
}
}
}
})
</script>Loading State
Configure the loading state of the table by setting the loading property.
Name | Age | Email |
|---|---|---|
Alice | 25 | alice@example.com |
Bob | 30 | bob@example.com |
Charlie | 35 | charlie@example.com |
<template>
<px-switch active-tip="loading" inactive-tip="not loading" v-model="loading"></px-switch>
<px-table
:data="data"
:columns="columns"
:loading="loading"
style="margin-top: 16px"
></px-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const loading = ref(false)
const data = ref([
{ key: 1, name: 'Alice', age: 25, email: 'alice@example.com' },
{ key: 2, name: 'Bob', age: 30, email: 'bob@example.com' },
{ key: 3, name: 'Charlie', age: 35, email: 'charlie@example.com' }
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name'
},
{
key: 'age',
label: 'Age',
field: 'age'
},
{
key: 'email',
label: 'Email',
field: 'email'
}
]
</script>Client-side Pagination
The table component supports automatic pagination of the passed data by default. The pagination configuration can be set via the pagination property.
ID | User | Email | Register |
|---|---|---|---|
1 | Lisa Jackson | lisa.jackson@example.com | 2025-04-06 |
2 | Stephanie Thompson | stephanie.thompson@example.com | 2026-01-05 |
3 | Michael Taylor | michael.taylor@example.com | 2025-08-13 |
4 | William Wilson | william.wilson@example.com | 2025-10-16 |
5 | Michael Anderson | michael.anderson@example.com | 2025-08-01 |
6 | Christopher Miller | christopher.miller@example.com | 2026-01-11 |
7 | Christopher Thomas | christopher.thomas@example.com | 2025-09-14 |
8 | James Jones | james.jones@example.com | 2025-06-25 |
9 | Emma Brown | emma.brown@example.com | 2025-07-15 |
10 | Christopher Harris | christopher.harris@example.com | 2025-10-19 |
<template>
<px-table
:data="data"
:columns="columns"
:pagination="{
showTotal: true,
showJumper: true,
showSize: true
}"
v-model:page-size="pageSize"
v-model:page="page"
></px-table>
<div style="margin-top: 16px">page: {{ page }}; pageSize: {{ pageSize }}</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const pageSize = ref(10)
const page = ref(1)
const columns = [
{
key: 'id',
label: 'ID',
field: 'id'
},
{
key: 'user',
label: 'User',
field: 'user'
},
{
key: 'email',
label: 'Email',
field: 'email'
},
{
key: 'register',
label: 'Register',
field: 'register'
}
]
const firstNames = [
'John',
'Emma',
'Michael',
'Sarah',
'David',
'Lisa',
'Robert',
'Jennifer',
'William',
'Jessica',
'James',
'Amanda',
'Christopher',
'Melissa',
'Daniel',
'Stephanie',
'Matthew',
'Laura',
'Joshua',
'Michelle'
]
const lastNames = [
'Smith',
'Johnson',
'Williams',
'Brown',
'Jones',
'Miller',
'Davis',
'Garcia',
'Rodriguez',
'Wilson',
'Martinez',
'Anderson',
'Taylor',
'Thomas',
'Jackson',
'White',
'Harris',
'Martin',
'Thompson',
'Moore'
]
function generateRandomData(count: number) {
const data: any[] = []
const currentDate = new Date()
for (let i = 1; i <= count; i++) {
const firstName = firstNames[Math.floor(Math.random() * firstNames.length)]
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)]
const fullName = `${firstName} ${lastName}`
const email = `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com`
const randomDaysAgo = Math.floor(Math.random() * 365)
const registerDate = new Date(currentDate)
registerDate.setDate(registerDate.getDate() - randomDaysAgo)
const registerDateStr = registerDate.toISOString().split('T')[0]
data.push({
id: i,
user: fullName,
email: email,
register: registerDateStr
})
}
return data
}
const data = ref(generateRandomData(50))
</script>Server-side Pagination
When the pagination.paginateMethod is set to 'custom', the table will not perform automatic sorting. This is often used for server-side pagination. The total number of data items can be set via pagination.total.
ID | User | Email | Register |
|---|---|---|---|
1 | David Thomas | david.thomas@example.com | 2025-11-07 |
2 | Stephanie Jackson | stephanie.jackson@example.com | 2025-07-20 |
3 | Matthew Martinez | matthew.martinez@example.com | 2025-11-12 |
4 | James Thomas | james.thomas@example.com | 2025-07-29 |
5 | Matthew White | matthew.white@example.com | 2025-10-21 |
6 | Jennifer Rodriguez | jennifer.rodriguez@example.com | 2025-12-10 |
7 | Christopher Garcia | christopher.garcia@example.com | 2026-02-10 |
8 | William Martinez | william.martinez@example.com | 2026-01-19 |
9 | Lisa Anderson | lisa.anderson@example.com | 2025-04-21 |
10 | Christopher Smith | christopher.smith@example.com | 2025-04-24 |
<template>
<px-table
:data="data"
:columns="columns"
:pagination="{
showTotal: true,
showJumper: true,
showSize: true,
total: 1000,
paginateMethod: 'custom'
}"
v-model:page-size="pageSize"
v-model:page="page"
:loading="loading"
></px-table>
<div style="margin-top: 16px">page: {{ page }}; pageSize: {{ pageSize }}</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
const pageSize = ref(10)
const page = ref(1)
const loading = ref(false)
watch([page, pageSize], () => {
loading.value = true
setTimeout(() => {
data.value = generateRandomData(pageSize.value, pageSize.value * (page.value - 1))
loading.value = false
}, 3000)
})
const columns = [
{
key: 'id',
label: 'ID',
field: 'id'
},
{
key: 'user',
label: 'User',
field: 'user'
},
{
key: 'email',
label: 'Email',
field: 'email'
},
{
key: 'register',
label: 'Register',
field: 'register'
}
]
const firstNames = [
'John',
'Emma',
'Michael',
'Sarah',
'David',
'Lisa',
'Robert',
'Jennifer',
'William',
'Jessica',
'James',
'Amanda',
'Christopher',
'Melissa',
'Daniel',
'Stephanie',
'Matthew',
'Laura',
'Joshua',
'Michelle'
]
const lastNames = [
'Smith',
'Johnson',
'Williams',
'Brown',
'Jones',
'Miller',
'Davis',
'Garcia',
'Rodriguez',
'Wilson',
'Martinez',
'Anderson',
'Taylor',
'Thomas',
'Jackson',
'White',
'Harris',
'Martin',
'Thompson',
'Moore'
]
function generateRandomData(count: number, startId: number) {
const data: any[] = []
const currentDate = new Date()
for (let i = 1; i <= count; i++) {
const firstName = firstNames[Math.floor(Math.random() * firstNames.length)]
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)]
const fullName = `${firstName} ${lastName}`
const email = `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com`
const randomDaysAgo = Math.floor(Math.random() * 365)
const registerDate = new Date(currentDate)
registerDate.setDate(registerDate.getDate() - randomDaysAgo)
const registerDateStr = registerDate.toISOString().split('T')[0]
data.push({
id: i + startId,
user: fullName,
email: email,
register: registerDateStr
})
}
return data
}
const data = ref(generateRandomData(pageSize.value, pageSize.value * (page.value - 1)))
</script>Hide Pagination on Single Page
Set pagination.hideWhenSinglePage to hide pagination when there is only a single page.
Name | Age | Email |
|---|---|---|
Alice | 25 | alice@example.com |
Bob | 30 | bob@example.com |
Charlie | 35 | charlie@example.com |
<template>
<px-table
:data="data"
:columns="columns"
:pagination="{ hideWhenSinglePage: true }"
></px-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{ key: 1, name: 'Alice', age: 25, email: 'alice@example.com' },
{ key: 2, name: 'Bob', age: 30, email: 'bob@example.com' },
{ key: 3, name: 'Charlie', age: 35, email: 'charlie@example.com' }
])
const columns = [
{
key: 'name',
label: 'Name',
field: 'name'
},
{
key: 'age',
label: 'Age',
field: 'age'
},
{
key: 'email',
label: 'Email',
field: 'email'
}
]
</script>Instance Methods
It is important to note that, for the purpose of state synchronization in controlled mode, instance methods such as selection, expansion, filtering, and sorting are designed asynchronously. Each invocation may require waiting for a microtask delay to take effect.
ID | Name | ||
|---|---|---|---|
1001 | Harper Brown | ||
1002 | Sophia Thomas | ||
1003 | Noah Wilson | ||
1004 | William Miller | ||
1005 | Charlotte Thomas | ||
1006 | James Rodriguez | ||
1007 | Sophia Walker | ||
1008 | Ava Moore | ||
1009 | James Miller | ||
1010 | Olivia Walker |
<template>
<div>
<px-space>
<px-button @click="logCurrentData" theme="info">Log Current Data</px-button>
<px-button @click="logPaginatedData" theme="info">Log Paginated Data</px-button>
</px-space>
<px-space style="margin-top: 16px">
<px-button @click="expand1stRow">Expand 1st Row</px-button>
<px-button @click="clearExpand" theme="warning">Clear Expand</px-button>
</px-space>
<px-space style="margin-top: 16px">
<px-button @click="select2ndRow">Select 2nd Row</px-button>
<px-button @click="clearSelect" theme="warning">Clear Select</px-button>
<px-button @click="selectAll">Select All</px-button>
</px-space>
<px-space style="margin-top: 16px">
<px-button @click="sort1stCol">Sort 1st Col</px-button>
<px-button @click="clearSort" theme="warning">Clear Sort</px-button>
<px-button @click="filter2stCol">FIlter 2st Col</px-button>
<px-button @click="clearFilter" theme="warning">Clear Filter</px-button>
</px-space>
<div style="margin-top: 16px">expandedKeys: {{ expandedKeys }}</div>
<div style="margin-top: 16px">selectedKeys: {{ selectedKeys }}</div>
<div style="margin-top: 16px">sortOrder: {{ sortOrder }}</div>
<div style="margin-top: 16px">filterValue: {{ filterValue }}</div>
<px-table
style="margin-top: 16px"
:data="data"
:columns="columns"
row-key="id"
expandable
v-model:expanded-keys="selectedKeys"
v-model:selected-keys="expandedKeys"
v-model:sort-order="sortOrder"
v-model:filter-value="filterValue"
:selection="{
multiple: true,
showSelectAll: true
}"
ref="tableRef"
></px-table>
</div>
</template>
<script setup lang="ts">
import type { TableData, Table, SortOrder, FilterValue, TableColumn } from '@pixelium/web-vue'
// When On-demand Import
// import type { TableData, Table, SortOrder, FilterValue } from '@pixelium/web-vue/es'
import { h, ref } from 'vue'
const tableRef = ref<null | InstanceType<typeof Table>>(null)
const logCurrentData = () => {
console.log(tableRef.value?.getCurrentData())
}
const logPaginatedData = () => {
console.log(tableRef.value?.getPaginatedData())
}
const filter2stCol = () => {
tableRef.value?.filter('name', ['C'])
}
const clearFilter = () => {
tableRef.value?.clearFilter()
}
const sort1stCol = () => {
tableRef.value?.sort('id', 'desc')
}
const clearSort = () => {
tableRef.value?.clearSort()
}
const select2ndRow = () => {
tableRef.value?.select(1002, true)
}
const clearSelect = () => {
tableRef.value?.clearSelect()
}
const selectAll = () => {
tableRef.value?.selectAll(true)
}
const expand1stRow = () => {
tableRef.value?.expand(1001, true)
}
const clearExpand = () => {
tableRef.value?.clearExpand()
}
const keys = ['email', 'city', 'address']
const expandRender = ({ record }: { record: TableData }) => {
return h(
'div',
{},
keys.map((e) => {
return h('div', { style: 'display: flex; align-items: center; gap: 8px' }, [
h('div', { style: 'color: gray; margin-right: 16px' }, e + ': '),
h('div', {}, record[e])
])
})
)
}
function generateData(count: number, startId: number = 1001) {
const firstNames = [
'Emma',
'James',
'Sophia',
'Michael',
'Olivia',
'Liam',
'Ava',
'Noah',
'Isabella',
'William',
'Mia',
'Ethan',
'Charlotte',
'Alexander',
'Amelia',
'Benjamin',
'Harper',
'Daniel',
'Evelyn',
'Matthew'
]
const lastNames = [
'Johnson',
'Wilson',
'Chen',
'Brown',
'Davis',
'Miller',
'Garcia',
'Rodriguez',
'Martinez',
'Taylor',
'Anderson',
'Thomas',
'Jackson',
'White',
'Harris',
'Martin',
'Thompson',
'Moore',
'Walker',
'Clark'
]
const cities = [
'New York',
'Los Angeles',
'Chicago',
'Miami',
'Seattle',
'Houston',
'Phoenix',
'Philadelphia',
'San Antonio',
'San Diego',
'Dallas',
'San Jose',
'Austin',
'Jacksonville',
'Fort Worth',
'Columbus',
'Charlotte',
'San Francisco',
'Indianapolis',
'Denver'
]
const streetNames = [
'Main Street',
'Oak Avenue',
'Pine Road',
'Beach Boulevard',
'Lakeview Drive',
'Maple Lane',
'Cedar Street',
'Elm Avenue',
'Hill Road',
'Park Boulevard',
'River Drive',
'Sunset Avenue',
'Mountain Road',
'Valley Drive',
'Ocean Boulevard',
'Forest Lane',
'Spring Avenue',
'Summer Road',
'Winter Drive',
'Autumn Lane'
]
const data = []
for (let i = 0; i < count; i++) {
const firstName = firstNames[Math.floor(Math.random() * firstNames.length)]
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)]
const city = cities[Math.floor(Math.random() * cities.length)]
const streetName = streetNames[Math.floor(Math.random() * streetNames.length)]
const streetNumber = Math.floor(Math.random() * 900) + 100
const aptTypes = ['Apt', 'Suite', 'Unit', '#', null]
const aptType = aptTypes[Math.floor(Math.random() * aptTypes.length)]
const aptNumber = aptType
? Math.floor(Math.random() * 20) +
1 +
(Math.random() > 0.5 ? String.fromCharCode(65 + Math.floor(Math.random() * 5)) : '')
: ''
let address = `${streetNumber} ${streetName}`
if (aptType && aptNumber) {
address += `, ${aptType} ${aptNumber}`
}
const item = {
id: startId + i,
name: `${firstName} ${lastName}`,
email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com`,
city: city,
address: address,
expand: expandRender
}
data.push(item)
}
return data
}
const data = ref(generateData(50))
const columns: TableColumn[] = [
{
key: 'id',
label: 'ID',
field: 'id',
fixed: 'left',
sortable: {
orders: ['asc', 'desc']
}
},
{
key: 'name',
label: 'Name',
field: 'name',
filterable: {
filterOptions: Array.from({ length: 26 }, (_, i) => ({
label: `${String.fromCharCode(65 + i)}...`,
value: String.fromCharCode(65 + i)
})),
filterMethod: (value: string[], record: TableData) => {
if (value.length) {
return record.name[0] === value[0]
} else {
return true
}
}
}
}
]
const expandedKeys = ref<number[]>([])
const selectedKeys = ref<number[]>([])
const sortOrder = ref<SortOrder>({})
const filterValue = ref<FilterValue>({})
</script>Cross-Page Select All
selection.selectAllMethod can asynchronously customize the selectedKeys value updated during a select-all operation.
selection.supersetSelectAllRef configures the superset referenced for the select-all checkbox's checked status: 'current' (data on the current page, default) or 'all' (data across all pages).
ID | User | Email | Register | |
|---|---|---|---|---|
1 | David Miller | david.miller@example.com | 2025-04-12 | |
2 | Robert Wilson | robert.wilson@example.com | 2025-11-20 | |
3 | Laura Taylor | laura.taylor@example.com | 2025-12-09 | |
4 | Emma Smith | emma.smith@example.com | 2025-08-30 | |
5 | Matthew Wilson | matthew.wilson@example.com | 2025-06-03 | |
6 | Christopher Garcia | christopher.garcia@example.com | 2025-04-23 | |
7 | Laura White | laura.white@example.com | 2025-10-02 | |
8 | Michael Harris | michael.harris@example.com | 2025-06-13 | |
9 | Robert Garcia | robert.garcia@example.com | 2025-07-19 | |
10 | Sarah Williams | sarah.williams@example.com | 2025-08-23 |
<template>
<div style="display: flex; align-items: center">
Superset for Select All State Reference
<px-switch
style="margin-left: 16px"
v-model="supersetWasAllData"
active-label="All Data"
></px-switch>
</div>
<px-table
style="margin-top: 16px"
:data="data"
rowKey="id"
:columns="columns"
:pagination="{
showTotal: true,
showSize: true
}"
v-model:page-size="pageSize"
v-model:page="page"
:selection="{
selectAllMethod,
supersetSelectAllRef,
multiple: true
}"
v-model:selected-keys="selectedKeys"
ref="tableRef"
>
<template #footer>
<div
v-if="!crossPageSelectAll && currentPageSelectAll"
style="color: var(--px-neutral-8)"
>
Select all on current page
</div>
<div v-if="crossPageSelectAll" style="color: var(--px-primary-6)">
Select all across all pages
</div>
</template>
</px-table>
</template>
<script lang="ts" setup>
import type { DialogReturn, TableData } from '@pixelium/web-vue'
import { Button, Dialog, Table } from '@pixelium/web-vue'
// On-demand import
// import type { DialogReturn, TableData } from '@pixelium/web-vue/es'
// import { Button, Dialog, Table } from '@pixelium/web-vue/es'
import { ref, h, computed, watch, nextTick } from 'vue'
function intersection<T>(arr1: T[], arr2: T[]): T[] {
const map = new Map<any, { value: T; count: number }>()
const len1 = arr1.length
for (let i = 0; i < len1; i++) {
const key = arr1[i]
const gotVal = map.get(key)
if (!gotVal) {
map.set(key, { value: arr1[i], count: 1 })
} else {
gotVal.count += 1
}
}
const len2 = arr2.length
for (let i = 0; i < len2; i++) {
const key = arr2[i]
const gotVal = map.get(key)
if (!gotVal) {
map.set(key, { value: arr2[i], count: 1 })
} else {
gotVal.count += 1
}
}
const ans: T[] = []
for (const data of map.values()) {
if (data.count > 1) {
ans.push(data.value)
}
}
return ans
}
function union<T>(arr1: T[], arr2: T[]): T[] {
const map = new Map<any, T>()
const len1 = arr1.length
for (let i = 0; i < len1; i++) {
const key = arr1[i]
if (!map.has(key)) {
map.set(key, arr1[i])
}
}
const len2 = arr2.length
for (let i = 0; i < len2; i++) {
const key = arr2[i]
if (!map.has(key)) {
map.set(key, arr2[i])
}
}
return [...map.values()]
}
function difference<T>(arr1: T[], arr2: T[]): T[] {
const map = new Map<any, { value: T; count: number }>()
const len1 = arr1.length
for (let i = 0; i < len1; i++) {
const key = arr1[i]
if (!map.has(key)) {
map.set(key, { value: arr1[i], count: 1 })
}
}
const len2 = arr2.length
for (let i = 0; i < len2; i++) {
const key = arr2[i]
const gotVal = map.get(key)
if (gotVal) {
gotVal.count++
}
}
const ans: T[] = []
for (const data of map.values()) {
if (data.count === 1) {
ans.push(data.value)
}
}
return ans
}
const getKeys = (data: TableData[]) => data.filter((e) => !e.disabled).map((e) => e.id)
const pageSize = ref(10)
const page = ref(1)
const supersetWasAllData = ref(false)
const supersetSelectAllRef = computed(() => {
return supersetWasAllData.value ? 'all' : 'current'
})
const tableRef = ref<InstanceType<typeof Table> | null>(null)
const selectedKeys = ref<number[]>([])
const currentPageSelectAll = ref(false)
const crossPageSelectAll = ref(false)
watch([page, pageSize, selectedKeys], () => {
nextTick(() => {
const currentData = tableRef.value?.getCurrentData() || []
const paginatedData = tableRef.value?.getPaginatedData() || []
currentPageSelectAll.value =
difference(getKeys(paginatedData), selectedKeys.value).length === 0
crossPageSelectAll.value = difference(getKeys(currentData), selectedKeys.value).length === 0
})
})
const selectAllMethod = async (
value: boolean,
preState: { value: boolean; indeterminate: boolean },
extra: {
originData: TableData[]
currentData: TableData[]
paginatedData: TableData[]
selectedKeys: any[]
page: number
pageSize: number
}
) => {
const paginatedDataKeys = getKeys(extra.paginatedData)
const currentDataKeys = getKeys(extra.currentData)
let selectedKeys: any[] = []
const clearPageHandler = (dialogReturn: DialogReturn) => {
dialogReturn.close()
selectedKeys = difference(extra.selectedKeys, paginatedDataKeys)
}
const pageOnlyHandler = (dialogReturn: DialogReturn) => {
dialogReturn.close()
selectedKeys = getKeys(extra.paginatedData)
}
const pageAddHandler = (dialogReturn: DialogReturn) => {
dialogReturn.close()
selectedKeys = union(extra.selectedKeys, paginatedDataKeys)
}
const selectAllHandler = (dialogReturn: DialogReturn) => {
dialogReturn.close()
selectedKeys = union(extra.selectedKeys, currentDataKeys)
}
const clearAllHandler = (dialogReturn: DialogReturn) => {
dialogReturn.close()
selectedKeys = difference(extra.selectedKeys, currentDataKeys)
}
let dialogReturn: DialogReturn
if (value) {
const currentPageHasIntersection =
extra.selectedKeys.length &&
intersection(paginatedDataKeys, extra.selectedKeys).length > 0
const allDataHasIntersection =
supersetWasAllData.value &&
extra.selectedKeys.length &&
intersection(currentDataKeys, extra.selectedKeys).length > 0
dialogReturn = Dialog.warning({
title: 'Select All',
content: 'Select one action to perform:',
footer: () => {
return h('div', {}, [
currentPageHasIntersection && allDataHasIntersection
? h(
'div',
{
style:
'display: flex; justify-content: end; align-items: center; gap: 8px; margin-bottom: 8px'
},
{
default: () => [
currentPageHasIntersection
? h(
Button,
{
theme: 'info',
onClick: () => clearPageHandler(dialogReturn),
pollSizeChange: true
},
{ default: () => 'Clear Page' }
)
: null,
allDataHasIntersection
? h(
Button,
{
theme: 'warning',
onClick: () => clearAllHandler(dialogReturn),
pollSizeChange: true
},
{ default: () => 'Clear All' }
)
: null
]
}
)
: null,
h(
'div',
{ style: 'display: flex; justify-content: end; align-items: center; gap: 8px;' },
{
default: () => [
h(
Button,
{
theme: 'info',
onClick: () => pageOnlyHandler(dialogReturn),
pollSizeChange: true
},
{ default: () => 'This Page Only' }
),
h(
Button,
{
theme: 'primary',
onClick: () => pageAddHandler(dialogReturn),
pollSizeChange: true
},
{ default: () => 'Add This Page' }
),
h(
Button,
{
theme: 'primary',
onClick: () => selectAllHandler(dialogReturn),
pollSizeChange: true
},
{ default: () => 'Select All' }
)
]
}
)
])
}
})
} else {
dialogReturn = Dialog.warning({
title: 'Clear Select All',
content: 'Select one action to perform:',
footer: () => {
return h(
'div',
{ style: 'display: flex; justify-content: end; align-items: center; gap: 8px' },
{
default: () => [
h(
Button,
{
theme: 'info',
onClick: () => clearPageHandler(dialogReturn),
pollSizeChange: true
},
{ default: () => 'Clear Page' }
),
h(
Button,
{
theme: 'warning',
onClick: () => clearAllHandler(dialogReturn),
pollSizeChange: true
},
{ default: () => 'Clear All' }
)
]
}
)
}
})
}
await dialogReturn
return selectedKeys
}
const columns = [
{
key: 'id',
label: 'ID',
field: 'id'
},
{
key: 'user',
label: 'User',
field: 'user'
},
{
key: 'email',
label: 'Email',
field: 'email'
},
{
key: 'register',
label: 'Register',
field: 'register'
}
]
const firstNames = [
'John',
'Emma',
'Michael',
'Sarah',
'David',
'Lisa',
'Robert',
'Jennifer',
'William',
'Jessica',
'James',
'Amanda',
'Christopher',
'Melissa',
'Daniel',
'Stephanie',
'Matthew',
'Laura',
'Joshua',
'Michelle'
]
const lastNames = [
'Smith',
'Johnson',
'Williams',
'Brown',
'Jones',
'Miller',
'Davis',
'Garcia',
'Rodriguez',
'Wilson',
'Martinez',
'Anderson',
'Taylor',
'Thomas',
'Jackson',
'White',
'Harris',
'Martin',
'Thompson',
'Moore'
]
function generateRandomData(count: number) {
const data: any[] = []
const currentDate = new Date()
for (let i = 1; i <= count; i++) {
const firstName = firstNames[Math.floor(Math.random() * firstNames.length)]
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)]
const fullName = `${firstName} ${lastName}`
const email = `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com`
const randomDaysAgo = Math.floor(Math.random() * 365)
const registerDate = new Date(currentDate)
registerDate.setDate(registerDate.getDate() - randomDaysAgo)
const registerDateStr = registerDate.toISOString().split('T')[0]
data.push({
id: i,
user: fullName,
email: email,
register: registerDateStr
})
}
return data
}
const data = ref(generateRandomData(50))
</script>API
TableProps
| Attribute | Type | Optional | Default | Description | Version |
|---|---|---|---|---|---|
| data | TableData[] | True | [] | The table's data source. | 0.1.0 |
| columns | TableColumn[] | True | [] | The table's column configuration collection. | 0.1.0 |
| bordered | boolean | TableBordered | True | true | Whether to display table borders and related styles. | 0.1.0 |
| variant | 'normal' | 'striped' | 'checkered' | True | 'normal' | The table's display variant/style. | 0.1.0 |
| fixedHead | boolean | True | true | Whether the header is fixed. | 0.1.0 |
| spanMethod | (options: TableOptionsArg) => void | { colspan?: number, rowspan?: number} | True | | The method used to merge rows or columns. | 0.1.0 |
| rowKey | string | True | 'key' | The key name used to uniquely identify row data. | 0.1.0 |
| scroll | { x?: number | string } | True | | Table scroll configuration (e.g., horizontal scroll settings). | 0.1.0 |
| selection | boolean | TableSelection | True | false | Whether row selection is enabled and its configuration. | 0.1.0 |
| selectedKeys | any[] | null | True | | Currently selected row keys (controlled). | 0.1.0 |
| defaultSelectedKeys | any[] | null | True | | Default selected row keys (uncontrolled). | 0.1.0 |
| expandable | boolean | TableExpandable | True | false | Whether expandable rows are enabled and its configuration. | 0.1.0 |
| expandedKeys | any[] | null | True | | Currently expanded row keys (controlled). | 0.1.0 |
| defaultExpandedKeys | any[] | null | True | | Default expanded row keys (uncontrolled). | 0.1.0 |
| summary | TableSummary | True | | Configuration for the table's summary row. | 0.1.0 |
| filterValue | FilterValue | null | True | | Current filter values (controlled). | 0.1.0 |
| defaultFilterValue | FilterValue | null | True | | Default filter values (uncontrolled). | 0.1.0 |
| sortOrder | SortOrder | null | True | | Current sorting information (controlled). | 0.1.0 |
| defaultSortOrder | SortOrder | null | True | | Default sorting information (uncontrolled). | 0.1.0 |
| loading | boolean | True | false | Whether table is loading. | 0.1.0 |
| pagination | TablePagination | True | true | Configuration for table pagination. | 0.1.0 |
| page | number | null | True | | Current page number (controlled mode). | 0.1.0 |
| defaultPage | number | null | True | 1 | Default value of current page number (uncontrolled mode). | 0.1.0 |
| pageSize | number | null | True | | Current page size (controlled mode). | 0.1.0 |
| defaultPageSize | number | null | True | 10 | Default value of current page size (uncontrolled mode). | 0.1.0 |
| tableAreaProps | RestAttrs | True | | A table area property object. The element it acts on can be found via .px-table-area. | 0.1.0 |
| borderRadius | number | True | | Table border-radius settings. | 0.1.0 |
| pollSizeChange | boolean | True | | Enable polling for component size changes. This may affect performance and is commonly used when a container element affects the component's size, causing canvas rendering issues. This prop also applies to internal Radio, Checkbox and Pagination child components. | 0.1.0 |
TableEvents
| Event | Parameter | Description | Version |
|---|---|---|---|
| update:selectedKey | value: any[] | Callback when selectedKeys is updated. | 0.1.0 |
| select | value: boolean, key: any, record: TableData, event: InputEvent | Callback when a row is selected. | 0.1.0 |
| selectAll | value: boolean, event: InputEvent | Callback when select-all is triggered. | 0.1.0 |
| selectedChange | value: any[] | 0.1.0 | |
| update:expandedKeys | value: any[] | Callback when expandedKeys is updated. | 0.1.0 |
| expand | value: boolean, key: any, record: TableData, event: MouseEvent | Callback when a row is expanded. | 0.1.0 |
| expandedChange | value: any[] | Callback when the expanded rows change. | 0.1.0 |
| update:filterValue | value: FilterValue | Callback when filterValue is updated. | 0.1.0 |
| filterSelect | value: any[], key: string | number | symbol, option: TableFilterOption | string, column: TableColumn, event: InputEvent | Callback when an option in the filter is selected. | 0.1.0 |
| filterConfirm | key: string | number | symbol, event: MouseEvent | Callback when filter selection is confirmed. | 0.1.0 |
| filterReset | key: string | number | symbol, event: MouseEvent | Callback when filter selection is reset. | 0.1.0 |
| filterChange | value: FilterValue | Callback when the filter selection changes. | 0.1.0 |
| update:sortOrder | value: SortOrder | Callback when sortOrder is updated. | 0.1.0 |
| sortSelect | value: 'asc' | 'desc' | 'none', key: string | number | symbol, column: TableColumn, event: MouseEvent | Callback when a sort direction is selected. | 0.1.0 |
| sortOrderChange | value: SortOrder | Callback when the sort order changes. | 0.1.0 |
| cellMouseenter | column: TableColumn, record: TableData, colIndex: number, rowIndex: number, event: MouseEvent | Callback when the mouse enters a cell. | 0.1.0 |
| cellMouseleave | column: TableColumn, record: TableData, colIndex: number, rowIndex: number, event: MouseEvent | Callback when the mouse leaves a cell. | 0.1.0 |
| cellClick | column: TableColumn, record: TableData, colIndex: number, rowIndex: number, event: MouseEvent | Callback when a cell in the data area is clicked. | 0.1.0 |
| cellDblclick | column: TableColumn, record: TableData, colIndex: number, rowIndex: number, event: MouseEvent | Callback when a cell in the data area is double-clicked. | 0.1.0 |
| cellContextmenu | column: TableColumn, record: TableData, colIndex: number, rowIndex: number, event: MouseEvent | Callback when a cell in the data area is right-clicked (context menu). | 0.1.0 |
| headCellClick | column: TableColumn, indexPath: number[], event: MouseEvent | Callback when a header cell is clicked. | 0.1.0 |
| headCellDblclick | column: TableColumn, indexPath: number[], event: MouseEvent | Callback when a header cell is double-clicked. | 0.1.0 |
| headCellContextmenu | column: TableColumn, indexPath: number[], event: MouseEvent | Callback when a header cell is right-clicked (context menu). | 0.1.0 |
| rowClick | record: TableData, rowIndex: number, event: MouseEvent | Callback when a row in the data area is clicked. | 0.1.0 |
| rowDblclick | record: TableData, rowIndex: number, event: MouseEvent | Callback when a row in the data area is double-clicked. | 0.1.0 |
| rowContextmenu | record: TableData, rowIndex: number, event: MouseEvent | Callback when a row in the data area is right-clicked (context menu). | 0.1.0 |
| update:page | value: number | Callback for updating page. | 0.1.0 |
| update:pageSize | value: number | Callback for updating pageSize. | 0.1.0 |
TableSlots
| Slot | Parameter | Description | Version |
|---|---|---|---|
| [columns[].labelSlotName] | rowIndex: number, colIndex: number, column: TableColumn | Slot for custom content in header cells. | 0.1.0 |
| [columns[].slotName] | colIndex: number, rowIndex: number, column: TableColumn, record: TableData | Slot for custom content in table cells. | 0.1.0 |
| expand | rowIndex: number, record: TableData | Slot for expanded row content. | 0.1.0 |
TableExpose
| Attribute | Type | Optional | Default | Description | Version |
|---|---|---|---|---|---|
| getCurrentData | () => TableData[] | False | | Get the currently displayed data of the table after sorting and filtering. | 0.1.0 |
| getPaginatedData | () => TableData[] | False | | Get the currently displayed data of the table after pagination. | 0.1.0 |
| select | (key: any | any[], value: boolean) => Promise<void> | False | | Handle the selection state of table rows. | 0.1.0 |
| selectAll | (value: boolean, crossPage?: boolean, ignoreDisabled?: boolean) => Promise<void> | False | | Select all/deselect all table rows. crossPage indicates cross-page selection, default is false. ignoreDisabled controls whether to ignore rows with a disabled state, default is true. | 0.1.0 |
| clearSelect | () => Promise<void> | False | | Clear the selection state of all rows. | 0.1.0 |
| expand | (key: any | any[], value: boolean) => Promise<void> | False | | Handle the expand/collapse state of table rows. | 0.1.0 |
| clearExpand | () => Promise<void> | False | | Clear the expanded/collapsed state of all rows. | 0.1.0 |
| filter | (key: number | string | symbol, value: any[]) => Promise<void> | False | | Handle the filter state of table columns. | 0.1.0 |
| clearFilter | () => Promise<void> | False | | Clear the filter state of all columns. | 0.1.0 |
| sort | (key: number | string | symbol, value: 'none' | 'asc' | 'desc') => Promise<void> | False | | Handle the sort state of table columns. | 0.1.0 |
| clearSort | () => Promise<void> | False | | Clear the sort state of all columns. | 0.1.0 |
TableData
| Attribute | Type | Optional | Default | Description | Version |
|---|---|---|---|---|---|
| expand | boolean | string | (( arg: Pick<TableOptionsArg, 'record' | 'rowIndex'>) => VNode | string | JSX.Element | null | void) | True | | Configuration for expanded row content. | 0.1.0 |
| disabled | boolean | True | false | Whether row selection is disabled. | 0.1.0 |
| [x: string | number | symbol] | any | True | | Other properties. | 0.1.0 |
TableColumn
| Attribute | Type | Optional | Default | Description | Version |
|---|---|---|---|---|---|
| key | number | string | symbol | False | | Unique identifier for the column. | 0.1.0 |
| label | string | True | | Text displayed in the column header. | 0.1.0 |
| field | string | True | | The data field name corresponding to the cell. | 0.1.0 |
| width | number | True | 80 | Column width setting. | 0.1.0 |
| minWidth | number | True | 0 | Minimum width setting for the column. | 0.1.0 |
| align | 'left' | 'center' | 'right' | True | 'left' | Cell text alignment. | 0.1.0 |
| fixed | 'left' | 'right' | 'none' | True | 'none' | Column fixed position setting. | 0.1.0 |
| slotName | string | True | | Slot name for cell content. | 0.1.0 |
| render | string | ((arg: TableOptionsArg) => VNode | string | JSX.Element | null | void) | True | | Cell content render function or name. | 0.1.0 |
| labelSlotName | string | True | | Slot name for header cell content. | 0.1.0 |
| labelRender | string | ((arg: Omit<TableOptionsArg, 'record'>) => VNode | string | JSX.Element | null | void) | True | | Header cell content render function or name. | 0.1.0 |
| children | TableColumn[] | True | | Collection of child columns, used for multi-level headers. | 0.1.0 |
| filterable | TableFilterable | True | | Filter configuration for the column. | 0.1.0 |
| sortable | TableSortable | True | | Sort configuration for the column. | 0.1.0 |
| cellProps | RestAttrs | True | | Cell attributes object. | 0.1.0 |
| labelCellProps | RestAttrs | True | | Header cell attributes object. | 0.1.0 |
| contentProps | RestAttrs | True | | Cell content attributes object. | 0.1.0 |
| labelContentProps | RestAttrs | True | | Header content attributes object. | 0.1.0 |
TableBordered
| Attribute | Type | Optional | Default | Description | Version |
|---|---|---|---|---|---|
| table | boolean | True | true | Table outer border. | 0.1.0 |
| row | boolean | True | true | Table row horizontal border. | 0.1.0 |
| col | boolean | True | true | Table column vertical border. | 0.1.0 |
| head | boolean | True | true | Border between the header area and the content area. | 0.1.0 |
| side | boolean | True | true | Borders on the left and right sides of the table; only takes effect when bordered.table or bordered is true. | 0.1.0 |
TableSelection
| Attribute | Type | Optional | Default | Description | Version |
|---|---|---|---|---|---|
| multiple | boolean | True | false | Whether multiple selection is enabled. | 0.1.0 |
| showSelectAll | boolean | True | true | Whether to show the select-all button; only effective when selection.multiple is true. | 0.1.0 |
| selectAllMethod | (value: boolean, preState: { value: boolean, indeterminate: boolean }, arg: { originData: TableData[], currentData: TableData[], paginatedData: TableData[], page: number, pageSize: number }) => any[] | Promise<any[]> | True | | Customizes the selectedKeys value updated during a select‑all operation. | 0.1.0 |
| supersetSelectAllRef | 'current' | 'all' | True | 'current' | Configures the superset referenced for the select‑all checkbox's checked status. | 0.1.0 |
| label | string | True | | Header text for the row selection column. | 0.1.0 |
| width | number | True | 48 | Width of the row selection column. | 0.1.0 |
| minWidth | number | True | 0 | Minimum width of the row selection column. | 0.1.0 |
| fixed | boolean | True | false | Whether the row selection column is fixed; forced to the left when there are left-fixed columns. | 0.1.0 |
| onlyCurrent | boolean | True | false | Whether selected rows should only include rows in the current data prop. | 0.1.0 |
| cellProps | RestAttrs | True | | Column cell properties object. | 0.1.0 |
| labelCellProps | RestAttrs | True | | Column header cell properties object. | 0.1.0 |
| contentProps | RestAttrs | True | | Column cell content properties object. | 0.1.0 |
| labelContentProps | RestAttrs | True | | Column header content properties object. | 0.1.0 |
TableExpandable
| Attribute | Type | Optional | Default | Description | Version |
|---|---|---|---|---|---|
| defaultExpandAllRows | boolean | True | false | Whether all rows are expanded by default. | 0.1.0 |
| label | string | True | | Header text for the expand-button column. | 0.1.0 |
| width | number | True | 48 | Width of the expand-button column. | 0.1.0 |
| minWidth | number | True | 0 | Minimum width of the expand-button column. | 0.1.0 |
| fixed | boolean | True | false | Whether the expand-button column is fixed; forced to the left when there are left-fixed columns. | 0.1.0 |
| cellProps | RestAttrs | True | | Column cell properties object. | 0.1.0 |
| labelCellProps | RestAttrs | True | | Column header cell properties object. | 0.1.0 |
| contentProps | RestAttrs | True | | Column cell content properties object. | 0.1.0 |
| labelContentProps | RestAttrs | True | | Column header content properties object. | 0.1.0 |
TableSummary
| Attribute | Type | Optional | Default | Description | Version |
|---|---|---|---|---|---|
| data | TableData | TableData[] | True | | Summary row data. | 0.1.0 |
| placement | 'end' | 'start' | True | 'end' | Placement of the summary row. | 0.1.0 |
| summaryText | string | string[] | True | | Text for the first column of the summary row; if an array, applies to the corresponding indexed summary row; if a string, applies to all summary rows. | 0.1.0 |
| fixed | boolean | True | true | Whether the summary row is fixed; when the summary row is placed at the start of the data area, fixing the summary row requires fixedHead to be true. | 0.1.0 |
| spanMethod | (options: TableOptionsArg) => void | { colspan?: number, rowspan?: number } | True | | Method for merging cells in the summary row. | 0.1.0 |
TableFilterable
| Attribute | Type | Optional | Default | Description | Version |
|---|---|---|---|---|---|
| filterOptions | (string | TableFilterOption)[] | True | | Filter options for the column. | 0.1.0 |
| filterMethod | (filteredValue: any[], record: TableData, field?: string) => boolean | True | | Function to determine whether a record matches the selected filters. | 0.1.0 |
| defaultFilterValue | any[] | null | True | | Default filter values. | 0.1.0 |
| multiple | boolean | True | false | Whether multiple filter options can be selected. | 0.1.0 |
| popoverProps | Omit<PopoverProps, 'visible' | 'content'> & EmitEvent<PopoverEvents> | True | | Additional popover properties for the filter. | 0.1.0 |
TableSortable
| Attribute | Type | Optional | Default | Description | Version |
|---|---|---|---|---|---|
| orders | ('asc' | 'desc')[] | Readonly<('asc' | 'desc')[]> | True | | Sort directions available for the column. | 0.1.0 |
| sortMethod | 'custom' | ((a: TableData, b: TableData, order: 'asc' | 'desc', field?: string) => number) | True | | Sort method for the column. | 0.1.0 |
| defaultSortOrder | 'asc' | 'desc' | 'none' | null | True | | Default sort direction for the column. | 0.1.0 |
| multiple | boolean | True | false | Whether multi-column sorting is enabled. | 0.1.0 |
| priority | number | True | 0 | Priority for multi-column sorting; higher values take precedence. | 0.1.0 |
TablePagination
The paginateMethod field controls the pagination method inside the table component. When set to 'auto', the table will paginate the data based on the page capacity. When set to 'custom', no such behavior will occur; this configuration is often used for backend pagination.
export type TablePagination = {
paginateMethod?: 'custom' | 'auto'
} & PaginationProps &
EmitEvent<PaginationEvents> &
RestAttrsTableOptionsArg
export type TableOptionsArg = {
rowIndex: number
colIndex: number
record: TableData
column: TableColumn
}Option
export interface Option<T = any> {
value: T
label: string
}TableFilterOption
export interface TableFilterOption<T = any> extends Option<T> {
disabled?: boolean
key?: string | number | symbol
}SortOrder, FilterValue
export type SortOrder = {
[key: string | number | symbol]: 'asc' | 'desc' | 'none' | null | undefined
}
export type FilterValue = {
[key: string | number | symbol]: any[] | null | undefined
}RestAttrs
import type { StyleValue } from 'vue'
export type VueClassValue = string | Record<string, any> | VueClassValue[]
export type VueStyleValue = StyleValue
export type RestAttrs = {
style?: VueStyleValue | null
class?: VueClassValue | null
[x: string]: any
}EmitEvent
export type EmitEvent<T extends Record<string, any>> = {
[K in keyof T as `on${Capitalize<K & string>}`]?: (...args: T[K]) => void
}