HTML Prop to Slot Migration
Unsafe HTML string props are replaced by explicit slot-based rendering in BootstrapVueNext.
HTML Prop to Slot Migration
Summary
Unsafe HTML string props are replaced by explicit slot-based rendering in BootstrapVueNext.
Affected APIs
- BBreadcrumbItem
- BCard
- BCarouselSlide
- BDropdown
- BInputGroup
- BModal
- BPopover
- BProgressBar
- BTable
- BTableSimple
Breaking Change
BootstrapVue provided a number of different props named html and *-html that passed arbitrary data to Vue's v-html. While a warning was included with each instance of this use, it is not recommended practice to use v-html and obscuring that practice further by passing down other props is ill advised in our opinion. We have instead worked to ensure that you have the ability to access the same functionality via slots. In many cases slots were already available and took priority over the [*-]html props and we've filled in the gaps where there wasn't a direct replacement. We believe the developer experience in these cases is as good or better than when using props. Most importantly any use your code makes of v-html will be explicit. That means Vue ESLint rules can warn on the exact XSS-sensitive sites, and teams can audit all HTML injection points in one standardized way instead of relying on scattered html props. See the Vue Documentation for their take on the HTML Injection attack that use of v-html exposes.
| Component | Prop | Replacement Slot |
|---|---|---|
BBreadcrumbItem | html | default |
BCard | footer-html | footer |
BCard | header-html | header |
BCardFooter | html | default |
BCardHeader | html | default |
BCarouselSlide | caption-html | caption |
BCarouselSlide | text-html | default |
BDropdown | html | default |
BInputGroup | append-html | append |
BInputGroup | prepend-html | prepend |
BModal | cancel-title-html | cancel |
BModal | ok-title | ok |
BModal | title-html | title |
BNavItemDropdown | html | default |
BPopover * | html | default |
BProgressBar | label-html | default |
BTable | empty-filtered-html | empty-filtered |
BTable | empty-html | empty |
BTable | caption-html | table-caption |
BTableSimple | caption-html | table-caption |
Note: BootstrapVue b-popover didn't have an html attribute, but alpha versions of BootstrapVueNext did.
BFormCheckboxGroup and BFormRadioGroup implement a scoped slot option which takes a Record<string, unknown> parameter. You can add arbitrary fields to elements of the options array that you pass in and they will be accessible to the slot. The example below uses the data on the options object to create the html inline in the slot.
<template>
<div>
<BFormCheckboxGroup
v-model="model"
:options="options"
>
<template #option="{value}">
{{ (value as Name).first }} <b>{{ (value as Name).last }}</b>
</template>
</BFormCheckboxGroup>
<b>model = </b>{{ model }}
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
interface Name {
first: string
last: string
}
const model = ref<Name[]>([])
// Use standard {value, text, disabled?} format for full type safety
const options: {value: Name; text?: string; disabled?: boolean}[] = [
{value: {last: 'Brown', first: 'Christina'}},
{value: {last: 'Smith', first: 'John'}},
{value: {last: 'Doe', first: 'Jane'}},
{value: {last: 'Johnson', first: 'Michael'}},
{value: {last: 'Williams', first: 'Patricia'}},
{value: {last: 'Jones', first: 'Robert'}},
{value: {last: 'Garcia', first: 'Linda'}},
]
</script>Or you can do a straightforward translation of a BFormRadioGroup passing an HTML string through to its children. If you're passing user data, this still opens your code up to XSS attacks, if you do not first sanitize the user supplied string, but the BootstrapVueNext library isn't adding an extra layer of abstraction to this vulnerability.
<!-- eslint-disable vue/no-v-html -->
<template>
<div>
<BFormGroup
class="form-group"
label="Color"
label-for="color-group"
label-class="mb-1"
>
<BFormRadioGroup
id="color-group"
v-model="model"
name="color-group"
button-variant="outline-secondary"
:options="options"
buttons
>
<template #option="val"> <div v-html="val.html" /> </template>
</BFormRadioGroup>
</BFormGroup>
<b>model = </b>{{ model }}
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
const myColors = ['Black', 'Red', 'Green', 'Blue', 'Purple']
const model = ref(myColors[0])
const options = myColors.map((e) => ({
value: e,
html: `<span style="color:${e.toLowerCase()}" /> ${e}`,
}))
</script>Migration Notes
- Extracted from the canonical BootstrapVue → BootstrapVueNext migration guide.
Safe Automatic Rewrite
No. This entry includes behavioral or structural changes and should be reviewed manually before applying automated transforms.
Related Migrations
- None