Appearance
@vibe-labs/design-components-tables
Table component tokens, styles, and TypeScript types for the Vibe Design System.
Installation
css
@import "@vibe-labs/design-components-tables";ts
import { TableVariants, TableSizes, TableAlignments, TableSortDirections } from "@vibe-labs/design-components-tables/types";
import type { TableColumn, TableSortState, TableStyleProps } from "@vibe-labs/design-components-tables/types";Contents
Tokens (tables.css)
CSS custom properties defined in @layer vibe.tokens:
Container
| Token | Default |
|---|---|
--table-bg | --surface-base |
--table-border-color | --border-subtle |
--table-border-width | --border-width-1 |
--table-radius | --radius-lg |
Header
| Token | Default | Description |
|---|---|---|
--table-header-bg | --surface-subtle | Header background |
--table-header-color | --text-muted | Header text color |
--table-header-font-size | --text-xs | Header font size |
--table-header-font-weight | --font-bold | Header font weight |
--table-header-text-transform | uppercase | Header text transform |
--table-header-height-sm | 2rem | Header height (sm) |
--table-header-height-md | 2.5rem | Header height (md) |
--table-header-height-lg | 3rem | Header height (lg) |
--table-header-px | --space-3 | Horizontal padding |
Row
| Token | Default | Description |
|---|---|---|
--table-row-height-sm | 2.25rem | Row height (sm) |
--table-row-height-md | 2.75rem | Row height (md) |
--table-row-height-lg | 3.25rem | Row height (lg) |
--table-row-px | --space-3 | Horizontal padding |
--table-row-border-color | --border-subtle | Row separator color |
--table-row-bg | transparent | Default row background |
--table-row-bg-stripe | --surface-subtle | Alternating row background |
--table-row-hover-bg | --surface-hover-overlay | Hover background |
--table-row-selected-bg | accent @ 8% mix | Selected row background |
--table-row-selected-border | --color-accent | Selected row left accent strip |
Cell
| Token | Default |
|---|---|
--table-cell-px | --space-2 |
--table-cell-font-size-sm | --text-xs |
--table-cell-font-size-md | --text-sm |
--table-cell-font-size-lg | --text-base |
--table-cell-color | --text-primary |
Sort Indicator
| Token | Default |
|---|---|
--table-sort-indicator-color | --text-muted |
--table-sort-indicator-active-color | --color-accent |
--table-sort-indicator-size | 0.75rem |
Empty State
| Token | Default |
|---|---|
--table-empty-py | --space-12 |
--table-empty-color | --text-muted |
--table-empty-font-size | --text-sm |
Footer
| Token | Default |
|---|---|
--table-footer-bg | transparent |
--table-footer-border-color | --border-subtle |
--table-footer-px | --space-3 |
--table-footer-py | --space-2 |
Generated Styles (tables.g.css)
Component classes generated into @layer vibe.components.
Table Container
Flex column with border, radius, and overflow hidden. Supports four variants:
- default — border + background
- outlined — border only, transparent bg
- elevated — no border, shadow elevation
- ghost — no border, no background (rows only)
Size Modifier
data-size="sm|md|lg" controls header height, row height, and cell font size in one attribute.
Flags
- striped — alternating row backgrounds via
:nth-child(even) - hoverable — row hover background transition
- interactive — pointer cursor on rows
- fixed-header — sticky header with z-index
- full — width: 100%
Header Cell
Flex item with uppercase muted text. Sortable columns show a chevron indicator:
[data-sortable]— cursor: pointer, chevron appears on hover[data-sorted="asc"]— chevron rotated up, accent color[data-sorted="desc"]— chevron down, accent color
Row
Flex row with bottom border separator. Supports:
[data-selected]— accent-tinted background + left border accent strip[data-disabled]— reduced opacity, no pointer events- Last row automatically drops bottom border
Cell
Flex item with column padding and alignment:
[data-align="left|center|right"]— horizontal alignment[data-truncate]— ellipsis overflow (with child element support)[data-mono]— monospace font + tabular-nums[data-nowrap]— prevent wrapping without truncation
Empty State
Centred muted text with generous vertical padding for no-data states.
Footer
Flex row with top border. Attachment point for pagination or summary content.
[data-align="left|center|right|between"]— content alignment
TypeScript Types (types/)
ts
// Runtime constants
TableSizes; // ['sm', 'md', 'lg']
TableVariants; // ['default', 'outlined', 'elevated', 'ghost']
TableAlignments; // ['left', 'center', 'right']
TableSortDirections; // ['asc', 'desc']
// Column definition — generic over row type
interface TableColumn<T> {
key: keyof T & string;
label: string;
width?: string;
minWidth?: string;
align?: TableAlignment;
sortable?: boolean;
truncate?: boolean;
mono?: boolean;
hidden?: boolean;
cellClass?: string;
headerClass?: string;
}
// Sort state
interface TableSortState {
key: string;
direction: TableSortDirection;
}
// Row identifier
type TableRowKey<T> = (keyof T & string) | ((item: T) => string | number);
// Per-element style props
interface TableStyleProps {
variant?: TableVariant;
size?: TableSize;
striped?: boolean;
hoverable?: boolean;
interactive?: boolean;
fixedHeader?: boolean;
full?: boolean;
}
interface TableHeaderCellStyleProps {
align?: TableAlignment;
sortable?: boolean;
sorted?: TableSortDirection;
}
interface TableRowStyleProps { selected?: boolean; disabled?: boolean }
interface TableCellStyleProps { align?: TableAlignment; truncate?: boolean; mono?: boolean; nowrap?: boolean }
interface TableFooterStyleProps { align?: string }Dist Structure
| File | Description |
|---|---|
index.css | Barrel — imports tokens + generated styles |
tables.css | Token definitions |
tables.g.css | Generated component styles |
index.js / index.d.ts | TypeScript types + runtime constants |
Dependencies
Requires tokens from @vibe-labs/design (colors, surfaces, borders, spacing, typography, transitions, elevation).
Pagination delegates to @vibe-labs/design-components-pagination — this package does not own --pagination-* tokens.
Build
bash
npm run buildUsage Guide
Import
css
@import "@vibe-labs/design-components-tables";ts
import { TableVariants, TableSizes } from "@vibe-labs/design-components-tables/types";
import type { TableStyleProps, TableColumn } from "@vibe-labs/design-components-tables/types";Variants
| Attribute / Flag | Values | Notes |
|---|---|---|
data-variant | default outlined elevated ghost | Default: default |
data-size | sm md lg | Default: md |
data-striped | boolean flag | Alternating row backgrounds |
data-hoverable | boolean flag | Row hover highlight |
data-interactive | boolean flag | Pointer cursor on rows |
data-fixed-header | boolean flag | Sticky header |
data-full | boolean flag | Width: 100% |
data-selected | on row | Accent-tinted selection |
data-disabled | on row | Reduced opacity, no pointer |
data-sortable | on header cell | Shows sort chevron on hover |
data-sorted | asc desc on header cell | Active sort indicator |
data-align | left center right (+ between on footer) | Content alignment |
data-truncate | on cell | Ellipsis overflow |
data-mono | on cell | Monospace + tabular nums |
data-nowrap | on cell | No text wrapping |
Examples
Basic table
html
<div class="table">
<div class="table-header">
<div class="table-header-cell">Name</div>
<div class="table-header-cell">Role</div>
<div class="table-header-cell">Status</div>
</div>
<div class="table-row">
<div class="table-cell">Alice</div>
<div class="table-cell">Admin</div>
<div class="table-cell">Active</div>
</div>
<div class="table-row">
<div class="table-cell">Bob</div>
<div class="table-cell">User</div>
<div class="table-cell">Inactive</div>
</div>
</div>Striped + hoverable table
html
<div class="table" data-striped data-hoverable data-full>
<div class="table-header">
<div class="table-header-cell">Product</div>
<div class="table-header-cell" data-align="right">Price</div>
<div class="table-header-cell" data-align="right">Qty</div>
</div>
<div class="table-row">
<div class="table-cell">Widget A</div>
<div class="table-cell" data-align="right" data-mono>$12.00</div>
<div class="table-cell" data-align="right">42</div>
</div>
<div class="table-row">
<div class="table-cell">Widget B</div>
<div class="table-cell" data-align="right" data-mono>$8.50</div>
<div class="table-cell" data-align="right">17</div>
</div>
</div>Elevated variant with sortable columns
html
<div class="table" data-variant="elevated" data-size="sm">
<div class="table-header">
<div class="table-header-cell" data-sortable data-sorted="asc">Name</div>
<div class="table-header-cell" data-sortable>Date</div>
<div class="table-header-cell">Actions</div>
</div>
<div class="table-row">
<div class="table-cell">Alice</div>
<div class="table-cell" data-mono>2024-01-15</div>
<div class="table-cell"><button>Edit</button></div>
</div>
</div>Selected and disabled rows
html
<div class="table" data-interactive data-hoverable>
<div class="table-header">
<div class="table-header-cell">Item</div>
<div class="table-header-cell">Value</div>
</div>
<div class="table-row" data-selected>
<div class="table-cell">Selected row</div>
<div class="table-cell">100</div>
</div>
<div class="table-row" data-disabled>
<div class="table-cell">Disabled row</div>
<div class="table-cell">—</div>
</div>
</div>Table with empty state and footer
html
<div class="table" data-full>
<div class="table-header">
<div class="table-header-cell">Name</div>
<div class="table-header-cell">Value</div>
</div>
<div class="table-empty">No results found.</div>
<div class="table-footer" data-align="between">
<span>0 items</span>
<nav class="pagination"><!-- pagination here --></nav>
</div>
</div>Ghost variant
html
<div class="table" data-variant="ghost" data-size="lg">
<div class="table-header">
<div class="table-header-cell">Label</div>
<div class="table-header-cell" data-truncate>Long Description</div>
</div>
<div class="table-row">
<div class="table-cell">Alpha</div>
<div class="table-cell" data-truncate>Very long text that will be truncated with ellipsis...</div>
</div>
</div>With Vue
vue
<template>
<div class="table" :data-variant="variant" :data-size="size" :data-striped="striped || undefined" :data-full="full || undefined">
<div class="table-header">
<div
v-for="col in columns"
:key="col.key"
class="table-header-cell"
:data-align="col.align"
:data-sortable="col.sortable || undefined"
:data-sorted="sortState?.key === col.key ? sortState.direction : undefined"
@click="col.sortable && $emit('sort', col.key)"
>
{{ col.label }}
</div>
</div>
<div v-for="row in rows" :key="row.id" class="table-row">
<div v-for="col in columns" :key="col.key" class="table-cell" :data-align="col.align">
{{ row[col.key] }}
</div>
</div>
<div v-if="!rows.length" class="table-empty">No data available.</div>
</div>
</template>