All skills
onmax avatar

/nuxt-ui

@de09c7b official
by Maxonmax/nuxt-skills715 stars
38

Build UIs with @nuxt/ui v4 — 125+ accessible Vue components with Tailwind CSS theming. Use when creating interfaces, customizing themes to match a brand, building forms, or composing layouts like dashboards, docs sites, and chat interfaces.

Use this Skill: https://skilld.dev/gh/onmax/nuxt-skills/nuxt-ui

This session only. Nothing lands on disk.

referencesrecipesdata-tables.md

≈1.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Data Tables

Complete patterns for displaying and managing tabular data.

Basic table

<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'

const data = ref([
  { name: 'Alice', email: 'alice@example.com', role: 'Admin' },
  { name: 'Bob', email: 'bob@example.com', role: 'Editor' }
])

const columns: TableColumn<typeof data.value[number]>[] = [{
  accessorKey: 'name',
  header: 'Name'
}, {
  accessorKey: 'email',
  header: 'Email'
}, {
  accessorKey: 'role',
  header: 'Role'
}]
</script>

<template>
  <UTable :data="data" :columns="columns" />
</template>

With search and filters (dashboard)

<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'

const search = ref('')
const roleFilter = ref('All')

const rows = ref([
  { name: 'Alice', email: 'alice@example.com', role: 'Admin', status: 'Active' },
  { name: 'Bob', email: 'bob@example.com', role: 'Editor', status: 'Inactive' }
])

const columns: TableColumn[] = [
  { accessorKey: 'name', header: 'Name' },
  { accessorKey: 'email', header: 'Email' },
  { accessorKey: 'role', header: 'Role' },
  { accessorKey: 'status', header: 'Status' },
  { id: 'actions' }
]

const filteredRows = computed(() => {
  return rows.value.filter(row => {
    const matchesSearch = !search.value || row.name.toLowerCase().includes(search.value.toLowerCase())
    const matchesRole = roleFilter.value === 'All' || row.role === roleFilter.value
    return matchesSearch && matchesRole
  })
})
</script>

<template>
  <UDashboardPanel>
    <template #header>
      <UDashboardNavbar title="Users" />

      <UDashboardToolbar>
        <template #left>
          <UInput v-model="search" icon="i-lucide-search" placeholder="Search users..." />
        </template>
        <template #right>
          <USelect v-model="roleFilter" :items="['All', 'Admin', 'Editor', 'Viewer']" />
        </template>
      </UDashboardToolbar>
    </template>

    <template #body>
      <UTable :data="filteredRows" :columns="columns">
        <template #status-cell="{ row }">
          <UBadge :color="row.original.status === 'Active' ? 'success' : 'neutral'" :label="row.original.status" variant="subtle" />
        </template>

        <template #actions-cell="{ row }">
          <UDropdownMenu
            :items="[
              [{ label: 'Edit', icon: 'i-lucide-pencil', onSelect: () => edit(row.original) }],
              [{ label: 'Delete', icon: 'i-lucide-trash', color: 'error', onSelect: () => remove(row.original) }]
            ]"
          >
            <UButton icon="i-lucide-ellipsis" color="neutral" variant="ghost" />
          </UDropdownMenu>
        </template>
      </UTable>
    </template>
  </UDashboardPanel>
</template>

With row selection

Row selection uses TanStack Table's rowSelection state — a Record<string, boolean> keyed by row index.

<script setup lang="ts">
const table = useTemplateRef('table')
const rowSelection = ref<Record<string, boolean>>({})
</script>

<template>
  <UTable ref="table" v-model:row-selection="rowSelection" :data="data" :columns="columns" />

  <div class="px-4 py-3.5 text-sm text-muted">
    {{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
    {{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
  </div>
</template>

Add a checkbox column using the h function. Use tri-state modelValue (true, false, or 'indeterminate') for the "select all" header:

import { h } from 'vue'

const UCheckbox = resolveComponent('UCheckbox')

const columns: TableColumn[] = [{
  id: 'select',
  header: ({ table }) => h(UCheckbox, {
    'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
    'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
    'aria-label': 'Select all'
  }),
  cell: ({ row }) => h(UCheckbox, {
    'modelValue': row.getIsSelected(),
    'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
    'aria-label': 'Select row'
  })
},
// ... other columns
]

With pagination

Use v-model:pagination on UTable with TanStack's getPaginationRowModel, then wire UPagination to the table API. UPagination's total is total items (not pages) — it calculates page count from total / items-per-page.

<script setup lang="ts">
import { getPaginationRowModel } from '@tanstack/vue-table'

const table = useTemplateRef('table')

const pagination = ref({
  pageIndex: 0,
  pageSize: 5
})
</script>

<template>
  <UTable
    ref="table"
    v-model:pagination="pagination"
    :data="data"
    :columns="columns"
    :pagination-options="{ getPaginationRowModel: getPaginationRowModel() }"
  />

  <div class="flex justify-end p-4">
    <UPagination
      :page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
      :items-per-page="table?.tableApi?.getState().pagination.pageSize"
      :total="table?.tableApi?.getFilteredRowModel().rows.length"
      @update:page="(p) => table?.tableApi?.setPageIndex(p - 1)"
    />
  </div>
</template>

With async data (Nuxt)

Use status === 'pending' || status === 'idle' for loading state — idle covers the initial render before useLazyFetch starts.

<script setup lang="ts">
const { data, status } = useLazyFetch('/api/users', { server: false })
</script>

<template>
  <UTable :data="data" :columns="columns" :loading="status === 'pending' || status === 'idle'" />
</template>

For server-side pagination:

<script setup lang="ts">
const page = ref(1)

const { data, status } = await useAsyncData(
  'users',
  () => $fetch('/api/users', { query: { page: page.value } }),
  { watch: [page] }
)
</script>

<template>
  <UTable :data="data?.items" :columns="columns" :loading="status === 'pending'" />

  <div class="flex justify-end p-4">
    <UPagination v-model="page" :total="data?.total" :items-per-page="data?.pageSize" />
  </div>
</template>

Tips

  • Table is built on TanStack Table v8 — columns use ColumnDef format with accessorKey, header, cell
  • Use #<column>-cell and #<column>-header template slots to customize rendering with Vue templates
  • Alternatively, use the h function inside header and cell column properties for inline rendering
  • Row data in slots is accessed via row.original (not row directly)
  • Use v-model:row-selection for selection, v-model:sorting for sort state
  • Wrap tables in UDashboardPanel with #header toolbar for the dashboard pattern
  • For empty states, use the #empty slot

Source: SKILL.md on GitHub

No alerts12d5 checks · Risk SAFE
  • Gen Agent Trust Hub12d

    The skill provides comprehensive documentation and implementation guidance for the Nuxt UI v4 component library. It includes a maintenance script for synchronizing documentation with the official repository. No malicious patterns or security risks were identified.

  • Socket12d

    No alerts

  • Snyk12d

    Risk: LOW · No issues

  • Runlayer7mo

    129 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at de09c7b. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated last week

README badge

README badge for onmax/nuxt-skills/nuxt-ui

Builds styled UI for Nuxt 4+ using @nuxt/ui v4 components with Tailwind CSS and Tailwind Variants. Covers forms with validation, data tables, modals, and theme customization through semantic colors and CSS variables.

Generated from the current SKILL.md.

Does this skill cover Reka UI headless primitives?
No. This skill focuses on @nuxt/ui v4 components (styled, opinionated). For Reka UI primitives, accessibility patterns, and asChild behavior, use the reka-ui skill instead.
What form validation libraries does this support?
Zod and Valibot. The skill includes guidance on useFormField and form component integration with both libraries.
Do I need UApp wrapper?
Yes, UApp is required to use Toast, Tooltip, and overlay components like Modal and Slideover.
Can I customize colors and theme?
Yes. The skill covers semantic colors, CSS variables, app.config.ts setup, and Tailwind Variants for type-safe styling.
What Nuxt and Vue versions does this require?
Vue 3 and Nuxt 4+. The current stable version is v4.4.0 (January 2026).

Generated from the current SKILL.md. These answers refresh after source changes.