Table
The table presents data in rows and columns, giving users a structured way to scan and compare information. It is the low-level building block behind the Data table and can be composed by hand for full control over headers, cells, groups and rows.
Accessible loading
While is-loading is set the table exposes aria-busy on the underlying <table>, so assistive technologies announce that the data is updating.
Props
caption-side?: "top" | "bottom"
The side where the caption should be placed.
Default: bottom
is-filled?: boolean
Renders a filler row that stretches to the bottom of the table, so the column dividers reach the bottom on full-height tables.
is-hoverable?: boolean
If each row should be highlighted on hover.
is-loading?: boolean
If the table is in a loading state.
is-sticky?: boolean
If the table header, including a table bar in the header, sticks to the top while scrolling.
Slots
default
The slot for the table content.
caption
The slot for the table caption.
empty
Renders a full-width row that stretches to the remaining height of the table, typically for an empty state. The content is vertically centered.
footer
The slot for the table footer.
header
The slot for the table headers.
loading
Replaces the default spinner overlay while `is-loading` is set. Renders inside the table body on the table's column grid, so skeleton rows built from `FluxTableRow` and `FluxTableCell` align with the columns. The regular rows are not rendered while this slot shows.
pagination
The slot for pagination.
Examples
Basic
A basic table.
<template>
<FluxTable>
<template #header>
<FluxTableHeader
v-for="header in 3">
Header {{ header }}
</FluxTableHeader>
</template>
<FluxTableRow
v-for="row in 3"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
Cell {{ cell }}×{{ row }}
</FluxTableCell>
</FluxTableRow>
</FluxTable>
</template>
<script
setup
lang="ts">
import { FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
</script>Pane
A table inside a pane.
<template>
<FluxPane>
<FluxTable>
<template #header>
<FluxTableHeader
v-for="header in 3">
Header {{ header }}
</FluxTableHeader>
</template>
<FluxTableRow
v-for="row in 3"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
Cell {{ cell }}×{{ row }}
</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
</script>Caption
A table with an caption.
<template>
<FluxPane>
<FluxTable>
<template #header>
<FluxTableHeader
v-for="header in 3">
Header {{ header }}
</FluxTableHeader>
</template>
<FluxTableRow
v-for="row in 3"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
Cell {{ cell }}×{{ row }}
</FluxTableCell>
</FluxTableRow>
<template #caption>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet animi asperiores, commodi cupiditate doloremque eligendi exercitationem facilis, illum ipsa ipsam modi nesciunt nobis, odit quidem sit temporibus ullam. Et, nam.
</template>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
</script>Caption on top
A table whose caption renders above the table instead of below it.
<template>
<FluxPane>
<FluxTable caption-side="top">
<template #header>
<FluxTableHeader
v-for="header in 3">
Header {{ header }}
</FluxTableHeader>
</template>
<FluxTableRow
v-for="row in 3"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
Cell {{ cell }}×{{ row }}
</FluxTableCell>
</FluxTableRow>
<template #caption>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet animi asperiores, commodi cupiditate doloremque eligendi exercitationem facilis, illum ipsa ipsam modi nesciunt nobis, odit quidem sit temporibus ullam. Et, nam.
</template>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
</script>Sticky
A table with sticky headers.
<template>
<FluxPane>
<FluxTable
is-sticky
style="max-height: 330px">
<template #header>
<FluxTableHeader
is-shrinking
v-for="header in 3">
Header {{ header }}
</FluxTableHeader>
</template>
<FluxTableRow
v-for="row in 30"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
Cell {{ cell }}×{{ row }}
</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
</script>Sticky with bar
A table where the bar and headers stick together.
<template>
<FluxPane>
<FluxTable
is-sticky
style="max-height: 330px">
<template #header>
<FluxTableBar>
<FluxFlex
align="center"
:gap="9">
<FluxIcon name="circle-info"/>
<span>The bar and headers stick together while scrolling.</span>
</FluxFlex>
</FluxTableBar>
<FluxTableRow>
<FluxTableHeader
v-for="header in 3"
:key="header">
Header {{ header }}
</FluxTableHeader>
</FluxTableRow>
</template>
<FluxTableRow
v-for="row in 30"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
Cell {{ cell }}×{{ row }}
</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxFlex, FluxIcon, FluxPane, FluxTable, FluxTableBar, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
</script>Pinned columns
A wide table with multiple columns pinned to the left and right edges.
<template>
<FluxPane>
<FluxTable is-hoverable>
<template #header>
<FluxTableHeader
pinned
:min-width="180">
Project
</FluxTableHeader>
<FluxTableHeader
pinned
:min-width="150">
Owner
</FluxTableHeader>
<FluxTableHeader :min-width="150">Status</FluxTableHeader>
<FluxTableHeader :min-width="150">Priority</FluxTableHeader>
<FluxTableHeader :min-width="150">Start</FluxTableHeader>
<FluxTableHeader :min-width="150">Due</FluxTableHeader>
<FluxTableHeader :min-width="180">Client</FluxTableHeader>
<FluxTableHeader
align="end"
pinned="end"
:min-width="140">
Budget
</FluxTableHeader>
</template>
<FluxTableRow
v-for="project in projects"
:key="project.id">
<FluxTableCell>
<strong>{{ project.name }}</strong>
</FluxTableCell>
<FluxTableCell>{{ project.owner }}</FluxTableCell>
<FluxTableCell>{{ project.status }}</FluxTableCell>
<FluxTableCell>{{ project.priority }}</FluxTableCell>
<FluxTableCell>{{ project.start }}</FluxTableCell>
<FluxTableCell>{{ project.due }}</FluxTableCell>
<FluxTableCell>{{ project.client }}</FluxTableCell>
<FluxTableCell
align="end"
is-numeric>
{{ project.budget }}
</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script setup lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
const projects = [
{id: 1, name: 'Aurora Redesign', owner: 'Mila Vega', status: 'In progress', priority: 'High', start: 'Jan 8', due: 'Mar 2', client: 'Lumen Co.', budget: '€ 48,000'},
{id: 2, name: 'Borealis Mobile', owner: 'Sven Halloran', status: 'Planned', priority: 'Medium', start: 'Feb 1', due: 'Apr 18', client: 'Nordic Works', budget: '€ 32,500'},
{id: 3, name: 'Cinder Analytics', owner: 'Priya Nandal', status: 'In progress', priority: 'High', start: 'Jan 22', due: 'May 6', client: 'Clayworks', budget: '€ 61,200'},
{id: 4, name: 'Delta Onboarding', owner: 'Theo Marsh', status: 'Review', priority: 'Low', start: 'Dec 12', due: 'Feb 9', client: 'Nordic Works', budget: '€ 18,900'},
{id: 5, name: 'Ember Storefront', owner: 'Lena Ortiz', status: 'In progress', priority: 'Medium', start: 'Feb 14', due: 'Jun 1', client: 'Clayworks', budget: '€ 54,750'}
];
</script>Column sizing
Columns mixing a fixed width, a min/max range and a shrinking column.
<template>
<FluxPane>
<FluxTable is-hoverable>
<template #header>
<FluxTableHeader :width="120">SKU</FluxTableHeader>
<FluxTableHeader>Product</FluxTableHeader>
<FluxTableHeader
:min-width="120"
:max-width="220">
Category
</FluxTableHeader>
<FluxTableHeader
align="end"
is-shrinking>
Stock
</FluxTableHeader>
<FluxTableHeader is-shrinking>Status</FluxTableHeader>
</template>
<FluxTableRow
v-for="product in products"
:key="product.sku">
<FluxTableCell>{{ product.sku }}</FluxTableCell>
<FluxTableCell>
<strong>{{ product.name }}</strong>
</FluxTableCell>
<FluxTableCell>{{ product.category }}</FluxTableCell>
<FluxTableCell
align="end"
is-numeric>
{{ product.stock }}
</FluxTableCell>
<FluxTableCell>
<FluxBadge
:color="product.stock > 0 ? 'success' : 'danger'"
:label="product.stock > 0 ? 'In stock' : 'Sold out'"/>
</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxBadge, FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
type Product = {
readonly sku: string;
readonly name: string;
readonly category: string;
readonly stock: number;
};
const products: Product[] = [
{sku: 'AC-1024', name: 'Aurora Ceiling Light', category: 'Lighting', stock: 34},
{sku: 'FD-2087', name: 'Borealis Standing Desk', category: 'Furniture', stock: 8},
{sku: 'KD-3391', name: 'Cinder Ceramic Mug', category: 'Kitchen & dining', stock: 0},
{sku: 'FD-4410', name: 'Delta Lounge Chair', category: 'Furniture', stock: 15},
{sku: 'KD-5502', name: 'Ember Electric Kettle', category: 'Kitchen & dining', stock: 21}
];
</script>Numeric alignment
Right-aligned numeric columns with sortable numeric headers.
<template>
<FluxPane>
<FluxTable is-hoverable>
<template #header>
<FluxTableHeader :min-width="200">Endpoint</FluxTableHeader>
<FluxTableHeader
align="end"
data-type="numeric"
is-numeric
is-sortable
:sort="sortColumn === 'requests' ? sortDirection : undefined"
@sort="setSort('requests', $event)">
Requests
</FluxTableHeader>
<FluxTableHeader
align="end"
data-type="numeric"
is-numeric
is-sortable
:sort="sortColumn === 'latency' ? sortDirection : undefined"
@sort="setSort('latency', $event)">
Avg latency
</FluxTableHeader>
<FluxTableHeader
align="end"
data-type="numeric"
is-numeric
is-sortable
:sort="sortColumn === 'errorRate' ? sortDirection : undefined"
@sort="setSort('errorRate', $event)">
Error rate
</FluxTableHeader>
</template>
<FluxTableRow
v-for="row in sortedRows"
:key="row.endpoint">
<FluxTableCell>{{ row.endpoint }}</FluxTableCell>
<FluxTableCell>{{ row.requests.toLocaleString('en') }}</FluxTableCell>
<FluxTableCell>{{ row.latency }} ms</FluxTableCell>
<FluxTableCell>{{ row.errorRate.toFixed(2) }}%</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
import { computed, ref } from 'vue';
type SortDirection = 'ascending' | 'descending';
type SortColumn = 'requests' | 'latency' | 'errorRate';
type Metric = {
readonly endpoint: string;
readonly requests: number;
readonly latency: number;
readonly errorRate: number;
};
const rows: Metric[] = [
{endpoint: 'GET /api/orders', requests: 128420, latency: 84, errorRate: 0.12},
{endpoint: 'POST /api/orders', requests: 24180, latency: 213, errorRate: 1.34},
{endpoint: 'GET /api/products', requests: 302914, latency: 47, errorRate: 0.05},
{endpoint: 'GET /api/customers', requests: 58210, latency: 132, errorRate: 0.42},
{endpoint: 'DELETE /api/sessions', requests: 9120, latency: 76, errorRate: 2.18}
];
const sortColumn = ref<SortColumn | null>('requests');
const sortDirection = ref<SortDirection>('descending');
const sortedRows = computed(() => {
const column = sortColumn.value;
if (!column) {
return rows;
}
const factor = sortDirection.value === 'ascending' ? 1 : -1;
return [...rows].sort((a, b) => (a[column] - b[column]) * factor);
});
function setSort(column: SortColumn, direction: SortDirection | null): void {
if (direction === null) {
sortColumn.value = null;
return;
}
sortColumn.value = column;
sortDirection.value = direction;
}
</script>Spanning cells
Section rows whose single cell spans every column.
<template>
<FluxPane>
<FluxTable is-hoverable>
<template #header>
<FluxTableHeader :width="96">Time</FluxTableHeader>
<FluxTableHeader :min-width="220">Session</FluxTableHeader>
<FluxTableHeader :min-width="160">Speaker</FluxTableHeader>
<FluxTableHeader is-shrinking>Room</FluxTableHeader>
</template>
<template
v-for="block in schedule"
:key="block.label">
<FluxTableRow>
<FluxTableCell :colspan="4">
<strong>{{ block.label }}</strong>
</FluxTableCell>
</FluxTableRow>
<FluxTableRow
v-for="session in block.sessions"
:key="session.title">
<FluxTableCell is-numeric>{{ session.time }}</FluxTableCell>
<FluxTableCell>{{ session.title }}</FluxTableCell>
<FluxTableCell>{{ session.speaker }}</FluxTableCell>
<FluxTableCell>{{ session.room }}</FluxTableCell>
</FluxTableRow>
</template>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
type Session = {
readonly time: string;
readonly title: string;
readonly speaker: string;
readonly room: string;
};
type Block = {
readonly label: string;
readonly sessions: Session[];
};
const schedule: Block[] = [
{
label: 'Morning track',
sessions: [
{time: '09:00', title: 'Designing for the grid', speaker: 'Mila Vega', room: 'Hall A'},
{time: '10:15', title: 'Accessible tables in practice', speaker: 'Sven Halloran', room: 'Hall A'}
]
},
{
label: 'Afternoon track',
sessions: [
{time: '13:30', title: 'Subgrid deep dive', speaker: 'Priya Nandal', room: 'Hall B'},
{time: '15:00', title: 'From table to layout', speaker: 'Theo Marsh', room: 'Hall B'}
]
}
];
</script>Spanning rows
A first column whose cell spans every row of its track through rowspan.
<template>
<FluxPane>
<FluxTable is-hoverable>
<template #header>
<FluxTableHeader is-shrinking>Track</FluxTableHeader>
<FluxTableHeader :width="96">Time</FluxTableHeader>
<FluxTableHeader :min-width="220">Session</FluxTableHeader>
<FluxTableHeader :min-width="160">Speaker</FluxTableHeader>
</template>
<template
v-for="track in schedule"
:key="track.name">
<FluxTableRow
v-for="(session, index) in track.sessions"
:key="session.title">
<FluxTableCell
v-if="index === 0"
:rowspan="track.sessions.length">
<strong>{{ track.name }}</strong>
</FluxTableCell>
<FluxTableCell is-numeric>{{ session.time }}</FluxTableCell>
<FluxTableCell>{{ session.title }}</FluxTableCell>
<FluxTableCell>{{ session.speaker }}</FluxTableCell>
</FluxTableRow>
</template>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
type Session = {
readonly time: string;
readonly title: string;
readonly speaker: string;
};
type Track = {
readonly name: string;
readonly sessions: Session[];
};
const schedule: Track[] = [
{
name: 'Hall A',
sessions: [
{time: '09:00', title: 'Designing for the grid', speaker: 'Mila Vega'},
{time: '10:15', title: 'Accessible tables in practice', speaker: 'Sven Halloran'},
{time: '11:30', title: 'Layout without tables', speaker: 'Ada Fenwick'}
]
},
{
name: 'Hall B',
sessions: [
{time: '13:30', title: 'Subgrid deep dive', speaker: 'Priya Nandal'},
{time: '15:00', title: 'From table to layout', speaker: 'Theo Marsh'}
]
}
];
</script>Footer totals
Line items with a footer that sums the amounts across spanning cells.
<template>
<FluxPane>
<FluxTable>
<template #header>
<FluxTableHeader :min-width="240">Description</FluxTableHeader>
<FluxTableHeader
align="end"
is-shrinking>
Qty
</FluxTableHeader>
<FluxTableHeader
align="end"
:min-width="120">
Unit price
</FluxTableHeader>
<FluxTableHeader
align="end"
:min-width="120">
Amount
</FluxTableHeader>
</template>
<FluxTableRow
v-for="line in lines"
:key="line.id">
<FluxTableCell>{{ line.description }}</FluxTableCell>
<FluxTableCell
align="end"
is-numeric>
{{ line.quantity }}
</FluxTableCell>
<FluxTableCell
align="end"
is-numeric>
{{ formatCurrency(line.unitPrice) }}
</FluxTableCell>
<FluxTableCell
align="end"
is-numeric>
{{ formatCurrency(line.quantity * line.unitPrice) }}
</FluxTableCell>
</FluxTableRow>
<template #footer>
<FluxTableRow>
<FluxTableCell
align="end"
:colspan="3">
Subtotal
</FluxTableCell>
<FluxTableCell
align="end"
is-numeric>
{{ formatCurrency(subtotal) }}
</FluxTableCell>
</FluxTableRow>
<FluxTableRow>
<FluxTableCell
align="end"
:colspan="3">
VAT 21%
</FluxTableCell>
<FluxTableCell
align="end"
is-numeric>
{{ formatCurrency(vat) }}
</FluxTableCell>
</FluxTableRow>
<FluxTableRow>
<FluxTableCell
align="end"
:colspan="3">
<strong>Total</strong>
</FluxTableCell>
<FluxTableCell
align="end"
is-numeric>
<strong>{{ formatCurrency(total) }}</strong>
</FluxTableCell>
</FluxTableRow>
</template>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
import { computed } from 'vue';
type InvoiceLine = {
readonly id: number;
readonly description: string;
readonly quantity: number;
readonly unitPrice: number;
};
const lines: InvoiceLine[] = [
{id: 1, description: 'UX audit and research', quantity: 12, unitPrice: 95},
{id: 2, description: 'Design system components', quantity: 24, unitPrice: 110},
{id: 3, description: 'Prototype and handoff', quantity: 8, unitPrice: 120},
{id: 4, description: 'Accessibility review', quantity: 6, unitPrice: 85}
];
const subtotal = computed(() => lines.reduce((sum, line) => sum + line.quantity * line.unitPrice, 0));
const vat = computed(() => subtotal.value * 0.21);
const total = computed(() => subtotal.value + vat.value);
function formatCurrency(value: number): string {
return new Intl.NumberFormat('en', {
currency: 'EUR',
style: 'currency'
}).format(value);
}
</script>Grouped rows
Rows organized under collapsible groups.
<template>
<FluxPane>
<FluxTable is-hoverable>
<template #header>
<FluxTableHeader :min-width="200">Name</FluxTableHeader>
<FluxTableHeader is-shrinking>Role</FluxTableHeader>
</template>
<FluxTableGroup
v-for="group in groups"
:key="group.name"
v-model:is-expanded="expanded[group.name]"
:icon="group.icon"
:label="group.name"
is-expandable>
<template #after>
<FluxBadge :label="`${group.members.length}`"/>
</template>
<FluxTableRow
v-for="member in group.members"
:key="member.name">
<FluxTableCell>{{ member.name }}</FluxTableCell>
<FluxTableCell>{{ member.role }}</FluxTableCell>
</FluxTableRow>
</FluxTableGroup>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxBadge, FluxPane, FluxTable, FluxTableCell, FluxTableGroup, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
import type { FluxIconName } from '@flux-ui/types';
import { reactive } from 'vue';
type Group = {
readonly name: string;
readonly icon: FluxIconName;
readonly members: { readonly name: string; readonly role: string }[];
};
const groups: Group[] = [
{
name: 'Engineering',
icon: 'users',
members: [
{name: 'Ada Lovelace', role: 'Lead'},
{name: 'Alan Turing', role: 'Engineer'}
]
},
{
name: 'Design',
icon: 'palette',
members: [
{name: 'Grace Hopper', role: 'Designer'}
]
},
{
name: 'Operations',
icon: 'folder',
members: [
{name: 'Katherine Johnson', role: 'Manager'},
{name: 'Margaret Hamilton', role: 'Analyst'}
]
}
];
const expanded = reactive<Record<string, boolean>>({
Engineering: true,
Design: true,
Operations: false
});
</script>Sticky groups
Collapsible groups whose column header sticks while scrolling.
<template>
<FluxPane>
<FluxTable
is-hoverable
is-sticky
style="max-height: 360px">
<template #header>
<FluxTableHeader :min-width="200">Name</FluxTableHeader>
<FluxTableHeader :min-width="160">Role</FluxTableHeader>
<FluxTableHeader is-shrinking>Location</FluxTableHeader>
</template>
<FluxTableGroup
v-for="group in groups"
:key="group.name"
v-model:is-expanded="expanded[group.name]"
:icon="group.icon"
:label="group.name"
is-expandable>
<template #after>
<FluxBadge :label="`${group.members.length}`"/>
</template>
<FluxTableRow
v-for="member in group.members"
:key="member.name">
<FluxTableCell>{{ member.name }}</FluxTableCell>
<FluxTableCell>{{ member.role }}</FluxTableCell>
<FluxTableCell no-wrap>{{ member.location }}</FluxTableCell>
</FluxTableRow>
</FluxTableGroup>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxBadge, FluxPane, FluxTable, FluxTableCell, FluxTableGroup, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
import type { FluxIconName } from '@flux-ui/types';
import { reactive } from 'vue';
type Member = {
readonly name: string;
readonly role: string;
readonly location: string;
};
type Group = {
readonly name: string;
readonly icon: FluxIconName;
readonly members: Member[];
};
const groups: Group[] = [
{
name: 'Engineering',
icon: 'users',
members: [
{name: 'Ada Lovelace', role: 'Lead', location: 'London'},
{name: 'Alan Turing', role: 'Engineer', location: 'Manchester'},
{name: 'Linus Powell', role: 'Engineer', location: 'Berlin'},
{name: 'Nadia Reyes', role: 'Engineer', location: 'Madrid'}
]
},
{
name: 'Design',
icon: 'palette',
members: [
{name: 'Grace Hopper', role: 'Lead designer', location: 'New York'},
{name: 'Theo Marsh', role: 'Product designer', location: 'Toronto'},
{name: 'Lena Ortiz', role: 'Brand designer', location: 'Lisbon'}
]
},
{
name: 'Operations',
icon: 'folder',
members: [
{name: 'Katherine Johnson', role: 'Manager', location: 'Hampton'},
{name: 'Margaret Hamilton', role: 'Analyst', location: 'Boston'},
{name: 'Sven Halloran', role: 'Coordinator', location: 'Oslo'}
]
},
{
name: 'Sales',
icon: 'chart-line',
members: [
{name: 'Mila Vega', role: 'Account lead', location: 'Amsterdam'},
{name: 'Priya Nandal', role: 'Account manager', location: 'Mumbai'},
{name: 'Oscar Bright', role: 'Sales rep', location: 'Chicago'}
]
}
];
const expanded = reactive<Record<string, boolean>>({
Engineering: true,
Design: true,
Operations: true,
Sales: true
});
</script>Stacked content
Cells that stack a primary and secondary line of content.
<template>
<FluxPane>
<FluxTable is-hoverable>
<template #header>
<FluxTableHeader :min-width="220">Member</FluxTableHeader>
<FluxTableHeader :min-width="160">Role</FluxTableHeader>
<FluxTableHeader is-shrinking>Location</FluxTableHeader>
</template>
<FluxTableRow
v-for="member in members"
:key="member.email">
<FluxTableCell
content-direction="column"
:content-gap="3">
<strong>{{ member.name }}</strong>
<small>{{ member.email }}</small>
</FluxTableCell>
<FluxTableCell
content-direction="column"
:content-gap="3">
<span>{{ member.role }}</span>
<small>{{ member.department }}</small>
</FluxTableCell>
<FluxTableCell>{{ member.location }}</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
type Member = {
readonly name: string;
readonly email: string;
readonly role: string;
readonly department: string;
readonly location: string;
};
const members: Member[] = [
{name: 'Ada Lovelace', email: 'ada@example.com', role: 'Lead engineer', department: 'Platform', location: 'London'},
{name: 'Alan Turing', email: 'alan@example.com', role: 'Engineer', department: 'Platform', location: 'Manchester'},
{name: 'Grace Hopper', email: 'grace@example.com', role: 'Product designer', department: 'Design', location: 'New York'},
{name: 'Katherine Johnson', email: 'katherine@example.com', role: 'Engineering manager', department: 'Operations', location: 'Hampton'}
];
</script>Wrapping rows
A wrapping column whose siblings stay aligned and on a single line.
<template>
<FluxPane>
<FluxTable>
<template #header>
<FluxTableHeader :width="96">Version</FluxTableHeader>
<FluxTableHeader is-shrinking>Date</FluxTableHeader>
<FluxTableHeader :min-width="280">Change</FluxTableHeader>
</template>
<FluxTableRow
v-for="entry in changelog"
:key="entry.version">
<FluxTableCell no-wrap>
<strong>{{ entry.version }}</strong>
</FluxTableCell>
<FluxTableCell no-wrap>{{ entry.date }}</FluxTableCell>
<FluxTableCell>{{ entry.change }}</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
type ChangelogEntry = {
readonly version: string;
readonly date: string;
readonly change: string;
};
const changelog: ChangelogEntry[] = [
{version: '2.4.0', date: 'Mar 2', change: 'Migrated the table components to CSS Grid so rows share a single track template and columns can be pinned, sized and spanned without wrapper elements.'},
{version: '2.3.1', date: 'Feb 18', change: 'Fixed sticky header alignment when a table bar is present and corrected the focus ring on sortable column headers.'},
{version: '2.3.0', date: 'Feb 4', change: 'Added per-column sizing props (width, min-width and max-width) and a shrinking mode for columns that should hug their content.'},
{version: '2.2.0', date: 'Jan 20', change: 'Introduced collapsible table groups with an optional trailing badge slot for counts and other metadata.'}
];
</script>Consistent height
A short table that keeps a fixed height with filler rows.
<template>
<FluxPane>
<FluxTable
is-filled
is-hoverable
style="min-height: 360px">
<template #header>
<FluxTableHeader :width="96">Time</FluxTableHeader>
<FluxTableHeader :min-width="180">Guest</FluxTableHeader>
<FluxTableHeader
align="end"
is-shrinking>
Party
</FluxTableHeader>
<FluxTableHeader is-shrinking>Table</FluxTableHeader>
</template>
<FluxTableRow
v-for="booking in bookings"
:key="booking.id">
<FluxTableCell is-numeric>{{ booking.time }}</FluxTableCell>
<FluxTableCell>{{ booking.guest }}</FluxTableCell>
<FluxTableCell
align="end"
is-numeric>
{{ booking.party }}
</FluxTableCell>
<FluxTableCell>{{ booking.table }}</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
type Booking = {
readonly id: number;
readonly time: string;
readonly guest: string;
readonly party: number;
readonly table: string;
};
const bookings: Booking[] = [
{id: 1, time: '18:30', guest: 'Vega party', party: 2, table: 'T4'},
{id: 2, time: '19:00', guest: 'Halloran', party: 4, table: 'T9'},
{id: 3, time: '20:15', guest: 'Nandal', party: 3, table: 'T2'}
];
</script>Actions
A table with actions.
<template>
<FluxPane>
<FluxTable>
<template #header>
<FluxTableHeader
v-for="header in 3">
Header {{ header }}
</FluxTableHeader>
<FluxTableHeader is-shrinking/>
</template>
<FluxTableRow
v-for="row in 3"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
Cell {{ cell }}×{{ row }}
</FluxTableCell>
<FluxTableCell>
<FluxTableActions>
<FluxAction
icon="pen"/>
<FluxAction
icon="ellipsis-h"/>
</FluxTableActions>
</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxAction, FluxPane, FluxTable, FluxTableActions, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
</script>Loading
A table with a loading state.
<template>
<FluxPane>
<FluxTable
is-loading>
<template #header>
<FluxTableHeader
v-for="header in 3">
Header {{ header }}
</FluxTableHeader>
</template>
<FluxTableRow
v-for="row in 3"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
Cell {{ cell }}×{{ row }}
</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
</script>Skeleton loading
A table whose loading slot replaces the spinner with skeleton rows.
<template>
<FluxPane>
<FluxTable is-loading>
<template #header>
<FluxTableHeader
v-for="header in 3">
Header {{ header }}
</FluxTableHeader>
</template>
<template #loading>
<FluxTableRow
v-for="row in 3"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
<FluxSkeleton/>
</FluxTableCell>
</FluxTableRow>
</template>
<FluxTableRow
v-for="row in 3"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
Cell {{ cell }}×{{ row }}
</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxSkeleton, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
</script>Hoverable
A table with rows that have a hoverable state.
<template>
<FluxPane>
<FluxTable
is-hoverable>
<template #header>
<FluxTableHeader
v-for="header in 3">
Header {{ header }}
</FluxTableHeader>
</template>
<FluxTableRow
v-for="row in 3"
:key="row">
<FluxTableCell
v-for="cell in 3"
:key="cell">
Cell {{ cell }}×{{ row }}
</FluxTableCell>
</FluxTableRow>
</FluxTable>
</FluxPane>
</template>
<script
setup
lang="ts">
import { FluxPane, FluxTable, FluxTableCell, FluxTableHeader, FluxTableRow } from '@flux-ui/components';
</script>