Skip to main content
Vuetify0 v1.0 is here
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

BuModal

Bulma’s .modal with the JavaScript it never shipped: open state, backdrop dismissal, Escape, focus trapping and focus return.

Edit this page
Report a Bug
Open issues
Copy Markdown

Renders elementIntermediateAug 24, 2026
Note

Reference: Modal on bulma.io↗︎ — classes and visual variants. This page is the JavaScript.

Usage

v-model owns the open state. The panel is whichever part you compose inside: BuModalContent for the plain variant, BuModalCard for the head/body/foot one. Everything inside the panel is Bulma’s.

<script setup lang="ts">
  import { BuModal, BuModalClose, BuModalContent } from '@paper/bulma'

  import { shallowRef } from 'vue'

  const open = shallowRef(false)
</script>

<template>
  <button class="button is-primary" type="button" @click="open = true">
    Open modal
  </button>

  <BuModal v-model="open">
    <BuModalContent>
      <div class="box">
        <h3 class="title is-5">Bring your own JS</h3>

        <p>
          Bulma ships the <code>.modal</code> styles and stops there. Escape, focus
          return and the backdrop click come from Vuetify0.
        </p>
      </div>
    </BuModalContent>

    <BuModalClose />
  </BuModal>
</template>

Anatomy

Both panels are shown here for completeness — a modal composes one or the other, never both.

vue
<script setup lang="ts">
  import {
    BuModal,
    BuModalBody,
    BuModalCard,
    BuModalClose,
    BuModalContent,
    BuModalDelete,
    BuModalFoot,
    BuModalHead,
    BuModalTitle,
  } from '@paper/bulma'
</script>

<template>
  <BuModal>
    <BuModalContent />

    <BuModalClose />

    <BuModalCard>
      <BuModalHead>
        <BuModalTitle />

        <BuModalDelete />
      </BuModalHead>

      <BuModalBody />

      <BuModalFoot />
    </BuModalCard>
  </BuModal>
</template>

Composed on v0

Wraps v0’s Dialog. Dialog.Root owns v-model. Dialog.Content is renderless so the host can stay a <div class="modal"> — the fixture demands that, never a native <dialog>. BuModalContent and BuModalCard take the identity Dialog would have bound (id, role, aria-modal, and on the card aria-labelledby) off the dialog context; the stack z-index lands on .modal.

Two things v0 ships that this wrapper skips. Scrim is global per stack ticket — mounting it behind a modal that already paints .modal-background would double the backdrop, so the backdrop is hand-rolled. And v0 has no useFocusTrap: Tab and Shift+Tab wrap inside .modal, and focus returns to the trigger on close, including when the modal unmounts while open.

The parts that do map through: BuModalTitle is Dialog.Title as="p". BuModalClose is Dialog.Close with class modal-close (the content-variant large X). BuModalDelete is Dialog.Close with class delete (the card-head X). They are not interchangeable.

blocking is Dialog’s blocking — backdrop clicks only. Escape still closes.

The markup you know

The Bulma tab is the markup published on bulma.io↗︎, captured verbatim in the conformance fixture. The Vue tab is the component that renders it. The conformance suite diffs the two on every test run — element for element, class for class.

html
<div class="modal is-active">
  <div class="modal-background"></div>
  <div class="modal-card">
    <header class="modal-card-head">
      <p class="modal-card-title">Modal title</p>
      <button class="delete" aria-label="close"></button>
    </header>
    <section class="modal-card-body">
      <!-- Content ... -->
    </section>
    <footer class="modal-card-foot">
      <div class="buttons">
        <button class="button is-success">Save changes</button>
        <button class="button">Cancel</button>
      </div>
    </footer>
  </div>
</div>

You write no is-active and no .modal-background. Compose BuModalDelete in the card head (or BuModalClose beside the content panel) for the documented X; omitting it is a valid head without a dismiss.

Examples

Card variant

Composing BuModalCard instead of BuModalContent gives Bulma’s three-region layout — a sticky head and foot with a scrollable body, sized to the viewport. Compose BuModalDelete inside BuModalHead for the documented card-head X; a head without it is a title-only bar. Footer buttons still write v-model themselves.

Reach for it when the modal has an obvious title and a decision to commit — a form, a confirmation. The plain content variant, with its large corner close button, suits a modal whose body is a single self-contained block.

Props

BuModal renders .modal and its backdrop, and owns the open state. Everything else is a part.

PropTypeDefaultDescription
v-modelbooleanfalseOpen state
blockingbooleanfalseIgnore clicks on .modal-background
PartRendersNotes
BuModalContentdiv.modal-contentPlain variant panel; carries role="dialog"
BuModalClosebutton.modal-closeSibling of the content panel; size prop, default large
BuModalCarddiv.modal-cardCard variant panel; carries role="dialog"
BuModalHeadheader.modal-card-headSlot only; compose Title and optional Delete
BuModalTitlep.modal-card-titleSupplies the panel’s accessible name
BuModalDeletebutton.deleteCard-head X; Dialog.Close — not BuModalClose
BuModalBodysection.modal-card-bodyScrollable region
BuModalFootfooter.modal-card-footAction row

Accessibility

The panel part — BuModalContent or BuModalCard — carries role="dialog" and aria-modal. Tab and Shift+Tab cycle inside the modal, Escape closes it, and focus returns to whatever opened it, including when the modal is unmounted while open.

Important

BuModalCard binds aria-labelledby to the id BuModalTitle renders. Compose a title, or that reference dangles and the dialog has no accessible name.

Note

blocking gates backdrop clicks only. Escape still closes a blocking modal, matching Vuetify0’s Dialog semantics and the native dialog element’s cancel behavior.

Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/