Product-grade HTML table framework for BI dashboards.
Not just a table. A product.
Version: 2.0.0
Most BI report frameworks use off-the-shelf component libraries — Ant Design, Element Plus, VxeTable. They work. But they all look the same. A grid of white cells that screams "this is a tool, not a product."
DarkTable is different. It treats every report as a product — something you'd be proud to sell, not just something that works.
| Typical BI Table | DarkTable | |
|---|---|---|
| Visual | Template-driven, looks like every other dashboard | Dark starfield theme with gradient color coding |
| Interact | Scrollbar or mouse wheel only | Drag-to-scroll with grab/grabbing cursor (like a native app) |
| Click | Nothing, or accidental selection | Click-to-copy with 5px threshold (drag < 5px = copy, ≥ 5px = scroll) |
| Data density | Walls of numbers | 3-stop gradient progress bars + status badges + color-coded values |
| Personality | None | Unique by design — competitors can't just change a few CSS variables |
Every report is a product someone paid for. It must have:
- Visual identity — recognizable at a glance
- Interaction quality — feels good to use
- Information hierarchy — not just data, but insight
Pure HTML <table> + CSS + vanilla JS. No VxeTable, no AG Grid, no Ant Design Table.
Why?
- Full control over every pixel and behavior
- Zero breaking changes from upstream updates
- Tiny bundle size (~5KB gzipped vs 200KB+ for alternatives)
- Features like drag-to-scroll + click-to-copy require controlling the DOM directly
All reports follow a consistent structure:
report-page
├── top-bar (title + actions)
├── filter-card (optional filters)
├── error-banner (error messages)
└── table-card
├── table-info (count + hints)
├── table-scroll (drag-to-scroll + sticky header)
└── pagination-bar (page controls)
┌─────────────────────────────────────────────────┐
│ DarkTable.vue │ ← Generic component
├─────────────────────────────────────────────────┤
│ Props: │
│ title, columns, data, loading │ ← Declarative API
│ serverSide, totalCount, pageSize │ ← Server-side pagination
│ rowStyle, rowIndex, errorMsg │ ← Row customization
├─────────────────────────────────────────────────┤
│ Built-in: │
│ Drag-to-scroll (5px threshold) │
│ Click-to-copy (with toast) │
│ Column width drag resize │
│ Column order drag & drop │
│ Starfield canvas animation │
│ Sticky header + pagination │
│ Progress bars (3-stop gradient) │
│ Status badges (configurable color map) │
│ Client-side / Server-side sorting │
│ Multi-contract row highlighting (CSS vars) │
├─────────────────────────────────────────────────┤
│ Events: @export, @page-change, @update:pageSize│
├─────────────────────────────────────────────────┤
│ Slots: │
│ #header-extra (extra buttons/switchers) │
│ #filter (custom filters) │
│ #cell-{field} (custom cell render) │
└─────────────────────────────────────────────────┘
<template>
<DarkTable
title="Sales Report"
:columns="[
{ field: 'region', title: 'Region' },
{ field: 'revenue', title: 'Revenue', type: 'money' },
{ field: 'growth', title: 'Growth', type: 'progress', sortable: true },
{ field: 'status', title: 'Status', type: 'badge', badgeMap: { active: 'dt-badge-green' } },
{ field: 'notes', title: 'Notes', type: 'custom', className: 'col-num' },
]"
:data="tableData"
:loading="loading"
:total-count="pagination.total"
:server-side="true"
:row-style="rowStyle"
:row-index="groupedIndex"
@export="handleExport"
@page-change="onPageChange"
@update:page-size="onPageSizeChange"
>
<template #filter>
<!-- Custom filter controls -->
</template>
<template #cell-notes="{ row }">
<span :class="{ 'dt-text-red': row.urgent }">{{ row.notes }}</span>
</template>
</DarkTable>
</template>Dark theme by default.
| Element | Dark |
|---|---|
| Background | #0b1121 → #0f172a → #0c1328 gradient |
| Card | rgba(15,23,42,.85) + blur |
| Text | #e2e8f0 |
| Accent | #38BDF8 (cyan) |
Progress bar color coding:
- ≥ 80%: Green gradient
#22c55e → #4ade80 - ≥ 60%: Yellow gradient
#f59e0b → #fbbf24 - < 60%: Red gradient
#ef4444 → #f87171
| type | Renders as | Example |
|---|---|---|
text |
Plain text | "East Region" |
num |
Right-aligned number | 1,352 |
money |
Currency formatted | $1,234.00 |
progress |
Gradient progress bar + % | ████ 85% |
badge |
Colored badge (configurable map) | ● Active |
date |
Formatted date | 2026-01-15 |
custom |
Slot-based overlay | Your own template |
Column options:
| Option | Type | Description |
|---|---|---|
sortable |
boolean | Enable client-side sorting (3-state: ↕→▲→▼) |
decimals |
number | Decimal places for num/money type |
badgeMap |
Record | Status → CSS class mapping for badge type |
mono |
boolean | Monospace font (e.g. contract numbers) |
className |
string | Additional CSS class on <td> (e.g. 'col-num') |
| Feature | v1.0 | v2.0 |
|---|---|---|
| Column width drag resize | ❌ | ✅ |
| Column order drag & drop | ❌ | ✅ |
Server-side pagination (serverSide, totalCount) |
❌ | ✅ |
Custom row style (rowStyle) |
❌ | ✅ |
Custom row index (rowIndex) |
❌ | ✅ |
badgeMap configurable |
❌ hardcoded | ✅ |
mono font option |
❌ | ✅ |
className option |
❌ | ✅ |
| Multi-contract row highlight | ❌ | ✅ |
errorMsg prop |
❌ | ✅ |
showPagination toggle |
❌ | ✅ |
page-change / update:pageSize events |
❌ | ✅ |
| 3-state sort toggle (↕→▲→▼→↕) | 2-state | ✅ |
| Toast animation (smooth ease-out) | janky | ✅ |
MIT — use it, sell it, build on it. Just keep the attribution.
Born from a real commercial real-estate BI system. Battle-tested with 5 reports × 50+ columns × 500+ rows.