Sitemap

Vue-Lynx: Native Performance with the Simplicity of Vue

5 min readMar 31, 2026

--

Introduction

In recent days, the pre-alpha of Vue-Lynx was announced by Xuan Huang, marking an important step toward bringing the Vue ecosystem into the world of native mobile development. Notably, Xuan Huang achieved this milestone with the support of AI, highlighting how artificial intelligence is increasingly accelerating the development of new technologies.

Press enter or click to view image in full size

Lynx is an open-source technology developed by ByteDance with the goal of enabling developers to build truly native applications using web technologies. The idea is to deliver high performance and a smooth user experience, overcoming the typical limitations of hybrid solutions.

One of Lynx’s most interesting aspects is its framework-agnostic approach: it is not tied to a specific library like React or Vue.js, but is designed to integrate with different frameworks, giving developers full freedom in choosing their tools.

In this context, Vue-Lynx represents the first concrete attempt to integrate Lynx with Vue, opening up new possibilities for cross-platform development.

Get micbianchi97’s stories in your inbox

Join Medium for free to get updates from this writer.

In the next step, we’ll see how to use this technology by building a simple todo list with Vue-Lynx.

Project Setup

To create a new project and initialize it, run the following commands:

npm create vue-lynx@latest
cd <your-project-name>
npm install
npm run dev

Once the project is running, you can replace the contents of App.vue and App.css with the example code.

App.vue

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

import './App.css'

type Todo = {
id: string
text: string
completed: boolean
createdAt: number
}

const todos = ref<Todo[]>([
{ id: 't-1', text: 'Learn Vue Lynx', completed: false, createdAt: Date.now() },
{ id: 't-2', text: 'Build a todo list', completed: true, createdAt: Date.now() },
])

const newTodoText = ref('')

const remainingCount = computed(() => todos.value.filter((t) => !t.completed).length)
const completedCount = computed(() => todos.value.filter((t) => t.completed).length)

/**
* Handles `<input>` changes (native input `v-model` is not supported).
*/
function handleTodoInput(e: unknown) {
const detail = (e as { detail?: { value?: unknown } }).detail
const value = detail?.value
if (typeof value !== 'string') return
newTodoText.value = value
}

/**
* Adds a new todo from `newTodoText`.
*/
function handleAddTodo() {
const text = newTodoText.value.trim()
if (!text) return

const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`
todos.value = [
...todos.value,
{ id, text, completed: false, createdAt: Date.now() },
]
newTodoText.value = ''
}

/**
* Toggles the completion status of a todo by id.
*/
function handleToggleTodo(id: string) {
todos.value = todos.value.map((t) =>
t.id === id ? { ...t, completed: !t.completed } : t,
)
}

/**
* Deletes a todo by id.
*/
function handleDeleteTodo(id: string) {
todos.value = todos.value.filter((t) => t.id !== id)
}

/**
* Clears all completed todos.
*/
function handleClearCompleted() {
if (completedCount.value === 0) return
todos.value = todos.value.filter((t) => !t.completed)
}
</script>

<template>
<view class="todo-app">
<view class="todo-header-row">
<text class="todo-header">Todo list</text>
</view>

<view class="todo-composer">
<input
class="todo-input"
:value="newTodoText"
@input="handleTodoInput"
placeholder="Write a new task"
/>
<view class="todo-add-button" :bindtap="handleAddTodo">
<text class="todo-add-button-text">Add</text>
</view>
</view>

<text class="todo-meta">
{{ remainingCount }} to do
<text class="todo-meta-dim"> · {{ completedCount }} completed</text>
</text>

<text v-if="todos.length === 0" class="todo-empty">No tasks. Add one above.</text>

<view v-else class="todo-list">
<view
v-for="todo in todos"
:key="todo.id"
class="todo-item"
:class="{ 'todo-item--completed': todo.completed }"
>
<view class="todo-check" :bindtap="() => handleToggleTodo(todo.id)">
<text class="todo-check-icon">{{ todo.completed ? '✓' : '' }}</text>
</view>

<text
class="todo-text"
:style="{
textDecoration: todo.completed ? 'line-through' : 'none',
color: todo.completed ? 'rgba(255, 255, 255, 0.6)' : '#fff',
}"
>
{{ todo.text }}
</text>

<view class="todo-delete-button" :bindtap="() => handleDeleteTodo(todo.id)">
<text class="todo-delete-text">Delete</text>
</view>
</view>
</view>

<view v-if="completedCount > 0" class="todo-footer">
<view class="todo-clear-completed" :bindtap="handleClearCompleted">
<text class="todo-clear-completed-text">Remove completed</text>
</view>
</view>
</view>
</template>

App.css

:root { background-color: #000; --w: 720rpx; --txt: #fff; --s1: rgba(255, 255, 255, 0.06); --s2: rgba(255, 255, 255, 0.08); --b1: rgba(255, 255, 255, 0.12); --b2: rgba(255, 255, 255, 0.18); }
text { color: var(--txt); }

.todo-app {
min-height: 100vh;
width: 100%;
box-sizing: border-box;
padding: 90px 26rpx 26rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 18rpx;
}

.todo-header-row, .todo-composer, .todo-meta, .todo-empty, .todo-list, .todo-footer { width: 100%; max-width: var(--w); }
.todo-header-row, .todo-composer, .todo-item, .todo-add-button, .todo-delete-button, .todo-clear-completed, .todo-check { display: flex; flex-direction: row; align-items: center; box-sizing: border-box; }
.todo-add-button, .todo-delete-button, .todo-clear-completed, .todo-check { justify-content: center; }
.todo-add-button-text, .todo-delete-text, .todo-clear-completed-text { font-weight: 700; }

.todo-header { font-size: 40rpx; font-weight: 800; margin-bottom: 12rpx; }
.todo-header-row { justify-content: space-between; gap: 16rpx; }
.todo-composer { gap: 16rpx; }
.todo-input { flex: 1; height: 72rpx; padding: 0 20rpx; border-radius: 16rpx; background: var(--s2); border: 1px solid var(--b2); color: #fff; font-size: 26rpx; }
.todo-add-button { height: 72rpx; padding: 0 22rpx; border-radius: 16rpx; background: #1565c0; border: 1px solid var(--b1); }

.todo-meta { font-size: 24rpx; color: rgba(255, 255, 255, 0.8); }
.todo-meta-dim { color: rgba(255, 255, 255, 0.6); }
.todo-empty { font-size: 24rpx; color: rgba(255, 255, 255, 0.7); padding: 26rpx 0; }
.todo-list { display: flex; flex-direction: column; gap: 14rpx; }
.todo-item { width: 100%; gap: 16rpx; padding: 18rpx; border-radius: 18rpx; background: var(--s1); border: 1px solid var(--b1); }
.todo-check { width: 44rpx; height: 44rpx; border-radius: 12rpx; border: 1px solid rgba(255, 255, 255, 0.38); }
.todo-item--completed .todo-check { background: rgba(0, 200, 120, 0.25); border-color: rgba(0, 200, 120, 0.7); }
.todo-text { flex: 1; font-size: 26rpx; line-height: 1.3; }

.todo-delete-button { height: 56rpx; padding: 0 18rpx; border-radius: 14rpx; background: rgba(255, 100, 100, 0.15); border: 1px solid rgba(255, 100, 100, 0.35); }
.todo-footer { display: flex; justify-content: flex-start; margin-top: 4rpx; }
.todo-clear-completed { height: 64rpx; padding: 0 22rpx; border-radius: 18rpx; background: var(--s1); border: 1px solid var(--b2); }

.todo-add-button-text { font-size: 26rpx; }
.todo-delete-text { font-size: 22rpx; color: rgba(255, 200, 200, 0.95); }
.todo-clear-completed-text { font-size: 24rpx; color: rgba(255, 255, 255, 0.85); }
.todo-check-icon { font-size: 30rpx; color: rgba(255, 255, 255, 0.95); }

Key Differences Compared to Vue

  • Core Vue features (such as ref, computed, etc.) can now be imported directly from vue-lynx.
  • Unlike the web, standard HTML elements are not used (like react-native): <div> → <view> <span> → <text>
  • Event handling works differently: @click → :bindtap

Current Limitations

Being still in the pre-alpha phase, some Vue features are not yet supported, including:

  • KeepAlive
  • Teleport
  • Transition
  • v-bind

Final Comment

Vue-Lynx clearly represents a big step toward native mobile development for Vue developers. At the moment, the Lynx ecosystem is still limited and many features are under development, but the potential is huge. It will be interesting to follow the updates and see how this technology evolves over the next years.

Github :

https://github.com/micbianchi97/todo-vue-lynx

Thanks for reading! 😊

--

--