Docs
Components

Vue

Register the root component

import ResourcesWebGPU from '@uno.build/ui/ResourcesWebGPU'
import UI from '@uno.build/ui/UI'
import { registerRootComponent } from '@uno.build/ui/vue'
import App from './App.vue'

const canvas = document.querySelector('canvas')
const resources = await ResourcesWebGPU.create({ canvas })
const { ui } = await UI.create({ resources })

// Vue
const vue = registerRootComponent(App, { ui })
vue.mount({})

Vue updates call ui.update() automatically after rendering changes. Keep the viewport setup and render loop from First Layout to draw the UI with ui.draw().

Use one registered root per UI. Call vue.mount(props) again to update the root component's props while preserving its state.

Call vue.unmount() to remove the component tree and run its cleanup before destroying the UI. It does not destroy the UI or dispose shared resources; see Destroy.

Configure the compiler

The examples below use Vue single-file components. With Vite, pass compilerConfig to the Vue plugin and add stylesPlugin() to process uno styles:

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { compilerConfig, stylesPlugin } from '@uno.build/ui/vue/config'

export default defineConfig({
  plugins: [vue(compilerConfig), stylesPlugin()],
  resolve: {
    alias: {
      'uno-ui/vue': '@uno.build/ui/vue',
    },
  },
})

compilerConfig preserves image resource identifiers and disables static hoisting for the uno renderer. The current styles plugin generates imports from uno-ui/vue, so the alias above resolves them to @uno.build/ui/vue.

The demo below compiles .vue files directly in the browser. Its loader passes compilerConfig.template to compileScript, processes styles with compileStyles, and registers the resulting rules with registerStyleSheet.

Components

@uno.build/ui/vue exports five components: View, Text, Image, ScrollView, and Input. They render uno nodes and share the style and event bindings below. Each component's section describes its additional props and behavior.

<View>

View is a container for layout, backgrounds, and child components. Rendering a View creates an uno node, equivalent to calling ui.create() in the imperative API. Vue adds the node to its parent and manages its updates and removal. Use reactive state to update its styles in response to events.

<!-- App.vue -->
<script setup>
import { ref } from 'vue'
import { View } from '@uno.build/ui/vue'

const hovered = ref(false)
</script>

<template>
  <View
    :style="{
      width: '100%',
      height: '100%',
      alignItems: 'center',
      justifyContent: 'center',
    }"
  >
    <View
      @pointerover="hovered = true"
      @pointerout="hovered = false"
      :style="{
        width: '200px',
        height: '150px',
        backgroundColor: '#42B883',
        opacity: hovered ? '0.5' : '1',
      }"
    />
  </View>
</template>

<Text>

Use Text for all text content. Follow Texts & Fonts to load and register a font before rendering the examples below.

<script setup>
import { View, Text } from '@uno.build/ui/vue'
</script>

<template>
  <View :style="{ padding: '16px', gap: '8px' }">
    <Text :style="{ color: '#ffffff' }">Hello from Vue.</Text>
  </View>
</template>

A string directly inside View, such as <View>Hello</View>, is invalid. Text is a leaf node: it accepts strings, numbers, interpolations, and fragments containing only text, but cannot contain elements or components. To use different styles within a line, place sibling Text components inside a View with flexDirection: 'row'.

<Image>

Image displays a registered image resource. It does not download the image from src. Follow Images to load and register the asset before calling vue.mount({}).

PropertyValueDefaultUsage
srcStringRequiredThe registered image resource identifier.
widthStringNot setWidth with units, such as '128px'. If only this dimension is set, the registered aspect ratio is preserved.
heightStringNot setHeight with units, such as '100px'. If only this dimension is set, the registered aspect ratio is preserved.
style.objectFit'fill', 'contain', 'cover', or 'none''fill'Controls how the image fits within the component's dimensions. Set it inside style.

With neither dimension set, the component uses the registered image size. Dimensions from classes or :style take precedence over the width and height props.

Use the same identifier as the component's src:

<script setup>
import { Image, View } from '@uno.build/ui/vue'
</script>

<template>
  <View :style="{ flexDirection: 'row', gap: '16px' }">
    <Image src="assets/coin.png" width="128px" />
    <Image src="assets/coin.png" width="160px" height="100px" :style="{ objectFit: 'contain' }" />
  </View>
</template>

<ScrollView>

ScrollView clips its children and provides vertical scrolling by default.

PropertyValueDefaultUsage
horizontalBooleanfalseArranges the content in a row and enables horizontal scrolling when true.

Give it a limited height so that content can overflow the viewport:

<script setup>
import { ScrollView, Text, View } from '@uno.build/ui/vue'

const ITEMS = Array.from({ length: 20 }, (_, index) => index + 1)
</script>

<template>
  <ScrollView :style="{ width: '240px', height: '200px' }">
    <View v-for="item in ITEMS" :key="item" :style="{ padding: '12px', flexShrink: '0', backgroundColor: '#42B883' }">
      <Text :style="{ color: '#ffffff' }">Item {{ item }}</Text>
    </View>
  </ScrollView>
</template>

Set horizontal to arrange the content in a row and enable horizontal scrolling:

<ScrollView horizontal :style="{ width: '240px', height: '80px' }">
  <View
    v-for="item in ITEMS"
    :key="item"
    :style="{ width: '120px', padding: '12px', flexShrink: '0' }"
  >
    <Text :style="{ color: '#ffffff' }">Item {{ item }}</Text>
  </View>
</ScrollView>

The style and class bindings apply to the viewport. Use @scroll to respond to scrolling.

<Input>

Input is a visual component controlled by value. It displays text, a placeholder, and a blinking caret while focused.

PropertyValueDefaultUsage
valueString, number, or nullundefinedThe displayed input value. An empty string, null, or undefined is treated as empty.
placeholderString, number, or nullundefinedText displayed only when value is empty and the input is unfocused.
placeholder-text-colorColor string'#777777'The color used for the placeholder text (placeholderTextColor in JavaScript).

Pass the displayed text through :value:

<script setup>
import { Input } from '@uno.build/ui/vue'

defineProps({ value: { type: String, default: '' } })
</script>

<template>
  <Input
    :value="value"
    placeholder="Your name"
    :style="{ width: '240px', height: '40px', padding: '8px', color: '#111827' }"
  />
</template>

Input does not create a DOM input, emit value updates for v-model, or handle IME, selection, or clipboard input. To support typing in a browser, connect a native <input> outside the uno component tree, synchronize its value with a Vue ref, and manage its focus through @focus and @blur. See the demo below for a complete example.

Styles

Pass an object to :style with camelCase property names and string values, as in the examples above. All five components also accept class and :class, including strings, arrays, and objects for conditional classes.

With the compiler setup above, <style> and <style scoped> blocks apply uno styles to those classes:

<script setup>
import { ref } from 'vue'
import { View } from '@uno.build/ui/vue'

const hovered = ref(false)
</script>

<template>
  <View class="box" :class="{ hovered }" @pointerover="hovered = true" @pointerout="hovered = false" />
</template>

<style scoped>
.box {
  width: 200px;
  height: 150px;
  background-color: #42b883;
}

.box.hovered {
  opacity: 0.5;
}
</style>

Inline :style values override class styles. Style blocks support class selectors such as .box, .box.hovered, and comma-separated class selectors. Use uno style properties; these rules are applied to uno nodes.

Descendant selectors, tag selectors, pseudo-classes such as :hover, nested rules, at-rules, !important, CSS custom properties, and var() are not supported. Style blocks must contain inline CSS: src, CSS modules, preprocessors, and CSS v-bind() are not supported. Use reactive classes or :style for dynamic values.

Events

All five components accept the following event bindings. Pass a handler function to subscribe; omit the binding or set its handler to null or undefined to remove it. Vue manages the listener registration and cleanup.

Template bindinguno eventWhen it fires
@pointerdownpointerdownA pointer is pressed on a node.
@pointermovepointermoveA pointer moves over the UI or during an active pointer interaction.
@pointeruppointerupAn active pointer is released.
@pointercancelpointercancelThe platform cancels an active pointer interaction.
@pointeroverpointeroverA node becomes the pointer's hover target.
@pointeroutpointeroutA node stops being the pointer's hover target.
@clickclickA node is activated by a completed click or tap.
@wheelwheelWheel or trackpad input occurs over a node.
@scrollscrollA scroll container's offset changes through scrolling.
@focusfocusA node gains uno focus.
@blurblurA node loses uno focus.

Use the hyphenated pointer bindings shown above, such as @pointerdown, so Vue generates uno's onPointerDown prop.

Handlers receive uno events rather than DOM events. Each event includes these common members:

MemberMeaning
typeThe uno event name, such as 'pointerdown'.
targetThe uno node where the event originated.
current_targetThe uno node whose handler is currently running.
source_eventThe original platform event, or null for a programmatic event without a source.
stopPropagation()Stops the event from continuing to ancestor nodes.

Events bubble through the uno node tree, so a component's handler can receive events from its descendants. Event-specific data, such as pointer coordinates, wheel deltas, scroll offsets, and focus transitions, is documented in Events.

To prevent the source event's browser default action, call event.source_event?.preventDefault() in your handler. uno events do not expose their own preventDefault() method, so Vue's .prevent modifier cannot be used here.

Refs and handles

Use a Vue template ref to access a component's uno nodes. These refs expose handles rather than DOM elements. Import NodeHandle, ScrollViewHandle, and InputHandle from @uno.build/ui/vue when using TypeScript. Use shallowRef to hold a handle without deeply wrapping its nodes in reactive proxies.

ComponentRef valueAvailable members
View, Text, ImageNodeHandlenodes.main
ScrollViewScrollViewHandlenodes.main (viewport), nodes.content (scrollable content)
InputInputHandlenodes.main, nodes.content, nodes.text, nodes.caret, focus(), blur()

nodes.caret is null when the input is unfocused. focus() and blur() change uno focus; they do not open a platform keyboard or provide text input.

useUI()

useUI() is a Vue composable that returns the UI passed to registerRootComponent. Call it during setup() or <script setup> inside that registered component tree. For example, a component can read the original dimensions of the registered assets/coin.png image:

<script setup>
import { Text, useUI } from '@uno.build/ui/vue'

const ui = useUI()
const { width, height } = ui.resources.getImageSize('assets/coin.png')
</script>

<template>
  <Text :style="{ color: '#ffffff' }">Image size: {{ width }} × {{ height }}</Text>
</template>

Demo

On this page