useModal
The useModal composable provides a powerful API to create, manage, and control modals programmatically from anywhere in your application. It allows you to create modals on-demand, manage existing modals, and handle modal interactions through promises.
Setup
To use useModal, you need one of the following setup approaches:
BApp Component (Recommended)
The easiest way is to wrap your application with the BApp component, which automatically sets up the orchestrator and registry:
<template>
<BApp>
<RouterView />
</BApp>
</template> Due to how Vue's provide/inject system works, composables like useToast(), useModal(), and usePopover()cannot be called in the same component that declares <BApp>. They rely on values provided by BApp, and Vue's inject only works in child components — not in the component that calls provide itself.
Place <BApp> at least one component level above where these composables are called.
Plugin Setup (Legacy)
Alternatively, you can use the traditional plugin approach.
<BApp>, you must have initialized the createBootstrap plugin for this to work properly. Read hereCreating Modals
Creating a modal is done through the create method:
<template>
<BButton @click="showExample">Click me</BButton>
</template>
<script setup lang="ts">
import { BButton } from 'bootstrap-vue-next/components/BButton'
import { useModal } from 'bootstrap-vue-next/composables/useModal'
const { create } = useModal()
const showExample = async () => {
const value = await create({ title: 'Hello World!' }).show()
if (value && typeof value === 'object' && 'ok' in value && 'trigger' in value) {
await using _ = await create({
body: `Promise resolved to object with {ok: ${value.ok}, trigger: ${value.trigger}}`,
}).show()
}
}
</script>Reactivity Within create
create props property can accept a MaybeRef, meaning that you can make properties reactive
<template>
<BButton @click="showReactiveExample">Click me</BButton>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
import { BButton } from 'bootstrap-vue-next/components/BButton'
import { useModal } from 'bootstrap-vue-next/composables/useModal'
const { create } = useModal()
const title = ref('Hello')
let intervalId: ReturnType<typeof setInterval> | undefined
onMounted(() => {
intervalId = setInterval(() => {
title.value = title.value === 'Hello' ? 'World' : 'Hello'
}, 2500)
})
onUnmounted(() => {
if (intervalId) {
clearInterval(intervalId)
}
})
// `create()` needs a writable ref/plain object (it needs to control `modelValue` itself),
// so `reactive()` is not used here. Instead, derive the reactive pieces with `computed()`,
// then sync them onto a plain ref via `watchEffect()`.
const derivedTitle = computed(() => title.value)
const myModal = ref({
title: derivedTitle.value,
modelValue: false,
})
watchEffect(() => {
myModal.value.title = derivedTitle.value
})
const showReactiveExample = async () => {
// dispose the reference to keep modals from floating around
await using _ = await create(myModal).show()
}
</script>Advanced Creation
Using props can work for most situations, but it leaves some finer control to be desired. For instance, you cannot add HTML to any slot value using props alone. This is where the component property comes into play. Using the component property, you can input the component to render. This can either be an imported SFC or an inline render function.
You can also use component slots to render what you want. This is done through the slots property. The slots property is an object that contains the slot name as the key and a render function or component as the value. The render function is passed a scope object that contains the slots scope.
<template>
<BButton @click="showMeAdvancedExample">Click me</BButton>
</template>
<script setup lang="ts">
import { h, markRaw, onMounted, onUnmounted, ref } from 'vue'
import { BButton } from 'bootstrap-vue-next/components/BButton'
import { useModal } from 'bootstrap-vue-next/composables/useModal'
const { create } = useModal()
const firstRef = ref({
body: 'foo',
})
let intervalId: ReturnType<typeof setInterval> | undefined
onMounted(() => {
intervalId = setInterval(() => {
firstRef.value.body = firstRef.value.body === 'foo' ? 'bar' : 'foo'
}, 1000)
})
onUnmounted(() => {
if (intervalId) {
clearInterval(intervalId)
}
})
const showMeAdvancedExample = async () => {
await using _ = await create({
slots: {
default: (scope) =>
markRaw(h('div', null, `custom ${firstRef.value.body} - ${scope.visible}`)),
},
}).show()
// Demonstration pseudocode, you can import a component and use it
// const importedComponent = () => {
// create({
// component: markRaw((await import('./TestModal.vue')).default),
// })
// }
}
</script>Return Value
The create method returns a controller object. Call .show() to display the modal; .show() returns a promise that resolves to a BvTriggerableEvent when the modal closes. Using options.resolveOnHide, the promise resolves when hide begins rather than after the full hide lifecycle.
const value = await create({
title: 'Hello World!',
options: {resolveOnHide: true},
}).show()This object contains the following properties:
ok: booleanClicking the
okbutton resolve this totrue,canceltofalseand any other closable actionnull(clicking the backdrop, or some other custom closing action. More accurately, when thehidefunction does not pass in the trigger parameter ofokorcancel)trigger: string | nullThis is the trigger that closed the modal. This is useful for determining what action closed the modal.
The promise also contains functions to control the modal:
show: () => voidThis function shows the modal.
hide: (trigger?: string) => voidThis function hides the modal. If a trigger is passed, it will be passed to the
triggerproperty of the resolved promisetoggle: () => voidThis function toggles the visibility of the modal.
set: (props: Partial<ModalOrchestratorParam>) => voidThis function sets the props of the modal. This is useful for updating the modal after it has been created.
destroy: () => Promise<void>This function destroys the modal and cleans up any resources associated with it.
Lifecycle
Created modal instances persist until you explicitly dispose them. Hiding a modal does not remove it from the orchestrator store.
const modal = create({
title: 'Hello World!',
})
try {
await modal.show()
// do something
} finally {
await modal.destroy()
}You can also use the TypeScript await using syntax for automatic disposal when the scope exits.
await using modal = create({title: 'Hello World!'})
await modal.show()Globally Hiding Modals
In addition to creating modals in a global context, you can also hide modals from anywhere in the app. This feature does not require an orchestrator component to be present.
<template>
<BButton @click="nestedModal1 = !nestedModal1">Open First Modal</BButton>
<BModal
v-model="nestedModal1"
title="First Modal"
ok-only
>
<p class="my-2">First Modal</p>
<BButtonGroup>
<BButton @click="nestedModal2 = !nestedModal2">Open Second Modal</BButton>
<BButton @click="() => hide()">Hide Last</BButton>
<BButton @click="() => hideAll()">Hide All</BButton>
</BButtonGroup>
</BModal>
<BModal
v-model="nestedModal2"
title="Second Modal"
ok-only
>
<p class="my-2">Second Modal</p>
<BButtonGroup>
<BButton
size="sm"
@click="nestedModal3 = !nestedModal3"
>Open Third Modal</BButton
>
<BButton @click="() => hide()">Hide Last</BButton>
<BButton @click="() => hideAll()">Hide All</BButton>
</BButtonGroup>
</BModal>
<BModal
v-model="nestedModal3"
title="Third Modal"
ok-only
>
<p class="my-1">Third Modal</p>
<BButtonGroup>
<BButton @click="() => hide()">Hide Last</BButton>
<BButton @click="() => hideAll()">Hide All</BButton>
</BButtonGroup>
</BModal>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import {BButton, BButtonGroup, BModal, useModal} from 'bootstrap-vue-next'
const nestedModal1 = ref(false)
const nestedModal2 = ref(false)
const nestedModal3 = ref(false)
const {hide, hideAll} = useModal()
</script>