BuInput
Bulma’s .input as a native text control, with v-model, the color and size modifiers the stylesheet already knows, and validation that paints is-danger when the value fails.
Reference: Input on bulma.io↗︎ — classes and visual variants. This page is the JavaScript.
Usage
v-model is a string and defaults to ''. It stays a string for every type, including number — the DOM gives you a string, and quietly coercing it is how forms end up with NaN in a payload.
A wrapper is optional. Bulma’s .input is bare-capable, so BuInput renders the native control and nothing around it. Put it in a BuField when you need a label or help text; put it in a BuControl when you need icons, a loading spinner, or is-expanded.
Anatomy
<script setup lang="ts">
import { BuInput } from '@paper/bulma'
</script>
<template>
<BuInput />
</template>Composed on v0
BuInput is a renderless Input.Root around a native Input.Control. The root owns the value, focus, the validation pipeline and the aria ids; the control is the element you type into. No wrapper element lands, which is why the conformance fixture is a bare <input class="input">.
v0’s control already is a native <input>, and Bulma styles that element, so the compound is the right primitive — not a hand-rolled input. is-danger is the one piece the skin owns: it lands on the control when isValid === false, because Bulma’s invalid state is a class, not a data attribute.
Inside an existing Input.Root the wrapper is skipped and the control binds to the ambient context instead. A nested root would shadow it, which is why the skip exists — and why the ambient root then owns the whole behavioral surface. See Validation and error text.
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.
<input class="input" type="text" placeholder="Text input" /><template>
<BuInput placeholder="Text input" />
</template>placeholder, autocomplete, maxlength and the rest of the native surface fall through to the input. class and style do too — there is no wrapper to land them on.
Loading is not an input modifier. is-loading goes on the wrapping BuControl, and size on that control pairs the spinner with the input size.
Examples
Props
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | string | '' | Field value. Always a string, whatever the type |
v-model:focused | boolean | false | Focus state |
color | 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger' | — | is-{color} |
disabled | boolean | false | Disables the input |
error | boolean | false | Force the invalid state — paints is-danger |
error-messages | string | string[] | — | Manual errors, merged with rule errors |
form | string | — | Id of the form to associate with |
id | string | number | auto | Input id; generated when omitted |
label | string | — | Accessible name for the input |
name | string | — | Form field name |
namespace | string | 'v0:input:root' | Context namespace the control binds to |
plaintext | boolean | false | is-static + readonly. Class-only inside an ambient root |
readonly | boolean | false | Input stays focusable; value cannot change |
required | boolean | — | Marks the field required |
rounded | boolean | false | is-rounded |
rules | ValidationRule[] | — | Validation rules |
size | 'small' | 'normal' | 'medium' | 'large' | — | is-{size} — omitted until passed |
type | string | 'text' | Native input type |
validate-on | ValidateOn | — | When validation runs. v0 default is 'blur' |
Inside an ambient Input.Root, every behavioral row in that table is ignored. Native attributes (placeholder, autocomplete, maxlength, …) fall through to the input.
Accessibility
The control is a real <input>. It needs an accessible name like any other: a sibling BuLabel pointed at its id, the label prop, or aria-labelledby. An unnamed field fails the axe label rule.
Naming
Prefer a visible BuLabel over label on the input. The prop becomes an aria-label and hides the visible text from the accessible name if both exist. placeholder is not a name — it disappears the moment someone types, and it fails contrast in most themes.
States
| State | Focusable | Submitted | Visual |
|---|---|---|---|
readonly | Yes | Yes | Native readonly. Use plaintext when it should also look like static text (is-static) |
disabled | No | No | Native disabled, Bulma’s disabled treatment |
error / failed rules | Yes | Yes | is-danger on the input; aria-invalid from v0 |
Validation
aria-invalid follows isValid === false. aria-errormessage points at a BuHelp validation only when that help is inside the same ambient root. A BuInput with its own internal root and a sibling help will announce invalid without a message — the half-wired trap above.