
初探 Nuxt UI v4
Nuxt UI v4 熱騰騰釋出囉!੭ ˙ᗜ˙ )੭
期待很久了,終於等到這一天,馬上來研究看看 ლ(´∀`ლ)
研究目標
雖然 Quasar 目前滿足大部分情境,但是 Quasar 更新放緩,作者心力分散至 Quasar CLI、SSR 開發,許多 UI 相關 issue 排不進排程,隨著需求與限制增加,可能會遇到瓶頸
而我目前僅用到 UI,至於為甚麼只用到 UI,以下是我的考量:
Electron、Capacitor 整合:
Quasar 雖然可以整合 Electron、Capacitor,但是截至目前的開發經驗,依照需求直接使用 Electron、Capacitor 會更好
因為 Quasar CLI 二次封裝後,不但更難除錯(很難知道是 CLI 有問題、還是框架有問題),且封裝後許多設定參數脫離原框架的文件,很難找到參考資料
SSR:
我比較信任更成熟的 Nuxt,畢竟 SSR、SSG 是一個很複雜的問題,Nuxt 團隊有更多資源與人力可以解決問題
Nuxt UI 除了有官方背景外,還深度整合 Nuxt,潛力無窮,順便解決以下問題:
- Quasar utils class 衝突問題(因為統一使用 Tailwind CSS 了)
- Design Token 分散問題(雖然這個已經靠 UnoCSS 解決)
- SSR 整合問題(Quasar + Nuxt 有時候會遇到一些水合小毛病)
背景資料
基於 Reka UI、Tailwind CSS 與 Tailwind Variants 開發,內部還整合了 VueUse、多個 unplugin 套件與資源,滿滿自家人的概念 (◐‿◑)
還有以下特點:
- MIT 授權
- a11y 完善
- VueUse 深度整合
- Nuxt SSR 整合
- Vue i18n 整合
- 元件超多
重點筆記
元件特點
以下筆記主要相對於 Quasar UI 出發。
ui prop
<template>
<u-button
trailing-icon="i-lucide-chevron-right"
size="md"
color="neutral"
variant="outline"
:ui="{
trailingIcon: 'rotate-90 size-3',
}"
>
Button
</u-button>
</template>可以注入 class 至元件指定層級 DOM 中,這個功能與 PrimeVue 的 passthrough 類似
雖然實務開發上不是甚麼大問題,不過我自己有一個小疑問:基於 BEM 規範的 UI 套件若要魔改樣式,因為命名結構明確,可以很容易指向目標,但這類基於 Tailwind CSS 的 UI 套件若 ui 參數沒有開到,要指向特定的 DOM 是不是就需要相對複雜的 CSS 選擇器?
Form
欄位驗證可以直接用 Zod(支援 Standard Schema 的驗證器都可以)
配合 ts-rest 更簡單惹!(ゝ∀・)b
Icon
內部使用 unplugin-icons,超多 icon 可以用!੭ ˙ᗜ˙ )੭
會自動對 icon 進行按需引入(On-demand),不會載一大包字型檔案

SSR、SSG 友善,不會有 FOUC 問題
Dashboard
一系列 Dashboard 元件,可以輕鬆拼出專業後台
Page
一系列 Page 元件,幾乎內建 Landing 頁面會用到的基礎元件了
綜合 Dashboard 元件,Nuxt UI 用來開發前後台都很適合
Image
沒有額外封裝 img 元件,不過有提供一個 ColorModeImage,自動依照明暗模式切換圖片
若有安裝 @nuxt/image 會在內部自動依賴 NuxtImg 元件
Table
基於 TanStack Table 開發功能強大
cell 內容同時支援 slot 與 h function 更彈性
h function 可以用來建立 vDOM,與 JSX 概念類似,適合用來渲染動態內容。
先前有一篇文章有提到其應用:Vue h function 讓 Quasar Dialog 更容易重複使用 :::
如果內容只是簡單的調整樣式、排版,或簡單元件(例如:Badge、Tag) 等等,可以直接使用 h function:
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import { h, resolveComponent } from 'vue'
const UBadge = resolveComponent('UBadge')
const data = [
// ...
]
const columns: TableColumn[] = [
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status'))
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
</script>
<template>
<u-table :data="data" :columns="columns" class="flex-1" />
</template>內容更內聚,不需要額外建立 slot 元件
但如果是較複雜的內容,還是使用 slot 更好,除了支援度更好外,template 編譯性能也比較好:
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
const data = [
// ...
]
const columns: TableColumn[] = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'name',
header: 'Name'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'role',
header: 'Role'
},
{
id: 'action'
}
]
</script>
<template>
<u-table :data="data" :columns="columns" class="flex-1">
<template #name-cell="{ row }">
<div class="flex items-center gap-3">
<u-avatar
:src="`https://i.pravatar.cc/120?img=${row.original.id}`"
size="lg"
:alt="`${row.original.name} avatar`"
/>
<div>
<p class="font-medium text-highlighted">
{{ row.original.name }}
</p>
<p>
{{ row.original.position }}
</p>
</div>
</div>
</template>
</u-table>
</template>Modal
Quasar 是 Dialog,在 Nuxt UI 中叫做 Modal
開啟方式不同於 Quasar,可以不用傳遞額外變數控制開啟(有需要的話還是可以綁定變數)
在 default slot 放入按鈕,即可以開啟,內容則寫在 content slot 中
<template>
<u-modal>
<u-button label="Open" color="neutral" variant="subtle" />
<template #content>
<placeholder class="h-48 m-4" />
</template>
</u-modal>
</template>Design System
承襲 Tailwind CSS v4 設計,設定變成基於 CSS 變數
Figma Kit
所有的顏色、樣式幾乎都已用 token 定義,與 CSS 名稱一致,可以直接複製貼上,元件也有定義相關變體,可以直接切換
這個資源很讚,這樣 UI 或 PM 要出設計稿或草稿時,都可以直接使用、不用重畫,更不容易出現認知落差。(・∀・)9
Template
可以從模板借鑒抄襲內容過來用 (≖‿ゝ≖)✧
AI 整合
MCP Server
可以與 AI 工具整合,甚至 ChatGPT 也可以用
安裝完成後使用指令(輸入 /)會出現以下提示:

find_component_for_usecase: Find the best component for your specific use給它功能情境,會幫你從 Nuxt UI 元件庫中挑最適合的元件(或元件組合),並說明為什麼、需要搭配哪些 props/slots/events、還會附上簡短使用片段。
implement_component_with_props: Generate complete component implementation with proper 當你已經決定「要用哪個 Nuxt UI 元件」與「要怎麼配置 props/事件/slots」,這個 prompt 會直接產出完整可用的 SFC 實作(含<script setup>型別、狀態、事件、slot 範例、a11y 小細節)。setup_project_with_template: Get guided setup instructions for project引導式教學,教你怎麼設定環境
LLMs.txt
可以在詢問時提及 LLMs.txt URL,簡單來說就是一個適合 AI 閱讀文件,可以讓 AI 回應更精確
以下是 GPT-5 的問答結果:
Table 提問:

Modal 提問:

配合 MCP 與 LLMs.txt 可以有效降低 Nuxt UI 入門門檻 ✧⁑。٩(ˊᗜˋ*)و✧⁕。
問題討論
沒有 Directives、Utils
Quasar 內建有許多方便的 directives 與 utils,不過 VueUse 有提供許多 directives 可以彌補這個缺點。
顯示引入元件
底層是 unplugin-auto-import 與 unplugin-vue-components,印象中可以這樣導入:

不知道為甚麼不行,直接從 @nuxt/ui 引入也不行
不過還好可以根據目錄引入元件
import UModal from '@nuxt/ui/components/Modal.vue'ui prop 與 DOM 結構
ui prop 可以直接將 class 放到對應的 DOM 結構中,不過文件沒有明確說明 ui prop 具體對應哪一個 DOM?
PrimeVue 會在 DOM 之 attr 加入 data-pc-section 屬性,方便尋找。
雖然大部分元件的結構很單純,不難猜出位置,不過有些元件的 ui prop 有點多,例如 CommandPalette:
{
root?: ClassNameValue;
input?: ClassNameValue;
close?: ClassNameValue;
back?: ClassNameValue;
content?: ClassNameValue;
footer?: ClassNameValue;
viewport?: ClassNameValue;
group?: ClassNameValue;
empty?: ClassNameValue;
label?: ClassNameValue;
item?: ClassNameValue;
itemLeadingIcon?: ClassNameValue;
itemLeadingAvatar?: ClassNameValue;
itemLeadingAvatarSize?: ClassNameValue;
itemLeadingChip?: ClassNameValue;
itemLeadingChipSize?: ClassNameValue;
itemTrailing?: ClassNameValue;
itemTrailingIcon?: ClassNameValue;
itemTrailingHighlightedIcon?: ClassNameValue;
itemTrailingKbds?: ClassNameValue;
itemTrailingKbdsSize?: ClassNameValue;
itemLabel?: ClassNameValue;
itemLabelBase?: ClassNameValue;
itemLabelPrefix?: ClassNameValue;
itemLabelSuffix?: ClassNameValue;
}目前看來好像只能直接試試看了。(´・ω・`)
Table 尚未支援 Virtual Scroll~
v4.1.0 後支援
使用 virtualize 即可啟用虛擬滾動 ੭ ˙ᗜ˙ )੭
底層的 TanStack Table 可以使用 @tanstack/vue-virtual 支援虛擬滾動,不過 Nuxt UI 的 Table 尚未支援,只能等待更新了
Table 原始碼沒有複雜依賴,真的需要也是可以考慮自行加上 @tanstack/vue-virtual 封裝
useToast 位置不能單一設定
TIP
感謝狐狸大大的補充 (*´∀`)~♥
Toast 位置只能全域設定,不能像 Quasar 那樣每個 Toast 可以獨立設定位置
官方說 Headless UI 和 Radix Vue 都不支援單一設定,目前沒有計畫支援 (´・ω・`)
不過實際上影響不大,沒事不會一直換位置 XD
useOverlay
文件預期用來開啟 Modal 元件,這樣的缺點是需建立一個被開啟元件的 Modal 變體,使用上沒那麼方便,所以同先前 Quasar Dialog 的封裝,我也做了一個 openUsingModal function。
import type { Component } from 'vue'
import type { ComponentProps, ComponentSlots } from 'vue-component-type-helpers'
import UModal from '@nuxt/ui/components/Modal.vue'
import { h } from 'vue'
type ModelProps = ComponentProps<typeof UModal>
/** 將 Vue SFC 元件包装為 UModal,可以更簡單配合 useOverlay 使用
*
* @param component Vue SFC 元件
* @param props SFC 內所有參數,包含 class、style、event 等等
* @param modalProps UModal 原本參數
* @param slots SFC 插槽
*/
function wrapWithModal<Comp extends Component>(
component: Comp,
props?: ComponentProps<Comp>,
modalProps?: ModelProps,
slots?: ComponentSlots<Comp>,
) {
return h(UModal, {
class: 'w-auto!',
...modalProps,
}, {
body: () => h(component, props ?? {}, slots ?? {}),
})
}
/** 使用 useOverlay + UModal 開啟元件
*
* @param component Vue SFC 元件
* @param props SFC 內所有參數,包含 class、style、event 等等
* @param modalProps UModal 原本參數
* @param slots SFC 插槽
*
* @example
* ```typescript
* const dialog = openUsingModal(BrandEditStatusForm, {
* data,
* onSuccess() {
* dialog.hide();
* handleEditSuccess();
* },
* });
* ```
*/
export function openUsingModal<Comp extends Component>(
component: Comp,
props?: ComponentProps<Comp>,
modalProps?: ModelProps,
slots?: ComponentSlots<Comp>,
) {
const overlay = useOverlay()
const modal = overlay.create(
wrapWithModal(
component,
props,
{
close: false,
ui: { body: 'p-0!' },
...modalProps,
},
slots,
),
{ props },
)
modal.open()
return modal
}沒有簡易所見及所得編輯器
需要額外引入,目前候選為:
- Tiptap:MIT
- TinyMCE:GPLv2+
TinyMCE 功能強大,不過 GPLv2+ 授權不太適合商業專案
自定義 Form 欄位
Quasar 可以使用 useFormChild 或 Field 元件包裝,自定義 Form 內的驗證欄位
研究了一下,其實有提供 useFormField,只是不知道為甚麼文件網頁沒有出現,但文件原始碼有此 md 檔。
沒有 Time Picker
官方說沒有計畫。...(›´ω`‹ )
欸不是,都有 Calendar 了,做個 Time Picker 不過份吧!ლ(´口`ლ)
只能自己拼一個或是用 input 將 type 設為 time 版本
不過原生的 time input 在 window 的 chrome 上有點醜就是了 (。-`ω´-)
<UInput
v-model="data"
type="time"
/>
總結 🐟
- Nuxt UI v4 元件多、功能強大且免費,潛力無窮
- Nuxt 深度整合,SSR、SSG 友善
- VueUse、Vue i18n、unplugin 等等 Vue 生態系深度整合,可以省去不少設定時間
- AI 整合(MCP、LLMs.txt),降低入門門檻
以上就是這次初探,其他細節或坑就要等到實務開發時再慢慢研究了 ◝( •ω• )◟
Nuxt UI v4 整體感覺很不錯,不過有一個小小遺憾,就是不能使用 UnoCSS ( ˘・з・)
雖然有基於 UnoCSS 的 Una UI,不過考量生態系問題,還是先用 Nuxt UI 比較實際