A flexible data table for displaying structured datasets with support for sorting, sticky headers, column filters (text, select, multiselect), row hover/stripe styles, and a responsive mobile cards view.
When to Use
- For displaying structured datasets with named columns that benefit from sorting and filtering
- In dashboards, admin panels, or reporting views where users need to scan, sort, and narrow down rows
- When mobile responsiveness is required — the mobile cards view adapts the layout for small screens
When Not to Use
- For simple key–value information — use a
<dl>or a card layout instead - For lists without columns — use
QContainedList - When the dataset is empty by design — show an
QEmptyStaterather than an empty table
Component Anatomy
- Header row — column labels; sortable columns show sort direction arrows
- Data rows — one row per item;
hoveradds a highlight on mouse-over,stripedalternates background colors - Sticky header — header remains visible during scroll when
stickyHeaderis enabled - Column filters — optional filter row below the header; each column can have a text, select, or multiselect filter
- Mobile cards — collapses columns into stacked card items on small viewports
Fields and Items model
The following Vue reference will be used for any use case below:
Container Background — Uniform Rows
All rows share the same --q-body-container-bg surface. Use this when the table sits inside a card or panel with a different background, so each row reads as a clean slot.
Container Background — Striped Rows
Alternates between --q-body-container-bg (odd) and --q-body-quaternary-color (even). The quaternary tone is subtle enough to preserve readability at any font size while still clearly separating rows at a glance.
Row Hover
Sticky Header
Keep column headers visible while scrolling through long tables. Perfect for maintaining context when working with large datasets.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
stickyHeader | boolean | false | Enable sticky table header that remains visible during scroll |
stickyHeaderMaxHeight | string | "600px" | Maximum height of the scrollable table area |
Column Filters
Enable column-level filtering to help users quickly find the data they need. Supports text search, single select, and multi-select filters.
Field Configuration
Configure filtering for each column using the QTableField interface:
const fields: QTableField[] = [
{
key: "name",
label: "Product Name",
filterable: true,
filterType: "text" // Text search with debounce
},
{
key: "category",
label: "Category",
filterable: true,
filterType: "select", // Single selection dropdown
filterOptions: [
{ value: "Fruit", text: "Fruit" },
{ value: "Vegetable", text: "Vegetable" }
]
},
{
key: "tags",
label: "Tags",
filterable: true,
filterType: "multiselect", // Multiple selection with checkboxes
filterOptions: [
{ value: "organic", text: "Organic" },
{ value: "local", text: "Local" },
{ value: "imported", text: "Imported" }
]
}
];Filter Types
- text: Case-insensitive text search with 300ms debounce
- select: Single selection dropdown with "All" option
- multiselect: Multiple selection with checkboxes (AND logic)
Props
| Prop | Type | Default | Description |
|---|---|---|---|
columnFilters | boolean | false | Enable column-level filtering |
filters | QTableFilters | {} | v-model for current filter state |
Mobile Cards View
Automatically switch to a card-based layout on mobile devices for better usability on smaller screens.
Field Configuration
Configure mobile display behavior using the QTableField interface:
const fields: QTableField[] = [
{
key: "name",
label: "Product Name",
mobileLabel: true // Display as card title
},
{
key: "quantity",
label: "Quantity",
mobilePriority: 1 // Display order (lower = higher priority)
},
{
key: "price",
label: "Price",
mobilePriority: 2
},
{
key: "internal_id",
label: "Internal ID",
mobileHidden: true // Hide in mobile view
}
];Mobile Options
- mobileLabel: Mark a field to display as the card title (defaults to first field)
- mobilePriority: Control display order in cards (1 = highest priority)
- mobileHidden: Hide field in mobile view
- mobileSecondary: Mark field for secondary display styling
Props
| Prop | Type | Default | Description |
|---|---|---|---|
mobileCards | boolean | false | Enable card view on mobile devices |
mobileBreakpoint | BreakpointKey | "isSmall" | Breakpoint for switching to mobile view (< 768px) |
mobileCardTitle | string | undefined | Field key to use as card title (overrides mobileLabel) |
Combined Features
All features can be combined for a fully-featured responsive data table:
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | TableItem[] | required | Array of data objects to display as table rows. |
fields | QTableField[] | required | Column definitions (see QTableField type below). |
hover | boolean | false | Highlights rows on mouse-over. |
striped | boolean | false | Alternates background color on even/odd rows. |
bordered | boolean | false | Adds borders to cells and the table outline. |
small | boolean | false | Reduces cell padding for a more compact table. |
stickyHeader | boolean | false | Keeps the header row visible while scrolling. |
stickyHeaderMaxHeight | string | "600px" | Maximum height of the scrollable area when stickyHeader is on. |
columnFilters | boolean | false | Enables per-column filter controls. |
filters | QTableFilters | {} | Current filter state (use with v-model:filters). |
mobileCards | boolean | false | Switches to a stacked card layout on small viewports. |
mobileBreakpoint | BreakpointKey | "isSmall" | Viewport breakpoint below which the mobile cards view is applied. |
mobileCardTitle | string | — | Field key to use as the card title, overriding mobileLabel. |
QTableField Type
type QTableField = {
key: string; // Property key in the item object
label?: string; // Column header text
sortable?: boolean; // Enables sort arrows on the column header
formatter?: (value: any, key: string, item: any) => any; // Custom cell formatter
filterable?: boolean; // Enables a filter control for this column
filterType?: "text" | "select" | "multiselect"; // Filter input type
filterOptions?: { value: any; text: string }[]; // Options for select/multiselect filters
mobileLabel?: boolean; // Use as card title in mobile cards view
mobileSecondary?: boolean; // Secondary styling in mobile view
mobilePriority?: number; // Display order in mobile cards (lower = higher priority)
mobileHidden?: boolean; // Hide column in mobile cards view
};Events
| Event | Payload | Description |
|---|---|---|
update:filters | QTableFilters | Emitted when a column filter value changes. Use with v-model:filters. |
row-clicked | TableItem | Emitted when a data row is clicked, with the row's item data. |
sort-changed | { key: string; order: "asc" | "desc" } | Emitted when the user clicks a sortable column header. |
Accessibility
- Column headers use
<th scope="col">elements for proper screen reader association - Sortable columns communicate their state via
aria-sort("ascending"or"descending") - Row hover and striped styles are visual only; do not rely on them to convey row state
- When using column filters, ensure filter inputs have visible labels or
aria-labelattributes - On mobile cards view, ensure the card layout maintains a logical reading order for screen readers