BuPanel
Bulma’s .panel with the JavaScript it never shipped: independent selection for blocks and for tabs, composed as the flat children Bulma already documents.
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.
Anatomy
<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.
<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><template>
<BuPanel v-model="selected">
<BuPanelHeading>Repositories</BuPanelHeading>
<div class="panel-block">
<p class="control has-icons-left">
<input class="input" placeholder="Search" type="text">
<span class="icon is-left">
<i aria-hidden="true" class="fas fa-search" />
</span>
</p>
</div>
<BuPanelTabs v-model="tab">
<BuPanelTab value="All">All</BuPanelTab>
<BuPanelTab value="Public">Public</BuPanelTab>
<BuPanelTab value="Private">Private</BuPanelTab>
<BuPanelTab value="Sources">Sources</BuPanelTab>
<BuPanelTab value="Forks">Forks</BuPanelTab>
</BuPanelTabs>
<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="jgthms.github.io">
<BuPanelIcon icon="fas fa-book" />
jgthms.github.io
</BuPanelBlock>
<BuPanelBlock value="daniellowtw/infboard">
<BuPanelIcon icon="fas fa-code-branch" />
daniellowtw/infboard
</BuPanelBlock>
<BuPanelBlock value="mojs">
<BuPanelIcon icon="fas fa-code-branch" />
mojs
</BuPanelBlock>
<label class="panel-block">
<input type="checkbox">
remember me
</label>
<div class="panel-block">
<button class="button is-link is-outlined is-fullwidth" type="button">
Reset all filters
</button>
</div>
</BuPanel>
</template>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
Props
BuPanel renders nav.panel and owns the block selection. Everything else is a part, or passthrough markup.
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | T | T[] | — | Selected block value(s) |
multiple | boolean | false | Allow multiple selected a.panel-block rows |
color | 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger' | — | is-{color} on .panel |
| Part | Renders | Notes |
|---|---|---|
BuPanelHeading | p.panel-heading | Title bar; pure markup |
BuPanelTabs | p.panel-tabs | Own Single v-model; mandatory="force" |
BuPanelTab | a | value matched against the tabs v-model |
BuPanelBlock | a.panel-block | value matched against the panel v-model |
BuPanelIcon | span.panel-icon | icon 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.