Back to migration overview

HTML Prop to Slot Migration

Unsafe HTML string props are replaced by explicit slot-based rendering in BootstrapVueNext.

Edit this page on GitHub

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.

ComponentPropReplacement Slot
BBreadcrumbItemhtmldefault
BCardfooter-htmlfooter
BCardheader-htmlheader
BCardFooterhtmldefault
BCardHeaderhtmldefault
BCarouselSlidecaption-htmlcaption
BCarouselSlidetext-htmldefault
BDropdownhtmldefault
BInputGroupappend-htmlappend
BInputGroupprepend-htmlprepend
BModalcancel-title-htmlcancel
BModalok-titleok
BModaltitle-htmltitle
BNavItemDropdownhtmldefault
BPopover *htmldefault
BProgressBarlabel-htmldefault
BTableempty-filtered-htmlempty-filtered
BTableempty-htmlempty
BTablecaption-htmltable-caption
BTableSimplecaption-htmltable-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.

model = []
HTML
vue
<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.

model = Black
HTML
vue
<!-- 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.

  • None