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

BuPanel

Edit this page
Report a Bug
Open issues
Copy Markdown

Renders elementIntermediateAug 24, 2026

Bulma’s .panel with the JavaScript it never shipped: independent selection for blocks and for tabs, composed as the flat children Bulma already documents.

Note

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

Usage

BuPanel renders nav.panel and owns the block v-model. Compose the rest in document order, with no list wrapper: BuPanelHeading, optional passthrough .panel-block markup, BuPanelTabs / BuPanelTab for the filter row, BuPanelBlock for selectable rows, BuPanelIcon for the leading glyph.

Tabs have a second v-model of their own. The two selections never share.

<script setup lang="ts">
  import { BuPanel, BuPanelBlock, BuPanelHeading, BuPanelIcon } from '@paper/bulma'

  import { shallowRef } from 'vue'

  const selected = shallowRef('bulma')
</script>

<template>
  <BuPanel v-model="selected">
    <BuPanelHeading>Repositories</BuPanelHeading>

    <BuPanelBlock value="bulma">
      <BuPanelIcon icon="fas fa-book" />
      bulma
    </BuPanelBlock>

    <BuPanelBlock value="marksheet">
      <BuPanelIcon icon="fas fa-book" />
      marksheet
    </BuPanelBlock>

    <BuPanelBlock value="minireset.css">
      <BuPanelIcon icon="fas fa-book" />
      minireset.css
    </BuPanelBlock>

    <BuPanelBlock value="mojs">
      <BuPanelIcon icon="fas fa-code-branch" />
      mojs
    </BuPanelBlock>
  </BuPanel>
</template>

Anatomy

vue
<script setup lang="ts">
  import {
    BuPanel,
    BuPanelBlock,
    BuPanelHeading,
    BuPanelIcon,
    BuPanelTab,
    BuPanelTabs,
  } from '@paper/bulma'
</script>

<template>
  <BuPanel>
    <BuPanelHeading />

    <BuPanelTabs>
      <BuPanelTab />
    </BuPanelTabs>

    <BuPanelBlock>
      <BuPanelIcon />
    </BuPanelBlock>
  </BuPanel>
</template>

Composed on v0

BuPanel wraps its children in v0’s Selection. That is the block v-model. BuPanelBlock is a renderless Selection.Item with the same hand-picked anchor bindings BuMenu uses: is-active, data-selected, click to select — never the Item attrs spread, because aria-selected is invalid on a role-less <a>. multiple is Selection’s multiple, forwarded.

BuPanelTabs opens a nested Single of its own, with mandatory="force" so one tab is always active, matching Bulma’s docs. Its v-model never shares the panel’s. Clicking a tab cannot select a block, and the reverse.

label.panel-block > input[type=checkbox] rows, search fields, and footer buttons stay native passthrough. They are not BuPanelBlock and they do not enroll in either selection.

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
<nav class="panel">
  <p class="panel-heading">Repositories</p>
  <div class="panel-block">
    <p class="control has-icons-left">
      <input class="input" type="text" placeholder="Search" />
      <span class="icon is-left">
        <i class="fas fa-search" aria-hidden="true"></i>
      </span>
    </p>
  </div>
  <p class="panel-tabs">
    <a class="is-active">All</a>
    <a>Public</a>
    <a>Private</a>
    <a>Sources</a>
    <a>Forks</a>
  </p>
  <a class="panel-block is-active">
    <span class="panel-icon">
      <i class="fas fa-book" aria-hidden="true"></i>
    </span>
    bulma
  </a>
  <a class="panel-block">
    <span class="panel-icon">
      <i class="fas fa-book" aria-hidden="true"></i>
    </span>
    marksheet
  </a>
  <a class="panel-block">
    <span class="panel-icon">
      <i class="fas fa-book" aria-hidden="true"></i>
    </span>
    minireset.css
  </a>
  <a class="panel-block">
    <span class="panel-icon">
      <i class="fas fa-book" aria-hidden="true"></i>
    </span>
    jgthms.github.io
  </a>
  <a class="panel-block">
    <span class="panel-icon">
      <i class="fas fa-code-branch" aria-hidden="true"></i>
    </span>
    daniellowtw/infboard
  </a>
  <a class="panel-block">
    <span class="panel-icon">
      <i class="fas fa-code-branch" aria-hidden="true"></i>
    </span>
    mojs
  </a>
  <label class="panel-block">
    <input type="checkbox" />
    remember me
  </label>
  <div class="panel-block">
    <button class="button is-link is-outlined is-fullwidth">
      Reset all filters
    </button>
  </div>
</nav>

You write no is-active on tabs or blocks. Each v-model owns its own row of it. Search, checkbox and footer stay the markup Bulma already gave you.

Examples

Tabs and blocks

BuPanelTabs is its own Single with mandatory="force". One tab is always active — an empty shallowRef is enough; the first tab is selected as it registers. That v-model is independent of the panel’s. Filter the list from the tab value if you want; the component will not do it for you, because Bulma’s panel does not either.

BuPanelBlock rows live in the parent Selection. Default is exclusive. Pass multiple on BuPanel when several rows should stay is-active together — a tag filter, a batch of checked-looking rows that are not actually checkboxes.

Everything else in the panel is passthrough. A search field is div.panel-block. A checkbox row is label.panel-block > input. A footer button is div.panel-block > button. None of those enroll in either selection, which is why a “remember me” checkbox does not steal is-active from the repo list.

Props

BuPanel renders nav.panel and owns the block selection. Everything else is a part, or passthrough markup.

PropTypeDefaultDescription
v-modelT | T[]Selected block value(s)
multiplebooleanfalseAllow multiple selected a.panel-block rows
color'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger'is-{color} on .panel
PartRendersNotes
BuPanelHeadingp.panel-headingTitle bar; pure markup
BuPanelTabsp.panel-tabsOwn Single v-model; mandatory="force"
BuPanelTabavalue matched against the tabs v-model
BuPanelBlocka.panel-blockvalue matched against the panel v-model
BuPanelIconspan.panel-iconicon is the inner <i> class list (fas fa-book); default slot replaces it

BuPanelTabs takes only v-model. BuPanelTab and BuPanelBlock take value. BuPanelIcon takes icon.

Accessibility

.panel is a <nav> without a name in the markup Bulma ships; this component does not invent one. Add aria-label yourself if the panel is the page’s only navigation, or leave it when it sits beside a named landmark.

Tabs and blocks are role-less anchors with is-active / data-selected. They are not a tablist and not a listbox. Spreading v0 Item attrs onto them fails axe (aria-allowed-attr, critical) — the parts never do that.

Checkbox rows stay native label > input[type=checkbox]. That is the right control for a boolean; do not restyle a BuPanelBlock into a checkbox.

Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/