Getting Started

Installation

Install @vyui/core and @vyui/kit into a Vue-Lynx app.

Setup

Add to a Vue-Lynx project

Install the @vyui/core package

pnpm add @vyui/core

Follow VyUI worklets in the Lynx build

VyUI components register 'main thread' worklets (gestures, drag, animation). vue-lynx's main-thread loader skips node_modules unless a package is allowlisted — without this, the first gesture throws TypeError: cannot read property 'bind' of undefined. Requires vue-lynx@^0.4.2.

lynx.config.ts
pluginVueLynx({
  includeWorkletPackages: ['@vyui/core', '@vyui/kit'],
})

Use a primitive

Primitives are unstyled. Compose them with your own classes, tokens, or design system.

App.vue
<script setup>
import { SliderRoot, SliderTrack, SliderRange, SliderThumb } from '@vyui/core'
import { ref } from 'vue'

const value = ref(50)
</script>

<template>
  <SliderRoot v-model="value" :max="100">
    <SliderTrack>
      <SliderRange />
    </SliderTrack>
    <SliderThumb />
  </SliderRoot>
</template>

Add styled components with @vyui/kit

Install both packages

pnpm add @vyui/core @vyui/kit

@vyui/kit depends on @vyui/core, but it does not re-export the full core surface. Import styled Vy* components from kit, and import raw primitives or utilities directly from core unless the kit docs call out a specific Vy* convenience alias.

Configure Tailwind

@vyui/kit ships its styling as a Tailwind preset. Add it alongside the Lynx preset, and feed the kit ui-* state markers into the Lynx preset's uiVariants plugin — the class-based replacements for Lynx-incompatible data-[state] selectors. Without this step components render unstyled.

tailwind.config.ts
import type { Config } from 'tailwindcss'
import { createLynxPreset } from '@lynx-js/tailwind-preset'
import { createVyuiPreset, VYUI_UI_STATES } from '@vyui/kit/tailwind'

const lynxPreset = createLynxPreset({
  lynxUIPlugins: {
    uiVariants: {
      prefixes: defaults => ({ ...defaults, ui: [...defaults.ui, ...VYUI_UI_STATES] }),
    },
  },
})

export default {
  // Scan the kit + core component sources so their utility classes aren't purged.
  content: [
    './src/**/*.{vue,js,ts}',
    './node_modules/@vyui/kit/dist/**/*.js',
    './node_modules/@vyui/core/dist/**/*.js',
  ],
  presets: [lynxPreset, createVyuiPreset()],
} satisfies Config

Import the theme stylesheet

@vyui/kit/style.css defines the default design tokens using Tailwind theme() calls, so pull it in through your Tailwind pipeline with a CSS @import — not a plain JS import. Put any --ui-color-* overrides after it.

src/index.css
@tailwind base;
@tailwind utilities;

/* @vyui/kit default theme tokens — overrides go below this line. */
@import '@vyui/kit/style.css';

Register Vy UI

Vy UI needs its theme config provided once at your app entry. On Vue-Lynx — the primary target — do this with provideVyUI and import each component from its own deep entry where you use it (@vyui/kit/button), so the bundler ships only what you reference — see Bundle size & deep imports. Kit's defaults use lucide icons, so register that set up front, and install the Intl polyfill for Lynx's PrimJS engine.

src/index.ts
import { createApp } from 'vue-lynx'
import { installIntlPolyfill, registerIconSet } from '@vyui/core'
import { provideVyUI } from '@vyui/kit'
import lucide from '@iconify-json/lucide/icons.json'
import App from './App.vue'
import './index.css'

installIntlPolyfill()
registerIconSet('lucide', lucide)

const app = createApp(App)
provideVyUI(app)
app.mount()

Then import each component from its own entry where you use it:

SomeScreen.vue
<script setup>
import { VyButton } from '@vyui/kit/button'
</script>

<template>
  <VyButton>Tap me</VyButton>
</template>

The barrel (import { VyButton } from '@vyui/kit') works identically, but on Vue-Lynx it ships every component — see Bundle size & deep imports below.

Bundle size & deep imports

Every component is published twice: through the @vyui/kit barrel and through its own deep entry (@vyui/kit/button, @vyui/kit/tray, …). The bindings are identical — the canonical Vy* name — so choosing one is a specifier swap.

On the web, either is fine; a normal tree-shaking bundler drops what you don't reference from the barrel. On Vue-Lynx it does not. The main-thread worklet pipeline prunes by sideEffects globs over whatever is reached, and a bare side-effect import erases export-level usage — so a single barrel import pulls the whole set into both the background bundle and the worklet slice. Deep entries are the only way to ship less:

ImportModules reachedWorklet registrations
@vyui/kit/button~37~26
@vyui/kit (barrel)~294~118

So on Vue-Lynx, prefer deep imports for components. One barrel import anywhere in a module re-pulls everything, so keep component imports deep throughout. (The build-only entries @vyui/kit/config and @vyui/kit/tailwind are already isolated and never pull component code.)

On full Vue (web) you can instead call app.use(VyUI), which registers every Vy* component globally so <VyButton> works without an import — convenient, but it pulls the entire component set into your bundle. Vue-Lynx's createApp has no app.component, so app.use(VyUI) degrades to theme-only there (with a dev warning) — provideVyUI + named imports is the native path. Register just a subset globally with app.use(VyUI, { components: { VyButton } }).

Runtime modes

@vyui/kit runs on any Vue-compatible runtime, but only some expose app.component. Pick the entry that matches yours:

RuntimeEntryComponents
Vue-Lynx / minimal (native path)provideVyUI(app, config)Deep imports (import { VyButton } from '@vyui/kit/button')
Full Vue (web)app.use(VyUI, config)Registered globally
Build (Tailwind)createVyuiPreset(config)— (generates the class surface)

Vue-Lynx's createApp has no app.component, so app.use(VyUI) degrades to theme-only there (with a dev warning) — provideVyUI is the blessed native path. Runtime config selects from the classes Tailwind already emitted; it never creates styling.

Single-source config with defineVyuiConfig

The color set is meaningful to both planes: the Tailwind preset must generate the classes, and the runtime must select the same set. Author it once with defineVyuiConfig (from the light @vyui/kit/config entry — safe to import in a Tailwind config; it never pulls component code) and feed the result to both:

vyui.config.ts
import { defineVyuiConfig } from '@vyui/kit/config'

export default defineVyuiConfig({
  theme: {
    primary: 'orange',
    gray: 'stone',
    colors: ['primary', 'secondary', 'success', 'info', 'warning', 'error'],
  },
  components: {
    button: { slots: { base: 'rounded-xl' } },
  },
})
tailwind.config.ts
import vyuiConfig from './vyui.config'
import { createVyuiPreset } from '@vyui/kit/tailwind'

export default {
  presets: [lynxPreset, createVyuiPreset(vyuiConfig)],
}
src/index.ts
import vyuiConfig from './vyui.config'
import { provideVyUI } from '@vyui/kit' // or: app.use(VyUI, vyuiConfig) on web

provideVyUI(app, vyuiConfig)

Mount the overlay & toast hosts

VyModal, VyDrawer, VyPopover and friends render into a portal host, and VyToast needs a toast provider — the plugin does not wire these up for you. Mount both once near the root:

App.vue
<script setup>
import { OverlayRoot, ToastProvider } from '@vyui/core'
</script>

<template>
  <ToastProvider>
    <!-- your app -->
  </ToastProvider>
  <!-- Portal host for overlays; keep it a sibling at the app root. -->
  <OverlayRoot />
</template>

Use a Vy* component

App.vue
<script setup>
import { VyButton, VySlider } from '@vyui/kit'
import { ref } from 'vue'

const value = ref(50)
</script>

<template>
  <VySlider v-model="value" :max="100" />
  <VyButton variant="solid" color="primary">Save</VyButton>
</template>

Options

Kit ships sensible defaults — you only need to reach for these when you want to override theme tokens, swap a primary color, or change a component's default variant.

theme

Pass component overrides under ui.<component> to deep-merge them into the default Tailwind Variants theme. Every Vy* component reads from this — no rebuild required. Like Nuxt UI, everything lives under the single ui namespace.

src/index.ts
createApp(App).use(VyUI, {
  ui: {
    button: {
      defaultVariants: { color: 'primary' },
    },
  },
})

colors

Override the semantic color slots (primary, secondary, success, info, warning, error, neutral) by redefining the --ui-color-{semantic}-{shade} CSS variables in your own stylesheet, or add a brand-new color slot (e.g. tertiary) with defineVyuiConfig — see Theming → Colors for the full walkthrough, including the type-safety and codegen steps.

Prefer copying component source into your app? Use the Vy UI CLI to initialize a registry-backed setup and add individual components.

Troubleshooting

TypeError: cannot read property 'bind' of undefined

The app builds fine, then crashes on the main thread the first time a worklet-driven component runs — dragging a VySortable, a gesture, an animation:

main-thread.js exception: TypeError: cannot read property 'bind' of undefined

Cause. VyUI ships those handlers as 'main thread' worklets inside @vyui/core / @vyui/kit. vue-lynx's main-thread loader only walks first-party source, so worklets living in node_modules never get registered — and the main thread then tries to .bind a worklet that isn't there.

Fix. Allowlist the packages in your Lynx build (requires vue-lynx@^0.4.2):

lynx.config.ts
pluginVueLynx({
  includeWorkletPackages: ['@vyui/core', '@vyui/kit'],
})

If you set the project up with the CLI, run npx @vyui/cli check — it audits your config and reports this (and other common wiring gaps) with the fix, and exits non-zero so you can gate CI on it. init adds the allowlist for you on new projects.