About nuxt-unified-ui
nuxt-unified-ui is a skill that guides agents building Nuxt applications with shared UI, form, data, and resource APIs. Its apply workflow checks changed files individually and handles cross-file follow-ups.
Key features
- 01Schema-driven forms and custom elements
- 02Dialogs, toasts, tables, and UI components
- 03Typed resource and singleton controllers
- 04File placement and layer visibility rules
- 05Per-file apply checks and follow-ups
Use cases
- 01Building forms, tables, or dialogs in a Nuxt app
- 02Adding standard resource routes and dashboards
- 03Applying project structure and code-style rules to changed files
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ yooneskh/ nuxt-unified-ui --skill nuxt-unified-uiAfter installing, type/nuxt-unified-uiin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/apply.mdcode-style.mddata-fetching.mddialogs.mdforms.mdlayer-setup.mdpages.mdradashi.mdresources.mdtables.mdtoast-and-ui.mdDetails
From the project READMEnuxt-unified-ui
The main agent decides what code exists, where it lives, and which APIs it calls. When the work is done, the apply pipeline runs one subagent per file to check the skill's rules and apply the code style; it is the only place formatting rules apply. The main agent does not load references/code-style.md.
Finishing work: /nuxt-unified-ui apply
Whenever you finish implementing work that added or edited a .vue, .js, or .ts file, run /nuxt-unified-ui apply before your final reply: follow references/apply.md. Do the same when the skill is invoked with the argument apply, or the user asks to apply nuxt-unified-ui across the project or the current branch. Invoked without an argument, the skill only loads its guidance for the task at hand.
On dev, main, or master it processes uncommitted .vue, .js, and .ts files; when that branch is clean, or the project is not a Git worktree, it processes every eligible file. On any other branch, it processes only the files that branch added or changed relative to its base branch. One subagent per file, launched using a cheap fast model, applies the logical, structural, and code-style rules, then the main agent carries out the cross-file follow-ups.
APIs
Layer APIs come from this layer and are auto-imported across the app and every layer. Companion APIs come from the companion unified layers, which real projects always include next to this one. When an API is not listed here, check the source before using it.
| API | Use it to | From | Reference |
|---|---|---|---|
un-form / useForm | Render a schema-driven form; useForm returns { form, formTag } | layer | forms.md |
registerFormExtraElement | Add a custom form element identifier (call from a Nuxt plugin) | layer | forms.md |
useFormExtraElements | Read the registered custom elements (add them with registerFormExtraElement) | layer | forms.md |
un-card | Card with icon / title / subtitle header, body, and action rows | layer | toast-and-ui.md |
un-typography | Icon + title + subtitle + text block | layer | toast-and-ui.md |
un-spinner | Show a loading spinner | layer | toast-and-ui.md |
un-table | Show one page of rows with row actions and a pagination footer | layer | tables.md |
launchFormPickerDialog | Ask for form input in a modal; submit logic goes in submitButton.onClick | layer | dialogs.md |
launchChoicePickerDialog | Confirm or choose in a modal; logic goes in each button's onClick | layer | dialogs.md |
launchDialog | Open any dialog component; resolves with its close payload | layer | dialogs.md |
toast, toastSuccess, toastError, toastWarning, toastInfo | Show a toast (typed helpers set icon and color) | layer | toast-and-ui.md |
smartMatch | Test a function, mongo-style filter, or truthy value against an object | layer | forms.md |
unSet | Set a nested path on an object, creating missing levels (mutates) | layer | — |
formatDate / parseDate | Format and parse dates (@formkit/tempo); defaults come from app.config dateFormat | layer | layer-setup.md |
isSlotFilled | Check whether a slot has content | layer | — |
pathRelativeToBase | Resolve a path against a file URL; also exported from the package for nuxt.config | layer | layer-setup.md |
makeConfetti | Fire confetti: template picks a built-in effect (parade, on-top / on-left / on-right / on-bottom, on-frame, split-on-top, on-curtain), amount sets particles per burst, other args go to canvas-confetti | layer | — |
radXxx | Any radashi function (radGet, radPick, …), in app and server | layer | radashi.md |
ufetch | Make a one-off API request (submit, delete, click) | companion | data-fetching.md |
useUFetch | Load reactive page data (data, pending, refresh) | companion | data-fetching.md |
parseSchema | Compile a resource schema DSL into { schema, type, inferred } | companion | resources.md |
createUnifiedResourceController / UnifiedResourceController | Create the typed Mongo controller (dbo) for a resource | companion | resources.md |
createUnifiedSingletonController / UnifiedSingletonController | Create the typed controller for one fixed singleton row (schema, retrieve, update) | companion | resources.md |
app / UnifiedAppRegistry | Global typed registry of resources (app.users.dbo) | companion | resources.md |
handleResourceSchema, handleResourceList, handleResourceCreate, handleResourceCount, handleResourceRetrieve, handleResourceUpdate, handleResourceDelete | Implement the standard REST routes of a resource | companion | resources.md |
handleSingletonSchema, handleSingletonRetrieve, handleSingletonUpdate | Implement the singleton routes: schema, retrieve, and update of the one row | companion | resources.md |
useResourceName | Derive a resource's API path and display titles from its name | companion | resources.md |
useResourceMeta | Map a resource schema to form fields and table columns | companion | resources.md |
<resource-manager> | Full CRUD dashboard card for a resource; exposes refreshResources() | companion | resources.md |
<singleton-manager> | Form card for one singleton row; exposes refreshSingleton() | companion | resources.md |
<resource-explorer-table> | Filter bar + sortable headers + un-table for a resource | companion | tables.md |
assertBody | Validate a request body against a schema in a server route | companion | — |
assertRateLimit | Rate-limit a server route | companion | — |
createUnauthenticatedError | Throw a 401 from a server route | companion | — |
generateUuid | Generate a UUID | companion | — |
useToken | Read or set the auth token | companion | — |
is-authenticated middleware; dashboard / empty layouts | Protect pages; dashboard and full-bleed page layouts | companion | pages.md |
useJsonld | Emit JSON-LD on public pages, only when nuxt-jsonld is installed | nuxt-jsonld | pages.md |
File structure
The main agent decides where a file lives and which one job it has, and does it while implementing. Apply subagents only report misplaced files; they never move files or update callers.
Each .vue, .js, and .ts file has one responsibility. When a file would have two independent jobs, create two files.
Nuxt auto-imports app/components/ and app/utils/ across every layer. app/composables/ is public in the same way. A file that must stay inside one layer does not go in those directories.
| Role | Directory | Visibility |
|---|---|---|
| Private Vue component | app/atoms/ | This layer only |
| Private function, composable, or helper | app/libs/ | This layer only |
| Public Vue component | app/components/ | Whole app, auto-imported |
| Public util | app/utils/ | Whole app, auto-imported |
Generate private first. New components start in app/atoms/. New functions and similar helpers start in app/libs/. Promote a file to app/components/ or app/utils/ only when another layer needs it. After a promote, update every caller and remove the relative import; public modules are auto-imported.
Import atoms and libs with relative paths only (../atoms/foo.vue, ../libs/bar). Never use ~/, @/, #layers/, or another alias. Never import another layer's atoms or libs; promote that file first, then use the public auto-import.
Do not register atoms or libs with Nuxt components or imports config.
Before writing
Read the reference that matches the task before creating or editing files. The details stay in that reference.
| Task | Read first |
|---|---|
| Layer installation and required CSS | references/layer-setup.md |
| Forms, field schema, built-in and custom elements | references/forms.md |
| Dialogs | references/dialogs.md |
Toasts, un-* UI, button / badge / icon conventions | references/toast-and-ui.md |
| Tables | references/tables.md |
| Pages and routing | references/pages.md |
ufetch / useUFetch | references/data-fetching.md |
| Unified resources | references/resources.md |
Radashi radXxx exports | references/radashi.md |
Decisions the main agent owns
Make these choices while implementing. The apply pipeline checks them again afterward.
- Prefer the APIs above over hand-rolled equivalents (raw
$fetch, hand-builtu-modalflows, directradashiimports). - A new resource includes its server plugin, the full REST route set, and a dashboard nav entry or a custom
<resource-manager>page. - Use
ufetchfor a one-off request. UseuseUFetchfor reactive page data. - Every page has an explicit
definePageMeta.nameand sets SEO withuseHead(title) anduseSeoMeta(description). - Buttons, badges, and icons follow the component conventions in references/toast-and-ui.md. Read them before writing any button.
- When splitting or promoting a file, update its callers in the same task.
- Installing the layer includes the required host
assets/css/main.cssand thepathRelativeToBaseCSS entry innuxt.config.
i18n
i18n is optional. Conform to the project's current state.
- If the project does not translate its own strings (literal text, no
$t, no locale files of its own), keep writing literal strings. Do not introduce$t, locale files, or i18n config unless the user decides to integrate i18n. - If the project already uses i18n in another layout, follow that layout.
- The rules below apply once the project has decided to integrate i18n, or already follows them. This layer's own
$tusage does not count as a decision for the project. - Examples in the references often use English literals for brevity. In an i18n project those strings are
$t('...')keys.
When i18n is integrated
- Every user-facing string goes through
$t, in templates and in script.$tis available in both without callinguseI18n(). - Global i18n settings —
strategy,defaultLocale, and the fulllocaleslist (codes, names, languages) — go in thenuxt.config.tsof the aarde layer: the project's base app layer that extendsnuxt-unified-ui(for examplelayers/100-aarde/). If the project has no aarde layer, use the layer that extendsnuxt-unified-ui. - Every other layer (feature) keeps its own locale files in its own
i18n/locales/<code>.json. In its ownnuxt.config.tsit declares only those files —i18n: { locales: [{ code, file }] }— and no other i18n settings.@nuxtjs/i18nloads a layer's locale files only when that layer lists them, then merges the same locale from every layer into one message tree. - The layer that owns a file is the nearest directory above it with a
nuxt.config. Its strings go in that layer's locale files only — never in another layer's or the host's. - Each layer's locale file has exactly one top-level key: the layer's namespace, such as
patientsorbilling. Every key nests under it ($t('patients.single.title')), so merged trees never clash. - Add every new key to each locale that layer declares.
- This layer owns two top-level keys:
unfor its components andcommonfor shared labels (common.submit,common.cancel,common.close, …). Reusecommon.*instead of duplicating those labels, but never add keys tounorcommonfrom another layer.
Aarde layer nuxt.config.ts (fragment):
i18n: {
strategy: 'no_prefix',
defaultLocale: 'en',
locales: [
{
code: 'en',
name: 'English',
file: 'en.json',
},
{
code: 'de',
name: 'Deutsch',
file: 'de.json',
},
],
},
Feature layer nuxt.config.ts (fragment) and its i18n/locales/en.json:
i18n: {
locales: [
{
code: 'en',
file: 'en.json',
},
{
code: 'de',
file: 'de.json',
},
],
},
{
"patients": {
"single": {
"title": "Patient"
}
}
}