What is Vue Lynx?

Vue Lynx is a Vue 3 custom renderer for Lynx, enabling you to build native Lynx applications using Vue's familiar Composition API, single-file components, and reactive data model.

If you know Vue 3 and are familiar with Lynx, you already know how to use Vue Lynx.

Main Features

  • Familiar Vue 3 API — Use ref(), computed(), v-for, v-if, SFC <script setup>, and everything you love about Vue.
  • Cross-Platform Native Rendering — Lynx provides a high-performance native rendering engine on mobile and desktop, as well as first-class Web rendering. Your UI is never a WebView.
  • Dual-Thread Architecture — Vue runs on the background thread; the main thread handles native rendering. This separation keeps your app responsive. For latency-sensitive interactions (drag, scroll), Main Thread Script lets you mark functions with 'main thread' to run directly on the main thread with zero cross-thread delay.

For Vue Web Developers

If you're coming from Vue on the web, most of what you know carries over. Here are the key differences.

Change your import

Vue Lynx re-exports the Vue API, so you can replace vue with vue-lynx:

- import { ref, computed, onMounted } from 'vue';
+ import { ref, computed, onMounted } from 'vue-lynx';

Different elements

Lynx provides native elements instead of HTML. Use <view>, <text>, <image>, etc. in place of <div>, <span>, <img>:

- <div class="card">
-   <span>{{ message }}</span>
- </div>
+ <view class="card">
+   <text>{{ message }}</text>
+ </view>
Info

Text content must be wrapped in <text> — Lynx does not support bare text nodes inside <view>.

Different events

Lynx uses its own event system. Use :bindeventname for events that propagate, and :catcheventname for events that don't:

- <div @click="handleClick" @touchstart="onTouch">
+ <view :bindtap="handleClick" :bindtouchstart="onTouch">

See the Lynx event handling guide for more details.

No document or window

Lynx does not provide browser globals. Libraries that depend on document or window need adaptation to work. Instead, Lynx provides its own APIs via the lynx global object:

Understanding the Dual-Thread Model

If you've worked with Web Workers, Lynx's architecture will feel familiar. Vue runs on a background thread while native rendering happens on the main thread — just like offloading work to a Web Worker so the UI thread stays responsive. The difference is that in Lynx, this is the default — Vue reconciliation never blocks the main thread. (See Use the Main Thread Responsibly for Interactivity for the design rationale.)

Under the hood, Vue Lynx uses a custom renderer that builds a ShadowElement tree on the background thread. Changes are batched as ops and flushed to the main thread. User interactions flow back as events. This forms a cycle analogous to the browser event loop:

        ┌──────────────────────────────────────────────────────┐
        │                  Background Thread                   │
        │  Vue 3 runtime · reactivity · lifecycle · your code  │
        └──────────────┬──────────────────────▲────────────────┘
                  ops  │                      │  events
                       ▼                      │
        ┌──────────────────────────────────────┴───────────────┐
        │                    Main Thread                       │
        │  Native elements · layout · rendering · MTS handlers │
        └──────────────────────────────────────────────────────┘

One full cycle — ops down, events back up — is one tick. Vue Lynx's nextTick resolves after a complete cycle, so by the time your callback fires, native elements are fully materialized. This is the same mental model as Vue's nextTick on the web (wait for DOM update), just extended across threads.

Vue API: fully compatible

Vue Lynx reuses @vue/runtime-core directly. Reactivity, component model, lifecycle hooks, template directives — everything works as documented in the Vue 3 docs. See Vue Compatibility for a detailed feature-by-feature breakdown.

Caveats for the dual-thread environment

A few behaviors are adjusted because your code and native elements live on different threads.

Lifecycle runs on the background thread — use nextTick to wait for the main thread

Just like on the web, onMounted means "the component tree is built." The difference is that native elements are created asynchronously on the main thread. If you need to interact with native elements after mount, wait one tick:

<script setup lang="ts">
import { onMounted, nextTick } from 'vue-lynx'

onMounted(() => {
  nextTick(() => {
    // Native elements are now ready
    lynx.createSelectorQuery().select('.my-list').invoke(...)
  })
})
</script>

If your onMounted only sets up reactive state, timers, or data fetching, no nextTick is needed — those don't depend on native elements.

Background thread has only async access — use Main Thread Script for synchronous needs

From the background thread, native element access is always asynchronous. Template ref returns a ShadowElement (a background-thread reference to the native element), and layout queries like getBoundingClientRect() require an async API:

<script setup lang="ts">
import { onMounted, nextTick } from 'vue-lynx'

onMounted(() => {
  nextTick(() => {
    lynx.createSelectorQuery()
      .select('.target')
      .invoke({
        method: 'boundingClientRect',
        success: (res) => console.log(res),
      })
      .exec()
  })
})
</script>

When you need synchronous access — smooth animations, gesture handling, layout measurement — use Main Thread Script. Functions marked 'main thread' run directly on the main thread and can call Main Thread APIs like getComputedStyleProperty(), listen to main-thread-bindlayoutchange, and access native elements via useMainThreadRef():

<script setup lang="ts">
import { ref, useMainThreadRef } from 'vue-lynx'

const divRef = ref(null)               // ShadowElement (background thread)
const mtRef = useMainThreadRef(null)    // Native element (main thread, MTS only)
</script>

<template>
  <view ref="divRef" :main-thread-ref="mtRef" />
</template>

See Vue Features Compatibility for feature-specific caveats and unsupported features.

Next Steps