WeChat Mini Program Native Table Component: Fixed Header, Adaptive Column Width, Cell Click Support and Zebra Striping
FixedHeaderScrollTable
A native WeChat Mini Program table component built with fixed sticky headers, automatic adaptive column widths, cell-level click event callbacks, and alternating zebra stripe row styling.
Update Log
2024-06-06:
- Fixed layout conflict bug that prevented the table from rendering normally alongside other UI components on a single page.
Core Features
- Fixed sticky header: The header bar remains pinned to the top of the viewport during vertical scrolling, giving users constant column context when browsing large datasets.
- Cell-level click callback: Triggers a custom event when any non-header cell is tapped, pasing corresponding row and column indices for targeted business logic execution.
- Dual-axis scroll support: Table content supports both horizontal and vertical scrolling to fit datasets with large numbers of columns or rows.
- Adaptive column width: Minimum column width is automatically calculated based on the longest header text, ensuring consistent alignment between header and data cells.
- Zebra stripe styling: Alternating row background colors are applied by default to improve data readability for long tables.
- Customizable configurations: Supports custom header background colors and placeholder text for empty cells.
Component Properties
| Property | Type | Default | Required | Description |
|---|---|---|---|---|
| column-titles | Array | [] | Yes | Array of header column display names |
| dataset | Array | [] | Yes | Two-dimensional array of table row data |
| bind:cellclick | eventhandle | - | Yes | Callback triggered on non-header cell tap. e.detail.row returns the row index, e.detail.col returns the column index of the tapped cell |
| enable-zebra-stripe | Boolean | true | No | Toggle zebra stripe alternating row backgrounds |
| enable-cell-border | Boolean | false | No | Toggle cell border display |
| header-bg-color | String | #2d66cf |
No | Custom header background color |
| empty-cell-placeholder | String | N/A |
No | Placeholder text displayed for empty cells |
Component Source Code
1. FixedHeaderScrollTable.js
// components/FixedHeaderScrollTable/FixedHeaderScrollTable.js
Component({
properties: {
columnTitles: {
type: Array,
value: []
},
dataset: {
type: Array,
value: []
},
emptyCellPlaceholder: {
type: String,
value: 'N/A'
},
enableZebraStripe: {
type: Boolean,
value: true
},
enableCellBorder: {
type: Boolean,
value: false
},
headerBgColor: {
type: String,
value: '#2d66cf'
}
},
data: {
minColWidth: 75,
headerBarHeight: 2.8,
headerScrollOffset: 0
},
methods: {
calculateMinColWidth: function (colTitles) {
const horizontalPadding = 10;
const perCharWidth = 14;
const maxTitleLength = colTitles.reduce((maxLen, currentTitle) => {
return Math.max(maxLen, currentTitle.length)
}, 0);
return horizontalPadding + perCharWidth * maxTitleLength;
},
syncHeaderScroll: function (e) {
const currentScrollOffset = e.detail.scrollLeft;
this.setData({
headerScrollOffset: currentScrollOffset
});
},
handleCellTap: function (e) {
this.triggerEvent('cellclick', e.currentTarget.dataset);
}
},
ready: function () {
const computedMinWidth = this.calculateMinColWidth(this.data.columnTitles);
this.setData({
minColWidth: computedMinWidth
});
}
})
2. FixedHeaderScrollTable.json
{
"component": true,
"styleIsolation": "apply-shared",
"usingComponents": {}
}
3. FixedHeaderScrollTable.wxml
<!--components/FixedHeaderScrollTable/FixedHeaderScrollTable.wxml-->
<scroll-view
class="header-wrapper"
scroll-x="true"
scroll-left="{{headerScrollOffset}}"
style="pointer-events: none; position: sticky; top: 0; z-index: 99;"
>
<view class="table-row">
<view
class="header-cell"
style="min-width: {{minColWidth}}px; {{enableCellBorder ? 'border: 0.1px solid #333;' : ''}} background: {{headerBgColor}}"
wx:for="{{columnTitles}}"
wx:key="*this"
wx:for-item="title"
>
<text>{{title}}</text>
</view>
</view>
</scroll-view>
<scroll-view
class="table-content"
wx:if="{{columnTitles.length > 0}}"
scroll-x="true"
bindscroll="syncHeaderScroll"
style="padding-top: {{headerBarHeight}}rem;"
>
<view
class="table-row {{enableZebraStripe ? 'zebra-row' : ''}}"
wx:for="{{dataset}}"
wx:key="*this"
wx:for-index="rowIdx"
wx:for-item="rowData"
>
<view
class="data-cell"
style="min-width: {{minColWidth}}px; {{enableCellBorder ? 'border: 0.1px solid #333;' : ''}} {{enableZebraStripe && rowIdx % 2 === 1 ? 'background: #f5f5f5;' : ''}}"
wx:for="{{rowData}}"
wx:key="*this"
wx:for-item="cellData"
wx:for-index="colIdx"
bindtap="handleCellTap"
data-row="{{rowIdx}}"
data-col="{{colIdx}}"
>
{{cellData.length === 0 ? emptyCellPlaceholder : cellData}}
</view>
</view>
</scroll-view>
4. FixedHeaderScrollTable.wxss
/* components/FixedHeaderScrollTable/FixedHeaderScrollTable.wxss */
.table-row {
display: flex;
align-items: center;
height: 2.8rem;
width: 100%;
}
.header-cell, .data-cell {
display: flex;
justify-content: center;
align-items: center;
padding: 0 12px;
height: 100%;
}
.header-cell {
color: #ffffff;
font-size: 14px;
font-weight: 500;
}
.zebra-row {
background: #ffffff;
}
.zebra-row:nth-child(2n) {
background: #f5f5f5;
}
.data-cell {
font-size: 13px;
color: #333333;
}
Usage Example
Place all 4 source files in a folder named FixedHeaderScrollTable under your project component directory, then register the component in the page configuration file.
page.json
{
"usingComponents": {
"fixed-header-table": "./components/FixedHeaderScrollTable"
}
}
page.wxml
<fixed-header-table
column-titles="{{columnList}}"
dataset="{{tableData}}"
bind:cellclick="handleTableCellClick"
enable-zebra-stripe="{{useZebraStyle}}"
enable-cell-border="{{showCellBorder}}"
header-bg-color="{{customHeaderColor}}"
empty-cell-placeholder="--"
/>
page.js
Page({
data: {
useZebraStyle: true,
showCellBorder: false,
customHeaderColor: '#2d66cf',
columnList: ['Product ID', 'Product Name', 'Stock Count', 'Unit Price'],
tableData: [
['1001', 'Wireless Headphone', '246', '$89.9'],
['1002', 'Portable Charger', '120', '$29.9'],
['1003', 'Bluetooth Speaker', '87', '$45.9'],
['1004', 'Smart Watch', '53', '$159.9'],
['1005', 'Phone Case', '312', '$12.9'],
['1006', 'Screen Protector', '420', '$8.9'],
['1007', 'Laptop Stand', '76', '$24.9'],
['1008', 'Mechanical Keyboard', '49', '$79.9'],
['1009', 'Gaming Mouse', '68', '$39.9'],
['1010', 'Webcam', '32', '$59.9'],
['1011', 'USB-C Cable', '520', '$6.9'],
['1012', 'Wireless Charger', '94', '$32.9'],
['1013', 'Tablet Case', '41', '$19.9'],
['1014', 'Earbud Tips', '290', '$4.9']
]
},
handleTableCellClick: function (e) {
console.log('Tapped cell row index:', e.detail.row, ' | column index:', e.detail.col);
}
})
Notes
- This component only supports native WeChat Mini Program environments, no cross-platform compatibility is guaranteed.
- Multi-level nested headers are not supported in the current version.
- No default header tap event handler is provided out of the box.