BuField
The .field wrapper and the parts that live inside it: label, control, help. Addons, grouped and horizontal layouts are props on the field; the horizontal columns are parts you compose.
Reference: Form on bulma.io↗︎ — classes and visual variants. This page is the JavaScript.
Usage
BuField is markup. It does not own a value and it does not create a v0 context. Put a BuLabel, a BuControl around the input, and a BuHelp under it — that is the stacked field Bulma documents.
addons, grouped and horizontal are three layouts — pick one. The component will happily emit both has-addons and is-grouped if you set both, and Bulma has no stylesheet for that combination. addons attaches controls into one group (has-addons, plus has-addons-centered / has-addons-right when you pass those strings). grouped spaces them as separate controls. horizontal splits the row into a label column and a body column, which you compose from BuFieldLabel and BuFieldBody.
Anatomy
The first tree is a stacked field. The second is the horizontal layout — a field is one or the other, never both.
<script setup lang="ts">
import {
BuControl,
BuField,
BuFieldBody,
BuFieldLabel,
BuHelp,
BuLabel,
} from '@paper/bulma'
</script>
<template>
<BuField>
<BuLabel />
<BuControl />
<BuHelp />
</BuField>
<BuField>
<BuFieldLabel>
<BuLabel />
</BuFieldLabel>
<BuFieldBody>
<BuField>
<BuControl />
</BuField>
</BuFieldBody>
</BuField>
</template>Composed on v0
BuField, BuFieldLabel, BuFieldBody and BuControl are pure markup. They emit Bulma’s classes around a slot. There is no model, no context and no v0 primitive underneath.
BuLabel and BuHelp are the two that reach into v0. Both optionally inject an Input.Root on v0:input:root by default. BuLabel’s for falls back to that root’s id when you do not pass one. BuHelp with validation wraps a renderless Input.Error, so the error id, aria-live and aria-errormessage registration wire up without you tracking them.
v0 ships no Input.Label part, which is why BuLabel is hand-rolled markup rather than a styled v0 label.
Without an ambient root they still render — a standalone label has no for, and a help with validation renders an empty .help (and warns in development). That is the half-wired trap on BuInput: the input still paints is-danger, the sibling help stays blank. Wrap the field in <InputRoot renderless> when the label and the error text need to see the same context.
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.
<div class="field">
<label class="label">Label</label>
<div class="control">
<input class="input" type="text" placeholder="Text input">
</div>
<p class="help">This is a help text</p>
</div><template>
<BuField>
<BuLabel>Label</BuLabel>
<BuControl>
<BuInput placeholder="Text input" />
</BuControl>
<BuHelp>This is a help text</BuHelp>
</BuField>
</template>Icons and the loading spinner live on BuControl, not on the input. icons="both" emits has-icons-left and has-icons-right; loading emits is-loading; size on the control pairs the spinner with the input size. Drop the icon elements in as siblings of the input, the way Bulma documents them.
Examples
Props
BuField renders .field. Everything else is a part.
| Prop | Type | Default | Description |
|---|---|---|---|
addons | boolean | 'centered' | 'right' | false | has-addons, plus has-addons-{value} when a string |
grouped | boolean | 'centered' | 'right' | 'multiline' | false | is-grouped, plus is-grouped-{value} when a string |
horizontal | boolean | false | is-horizontal — compose from BuFieldLabel + BuFieldBody |
| Part | Renders | Notes |
|---|---|---|
BuFieldLabel | div.field-label | Label column of a horizontal field. size emits is-{size} |
BuFieldBody | div.field-body | Control column. Holds nested BuFields |
BuControl | div.control or p.control | Wraps the input. Icons, loading and expanded live here |
BuLabel | label.label | for falls back to the ambient Input.Root id |
BuHelp | p.help | Plain help text, or ambient Input.Error when validation is set |
BuFieldLabel
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'small' | 'normal' | 'medium' | 'large' | — | is-{size} on the column, aligning it with the control |
BuControl
| Prop | Type | Default | Description |
|---|---|---|---|
as | 'div' | 'p' | 'div' | Element tag — Bulma docs use both |
expanded | boolean | false | is-expanded — fill leftover width in grouped or addon fields |
icons | 'left' | 'right' | 'both' | — | has-icons-left / has-icons-right |
loading | boolean | false | is-loading — spinner on the control, not the input |
size | 'small' | 'normal' | 'medium' | 'large' | — | is-{size} — pairs the spinner with the input size |
BuLabel
| Prop | Type | Default | Description |
|---|---|---|---|
for | string | ambient id | Explicit target id; falls back to the ambient Input.Root id |
namespace | string | 'v0:input:root' | Context the label injects |
size | 'small' | 'normal' | 'medium' | 'large' | — | is-{size} on the label |
BuHelp
| Prop | Type | Default | Description |
|---|---|---|---|
color | 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger' | — | is-{color}. Defaults to danger in validation mode |
namespace | string | 'v0:input:root' | Context the help injects |
validation | boolean | false | Render ambient Input.Root errors via Input.Error |
Slot props when validation is set: errors: string[]. The default slot content is errors.join(' ').
Accessibility
Naming
BuLabel is a real <label>. Pass for, or let it fall back to an ambient Input.Root id, so clicking the text moves focus into the control. A label with neither is still visible — it just does not name anything, and the input has to get its accessible name some other way (label on the control, aria-label, aria-labelledby).
Errors
BuHelp with validation is Input.Error: the region carries aria-live="polite" and registers as aria-errormessage on the control. That only happens inside an ambient Input.Root. Outside one, validation is a no-op that warns in development — the input can still show is-danger from its own root, and the help stays empty.
Plain BuHelp (no validation) is help text. Color it yourself; it is not wired to the control unless you pass id and point aria-describedby at it.