Nuxt UI integration
Nuxt UI integration
When @nuxt/ui is present in your app, the Nuxt module registers an extra component built on top of UInput, UButton, and UModal.
<UBarcodeInput />
A UInput with the scan trigger tucked into its #trailing slot, bound via v-model. When the Barcode Detection API is available, tapping the trailing icon opens a modal with a live camera scanner; the first accepted barcode is written back to the model. The modal renders a <BarcodeDetectorOverlay> over the camera preview, so users see polygons over barcodes in real time — green for accepted matches, red for ones the accept predicate is filtering out. On unsupported browsers the scan button is hidden and the field behaves as a plain UInput.
Because the component renders as a single UInput, it composes cleanly inside a <UFieldGroup> (e.g. paired with a submit button or a unit suffix) and inside <UFormField> — UInput's built-in useFormField() integration carries through transparently for label, description, hint, and error wiring.
<script setup lang="ts">
import { ref } from 'vue'
import type { DetectedBarcode } from '@orbisk/vue-use-barcode-detection'
const value = ref('')
function onScan(barcode: DetectedBarcode) {
console.log('scanned', barcode.format, barcode.rawValue)
}
</script>
<template>
<UBarcodeInput
v-model="value"
placeholder="Type a value or tap the icon to scan"
@scan="onScan"
/>
</template>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | '' | The input value. Use with v-model. |
formats | BarcodeFormat[] | all supported | Formats to detect. Defaults to every format the browser supports. |
once | boolean | true | Close the modal after the first accepted detection. Set to false to keep scanning multiple barcodes. |
accept | (b: DetectedBarcode) => boolean | undefined | Predicate gating which detections count (e.g. by format or rawValue prefix). Non-matching scans are ignored entirely. |
transform | (b: DetectedBarcode) => string | null | undefined | (b) => b.rawValue | Map an accepted detection to the model value. Useful when the QR encodes JSON and only one field should be bound. Returning null/undefined keeps the modal open without writing to the model. |
scanLabel | string | 'Scan barcode' | Accessible label for the scan button and title for the modal. |
scanIcon | string | 'i-lucide-scan-line' | Icon for the scan button. Any name your Nuxt UI iconset exposes. |
overlay | BarcodeDetectorOverlayConfig | undefined | Customize the on-camera overlay (colors, stroke, per-barcode label, etc.). Forwarded to <BarcodeDetectorOverlay>. |
All UInput props (placeholder, color, variant, size, disabled, icon, leadingIcon, trailingIcon, etc.) are forwarded; wrap in <UFormField> for label/error integration.
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | string | Emitted on manual input or after an accepted scan. |
scan | DetectedBarcode | Emitted when an accepted barcode is detected. |
blur | FocusEvent | Forwarded from the inner UInput. |
change | Event | Forwarded from the inner UInput. |
Form validation
UBarcodeInput participates in UForm exactly like UInput does. Wrap it in UFormField and the name prop wires errors, labels, and accessibility attributes. Validation runs on the model value, so it doesn't matter whether the user typed the code or scanned it — both code paths feed the same string into the schema.
<script setup lang="ts">
import { reactive, ref } from 'vue'
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = z.object({
productCode: z
.string()
.min(1, 'Required.')
.regex(/^\d{8,13}$/, 'Must be 8–13 digits (EAN/UPC).'),
})
type Schema = z.output<typeof schema>
const state = reactive({ productCode: '' })
const submitted = ref<Schema | null>(null)
function onSubmit(event: FormSubmitEvent<Schema>) {
submitted.value = event.data
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField
label="Product code"
name="productCode"
help="EAN-8, EAN-13, or UPC-A — type one or tap the icon to scan."
required
>
<UBarcodeInput v-model="state.productCode" placeholder="e.g. 5901234123457" />
</UFormField>
<UButton type="submit">Submit</UButton>
</UForm>
</template>
See the Nuxt UI Form docs for the full set of options — alternative schema libraries (Valibot, Yup, Joi), a custom validate function, validation events, and nested forms.
Input masking
UBarcodeInput's model is a plain string, so any masking library that transforms a string works. A small watcher using maska covers both typed and scanned values — the same code path runs whether the user enters digits manually or scans a barcode.
<script setup lang="ts">
import { ref, watch } from 'vue'
import { Mask } from 'maska'
const m = new Mask({ mask: '#-###-#####-####' })
const value = ref('')
watch(value, (v) => {
const next = m.masked(v)
if (next !== v) value.value = next
})
</script>
<template>
<UFormField label="GTIN-13" help="Format: 1-234-56789-0123">
<UBarcodeInput v-model="value" placeholder="0-000-00000-0000" />
</UFormField>
</template>
For cursor-aware masking on a plain text field, see the Nuxt UI mask example which applies the v-maska directive to UInput directly. The directive only attaches to native <input> elements, so it doesn't propagate to the inner input through UBarcodeInput's wrapper — use the watcher pattern above when you need masking alongside the scan button.
Filtering scans
Pass an accept predicate to ignore detections that don't match (wrong format, wrong prefix, etc.). Rejected barcodes are still rendered in the modal overlay — just in red — so users get immediate feedback about why a code isn't being captured.
<script setup lang="ts">
import { ref } from 'vue'
import type { DetectedBarcode } from '@orbisk/vue-use-barcode-detection'
const value = ref('')
const onlyQr = (b: DetectedBarcode) => b.format === 'qr_code'
</script>
<template>
<UFormField label="Ticket QR" name="ticket" help="Scan the QR code on your ticket.">
<UBarcodeInput v-model="value" :accept="onlyQr" placeholder="QR-…" />
</UFormField>
</template>
Transforming the scanned value
When the QR encodes structured data (JSON, URL, etc.), use transform to bind only the field you care about. The full DetectedBarcode is still emitted via @scan if you need the rest.
<script setup lang="ts">
import { ref } from 'vue'
import type { DetectedBarcode } from '@orbisk/vue-use-barcode-detection'
const ticketId = ref('')
// Scanned QR contains e.g. {"id": "T-1234", "name": "Alice"} — bind only the id.
function pickId(b: DetectedBarcode): string | undefined {
try {
return JSON.parse(b.rawValue).id
} catch {
return undefined // keep scanning until we get something we can parse
}
}
</script>
<template>
<UBarcodeInput v-model="ticketId" :transform="pickId" placeholder="T-…" />
</template>
Returning null or undefined from transform skips the model write and keeps the modal open, so you can keep scanning until you get a payload you can use.
Customizing the overlay
The overlay prop is forwarded straight to <BarcodeDetectorOverlay>, so anything that component accepts — colors, stroke width, label function — can be passed through as a single object.
<script setup lang="ts">
import { ref } from 'vue'
import type { BarcodeDetectorOverlayConfig } from '@orbisk/vue-use-barcode-detection'
const value = ref('')
const overlay: BarcodeDetectorOverlayConfig = {
// Show the decoded value over each accepted polygon, "rejected" over filtered ones.
label: (b, accepted) => (accepted ? b.rawValue : 'rejected'),
labelFontSize: 32,
}
</script>
<template>
<UBarcodeInput v-model="value" :overlay="overlay" />
</template>
See the BarcodeDetectorOverlay reference for the full list of options.
Labeled scan button
The scan button defaults to icon-only — pass content via the #button-default slot to add a label. Because the button lives inside UInput's #trailing slot, widen the input's right padding (:ui="{ base: 'pe-24' }") so the labeled button doesn't crowd the typed value.
<script setup lang="ts">
import { ref } from 'vue'
const value = ref('')
</script>
<template>
<UBarcodeInput
v-model="value"
placeholder="Enter or scan a code"
:ui="{ base: 'pe-24' }"
>
<template #button-default>Scan me</template>
</UBarcodeInput>
</template>
SSR
The scan button is gated behind useMounted and useSupported, so the server and the client's first render emit identical markup. The button appears after hydration if the browser supports the Barcode Detection API.