Tables
For displaying tabular data, BTable
supports pagination, filtering, sorting, custom rendering, various style options, events, and asynchronous data. For simple display of tabular data without all the fancy features, BootstrapVueNext provides two lightweight alternative components BTableLite
and BTableSimple
.
Basic Usage
Age | First name | Last name |
---|---|---|
40 | Dickerson | Macdonald |
21 | Larsen | Shaw |
89 | Geneva | Wilson |
38 | Jami | Carney |
<template>
<div>
<BTable striped hover :items="items" />
</div>
</template>
<script setup lang="ts">
const items = [
{age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{age: 89, first_name: 'Geneva', last_name: 'Wilson'},
{age: 38, first_name: 'Jami', last_name: 'Carney'},
]
</script>
Items (record data)
items
is the table data in array format, where each record (row) data are keyed objects. Example format:
const items = [
{age: 32, first_name: 'Cyndi'},
{age: 27, first_name: 'Havij'},
{age: 42, first_name: 'Robert'},
]
<BTable>
automatically samples the first row to extract field names (the keys in the record data). Field names are automatically "humanized" by converting kebab-case
, snake_case
, and camelCase
to individual words and capitalizes each word. Example conversions:
first_name
becomesFirst Name
last-name
becomesLast Name
age
becomesAge
YEAR
remainsYEAR
isActive
becomesIs Active
These titles will be displayed in the table header, in the order they appear in the first record of data. See the Fields section below for customizing how field headings appear.
NOTE
Field order is not guaranteed. Fields will typically appear in the order they were defined in the first row, but this may not always be the case depending on the version of browser in use. See section Fields (column definitions) below to see how to guarantee the order of fields, and to override the headings generated.
Record data may also have additional special reserved name keys for colorizing rows and individual cells (variants), and for triggering additional row detail. The type TableItem defines the supported optional item record modifier properties (make sure your field keys do not conflict with these names):
Property | Type | Description |
---|---|---|
_cellVariants | Partial<Record<keyof T, ColorVariant>> | Bootstrap contextual state applied to individual cells. Keyed by field (See the Color Variants for supported values). These variants map to classes table-${variant} or bg-${variant} (when the dark prop is set). |
_rowVariant | ColorVariant | Bootstrap contextual state applied to the entire row (See the Color Variants for supported values). These variants map to classes table-${variant} or bg-${variant} (when the dark prop is set) |
_showDetails | boolean | Used to trigger the display of the row-details scoped slot. See section Row details support below for additional information |
Example: Using variants for table cells
Age | First name | Last name |
---|---|---|
40 | Dickerson | Macdonald |
21 | Larsen | Shaw |
89 | Geneva | Wilson |
40 | Thor | MacDonald |
29 | Dick | Dunlap |
<template>
<div>
<BTable hover :items="items" />
</div>
</template>
<script setup lang="ts">
const items = [
{age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{
age: 89,
first_name: 'Geneva',
last_name: 'Wilson',
_rowVariant: 'danger',
},
{
age: 40,
first_name: 'Thor',
last_name: 'MacDonald',
_cellVariants: {age: 'info', first_name: 'warning'},
},
{age: 29, first_name: 'Dick', last_name: 'Dunlap'},
]
</script>
items
can also be a reference to a provider function, which returns an Array
of items data. Provider functions can also be asynchronous:
- By returning
null
(orundefined
) and calling a callback, when the data is ready, with the data array as the only argument to the callback, - By returning a
Promise
that resolves to an array.
See the "Using Items Provider functions" section below for more details.
Table item notes and warnings
- Avoid manipulating record data in place, as changes to the underlying items data will cause either the row or entire table to be re-rendered. See Primary Key, below, for ways to minimize Vue's re-rendering of rows.
items
array records should be a simple object and must avoid placing data that may have circular references in the values within a row.<BTable>
serializes the row data into strings for sorting and filtering, and circular references will cause stack overflows to occur and your app to crash!
Fields (column definitions)
The fields
prop is used to customize the table columns headings, and in which order the columns of data are displayed. The field object keys (i.e. age
or first_name
as shown below) are used to extract the value from each item (record) row, and to provide additional features such as enabling sorting on the column, etc.
Fields can be provided as a simple array or an array of objects. Internally the fields data will be normalized into the array of objects format. Events or slots that include the column field
data will be in the normalized field object format (array of objects for fields
, or an object for an individual field
).
Fields as a simple array
Fields can be a simple array, for defining the order of the columns, and which columns to display:
First name | Last name | Age |
---|---|---|
Dickerson | Macdonald | 40 |
Larsen | Shaw | 21 |
Geneva | Wilson | 89 |
Jami | Carney | 38 |
<template>
<div>
<BTable striped hover :items="items" :fields="fields" />
</div>
</template>
<script setup lang="ts">
// Note `isActive` is left out and will not appear in the rendered table
const fields = ['first_name', 'last_name', 'age']
const items = [
{isActive: true, age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{isActive: false, age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{isActive: false, age: 89, first_name: 'Geneva', last_name: 'Wilson'},
{isActive: true, age: 38, first_name: 'Jami', last_name: 'Carney'},
]
</script>
Fields as an array of objects
Fields can be a an array of objects, providing additional control over the fields (such as sorting, formatting, etc.). Only columns (keys) that appear in the fields array will be shown:
Last Name | First Name | Person age |
---|---|---|
Macdonald | Dickerson | 40 |
Shaw | Larsen | 21 |
Wilson | Geneva | 89 |
Carney | Jami | 38 |
<template>
<div>
<BTable striped hover :items="items" :fields="fields" />
</div>
</template>
<script setup lang="ts">
import type {TableFieldRaw} from 'bootstrap-vue-next'
interface Person {
first_name: string
last_name: string
age: number
isActive: boolean
}
// Note 'isActive' is left out and will not appear in the rendered table
const fields: TableFieldRaw<Person>[] = [
{
key: 'last_name',
sortable: true,
},
{
key: 'first_name',
sortable: false,
},
{
key: 'age',
label: 'Person age',
sortable: true,
// Variant applies to the whole column, including the header and footer
variant: 'danger',
},
]
const items: Person[] = [
{isActive: true, age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{isActive: false, age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{isActive: false, age: 89, first_name: 'Geneva', last_name: 'Wilson'},
{isActive: true, age: 38, first_name: 'Jami', last_name: 'Carney'},
]
</script>
Field Definition Reference
The following field properties (defined as TableField) are recognized:
Property | Type | Description |
---|---|---|
key | LiteralUnion<keyof T> | The key for selecting data from the record in the items array. Required when setting the fields via an array of objects. The key is also used for generating the custom data rendering and custom header and footer slot names. |
label | string | Appears in the columns table header (and footer if foot-clone is set). Defaults to the field's key (in humanized format) if not provided. It's possible to use empty labels by assigning an empty string "" but be sure you also set headerTitle to provide non-sighted users a hint about the column contents. |
headerTitle | string | Text to place on the fields header <th> attribute title . Defaults to no title attribute. |
headerAbbr | string | Text to place on the fields header <th> attribute abbr . Set this to the unabbreviated version of the label (or title) if label (or title) is an abbreviation. Defaults to no abbr attribute. |
class | ClassValue | Class name (or array of class names) to add to <th> and <td> in the column. |
formatter | TableFieldFormatter<T> | A formatter callback function can be used instead of (or in conjunction with) scoped field slots. The formatter will be called with the syntax formatter<T>(value: unknown, key: string, item: T) . Refer to Custom Data Rendering for more details. |
sortable | boolean | Enable sorting on this column. Refer to the Sorting Section for more details. |
sortDirection | string | Set the initial sort direction on this column when it becomes sorted. Refer to the Change initial sort direction Section for more details.Not yet implemented |
sortByFormatted | boolean | TableFieldFormatter<T> | Sort the column by the result of the field's formatter callback function when set to true . Default is false . Boolean has no effect if the field does not have a formatter . Optionally accepts a formatter function reference to format the value for sorting purposes only. Refer to the Sorting Section for more details. |
filterByFormatted | boolean | TableFieldFormatter<T> | Filter the column by the result of the field's formatter callback function when set to true . Default is false . Boolean has no effect if the field does not have a formatter . Optionally accepts a formatter function reference to format the value for filtering purposes only. Refer to the Filtering section for more details. |
tdClass | TableStrictClassValue | ((value: unknown, key: string, item: T) => TableStrictClassValue) | Class name (or array of class names) to add to <tbody> data <td> cells in the column. If custom classes per cell are required, a callback function can be specified instead. See the typescript definition for accepted parameters and return types. |
thClass | ClassValue | Class name (or array of class names) to add to this field's <thead> /<tfoot> heading <th> cell. |
thStyle | StyleValue | CSS styles you would like to apply to the table <thead> /<tfoot> field <th> cell. |
variant | ColorVariant | null | Apply contextual class to all the <th> and <td> in the column. |
tdAttr | AttrsValue | ((value: unknown, key: string, item: T) => AttrsValue) | Object representing additional attributes to apply to the <tbody> field <td> cell. If custom attributes per cell are required, a callback function can be specified instead. See the typescript definition for accepted parameters and return types. |
thAttr | AttrsValue | ((value: unknown, key: string, item: T | null, type: TableRowThead) => AttrsValue | Object representing additional attributes to apply to the field's <thead> /<tfoot> heading <th> cell. If the field's isRowHeader is set to true , the attributes will also apply to the <tbody> field <th> cell. If custom attributes per cell are required, a callback function can be specified instead. See the typescript definition for accepted parameters and return types. |
isRowHeader | boolean | When set to true , the field's item data cell will be rendered with <th> rather than the default of <td> . |
stickyColumn | boolean | When set to true , and the table in responsive mode or has sticky headers, will cause the column to become fixed to the left when the table's horizontal scrollbar is scrolled. See Sticky columns for more details |
Notes:
- Field properties, if not present, default to
null
(falsey) unless otherwise stated above. class
,thClass
,tdClass
etc. will not work with classes that are defined in scoped CSS, unless you are using Vue's Deep selector.- For information on the syntax supported by
thStyle
, see Class and Style Bindings in the Vue.js guide. - Any additional properties added to the field definition objects will be left intact - so you can access them via the named scoped slots for custom data, header, and footer rendering.
For information and usage about scoped slots and formatters, refer to the Custom Data Rendering section below.
Feel free to mix and match simple array and object array together:
const fields = [
{key: 'first_name', label: 'First'},
{key: 'last_name', label: 'Last'},
'age',
'sex',
]
Primary Key
<BTable>
provides an additional prop primary-key
, which you can use to identify the name of the field key that uniquely identifies the row.
The value specified by the primary column key must be either a string
or number
, and must be unique across all rows in the table.
The primary key column does not need to appear in the displayed fields.
Table row ID generation
When provided, the primary-key
will generate a unique ID for each item row <tr>
element. The ID will be in the format of {table-id}__row_{primary-key-value}
, where {table-id}
is the unique ID of the <BTable>
and {primary-key-value}
is the value of the item's field value for the field specified by primary-key
.
Table render and transition optimization
The primary-key
is also used by <BTable>
to help Vue optimize the rendering of table rows. Internally, the value of the field key specified by the primary-key
prop is used as the Vue :key
value for each rendered item row <tr>
element.
If you are seeing rendering issue (i.e. tooltips hiding or unexpected subcomponent re-usage when item data changes or data is sorted/filtered/edited) or table row transitions are not working, setting the primary-key
prop (if you have a unique identifier per row) can alleviate these issues.
Specifying the primary-key
column is handy if you are using 3rd party table transitions or drag and drop plugins, as they rely on having a consistent and unique per row :key
value.
If primary-key
is not provided, <BTable>
will auto-generate keys based on the displayed row's index number (i.e. position in the displayed table rows). This may cause GUI issues such as sub components/elements that are rendering with previous results (i.e. being re-used by Vue's render patch optimization routines). Specifying a primary-key
column can alleviate this issue (or you can place a unique :key
on your element/components in your custom formatted field slots).
Refer to the Table body transition support section for additional details.
Table Style Options
Table styling
<BTable>
provides several props to alter the style of the table:
prop | Type | Description |
---|---|---|
striped | boolean | Add zebra-striping to the table rows within the <tbody> |
striped-columns | boolean | Add zebra-striping to the table colums within the <tbody> |
bordered | boolean | For borders on all sides of the table and cells. |
borderless | boolean | removes inner borders from table. |
outlined | boolean | For a thin border on all sides of the table. Has no effect if bordered is set. |
small | boolean | To make tables more compact by cutting cell padding in half. |
hover | boolean | To enable a hover highlighting state on table rows within a <tbody> |
dark | boolean | Invert the colors — with light text on dark backgrounds (equivalent to Bootstrap v5 class .table-dark ) |
fixed | boolean | Generate a table with equal fixed-width columns (table-layout: fixed; ) Not yet implemented |
responsive | boolean | Breakpoint | Generate a responsive table to make it scroll horizontally. Set to true for an always responsive table, or set it to one of the breakpoints 'sm' , 'md' , 'lg' , 'xl' or 'xxl' to make the table responsive (horizontally scroll) only on screens smaller than the breakpoint. See Responsive tables below for details. |
sticky-header | boolean | Numberish | Generates a vertically scrollable table with sticky headers. Set to true to enable sticky headers (default table max-height of 300px ), or set it to a string containing a height (with CSS units) to specify a maximum height other than 300px . See the Sticky header section below for details. |
stacked | boolean | Breakpoint | Generate a responsive stacked table. Set to true for an always stacked table, or set it to one of the breakpoints 'sm' , 'md' , 'lg' , 'xl' or 'xxl' to make the table visually stacked only on screens smaller than the breakpoint. See Stacked tables below for details. |
caption-top | boolean | Numberish | If the table has a caption, and this prop is set to true , the caption will be visually placed above the table. If false (the default), the caption will be visually placed below the table. |
variant | ColorVariant | null | Give the table an overall theme color variant. |
head-variant | ColorVariant | null | Make the table head a theme color different from the table |
foot-row-variant | ColorVariant | null | Make the table foot a theme color different from the table. If not set, head-variant will be used. Has no effect if foot-clone is not set |
head-row-variant | ColorVariant | null | Make the only the <tr> part of the <head> a specific theme color |
foot-variant | ColorVariant | null | Make the only the <tr> part of the <foot> a specific theme color. If not set, head-row-variant will be used. Has no effect if foot-clone is not set |
foot-clone | boolean | Turns on the table footer, and defaults with the same contents a the table header |
no-footer-sorting | boolean | When foot-clone is true and the table is sortable, disables the sorting icons and click behaviour on the footer heading cells. Refer to the Sorting section below for more details. Not yet implemented |
no-border-collapse | Boolean | Disables the default of collapsing of the table borders. Mainly for use with sticky headers and/or sticky columns. Will cause the appearance of double borders in some situations. Not yet implemented |
NOTE
The table style options fixed
, stacked
, no-border-collapse
, sticky headers, sticky columns and the table sorting feature, all require BootstrapVueNext's custom CSS.
First name | Last name | Age |
---|---|---|
Dickerson | Macdonald | 40 |
Larsen | Shaw | 21 |
Geneva | Wilson | 89 |
<template>
<div>
<BFormGroup v-slot="{ariaDescribedby}" label="Table Options" label-cols-lg="2">
<BFormCheckbox v-model="striped" :aria-describedby="ariaDescribedby" inline
>Striped</BFormCheckbox
>
<BFormCheckbox v-model="stripedColumns" :aria-describedby="ariaDescribedby" inline
>Striped Columns</BFormCheckbox
>
<BFormCheckbox v-model="bordered" :aria-describedby="ariaDescribedby" inline
>Bordered</BFormCheckbox
>
<BFormCheckbox v-model="borderless" :aria-describedby="ariaDescribedby" inline
>Borderless</BFormCheckbox
>
<BFormCheckbox v-model="outlined" :aria-describedby="ariaDescribedby" inline
>Outlined</BFormCheckbox
>
<BFormCheckbox v-model="small" :aria-describedby="ariaDescribedby" inline
>Small</BFormCheckbox
>
<BFormCheckbox v-model="hover" :aria-describedby="ariaDescribedby" inline
>Hover</BFormCheckbox
>
<BFormCheckbox v-model="dark" :aria-describedby="ariaDescribedby" inline>Dark</BFormCheckbox>
<BFormCheckbox v-model="fixed" :aria-describedby="ariaDescribedby" inline
>Fixed</BFormCheckbox
>
<BFormCheckbox v-model="footClone" :aria-describedby="ariaDescribedby" inline
>Foot Clone</BFormCheckbox
>
<BFormCheckbox v-model="noCollapse" :aria-describedby="ariaDescribedby" inline
>No border collapse</BFormCheckbox
>
</BFormGroup>
<BFormGroup label="Variant" label-for="table-style-variant" label-cols-lg="2" class="my-2">
<BFormSelect id="table-style-variant" v-model="variant" :options="variants">
<template #first>
<option :value="null">-- None --</option>
</template>
</BFormSelect>
</BFormGroup>
<BFormGroup label="Head Variant" label-for="head-style-variant" label-cols-lg="2" class="my-2">
<BFormSelect id="head-style-variant" v-model="headVariant" :options="variants">
<template #first>
<option :value="null">-- None --</option>
</template>
</BFormSelect>
</BFormGroup>
<BFormGroup label="Foot Variant" label-for="foot-style-variant" label-cols-lg="2" class="my-2">
<BFormSelect id="foot-style-variant" v-model="footVariant" :options="variants">
<template #first>
<option :value="null">-- None --</option>
</template>
</BFormSelect>
</BFormGroup>
<BTable
:striped="striped"
:striped-columns="stripedColumns"
:bordered="bordered"
:borderless="borderless"
:outlined="outlined"
:small="small"
:hover="hover"
:dark="dark"
:fixed="fixed"
:foot-clone="footClone"
:no-border-collapse="noCollapse"
:items="items"
:fields="fields"
:variant="variant"
:head-variant="headVariant"
:foot-variant="footVariant"
/>
</div>
</template>
<script setup lang="ts">
import type {ColorVariant} from 'bootstrap-vue-next'
import {ref} from 'vue'
const fields = ['first_name', 'last_name', 'age']
const items = [
{age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{age: 89, first_name: 'Geneva', last_name: 'Wilson'},
]
const striped = ref(false)
const stripedColumns = ref(false)
const bordered = ref(false)
const borderless = ref(false)
const outlined = ref(false)
const small = ref(false)
const hover = ref(false)
const dark = ref(false)
const fixed = ref(false)
const footClone = ref(false)
const variant = ref<ColorVariant | null>(null)
const headVariant = ref<ColorVariant | null>(null)
const footVariant = ref<ColorVariant | null>(null)
const noCollapse = ref(false)
const variants = ['primary', 'secondary', 'info', 'danger', 'warning', 'success', 'light', 'dark']
</script>
Row styling and attributes
You can also style every row using the tbody-tr-class
prop, and optionally supply additional attributes via the tbody-tr-attr
prop:
Property | Type | Description |
---|---|---|
tbody-tr-class | ((item: Items | null, type: TableRowType) => TableStrictClassValue) | TableStrictClassValue | Classes to be applied to every row on the table. |
tbody-tr-attr | ((item: Items | null, type: TableRowType) => AttrsValue) | AttrsValue | Attributes to be applied to every row on the table. |
When passing a function reference to tbody-tr-class
or tbody-tr-attr
, the function's arguments will be as follows:
item
- The item record data associated with the row. For rows that are not associated with an item record, this value will benull
orundefined
type
- The type of row being rendered (TableRowType).'row'
for an item row,'row-details'
for an item details row,'row-top'
for the fixed row top slot,'row-bottom'
for the fixed row bottom slot, or'table-busy'
for the table busy slot.
First name | Last name | Age |
---|---|---|
Dickerson | Macdonald | 40 |
Larsen | Shaw | 21 |
Geneva | Wilson | 89 |
<template>
<div>
<BTable :items="items" :fields="fields" :tbody-tr-class="rowClass" />
</div>
</template>
<script setup lang="ts">
import type {TableRowType, TableStrictClassValue} from 'bootstrap-vue-next'
interface Person {
age: number
first_name: string
last_name: string
status?: string
}
const fields = ['first_name', 'last_name', 'age']
const items = [
{age: 40, first_name: 'Dickerson', last_name: 'Macdonald', status: 'awesome'},
{age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{age: 89, first_name: 'Geneva', last_name: 'Wilson'},
]
const rowClass = (item: Person, type: TableRowType): TableStrictClassValue =>
type === 'row' && item.status === 'awesome' ? 'table-success' : ''
</script>
Responsive tables
Responsive tables allow tables to be scrolled horizontally with ease. Make any table responsive across all viewports by setting the prop responsive
to true
. Or, pick a maximum breakpoint with which to have a responsive table up to by setting the prop responsive
to one of the breakpoint values: sm
, md
, lg
, or xl
.
Heading1 | Heading2 | Heading3 | Heading4 | Heading5 | Heading6 | Heading7 | Heading8 | Heading9 | Heading10 | Heading11 | Heading12 |
---|---|---|---|---|---|---|---|---|---|---|---|
table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell |
table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell |
table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell | table cell |
<template>
<div>
<BTable responsive :items="items" />
</div>
</template>
<script setup lang="ts">
const items = [
{
heading1: 'table cell',
heading2: 'table cell',
heading3: 'table cell',
heading4: 'table cell',
heading5: 'table cell',
heading6: 'table cell',
heading7: 'table cell',
heading8: 'table cell',
heading9: 'table cell',
heading10: 'table cell',
heading11: 'table cell',
heading12: 'table cell',
},
{
heading1: 'table cell',
heading2: 'table cell',
heading3: 'table cell',
heading4: 'table cell',
heading5: 'table cell',
heading6: 'table cell',
heading7: 'table cell',
heading8: 'table cell',
heading9: 'table cell',
heading10: 'table cell',
heading11: 'table cell',
heading12: 'table cell',
},
{
heading1: 'table cell',
heading2: 'table cell',
heading3: 'table cell',
heading4: 'table cell',
heading5: 'table cell',
heading6: 'table cell',
heading7: 'table cell',
heading8: 'table cell',
heading9: 'table cell',
heading10: 'table cell',
heading11: 'table cell',
heading12: 'table cell',
},
]
</script>
Responsive table notes:
- Possible vertical clipping/truncation. Responsive tables make use of
overflow-y: hidden
, which clips off any content that goes beyond the bottom or top edges of the table. In particular, this may clip off dropdown menus and other third-party widgets. - Using props
responsive
andfixed
together will not work as expected. Fixed table layout uses the first row (table header in this case) to compute the width required by each column (and the overall table width) to fit within the width of the parent container — without taking cells in the<tbody>
into consideration — resulting in table that may not be responsive. To get around this limitation, you would need to specify widths for the columns (or certain columns) via one of the following methods:- Use
<col>
elements within thetable-colgroup
slot that have widths set (e.g.<col style="width: 20rem">
), or - Wrap header cells in
<div>
elements, via the use of custom header rendering, which have a minimum width set on them, or - Use the
thStyle
property of the field definition object to set a width for the column(s), or - Use custom CSS to define classes to apply to the columns to set widths, via the
thClass
orclass
properties of the field definition object.
- Use
Stacked tables
An alternative to responsive tables, BootstrapVue includes the stacked table option (using custom SCSS/CSS), which allow tables to be rendered in a visually stacked format. Make any table stacked across all viewports by setting the prop stacked
to true
. Or, alternatively, set a breakpoint at which the table will return to normal table format by setting the prop stacked
to one of the breakpoint values 'sm'
, 'md'
, 'lg'
, or 'xl'
.
Column header labels will be rendered to the left of each field value using a CSS ::before
pseudo element, with a width of 40%.
The stacked
prop takes precedence over the sticky-header
prop and the stickyColumn
field definition property.
Age | First name | Last name |
---|---|---|
40 | Dickerson | Macdonald |
21 | Larsen | Shaw |
89 | Geneva | Wilson |
<template>
<div>
<BTable stacked :items="items" />
</div>
</template>
<script setup lang="ts">
const items = [
{age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{age: 89, first_name: 'Geneva', last_name: 'Wilson'},
]
</script>
Note: When the table is visually stacked:
- The table header (and table footer) will be hidden.
- Custom rendered header slots will not be shown, rather, the fields'
label
will be used. - The table cannot be sorted by clicking the rendered field labels. You will need to provide an external control to select the field to sort by and the sort direction. See the Sorting section below for sorting control information, as well as the complete example at the bottom of this page for an example of controlling sorting via the use of form controls.
- The slots
top-row
andbottom-row
will be hidden when visually stacked. - The table caption, if provided, will always appear at the top of the table when visually stacked.
- In an always stacked table, the table header and footer, and the fixed top and bottom row slots will not be rendered.
BootstrapVueNext's custom CSS is required in order to support stacked tables.
Table caption
Add an optional caption to your table via the prop caption
or the named slot table-caption
(the slot takes precedence over the prop). The default Bootstrap v4 styling places the caption at the bottom of the table:
First name | Last name | Age |
---|---|---|
Dickerson | Macdonald | 40 |
Larsen | Shaw | 21 |
Geneva | Wilson | 89 |
<template>
<div>
<BTable :items="items" :fields="fields">
<template #table-caption>This is a table caption.</template>
</BTable>
</div>
</template>
<script setup lang="ts">
const fields = ['first_name', 'last_name', 'age']
const items = [
{age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{age: 89, first_name: 'Geneva', last_name: 'Wilson'},
]
</script>
You can have the caption placed at the top of the table by setting the caption-top
prop to true
:
First name | Last name | Age |
---|---|---|
Dickerson | Macdonald | 40 |
Larsen | Shaw | 21 |
Geneva | Wilson | 89 |
<template>
<div>
<BTable :items="items" :fields="fields" caption-top>
<template #table-caption>This is a table caption at the top.</template>
</BTable>
</div>
</template>
<script setup lang="ts">
const fields = ['first_name', 'last_name', 'age']
const items = [
{age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{age: 89, first_name: 'Geneva', last_name: 'Wilson'},
]
</script>
You can also use custom CSS to control the caption positioning.
Table colgroup
Not yet implemented Thetable-colgroup
slot is not yet implemented.Table busy state
<BTable>
provides a busy
model that will flag the table as busy, which you can set to true
just before you update your items, and then set it to false
once you have your items. When in the busy state, the table will have the attribute aria-busy="true"
.
During the busy state, the table will be rendered in a "muted" look (opacity: 0.55
), using the following custom CSS:
/* Busy table styling */
.table.b-table[aria-busy="true"] {
opacity: 0.55;
}
table-busy slot
First name | Last name | Age |
---|---|---|
Dickerson | MacDonald | 40 |
Larsen | Shaw | 21 |
Geneva | Wilson | 89 |
Jami | Carney | 38 |
<template>
<div>
<BButton @click="toggleBusy">Toggle Busy State</BButton>
<BTable :items="items" :busy="isBusy" class="mt-3" outlined>
<!-- <template #table-busy>
<div class="text-center text-danger my-2">
<BSpinner class="align-middle" />
<strong>Loading...</strong>
</div>
</template> -->
</BTable>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
const isBusy = ref(false)
const items = [
{first_name: 'Dickerson', last_name: 'MacDonald', age: 40},
{first_name: 'Larsen', last_name: 'Shaw', age: 21},
{first_name: 'Geneva', last_name: 'Wilson', age: 89},
{first_name: 'Jami', last_name: 'Carney', age: 38},
]
const toggleBusy = () => {
isBusy.value = !isBusy.value
}
</script>
Also see the Using Items Provider Functions below for additional information on the busy
state.
Notes:
- All click related and hover events, and sort-changed events will not be emitted when the table is in the
busy
state. - Busy styling and slot are not available in the
<BTableLite>
component.
Custom data rendering
Custom rendering for each data field in a row is possible using either scoped slots or a formatter callback function, or a combination of both.
Scoped field slots
Scoped field slots give you greater control over how the record data appears. You can use scoped slots to provided custom rendering for a particular field. If you want to add an extra field which does not exist in the records, just add it to the fields
array, and then reference the field(s) in the scoped slot(s). Scoped field slots use the following naming syntax: `'cell(${field_key})'`
.
You can use the default fall-back scoped slot 'cell()'
to format any cells that do not have an explicit scoped slot provided.
Index | Full Name | Age | Sex | First name and age |
---|---|---|---|---|
1 | DOE, John | 42 | Male | John is 42 years old |
2 | DOE, Jane | 36 | Female | Jane is 36 years old |
3 | KINCADE, Rubin | 73 | Male | Rubin is 73 years old |
4 | PARTRIDGE, Shirley | 62 | Female | Shirley is 62 years old |
<template>
<div>
<BTable small :fields="fields" :items="items" responsive="sm">
<!-- A virtual column -->
<template #cell(index)="data">
{{ data.index + 1 }}
</template>
<!-- A custom formatted column -->
<template #cell(name)="data">
<b class="text-info">{{ data.item.name.last.toUpperCase() }}</b
>, <b>{{ data.item.name.first }}</b>
</template>
<!-- A virtual composite column -->
<template #cell(nameage)="data">
{{ data.item.name.first }} is {{ data.item.age }} years old
</template>
<!-- Optional default data cell scoped slot -->
<template #cell()="data">
<i>{{ data.value }}</i>
</template>
</BTable>
</div>
</template>
<script setup lang="ts">
interface Person {
name: {first: string; last: string}
sex: string
age: number
}
const fields = [
// A virtual column that doesn't exist in items
'index',
// A column that needs custom formatting
{key: 'name', label: 'Full Name'},
// A regular column
'age',
// A regular column
'sex',
// A virtual column made up from two fields
{key: 'nameage', label: 'First name and age'},
]
const items: Person[] = [
{name: {first: 'John', last: 'Doe'}, sex: 'Male', age: 42},
{name: {first: 'Jane', last: 'Doe'}, sex: 'Female', age: 36},
{name: {first: 'Rubin', last: 'Kincade'}, sex: 'Male', age: 73},
{name: {first: 'Shirley', last: 'Partridge'}, sex: 'Female', age: 62},
]
</script>
The slot's scope variable (data
in the above sample) will have the following properties:
Property | Type | Description |
---|---|---|
index | number | The row number (indexed from zero) relative to the displayed rows |
item | Items | The entire raw record data (i.e. items[index] ) for this row (before any formatter is applied) |
value | unknown | The value for this key in the record (null or undefined if a virtual column), or the output of the field's formatter function |
unformatted | unknown | The raw value for this key in the item record (null or undefined if a virtual column), before being passed to the field's formatter function |
field | (typeof computedFields.value)[0] | The field's normalized field definition object |
detailsShowing | boolean | Will be true if the row's row-details scoped slot is visible. See section Row details support below for additional information |
toggleDetails | () => void | Can be called to toggle the visibility of the rows row-details scoped slot. See section Row details support below for additional information |
rowSelected | boolean | Will be true if the row has been selected. See section Row select support for additional information |
selectRow | (index?: number) => void | When called, selects the current row. See section Row select support for additional information |
unselectRow | (index?: number) => void | When called, unselects the current row. See section Row select support for additional information |
Notes:
index
will not always be the actual row's index number, as it is computed after filtering, sorting and pagination have been applied to the original table data. Theindex
value will refer to the displayed row number.- When using the
v-slot
syntax, note that slot names cannot contain spaces, and when using in-browser DOM templates the slot names will always be lower cased. To get around this, you can pass the slot name using Vue's dynamic slot names
Displaying raw HTML
By default BTable
escapes HTML tags in items data and results of formatter functions, if you need to display raw HTML code in BTable
, you should use v-html
directive on an element in a in scoped field slot.
Text | Html |
---|---|
This is <i>escaped</i> content | This is raw HTML content |
<template>
<div>
<b-table :items="items">
<template #cell(html)="data">
<!-- eslint-disable vue/no-v-html -->
<span v-html="data.value" />
</template>
</b-table>
</div>
</template>
<script setup lang="ts">
const items = [
{
text: 'This is <i>escaped</i> content',
html: 'This is <i>raw <strong>HTML</strong></i> <span style="color:red">content</span>',
},
]
</script>
WARNING
Be cautious of using the v-html
method to display user supplied content, as it may make your application vulnerable to XSS attacks, if you do not first sanitize the user supplied string.
Formatter callback
Optionally, you can customize field output by using a formatter callback function. To enable this, the field's formatter
property is used. The value of this property may be String or function reference. In case of a String value, the function must be defined at the parent component's methods. When providing formatter
as a Function
, it must be declared at global scope (window or as global mixin at Vue, or as an anonymous function), unless it has been bound to a this
context.
The callback function accepts three arguments - value
, key
, and item
, and should return the formatted value as a string (HTML strings are not supported)
Full Name | Age | Sex | Calculated Birth Year |
---|---|---|---|
John Doe | 42 | M | 1983 |
Jane Doe | 36 | F | 1989 |
Rubin Kincade | 73 | M | 1952 |
Shirley Partridge | 62 | F | 1963 |
<template>
<div>
<BTable :fields="fields" :items="items">
<template #cell(name)="data">
<!-- `data.value` is the value after formatted by the Formatter -->
<a :href="`#${(data.value as any as string).replace(/[^a-z]+/i, '-').toLowerCase()}`">{{
data.value
}}</a>
</template>
</BTable>
</div>
</template>
<script setup lang="ts">
import type {TableFieldRaw} from 'bootstrap-vue-next'
interface Name {
first: string
last: string
}
interface Person {
name: Name
sex: string
age: number
}
const fullName = (value: unknown) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const name = value as any as Name
return `${name.first} ${name.last}`
}
const fields: TableFieldRaw<Person>[] = [
{
// A column that needs custom formatting,
// calling formatter 'fullName' in this app
key: 'name',
label: 'Full Name',
formatter: fullName,
},
// A regular column
'age',
{
// A regular column with custom formatter
key: 'sex',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
formatter: (value) => (value as any as string).charAt(0).toUpperCase(),
},
{
// A virtual column with custom formatter
key: 'birthYear',
label: 'Calculated Birth Year',
formatter: (value_, key_, item: Person) => (new Date().getFullYear() - item.age).toString(),
},
]
const items = [
{name: {first: 'John', last: 'Doe'}, sex: 'Male', age: 42},
{name: {first: 'Jane', last: 'Doe'}, sex: 'Female', age: 36},
{name: {first: 'Rubin', last: 'Kincade'}, sex: 'male', age: 73},
{name: {first: 'Shirley', last: 'Partridge'}, sex: 'female', age: 62},
]
</script>
Header and Footer custom rendering via scoped slots
It is also possible to provide custom rendering for the table's thead
and tfoot
elements. Note by default the table footer is not rendered unless foot-clone
is set to true
.
Scoped slots for the header and footer cells uses a special naming convention of 'head(<fieldkey>)'
and 'foot(<fieldkey>)'
respectively. if a 'foot(...)'
slot for a field is not provided, but a 'head(...)'
slot is provided, then the footer will use the 'head(...)'
slot content.
You can use a default fall-back scoped slot 'head()'
or 'foot()'
to format any header or footer cells that do not have an explicit scoped slot provided.
FULL NAME | Age | Sex |
---|---|---|
John Doe | 42 | Male |
Jane Doe | 36 | Female |
Rubin Kincade | 73 | Male |
Shirley Partridge | 62 | Female |
Full Name | Age | Sex |
<template>
<div>
<BTable :fields="fields" :items="items" foot-clone>
<!-- A custom formatted data column cell -->
<template #cell(name)="data">
{{ (data.value as any as Name).first }} {{ (data.value as any as Name).last }}
</template>
<!-- A custom formatted header cell for field 'name' -->
<template #head(name)="data">
<span class="text-info">{{ data.label!.toUpperCase() }}</span>
</template>
<!-- A custom formatted footer cell for field 'name' -->
<template #foot(name)="data">
<span class="text-danger">{{ data.label }}</span>
</template>
<!-- Default fall-back custom formatted footer cell -->
<template #foot()="data">
<i>{{ data.label }}</i>
</template>
</BTable>
</div>
</template>
<script setup lang="ts">
type Name = {first: string; last: string}
const fields = [
// A column that needs custom formatting
{key: 'name', label: 'Full Name'},
// A regular column
'age',
// A regular column
'sex',
]
const items = [
{name: {first: 'John', last: 'Doe'}, sex: 'Male', age: 42},
{name: {first: 'Jane', last: 'Doe'}, sex: 'Female', age: 36},
{name: {first: 'Rubin', last: 'Kincade'}, sex: 'Male', age: 73},
{name: {first: 'Shirley', last: 'Partridge'}, sex: 'Female', age: 62},
]
</script>
The slots can be optionally scoped (data
in the above example), and will have the following properties:
Property | Type | Description |
---|---|---|
column | LiteralUnion<keyof Items> | The fields's key value |
field | TableField<Items> | the field's object (from the fields prop) |
label | string | undefined | The fields label value (also available as data.field.label ) |
isFoot | boolean | Currently rending the foot if true |
selectAllRows | () => void | Select all rows (applicable if the table is in selectable mode |
clearSelected | () => void | Unselect all rows (applicable if the table is in selectable mode |
When placing inputs, buttons, selects or links within a head(...)
or foot(...)
slot, note that head-clicked
event will not be emitted when the input, select, textarea is clicked (unless they are disabled). head-clicked
will never be emitted when clicking on links or buttons inside the scoped slots (even when disabled)
Notes:
- Slot names cannot contain spaces, and when using in-browser DOM templates the slot names will always
- be lower cased. To get around this, you can pass the slot name using Vue's dynamic slot names
Adding additional rows to the header
If you wish to add additional rows to the header you may do so via the thead-top
slot. This slot is inserted before the header cells row, and is not automatically encapsulated by <tr>..</tr>
tags. It is recommended to use the BootstrapVue table helper components, rather than native browser table child elements.
Name and ID | Type 1 | Type 2 | Type 3 | |||
---|---|---|---|---|---|---|
Name | ID | Type 1 | Type 2A | Type 2B | Type 2C | Type 3 |
Stephen Hawking | 1 | false | true | false | false | false |
Johnny Appleseed | 2 | false | true | true | false | false |
George Washington | 3 | false | false | false | false | true |
Albert Einstein | 4 | true | false | false | true | false |
Isaac Newton | 5 | true | true | false | true | false |
<template>
<div>
<BTable :items="items" :fields="fields" responsive="sm">
<template #thead-top>
<BTr>
<BTh colspan="2"><span class="visually-hidden">Name and ID</span></BTh>
<BTh variant="secondary">Type 1</BTh>
<BTh variant="primary" colspan="3">Type 2</BTh>
<BTh variant="danger">Type 3</BTh>
</BTr>
</template>
</BTable>
</div>
</template>
<script setup lang="ts">
const items = [
{
name: 'Stephen Hawking',
id: 1,
type1: false,
type2a: true,
type2b: false,
type2c: false,
type3: false,
},
{
name: 'Johnny Appleseed',
id: 2,
type1: false,
type2a: true,
type2b: true,
type2c: false,
type3: false,
},
{
name: 'George Washington',
id: 3,
type1: false,
type2a: false,
type2b: false,
type2c: false,
type3: true,
},
{
name: 'Albert Einstein',
id: 4,
type1: true,
type2a: false,
type2b: false,
type2c: true,
type3: false,
},
{
name: 'Isaac Newton',
id: 5,
type1: true,
type2a: true,
type2b: false,
type2c: true,
type3: false,
},
]
const fields = [
'name',
{key: 'id', label: 'ID'},
{key: 'type1', label: 'Type 1'},
{key: 'type2a', label: 'Type 2A'},
{key: 'type2b', label: 'Type 2B'},
{key: 'type2c', label: 'Type 2C'},
{key: 'type3', label: 'Type 3'},
]
</script>
Slot thead-top
can be optionally scoped, receiving an object with the following properties:
Property | Type | Description |
---|---|---|
columns | number | The number of columns in the rendered table |
fields | TableField<Items>[] | Array of field definition objects (normalized to the array of objects format) |
selectAllRows | () => void | Select all rows (applicable if the table is in selectable mode |
clearSelected | () => void | Unselect all rows (applicable if the table is in selectable mode |
Creating a custom footer
If you need greater layout control of the content of the <tfoot>
, you can use the optionally scoped slot custom-foot
to provide your own rows and cells. Use BootstrapVue's table helper sub-components <BTr>
, <BTh>
, and <BTd>
to generate your custom footer layout.
Slot custom-foot
can be optionally scoped, receiving an object with the following properties:
Property | Type | Description |
---|---|---|
columns | number | The number of columns in the rendered table |
fields | TableField<Items>[] | Array of field definition objects (normalized to the array of objects format) |
items | readonly Items[] | Array of the currently displayed items records - after filtering, sorting and pagination |
Notes:
- The
custom-foot
slot will not be rendered if thefoot-clone
prop has been set. head-clicked
events are not be emitted when clicking oncustom-foot
cells.- Sorting and sorting icons are not available for cells in the
custom-foot
slot. - The custom footer will not be shown when the table is in visually stacked mode.
Custom empty and empty-filtered rendering via slots
Aside from using empty-text
, empty-filtered-text
, it is also possible to provide custom rendering for tables that have no data to display using named slots.
In order for these slots to be shown, the show-empty
attribute must be set and items
must be either falsy or an array of length 0.
<BTable :fields="fields" :items="items" show-empty>
<template #empty="scope">
<h4>{{ scope.emptyText }}</h4>
</template>
<template #empty-filtered="scope">
<h4>{{ scope.emptyFilteredText }}</h4>
</template>
</BTable>
The slot can optionally be scoped. The slot's scope (scope
in the above example) will have the following properties:
Property | Type | Description |
---|---|---|
emptyFilteredHtml | string | The empty-filtered-html prop |
emptyFilteredText | string | The empty-filtered-text prop |
fields | TableField<Items>[] | The fields prop |
items | Items[] | The items prop. Exposed here to check null vs [] |
NOTE
If you prefiously used the emptyHtml
or emtpyFilteredHtml
scoped slots or the empty-html
or empty-filtered-html
props, please convert to using the empty-text
or empty-filtered-text
slots instead. See our migration guide for details.
Advanced Features
Sticky headers
Use the sticky-header
prop to enable a vertically scrolling table with headers that remain fixed (sticky) as the table body scrolls. Setting the prop to true
(or no explicit value) will generate a table that has a maximum height of 300px
. To specify a maximum height other than 300px
, set the sticky-header
prop to a valid CSS height (including units), i.e. sticky-header="200px"
. Tables with sticky-header
enabled will also automatically become always responsive horizontally, regardless of the responsive
prop setting, if the table is wider than the available horizontal space.
Heading1 | Heading2 | Heading3 |
---|---|---|
table cell | table cell | table cell |
table cell | table cell | table cell |
table cell | table cell | table cell |
table cell | table cell | table cell |
table cell | table cell | table cell |
table cell | table cell | table cell |
table cell | table cell | table cell |
table cell | table cell | table cell |
table cell | table cell | table cell |
table cell | table cell | table cell |
table cell | table cell | table cell |
table cell | table cell | table cell |
<template>
<div>
<BTable sticky-header :items="items" head-variant="light" />
</div>
</template>
<script setup lang="ts">
const items = [
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
{heading1: 'table cell', heading2: 'table cell', heading3: 'table cell'},
]
</script>
Sticky header notes:
- The
sticky-header
prop has no effect if the table has thestacked
prop set. - Sticky header tables are wrapped inside a vertically scrollable
<div>
with a maximum height set. - BootstrapVue's custom CSS is required in order to support
sticky-header
. - Bootstrap v5 uses the CSS style
border-collapse: collapsed
on table elements. This prevents the borders on the sticky header from "sticking" to the header, and hence the borders will scroll when the body scrolls. To get around this issue, set the propno-border-collapse
on the table (note that this may cause double width borders when using features such asbordered
, etc.).
Sticky columns
Columns can be made sticky, where they stick to the left of the table when the table has a horizontal scrollbar. To make a column a sticky column, set the stickyColumn
prop in the field's header definition. Sticky columns will only work when the table has either the sticky-header
prop set and/or the responsive
prop is set.
Row ID | Heading A | Heading B | Heading | Heading D | Heading E | Heading F | Heading G | Heading H | Heading I | Heading J | Heading K | Heading L |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
2 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
3 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
4 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
5 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
6 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
7 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
8 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
9 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
10 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
<template>
<div>
<div class="mb-2">
<b-form-checkbox v-model="stickyHeader" inline>Sticky header</b-form-checkbox>
<b-form-checkbox v-model="noCollapse" inline>No border collapse</b-form-checkbox>
</div>
<b-table
:sticky-header="stickyHeader"
:no-border-collapse="noCollapse"
responsive
:items="items"
:fields="fields"
>
<!-- We are using utility class `text-nowrap` to help illustrate horizontal scrolling -->
<template #head(id)>
<div class="text-nowrap">Row ID</div>
</template>
<template #head()="scope">
<div class="text-nowrap">Heading {{ scope.label }}</div>
</template>
</b-table>
</div>
</template>
<script setup lang="ts">
import type {TableFieldRaw} from 'bootstrap-vue-next'
import {ref} from 'vue'
const fields: TableFieldRaw[] = [
{key: 'id', stickyColumn: true, isRowHeader: true, variant: 'primary'},
'a',
'b',
{key: 'c', stickyColumn: true, variant: 'info'},
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
]
const items = [
{id: 1, a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7, i: 8, j: 9, k: 10, l: 11},
{id: 2, a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7, i: 8, j: 9, k: 10, l: 11},
{id: 3, a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7, i: 8, j: 9, k: 10, l: 11},
{id: 4, a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7, i: 8, j: 9, k: 10, l: 11},
{id: 5, a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7, i: 8, j: 9, k: 10, l: 11},
{id: 6, a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7, i: 8, j: 9, k: 10, l: 11},
{id: 7, a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7, i: 8, j: 9, k: 10, l: 11},
{id: 8, a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7, i: 8, j: 9, k: 10, l: 11},
{id: 9, a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7, i: 8, j: 9, k: 10, l: 11},
{id: 10, a: 0, b: 1, c: 2, d: 3, e: 4, f: 5, g: 6, h: 7, i: 8, j: 9, k: 10, l: 11},
]
const stickyHeader = ref(true)
const noCollapse = ref(false)
</script>
Sticky column notes:
- Sticky columns has no effect if the table has the
stacked
prop set. - Sticky columns tables require either the
sticky-header
and/orresponsive
modes, and are wrapped inside a horizontally scrollable<div>
. - When you have multiple columns that are set as
stickyColumn
, the columns will stack over each other visually, and the left-most sticky columns may "peek" out from under the next sticky column. To get around this behaviour, make sure your latter sticky columns are the same width or wider than previous sticky columns. - Bootstrap v5 uses the CSS style
border-collapse: collapsed
on table elements. This prevents any borders on the sticky columns from "sticking" to the column, and hence those borders will scroll when the body scrolls. To get around this issue, set the propno-border-collapse
on the table (note that this may cause double width borders when using features such asbordered
, etc.). - BootstrapVue's custom CSS is required in order to support sticky columns.
- The sticky column feature uses CSS style
position: sticky
to position the column cells. Internet Explorer does not supportposition: sticky
, hence for IE 11 the sticky column will scroll with the table body.
Row details support
If you would optionally like to display additional record information (such as columns not specified in the fields definition array), you can use the scoped slot row-details
, in combination with the special item record boolean
property _showDetails
.
If the record has its _showDetails
property set to true
, and a row-details
scoped slot exists, a new row will be shown just below the item, with the rendered contents of the row-details
scoped slot.
In the scoped field slot, you can toggle the visibility of the row's row-details
scoped slot by calling the toggleDetails
function passed to the field's scoped slot variable. You can use the scoped fields slot variable detailsShowing
to determine the visibility of the row-details
slot.
NOTE
If manipulating the _showDetails
property directly on the item data (i.e. not via the toggleDetails
function reference), the _showDetails
property must exist in the items data for proper reactive detection of changes to its value. Read more about how reactivity works in Vue.
Available row-details
scoped variable properties:
Property | Type | Description |
---|---|---|
item | Items | The entire row record data object |
index | number | The current visible row number |
fields | TableField<Items>[] | The normalized fields definition array (in the array of objects format) |
toggleDetails | () => void | Function to toggle visibility of the row's details slot |
rowSelected | boolean | Will be true if the row has been selected. See section Row select support for additional information |
selectRow | (index?: number) => void | When called, selects the current row. See section Row select support for additional information |
unselectRow | (index?: number) => void | When called, unselects the current row. See section Row select support for additional information |
NOTE
the row select related scope properties are only available in <BTable>
.
In the following example, we show two methods of toggling the visibility of the details: one via a button, and one via a checkbox. We also show the third row details defaulting to have details initially showing.
First name | Last name | Show details |
---|---|---|
Dickerson | Macdonald | |
Larsen | Shaw | |
Geneva | Wilson | |
Age: 89 Is Active: false | ||
Jami | Carney |
<template>
<div>
<BTable :items="items" :fields="fields" striped responsive="sm">
<template #cell(show_details)="row">
<BButton size="sm" class="mr-2" @click="row.toggleDetails">
{{ row.detailsShowing ? 'Hide' : 'Show' }} Details
</BButton>
<!-- As `row.showDetails` is one-way, we call the toggleDetails function on @change -->
<BFormCheckbox v-model="row.detailsShowing" @change="row.toggleDetails">
Details via check
</BFormCheckbox>
</template>
<template #row-details="row">
<BCard>
<BRow class="mb-2">
<BCol sm="3" class="text-sm-right"><b>Age:</b></BCol>
<BCol>{{ row.item.age }}</BCol>
</BRow>
<BRow class="mb-2">
<BCol sm="3" class="text-sm-right"><b>Is Active:</b></BCol>
<BCol>{{ row.item.isActive }}</BCol>
</BRow>
<BButton size="sm" @click="row.toggleDetails">Hide Details</BButton>
</BCard>
</template>
</BTable>
</div>
</template>
<script setup lang="ts">
const fields = ['first_name', 'last_name', 'show_details']
const items = [
{isActive: true, age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{isActive: false, age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{
isActive: false,
age: 89,
first_name: 'Geneva',
last_name: 'Wilson',
_showDetails: true,
},
{isActive: true, age: 38, first_name: 'Jami', last_name: 'Carney'},
]
</script>
Row select support
You can make rows selectable, by using the <BTable>
prop selectable
.
Users can easily change the selecting mode by setting the select-mode
prop.
'multi'
: Each click will select/deselect the row (default mode)'single'
: Only a single row can be selected at one time'range'
: Any row clicked is selected, any other deselected. Shift + click selects a range of rows, and Ctrl (or Cmd) + click will toggle the selected row.
When a table is selectable
and the user clicks on a row, <BTable>
will emit the update:selected-items
event, passing a single argument which is the complete list of selected items. This argument is read-only. In addition, row-selected
or row-unselected
events are emitted for each row.
Rows can also be programmatically selected and unselected via the following exposed methods on the <BTable>
instance:
Method | Description |
---|---|
selectRow(index: number) | Selects a row with the given index number. |
unselectRow(index: number) | Unselects a row with the given index number. |
selectAllRows() | Selects all rows in the table, except in single mode in which case only the first row is selected. |
clearSelected() | Unselects all rows. |
isRowSelected(index: number) | Returns true if the row with the given index is selected, otherwise it returns false . |
Programmatic row selection notes:
index
is the zero-based index of the table's visible rows, after filtering, sorting, and pagination have been applied.- In
single
mode,selectRow(index)
will unselect any previous selected row. - Attempting to
selectRow(index)
orunselectRow(index)
on a non-existent row will be ignored. - The table must be
selectable
for any of these methods to have effect. - You can disable selection of rows via click events by setting the
no-select-on-click
prop. Rows will then only be selectable programmatically.
Row select notes:
- Sorting, filtering, or paginating the table will clear the active selection. The
update:selected-items
event will be emitted with an empty array ([]
) if needed. - When the table is in
selectable
mode, all data item<tr>
elements will be in the document tab sequence (tabindex="0"
) for accessibility reasons, and will have the attributearia-selected
set to either'true'
or'false'
depending on the selected state of the row. - Not yet implementedWhen a table is
selectable
, the table will have the attributearia-multiselect
set to either'false'
forsingle
mode, and'true'
for eithermulti
orrange
modes.
Use the prop selected-variant
to apply a Bootstrap theme color to the selected row(s). Note, due to the order that the table variants are defined in Bootstrap's CSS, any row-variant might take precedence over the selected-variant
. You can set selected-variant
to an empty string if you will be using other means to convey that a row is selected (such as a scoped field slot in the below example).
The selected-variant
can be any of the standard (or custom) Bootstrap base color variants, or the special table active
variant (the default) which takes precedence over any specific row or cell variants.
For accessibility reasons (specifically for color blind users, or users with color contrast issues), it is highly recommended to always provide some other visual means of conveying that a row is selected, such as a virtual column as shown in the example below.
Selected | Is Active | Age | First name | Last name |
---|---|---|---|---|
Not selected | true | 40 | Dickerson | Macdonald |
Not selected | false | 21 | Larsen | Shaw |
Not selected | false | 89 | Geneva | Wilson |
Not selected | true | 38 | Jami | Carney |
Selected Rows:
[]
<template>
<div>
<BFormGroup label="Selection mode:" label-for="table-select-mode-select" label-cols-md="4">
<BFormSelect
id="table-select-mode-select"
v-model="selectMode"
:options="modes"
class="mb-3"
/>
</BFormGroup>
<BTable
ref="selectable-table"
:items="items"
:fields="fields"
:select-mode="selectMode"
responsive="sm"
selectable
@update:selected-items="onSelectedItems"
>
<!-- Example scoped slot for select state illustrative purposes -->
<template #cell(selected)="{rowSelected}">
<template v-if="rowSelected">
<span aria-hidden="true">✓</span>
<span class="sr-only">Selected</span>
</template>
<template v-else>
<span aria-hidden="true"> </span>
<span class="sr-only">Not selected</span>
</template>
</template>
</BTable>
<p>
<BButton size="sm" class="me-2" @click="selectAllRows">Select all</BButton>
<BButton size="sm" class="me-2" @click="clearSelected">Clear selected</BButton>
<BButton size="sm" class="me-2" @click="selectThirdRow">Select 3rd row</BButton>
<BButton size="sm" @click="unselectThirdRow">Unselect 3rd row</BButton>
</p>
<p>
Selected Rows:<br />
{{ selected }}
</p>
</div>
</template>
<script setup lang="ts">
import {ref, useTemplateRef} from 'vue'
import {BTable} from 'bootstrap-vue-next'
import type {ComponentExposed} from 'vue-component-type-helpers'
interface Person {
first_name: string
last_name: string
age: number
isActive: boolean
}
const selectableTable = useTemplateRef<ComponentExposed<typeof BTable>>('selectable-table')
const modes = ['multi', 'single', 'range']
const fields = ['selected', 'isActive', 'age', 'first_name', 'last_name']
const items: Person[] = [
{isActive: true, age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{isActive: false, age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{isActive: false, age: 89, first_name: 'Geneva', last_name: 'Wilson'},
{isActive: true, age: 38, first_name: 'Jami', last_name: 'Carney'},
]
const selectMode = ref<'multi' | 'single' | 'range'>('multi')
const selected = ref<Person[]>([])
const onSelectedItems = (selectedItems: readonly Person[]) => {
selected.value = [...selectedItems]
}
const selectAllRows = () => {
selectableTable?.value?.selectAllRows()
}
const clearSelected = () => {
selectableTable?.value?.clearSelected()
}
const selectThirdRow = () => {
// Rows are indexed from 0, so the third row is index 2
selectableTable?.value?.selectRow(2)
}
const unselectThirdRow = () => {
// Rows are indexed from 0, so the third row is index 2
selectableTable?.value?.unselectRow(2)
}
</script>
Table body transition support
Not yet implementedSorting
As mentioned in the Fields section above, you can make columns sortable in <BTable>
. Clicking on a sortable column header will sort the column in ascending direction (smallest first), while clicking on it again will switch the direction of sorting to descending (largest first). Clicking on it a third time will stop sorting on the column. For single column sorting (e.g. multisort===false
) clicking on a differnt sortable column header will sort that column in ascending order and clear the sort order for the previously sorted column.
You can control which column is pre-sorted and the order of sorting (ascending or descending). To pre-specify the column to be sorted use the sortBy
model. For single column sorting (e.g. multisort===false
) sortBy
should be an array containing a single BTableSortBy
object with a defined order
field.
type BTableSortByOrder = 'desc' | 'asc' | undefined
// Where T is the type of `items` in the table
type BTableSortByComparerFunction<T = unknown> = (a: T, b: T) => number
type BTableSortBy<T = unknown> = {
order: BTableSortByOrder
key: string
comparer?: BTableSortByComparerFunction<T>
}
- Ascending: Items are sorted lowest to highest (i.e.
A
toZ
) and will be displayed with the lowest value in the first row with progressively higher values in the following rows. - Descending: Items are sorted highest to lowest (i.e.
Z
toA
) and will be displayed with the highest value in the first row with progressively lower values in the following rows.
By default the comparer function does a numeric localeCompare
. If one wishes to change this, use a custom comparer function with that BTableSortBy
element.
To prevent the table from wiping out the comparer function, internally it will set the order
key to undefined
, instead of just removing the element from the sortBy
array. i.e. :sort-by="[]"
& :sort-by="[key: 'someKey', order: undefined]"
behave identically. Naturally if this value is given to a server, orders of undefined should be handled. See the computed singleSortBy
function below as a simple means of retrieving the single sortded column reference from a table that is in single sort mode.
Last Name | First Name | Age | Is Active |
---|---|---|---|
Macdonald | Zelda | 45 | true |
Shaw | Larsen | 21 | false |
Carney | Jami | 38 | true |
Wilson | Geneva | 89 | false |
Wilson | Gary | 89 | false |
Macdonald | Dickerson | 40 | true |
<template>
<BTable :sort-by="[{key: 'first_name', order: 'desc'}]" :items="items" :fields="fields" />
</template>
<script setup lang="ts">
import {type TableFieldRaw, type TableItem} from 'bootstrap-vue-next'
interface SortPerson {
first_name: string
last_name: string
age: number
isActive: boolean
}
const items: TableItem<SortPerson>[] = [
{isActive: true, age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{isActive: true, age: 45, first_name: 'Zelda', last_name: 'Macdonald'},
{isActive: false, age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{isActive: false, age: 89, first_name: 'Geneva', last_name: 'Wilson'},
{isActive: false, age: 89, first_name: 'Gary', last_name: 'Wilson'},
{isActive: true, age: 38, first_name: 'Jami', last_name: 'Carney'},
]
const fields: Exclude<TableFieldRaw<SortPerson>, string>[] = [
{key: 'last_name', sortable: true},
{key: 'first_name', sortable: true},
{key: 'age', sortable: true},
{key: 'isActive', sortable: false},
]
</script>
sorbBy
is a named model so it can be bound to an object that will be updated with the current sort state when the user changes sorting by clicking the headers.
Last Name | First Name | Age | Is Active |
---|---|---|---|
Macdonald | Zelda | 45 | true |
Shaw | Larsen | 21 | false |
Carney | Jami | 38 | true |
Wilson | Geneva | 89 | false |
Wilson | Gary | 89 | false |
Macdonald | Dickerson | 40 | true |
<template>
<BTable v-model:sort-by="sortBy" :items="items" :fields="fields" />
<div>sortBy = {{ JSON.stringify(sortBy) }}</div>
<div>singleSortBy = {{ JSON.stringify(singleSortBy) }}</div>
</template>
<script setup lang="ts">
import {type BTableSortBy, type TableFieldRaw, type TableItem} from 'bootstrap-vue-next'
import {computed, ref} from 'vue'
interface SortPerson {
first_name: string
last_name: string
age: number
isActive: boolean
}
const items: TableItem<SortPerson>[] = [
{isActive: true, age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{isActive: true, age: 45, first_name: 'Zelda', last_name: 'Macdonald'},
{isActive: false, age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{isActive: false, age: 89, first_name: 'Geneva', last_name: 'Wilson'},
{isActive: false, age: 89, first_name: 'Gary', last_name: 'Wilson'},
{isActive: true, age: 38, first_name: 'Jami', last_name: 'Carney'},
]
const fields: TableFieldRaw<SortPerson>[] = [
{key: 'last_name', sortable: true},
{key: 'first_name', sortable: true},
{key: 'age', sortable: true},
{key: 'isActive', sortable: false},
]
const sortBy = ref<BTableSortBy[]>([{key: 'first_name', order: 'desc'}])
const singleSortBy = computed(() => sortBy.value.find((sb) => sb.order !== undefined))
</script>
Tables can be sorted by multiple columns. Programmaticly, this can be done by adding more entries to the sortBy
array. From the user inteface, multi-sort works as follows:
- Clicking on a sortable header that isn't currently sorted adds it as
ascending
to the end of the sortBy list - Clicking on a sortable header that is currently sorted as ascending makes it descending, but leaves it in the same order in the
sortBy
list - Clicking on a sortable header that is currently sorted as descending will set the order to undefined. If
must-sort
istrue
OR ifmustSort
is an array that contains that columnskey
, it will skip to beascending
Last Name | First Name | Age | Is Active |
---|---|---|---|
Carney | Jami | 38 | true |
Macdonald | Dickerson | 40 | true |
Macdonald | Zelda | 45 | true |
Shaw | Larsen | 21 | false |
Wilson | Gary | 89 | false |
Wilson | Geneva | 89 | false |
<template>
<BTable v-model:sort-by="multiSortBy" :items="sortItems" :fields="sortFields" :multisort="true" />
<div>sortBy = {{ JSON.stringify(multiSortBy) }}</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import {type BTableSortBy, type TableFieldRaw, type TableItem} from 'bootstrap-vue-next'
interface SortPerson {
first_name: string
last_name: string
age: number
isActive: boolean
}
const sortItems: TableItem<SortPerson>[] = [
{isActive: true, age: 40, first_name: 'Dickerson', last_name: 'Macdonald'},
{isActive: true, age: 45, first_name: 'Zelda', last_name: 'Macdonald'},
{isActive: false, age: 21, first_name: 'Larsen', last_name: 'Shaw'},
{isActive: false, age: 89, first_name: 'Geneva', last_name: 'Wilson'},
{isActive: false, age: 89, first_name: 'Gary', last_name: 'Wilson'},
{isActive: true, age: 38, first_name: 'Jami', last_name: 'Carney'},
]
const sortFields: TableFieldRaw<SortPerson>[] = [
{key: 'last_name', sortable: true},
{key: 'first_name', sortable: true},
{key: 'age', sortable: true},
{key: 'isActive', sortable: false},
]
const multiSortBy = ref<BTableSortBy[]>([
{key: 'last_name', order: 'asc'},
{key: 'first_name', order: 'asc'},
])
</script>
Custom Sort Comparer(s)
Each item in the BSortBy
model may include a comparer
field of the type BTableSortByComparerFunction<T = any> = (a: T, b: T, key: string) => number
. This function takes the items to be compared and the key to compare on. Since the key is passed in, you may use the same function for multiple fields or you can craft a different comparer function for each fied. Leaving the comparer
field undefined (or not defining a field in the sortBy
array at all) will fall back to using hte default comparer, which looks like this:
const defaultComparer = (a: unknown, b: unknown): number =>
getStringValue(a).localeCompare(getStringValue(b), undefined, {numeric: true})
where getStringValue
retrieves the field value as a string.
If you have a particular field that you want to sort by, you can set up a record of the sortBy
model with a custom comparer:
const removeArticles = (str: string) => str.replace(/^(a |the )/i, '')
const sortBy = [
{
key: 'titleField',
comparer: (a: T, b: T, key: string) =>
removeArticles(a.titleField).localeCompare(removeArticles(b.titleField)),
},
]
Filtering
Filtering, when used, is applied by default to the original items array data. Btable
provides several options for how data is filtered.
It is currently not possible to filter based on result of formatting via scoped field slots.
Built in filtering
The item's row data values are stringified (see the sorting section above for how stringification is done) and the filter searches that stringified data (excluding any of the special properties that begin with an underscore '_'
). The stringification also, by default, includes any data not shown in the presented columns.
With the default built-in filter function, the filter
prop value can either be a string or a RegExp
object (regular expressions should not have the /g
global flag set). Not yet implemented Currently the filter
prop only supports a string, not a RegExp
.
If the stringified row contains the provided string value or matches the RegExp expression then it is included in the displayed results.
Set the filter
prop to null
or an empty string to clear the current filter.
Built in filtering options
There are several options for controlling what data the filter is applied against.
- Not yet implementedThe
filter-ignored-fields
prop accepts an array of top-level (immediate properties of the rowdata) field keys that should be ignored when filtering. - Not yet implementedThe
filter-included-fields
prop accepts an array of top-level (immediate properties of the rowdata) field keys that should used when filtering. All other field keys not included in this array will be ignored. This feature can be handy when you want to filter on specific columns. If the specified array is empty, then all fields are included, except those specified via the propfilter-ignored-fields
. If a field key is specified in bothfilter-ignored-fields
andfilter-included-fields
, thenfilter-included-fields
takes precedence. - Normally,
<BTable>
filters based on the stringified record data. If the field has aformatter
function specified, you can optionally filter based on the result of the formatter by setting the field definition propertyfilterByFormatted
totrue
. If the field does not have a formatter function, this option is ignored. Not yet implementedYou can optionally pass a formatter function reference, to be used for filtering only, to the field definition propertyfilterByFormatted
.
The props filter-ignored-fields
and filter-included-fields
, and the field definition property filterByFormatted
have no effect when using a custom filter function, or items provider based filtering.
Custom filter function
You can also use a custom filter function, by setting the prop filter-function
to a reference of custom filter test function. The filter function signature is (item: Readonly<Items>, filter: string | undefined) => boolean
item
is the original item row record data object.filter
value of thefilter
prop
The function should return true
if the record matches your criteria or false
if the record is to be filtered out.
For proper reactive updates to the displayed data, when not filtering you should set the filter
prop to null
or an empty string (and not an empty object or array). The filter function will not be called when the filter
prop is a falsey value.
The display of the empty-filter-text
relies on the truthiness of the filter
prop.
Filter events
When local filtering is applied, and the resultant number of items change, <BTable>
will emit the filtered
event with a single argument of type Items[]
: which is the complete list of items passing the filter routine. Treat this argument as read-only.
Setting the prop filter
to null or an empty string will clear local items filtering.
Pagination
To Be Completed
Using items provider functions
To Be Completed
Light-weight tables
To Be Completed
Simple tables
The BTableSimple
component gives the user complete control over the rendering of the table content, while providing basic Bootstrap v5 table styling. BTableSimple
is a wrapper component around the <table>
element. Inside the component, via the default slot, you can use any or all of the BootstrapVueNext table helper components: BThead
, BTfoot
, BTbody
, BTr
, BTh
, BTd
, and the HTML5 elements <caption>
, <colgroup>
and <col>
. Contrary to the component's name, one can create simple or complex table layouts with BTableSimple
.
BTableSimple
provides basic styling options via props: striped, bordered, borderless, outlined, small, hover, dark, fixed, responsive and sticky-header. Note that stacked mode is available but requires some additional markup to generate the cell headings, as described in the Simple tables and stacked mode section below. Sticky columns are also supported, but also require a bit of additional markup to specify which columns are to be sticky. See below for more information on using sticky columns.
Since BTableSimple
is just a wrapper component, of which you will need to render content inside, it does not provide any of the advanced features of BTable
(i.e. row events, head events, sorting, pagination, filtering, foot-clone, items, fields, etc.).
Region | Clothes | Accessories | ||||
---|---|---|---|---|---|---|
Country | City | Trousers | Skirts | Dresses | Bracelets | Rings |
Belgium | Antwerp | 56 | 22 | 43 | 72 | 23 |
Gent | 46 | 18 | 50 | 61 | 15 | |
Brussels | 51 | 27 | 38 | 69 | 28 | |
The Netherlands | Amsterdam | 89 | 34 | 69 | 85 | 38 |
Utrecht | 80 | 12 | 43 | 36 | 19 | |
Total Rows: 5 |
<BTableSimple hover small caption-top responsive>
<caption>
Items sold in August, grouped by Country and City:
</caption>
<colgroup>
<col />
<col />
</colgroup>
<colgroup>
<col />
<col />
<col />
</colgroup>
<colgroup>
<col />
<col />
</colgroup>
<BThead variant="dark">
<BTr>
<BTh colspan="2">Region</BTh>
<BTh colspan="3">Clothes</BTh>
<BTh colspan="2">Accessories</BTh>
</BTr>
<BTr>
<BTh>Country</BTh>
<BTh>City</BTh>
<BTh>Trousers</BTh>
<BTh>Skirts</BTh>
<BTh>Dresses</BTh>
<BTh>Bracelets</BTh>
<BTh>Rings</BTh>
</BTr>
</BThead>
<BTbody>
<BTr>
<BTh rowspan="3">Belgium</BTh>
<BTh class="text-end">Antwerp</BTh>
<BTd>56</BTd>
<BTd>22</BTd>
<BTd>43</BTd>
<BTd variant="success">72</BTd>
<BTd>23</BTd>
</BTr>
<BTr>
<BTh class="text-end">Gent</BTh>
<BTd>46</BTd>
<BTd variant="warning">18</BTd>
<BTd>50</BTd>
<BTd>61</BTd>
<BTd variant="danger">15</BTd>
</BTr>
<BTr>
<BTh class="text-end">Brussels</BTh>
<BTd>51</BTd>
<BTd>27</BTd>
<BTd>38</BTd>
<BTd>69</BTd>
<BTd>28</BTd>
</BTr>
<BTr>
<BTh rowspan="2">The Netherlands</BTh>
<BTh class="text-end">Amsterdam</BTh>
<BTd variant="success">89</BTd>
<BTd>34</BTd>
<BTd>69</BTd>
<BTd>85</BTd>
<BTd>38</BTd>
</BTr>
<BTr>
<BTh class="text-end">Utrecht</BTh>
<BTd>80</BTd>
<BTd variant="danger">12</BTd>
<BTd>43</BTd>
<BTd>36</BTd>
<BTd variant="warning">19</BTd>
</BTr>
</BTbody>
<BTfoot>
<BTr>
<BTd colspan="7" variant="secondary" class="text-end"> Total Rows: <b>5</b> </BTd>
</BTr>
</BTfoot>
</BTableSimple>
When in responsive or sticky-header mode, the <table>
element is wrapped inside a <div>
element. If you need to apply additional classes or attributes to the <table>
element, use the table-classes and table-attrs, respectively.
Simple tables and stacked mode
A bit of additional markup is required on your BTableSimple
body cells when the table is in stacked mode. Specifically, BootstrapVueNext uses a special data attribute to create the cell's heading, of which you can supply to BTd
or BTh
via the stacked-heading prop. Only plain strings are supported (not HTML markup), as we use the pseudo element ::before and css content property.
Here is the same table as above, set to be always stacked, which has the extra markup to handle stacked mode (specifically for generating the cell headings):
Region | Clothes | Accessories | ||||
---|---|---|---|---|---|---|
Country | City | Trousers | Skirts | Dresses | Bracelets | Rings |
Belgium (3 Cities) | Antwerp | 56 | 22 | 43 | 72 | 23 |
Gent | 46 | 18 | 50 | 61 | 15 | |
Brussels | 51 | 27 | 38 | 69 | 28 | |
The Netherlands (2 Cities) | Amsterdam | 89 | 34 | 69 | 85 | 38 |
Utrecht | 80 | 12 | 43 | 36 | 19 | |
Total Rows: 5 |
<BTableSimple hover small caption-top stacked>
<caption>
Items sold in August, grouped by Country and City:
</caption>
<colgroup>
<col />
<col />
</colgroup>
<colgroup>
<col />
<col />
<col />
</colgroup>
<colgroup>
<col />
<col />
</colgroup>
<BThead variant="dark">
<BTr>
<BTh colspan="2">Region</BTh>
<BTh colspan="3">Clothes</BTh>
<BTh colspan="2">Accessories</BTh>
</BTr>
<BTr>
<BTh>Country</BTh>
<BTh>City</BTh>
<BTh>Trousers</BTh>
<BTh>Skirts</BTh>
<BTh>Dresses</BTh>
<BTh>Bracelets</BTh>
<BTh>Rings</BTh>
</BTr>
</BThead>
<BTbody>
<BTr>
<BTh rowspan="3" class="text-center">Belgium (3 Cities)</BTh>
<BTh stacked-heading="City" class="text-start">Antwerp</BTh>
<BTd stacked-heading="Clothes: Trousers">56</BTd>
<BTd stacked-heading="Clothes: Skirts">22</BTd>
<BTd stacked-heading="Clothes: Dresses">43</BTd>
<BTd stacked-heading="Accessories: Bracelets" variant="success">72</BTd>
<BTd stacked-heading="Accessories: Rings">23</BTd>
</BTr>
<BTr>
<BTh stacked-heading="City">Gent</BTh>
<BTd stacked-heading="Clothes: Trousers">46</BTd>
<BTd stacked-heading="Clothes: Skirts" variant="warning">18</BTd>
<BTd stacked-heading="Clothes: Dresses">50</BTd>
<BTd stacked-heading="Accessories: Bracelets">61</BTd>
<BTd stacked-heading="Accessories: Rings" variant="danger">15</BTd>
</BTr>
<BTr>
<BTh stacked-heading="City">Brussels</BTh>
<BTd stacked-heading="Clothes: Trousers">51</BTd>
<BTd stacked-heading="Clothes: Skirts">27</BTd>
<BTd stacked-heading="Clothes: Dresses">38</BTd>
<BTd stacked-heading="Accessories: Bracelets">69</BTd>
<BTd stacked-heading="Accessories: Rings">28</BTd>
</BTr>
<BTr>
<BTh rowspan="2" class="text-center">The Netherlands (2 Cities)</BTh>
<BTh stacked-heading="City">Amsterdam</BTh>
<BTd stacked-heading="Clothes: Trousers" variant="success">89</BTd>
<BTd stacked-heading="Clothes: Skirts">34</BTd>
<BTd stacked-heading="Clothes: Dresses">69</BTd>
<BTd stacked-heading="Accessories: Bracelets">85</BTd>
<BTd stacked-heading="Accessories: Rings">38</BTd>
</BTr>
<BTr>
<BTh stacked-heading="City">Utrecht</BTh>
<BTd stacked-heading="Clothes: Trousers">80</BTd>
<BTd stacked-heading="Clothes: Skirts" variant="danger">12</BTd>
<BTd stacked-heading="Clothes: Dresses">43</BTd>
<BTd stacked-heading="Accessories: Bracelets">36</BTd>
<BTd stacked-heading="Accessories: Rings" variant="warning">19</BTd>
</BTr>
</BTbody>
<BTfoot>
<BTr>
<BTd colspan="7" variant="secondary" class="text-end"> Total Rows: <b>5</b> </BTd>
</BTr>
</BTfoot>
</BTableSimple>
Like BTable
and BTableLite
, table headers (<thead>
) and footers (<tfoot>
) are visually hidden when the table is visually stacked. If you need a header or footer, you can do so by creating an extra BTr
inside of the BTbody
component (or in a second BTbody
component), and set a role of columnheader on the child BTh
cells, and use Bootstrap v5 responsive display utility classes to hide the extra row (or BTbody
) above a certain breakpoint when the table is no longer visually stacked (the breakpoint should match the stacked table breakpoint you have set), i.e. <BTr class="d-md-none">
would hide the row on medium and wider screens, while <BTbody class="d-md-none">
would hide the row group on medium and wider screens.
NOTE
Note: stacked mode with BTableSimple
requires that you use the BootstrapVueNext table helper components. Use of the regular <tbody>
, <tr>
, <td>
and <th>
element tags will not work as expected, nor will they automatically apply any of the required accessibility attributes.
Simple tables and sticky columns
Sticky columns are supported with BTableSimple
, but you will need to set the sticky-column prop on each table cell (in the thead, tbody, and tfoot row groups) in the column that is to be sticky. For example:
<BTableSimple responsive>
<BThead>
<BTr>
<BTh sticky-column>Sticky Column Header</BTh>
<BTh>Heading 1</BTh>
<BTh>Heading 2</BTh>
<BTh>Heading 3</BTh>
<BTh>Heading 4</BTh>
</BTr>
</BThead>
<BTbody>
<BTr>
<BTh sticky-column>Sticky Column Row Header</BTh>
<BTd>Cell</BTd>
<BTd>Cell</BTd>
<BTd>Cell</BTd>
<BTd>Cell</BTd>
</BTr>
<BTr>
<BTh sticky-column>Sticky Column Row Header</BTh>
<BTd>Cell</BTd>
<BTd>Cell</BTd>
<BTd>Cell</BTd>
<BTd>Cell</BTd>
</BTr>
</BTbody>
<BTfoot>
<BTr>
<BTh sticky-column>Sticky Column Footer</BTh>
<BTh>Heading 1</BTh>
<BTh>Heading 2</BTh>
<BTh>Heading 3</BTh>
<BTh>Heading 4</BTh>
</BTr>
</BTfoot>
</BTableSimple>
As with BTable
and BTableLite
, sticky columns are not supported when the stacked prop is set on BTableSimple
.
Table Helper Components
To Be Completed
Accessibility
To Be Completed
Complete Example
- «
- ‹
Person full name | Person sortable name | Person age | Is Active | Actions |
---|---|---|---|---|
Dickerson Macdonald | Macdonald, Dickerson | 40 | Yes | |
Larsen Shaw | Shaw, Larsen | 21 | No | |
Mini Navarro | Navarro, Mini | 9 | No | |
Geneva Wilson | Wilson, Geneva | 89 | No | |
Jami Carney | Carney, Jami | 38 | Yes |
<template>
<BContainer class="py-5">
<!-- User Interface controls -->
<BRow>
<BCol lg="6" class="my-1">
<BFormGroup
v-slot="{ariaDescribedby}"
label="Sort"
label-for="sort-by-select"
label-cols-sm="3"
label-align-sm="right"
label-size="sm"
class="mb-0"
>
<BButton size="sm" @click="onAddSort">Add Sort...</BButton>
<BInputGroup v-for="sort in sortBy" :key="sort.key" size="sm">
<BFormSelect
id="sort-by-select"
v-model="sort.key"
:options="sortOptions"
:aria-describedby="ariaDescribedby"
class="w-75"
>
<template #first>
<option value="">-- none --</option>
</template>
</BFormSelect>
<BFormSelect
v-model="sort.order"
:disabled="!sortBy"
:aria-describedby="ariaDescribedby"
size="sm"
class="w-25"
>
<option value="asc">Asc</option>
<option value="desc">Desc</option>
</BFormSelect>
</BInputGroup>
</BFormGroup>
</BCol>
<BCol lg="6" class="my-1">
<BFormGroup
label="Filter"
label-for="filter-input"
label-cols-sm="3"
label-align-sm="right"
label-size="sm"
class="mb-0"
>
<BInputGroup size="sm">
<BFormInput
id="filter-input"
v-model="filter"
type="search"
placeholder="Type to Search"
/>
<BInputGroupText>
<BButton :disabled="!filter" @click="filter = ''">Clear</BButton>
</BInputGroupText>
</BInputGroup>
</BFormGroup>
</BCol>
<BCol lg="6" class="my-1">
<BFormGroup
v-slot="{ariaDescribedby}"
v-model="sortDirection"
label="Filter On"
description="Leave all unchecked to filter on all data"
label-cols-sm="3"
label-align-sm="right"
label-size="sm"
class="mb-0"
>
<div class="d-flex gap-2">
<BFormCheckbox v-model="filterOn" value="name" :aria-describedby="ariaDescribedby"
>Name</BFormCheckbox
>
<BFormCheckbox v-model="filterOn" value="age" :aria-describedby="ariaDescribedby"
>Age</BFormCheckbox
>
<BFormCheckbox v-model="filterOn" value="isActive" :aria-describedby="ariaDescribedby"
>Active</BFormCheckbox
>
</div>
</BFormGroup>
</BCol>
<BCol sm="5" md="6" class="my-1">
<BFormGroup
label="Per page"
label-for="per-page-select"
label-cols-sm="6"
label-cols-md="4"
label-cols-lg="3"
label-align-sm="right"
label-size="sm"
class="mb-0"
>
<BFormSelect id="per-page-select" v-model="perPage" :options="pageOptions" size="sm" />
</BFormGroup>
</BCol>
<BCol sm="7" md="6" class="my-1">
<BPagination
v-model="currentPage"
:total-rows="totalRows"
:per-page="perPage"
:align="'fill'"
size="sm"
class="my-0"
/>
</BCol>
</BRow>
<!-- Main table element for typed table-->
<BTable
v-model:sort-by="sortBy"
:sort-internal="true"
:items="items"
:fields="fields"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
:responsive="false"
:filterable="filterOn"
:small="true"
:multisort="true"
@filtered="onFiltered"
>
<template #cell(name)="row">
{{ (row.value as PersonName).first }}
{{ (row.value as PersonName).last }}
</template>
<template #cell(actions)="row">
<BButton size="sm" class="me-1" @click="info(row.item, row.index)"> Info modal </BButton>
<BButton size="sm" @click="row.toggleDetails">
{{ row.detailsShowing ? 'Hide' : 'Show' }} Details
</BButton>
</template>
<template #row-details="row">
<BCard>
<ul>
<li v-for="(value, key) in row.item" :key="key">{{ key }}: {{ value }}</li>
<BButton size="sm" @click="row.toggleDetails"> Toggle Details </BButton>
</ul>
</BCard>
</template>
</BTable>
<!-- Info modal -->
<BModal
:id="infoModal.id"
v-model="infoModal.open"
:title="infoModal.title"
:ok-only="true"
@hide="resetInfoModal"
>
<pre>{{ infoModal.content }}</pre>
</BModal>
</BContainer>
</template>
<script setup lang="ts">
import {
type BTableSortBy,
type ColorVariant,
type LiteralUnion,
type TableFieldRaw,
type TableItem,
} from 'bootstrap-vue-next'
import {computed, reactive, ref} from 'vue'
interface PersonName {
first: string
last: string
}
interface Person {
name: PersonName
age: number
isActive: boolean
}
const items: TableItem<Person>[] = [
{isActive: true, age: 40, name: {first: 'Dickerson', last: 'Macdonald'}},
{isActive: false, age: 21, name: {first: 'Larsen', last: 'Shaw'}},
{
isActive: false,
age: 9,
name: {first: 'Mini', last: 'Navarro'},
_rowVariant: 'success' as ColorVariant,
},
{isActive: false, age: 89, name: {first: 'Geneva', last: 'Wilson'}},
{isActive: true, age: 38, name: {first: 'Jami', last: 'Carney'}},
{isActive: false, age: 27, name: {first: 'Essie', last: 'Dunlap'}},
{isActive: true, age: 40, name: {first: 'Thor', last: 'Macdonald'}},
{
isActive: true,
age: 87,
name: {first: 'Larsen', last: 'Shaw'},
_cellVariants: {age: 'danger', isActive: 'warning'},
},
{isActive: false, age: 26, name: {first: 'Mitzi', last: 'Navarro'}},
{isActive: false, age: 22, name: {first: 'Genevieve', last: 'Wilson'}},
{isActive: true, age: 38, name: {first: 'John', last: 'Carney'}},
{isActive: false, age: 29, name: {first: 'Dick', last: 'Dunlap'}},
]
const fields: Exclude<TableFieldRaw<Person>, string>[] = [
{
key: 'name',
label: 'Person full name',
sortable: true,
sortDirection: 'desc',
},
{
key: 'sortableName',
label: 'Person sortable name',
sortable: true,
sortDirection: 'desc',
formatter: (_value: unknown, _key?: LiteralUnion<keyof Person>, item?: Person) =>
item ? `${item.name.last}, ${item.name.first}` : 'Something went wrong',
sortByFormatted: true,
filterByFormatted: true,
},
{key: 'age', label: 'Person age', sortable: true, class: 'text-center'},
{
key: 'isActive',
label: 'Is Active',
formatter: (value: unknown) => (value ? 'Yes' : 'No'),
sortable: true,
sortByFormatted: true,
filterByFormatted: true,
},
{key: 'actions', label: 'Actions'},
]
const pageOptions = [
{value: 5, text: '5'},
{value: 10, text: '10'},
{value: 15, text: '15'},
{value: 100, text: 'Show a lot'},
]
const totalRows = ref(items.length)
const currentPage = ref(1)
const perPage = ref(5)
const sortBy = ref<BTableSortBy[]>([])
const sortDirection = ref('asc')
const filter = ref('')
const filterOn = ref([])
const infoModal = reactive({
open: false,
id: 'info-modal',
title: '',
content: '',
})
// Create an options list from our fields
const sortOptions = computed(() =>
fields.filter((f) => f.sortable).map((f) => ({text: f.label, value: f.key}))
)
function info(item: TableItem<Person>, index: number) {
infoModal.title = `Row index: ${index}`
infoModal.content = JSON.stringify(item, null, 2)
infoModal.open = true
}
function resetInfoModal() {
infoModal.title = ''
infoModal.content = ''
}
function onFiltered(filteredItems: TableItem<Person>[]) {
// Trigger pagination to update the number of buttons/pages due to filtering
totalRows.value = filteredItems.length
currentPage.value = 1
}
function onAddSort() {
sortBy.value.push({key: '', order: 'asc'})
}
</script>
Component Reference
<BTable>
Prop | Type | Default | Description |
---|---|---|---|
busy | boolean | false | |
busy-loading-text | string | 'Loading...' | |
current-page | Numberish | 1 | |
empty-filtered-text | string | 'There are no records matching your request' | Text to display when no items are present in the `items` array after filtering |
empty-text | string | 'There are no records to show' | Text to display when no items are present in the `items` array |
filter | string | undefined | |
filter-function | (item: Readonly<Items>, filter: string | undefined) => boolean | undefined | Function called during filtering of items, gets passed the current item being filtered |
filterable | string[] | undefined | |
multisort | boolean | false | |
must-sort | boolean | string[] | false | |
no-local-sorting | boolean | false | |
no-provider | NoProviderTypes[] | undefined | |
no-provider-filtering | boolean | false | |
no-provider-paging | boolean | false | |
no-provider-sorting | boolean | false | |
no-select-on-click | boolean | false | Do not select row when clicked |
no-sortable-icon | boolean | false | |
per-page | Numberish | null | |
provider | BTableProvider | undefined | |
select-head | boolean | string | true | |
select-mode | 'multi' | 'single' | 'range' | 'multi' | |
selectable | boolean | false | |
selected-items | TableItem[] | undefined | |
selection-variant | ColorVariant | null | 'primary' | |
show-empty | boolean | false | Show the empty text when no items are present in the `items` array |
sort-by | BTableSortBy[] | undefined | Model representing the current sort state |
sticky-select | boolean | false |
Event | Args | Description |
---|---|---|
headClicked | key : TableField<Record<string, unknown>>.key: LiteralUnion<string, string> field : TableField event : MouseEvent is-footer : boolean | |
rowClicked | item : TableItem index : number event : MouseEvent | |
rowDblClicked | item : TableItem index : number event : MouseEvent | |
rowHovered | item : TableItem index : number event : MouseEvent | |
rowSelected | row-selected : TableItem | |
rowUnhovered | item : TableItem index : number event : MouseEvent | |
rowUnselected | row-unselected : TableItem | |
selection | selection : TableItem[] | |
sorted | value : BTableSortBy | Updated when the user clicks a sortable column heading and represents the column click and the sort state (`asc`, `desc`, or undefined) |
update:sortBy | value : string - BTableSortBy[] | undefined | Emitted when the `sortBy` model is changed and represents the current sort state |
Name | Scope | Description |
---|---|---|
custom-foot | ||
default | ||
empty | Content to display when no items are present in the `items` array | |
empty-filtered | Content to display when no items are present in the `items` array after filtering | |
row-details | ||
select-head | ||
selectCell | ||
table-busy | ||
table-caption | ||
table-caption | ||
thead-sub | ||
thead-top |
<BTableLite>
.table‑lite
Prop | Type | Default | Description |
---|---|---|---|
align | VerticalAlign | undefined | |
caption | string | undefined | |
details-td-class | ClassValue | undefined | |
field-column-class | (field: TableField) => Record<string, any>[] | string | Record<PropertyKey, any> | any[] | undefined | |
fields | TableFieldRaw[] | '() => []' | |
foot-clone | boolean | false | |
foot-row-variant | ColorVariant | null | undefined | |
foot-variant | ColorVariant | null | undefined | |
head-row-variant | ColorVariant | null | undefined | |
head-variant | ColorVariant | null | undefined | |
items | TableItem[] | '() => []' | |
label-stacked | boolean | false | When set, the labels will appear as actual label elements, rather than with the data-label attribute |
model-value | any | undefined | |
primary-key | string | undefined | |
tbody-class | ClassValue | undefined | |
tbody-tr-attrs | ClassValue | undefined | |
tbody-tr-class | ((item: TableItem | null, type: string) => string | any[] | null | undefined) | string | Record<PropertyKey, any> | any[] | undefined | |
tfoot-class | ClassValue | undefined | |
tfoot-tr-class | ClassValue | undefined | |
thead-class | ClassValue | undefined | |
thead-tr-class | ClassValue | undefined |
<BTableSimple>
.table‑simple
Prop | Type | Default | Description |
---|---|---|---|
border-variant | ColorVariant | null | null | Applies one of the Bootstrap theme color variants to the table border |
bordered | boolean | false | Adds borders to all the cells and headers |
borderless | boolean | false | Removes all borders from cells |
caption-top | boolean | false | When set, the table caption will appear above the table |
dark | boolean | false | Places the table in dark mode |
fixed | boolean | false | Makes all columns equal width (fixed layout table). Will speed up rendering for large tables. Column widths can be set via CSS or colgroup |
hover | boolean | false | Enables hover styling on rows |
id | string | undefined | Used to set the `id` attribute on the rendered content, and used as the base to generate any additional element IDs as needed |
no-border-collapse | boolean | false | Disable's the collapsing of table borders. Useful when table has sticky headers or columns |
outlined | boolean | false | Adds an outline border to the table element |
responsive | boolean | Breakpoint | false | Makes the table responsive in width, adding a horizontal scrollbar. Set to true for always responsive or set to one of the breakpoints to switch from responsive to normal: 'sm', 'md', 'lg', 'xl' |
small | boolean | false | Renders the table with smaller cell padding |
stacked | boolean | Breakpoint | false | Place the table in stacked mode. Set to true for always stacked, or set to one of the breakpoints to switch from stacked to normal: 'sm', 'md', 'lg', 'xl' |
sticky-header | boolean | Numberish | false | Makes the table header sticky. Set to true for a maximum height 300px tall table, or set to any valid CSS height (including units). Inputting a number type is converted to px height |
striped | boolean | false | Applies striping to the tbody rows |
striped-columns | boolean | false | Applies striping to the table columns |
table-attrs | AttrsValue | undefined | Attributes to apply to the table element |
table-class | ClassValue | undefined | Classes to apply to the table element |
variant | ColorVariant | null | null | Applies one of the Bootstrap theme color variants to the component. When implemented `bg-variant` and `text-variant` will take precedence |
Name | Scope | Description |
---|---|---|
default | Content to place in the table |
<BTbody>
Prop | Type | Default | Description |
---|---|---|---|
variant | ColorVariant | null |
Name | Scope | Description |
---|---|---|
default |
<BTd>
Prop | Type | Default | Description |
---|---|---|---|
colspan | string | number | undefined | |
rowspan | string | number | undefined | |
stacked-heading | string | undefined | |
sticky-column | boolean | false | |
variant | ColorVariant | null | null |
Name | Scope | Description |
---|---|---|
default |
<BTfoot>
Prop | Type | Default | Description |
---|---|---|---|
variant | ColorVariant | null | null |
Name | Scope | Description |
---|---|---|
default |
<BTh>
Prop | Type | Default | Description |
---|---|---|---|
colspan | string | number | undefined | |
rowspan | string | number | undefined | |
stacked-heading | string | undefined | |
sticky-column | boolean | false | |
variant | ColorVariant | null | null |
Name | Scope | Description |
---|---|---|
default |
<BThead>
Prop | Type | Default | Description |
---|---|---|---|
variant | ColorVariant | null |
Name | Scope | Description |
---|---|---|
default |
<BTr>
Prop | Type | Default | Description |
---|---|---|---|
variant | ColorVariant | null |
Name | Scope | Description |
---|---|---|
default |