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

BuInput

Edit this page
Report a Bug
Open issues
Copy Markdown

Renders elementIntermediateAug 24, 2026

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.

Note

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.

<script setup lang="ts">
  import { BuControl, BuField, BuInput, BuLabel } from '@paper/bulma'

  import { shallowRef } from 'vue'

  const name = shallowRef('')
</script>

<template>
  <BuField>
    <BuLabel>Name</BuLabel>

    <BuControl>
      <BuInput v-model="name" placeholder="Text input" />
    </BuControl>
  </BuField>
</template>

Anatomy

vue
<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.

html
<input class="input" type="text" placeholder="Text input" />

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

Validation and error text

Rules and error text come from an ambient InputRoot rather than from props on BuInput, and that is deliberate rather than incidental. BuLabel and BuHelp resolve their wiring by injection — the label’s for, the help text’s id, the input’s aria-errormessage — so all three need to see the same context. A BuInput that creates its own root scopes that context to its own subtree, where a sibling label and help cannot reach it.

Wrapping the field in <InputRoot renderless> puts the context one level up, where every sibling can inject it. BuInput detects the ambient root and renders only the control, so nothing is shadowed. The label and the help both need namespace="v0:input:root": that is their default, but a mismatched namespace injects nothing and renders unwired — no for, no error text, and no complaint at runtime besides a development warning on BuHelp.

Without that ambient root, BuHelp validation is empty while the input still shows is-danger. The field looks invalid and explains nothing. That is a known limitation, not a bug to work around with a slot.

Inside an ambient root, that root owns the whole behavioral surface. v-model, type, disabled, readonly, required, name, form, id, label and every validation prop belong to it, and the same props passed to BuInput are ignored. Only the presentational modifiers — color, size, rounded, plaintext — still apply, and plaintext is then class-only (set readonly on the ambient root for the attribute).

validateOn defaults to blur. The failing state itself is component-owned: BuInput puts is-danger on the input when validation fails; there is no class prop for it, and error forces the state rather than styling it.

Props

PropTypeDefaultDescription
v-modelstring''Field value. Always a string, whatever the type
v-model:focusedbooleanfalseFocus state
color'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger'is-{color}
disabledbooleanfalseDisables the input
errorbooleanfalseForce the invalid state — paints is-danger
error-messagesstring | string[]Manual errors, merged with rule errors
formstringId of the form to associate with
idstring | numberautoInput id; generated when omitted
labelstringAccessible name for the input
namestringForm field name
namespacestring'v0:input:root'Context namespace the control binds to
plaintextbooleanfalseis-static + readonly. Class-only inside an ambient root
readonlybooleanfalseInput stays focusable; value cannot change
requiredbooleanMarks the field required
roundedbooleanfalseis-rounded
rulesValidationRule[]Validation rules
size'small' | 'normal' | 'medium' | 'large'is-{size} — omitted until passed
typestring'text'Native input type
validate-onValidateOnWhen 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

StateFocusableSubmittedVisual
readonlyYesYesNative readonly. Use plaintext when it should also look like static text (is-static)
disabledNoNoNative disabled, Bulma’s disabled treatment
error / failed rulesYesYesis-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.

Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/