ui-ux-consultant-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/ui-ux-consultant/SKILL.md +844 -0
- package/assets/ui-ux-consultant/references/accessibility.md +175 -0
- package/assets/ui-ux-consultant/references/alt-libraries.md +90 -0
- package/assets/ui-ux-consultant/references/animations.md +448 -0
- package/assets/ui-ux-consultant/references/catalog/colors.md +91 -0
- package/assets/ui-ux-consultant/references/catalog/fonts.md +363 -0
- package/assets/ui-ux-consultant/references/catalog/products.md +340 -0
- package/assets/ui-ux-consultant/references/catalog/styles.md +165 -0
- package/assets/ui-ux-consultant/references/components.md +1116 -0
- package/assets/ui-ux-consultant/references/patterns.md +600 -0
- package/assets/ui-ux-consultant/references/performance.md +198 -0
- package/assets/ui-ux-consultant/references/stacks/astro.md +382 -0
- package/assets/ui-ux-consultant/references/stacks/flutter.md +308 -0
- package/assets/ui-ux-consultant/references/stacks/html-tailwind.md +415 -0
- package/assets/ui-ux-consultant/references/stacks/jetpack-compose.md +333 -0
- package/assets/ui-ux-consultant/references/stacks/laravel.md +521 -0
- package/assets/ui-ux-consultant/references/stacks/nextjs.md +275 -0
- package/assets/ui-ux-consultant/references/stacks/nuxt-ui.md +384 -0
- package/assets/ui-ux-consultant/references/stacks/nuxtjs.md +264 -0
- package/assets/ui-ux-consultant/references/stacks/react-native.md +346 -0
- package/assets/ui-ux-consultant/references/stacks/react.md +268 -0
- package/assets/ui-ux-consultant/references/stacks/shadcn.md +485 -0
- package/assets/ui-ux-consultant/references/stacks/svelte.md +429 -0
- package/assets/ui-ux-consultant/references/stacks/swiftui.md +336 -0
- package/assets/ui-ux-consultant/references/stacks/threejs.md +366 -0
- package/assets/ui-ux-consultant/references/stacks/vue.md +272 -0
- package/assets/ui-ux-consultant/references/theming.md +701 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +130 -0
- package/package.json +51 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Next.js UI/UX Guidelines
|
|
2
|
+
|
|
3
|
+
## When to read this
|
|
4
|
+
Read this file when building with Next.js 14+ App Router. Covers server vs client components, data fetching patterns, routing, images, metadata, and performance for production Next.js apps.
|
|
5
|
+
|
|
6
|
+
## Recommended UI Libraries
|
|
7
|
+
|
|
8
|
+
| Library | Best for | Install |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| shadcn/ui | UI components | `npx shadcn@latest init` |
|
|
11
|
+
| next-auth v5 | Authentication | `npm install next-auth@beta` |
|
|
12
|
+
| TanStack Query | Client-side caching | `npm install @tanstack/react-query` |
|
|
13
|
+
| Prisma | Database ORM | `npm install prisma` |
|
|
14
|
+
| Zod | Schema validation | `npm install zod` |
|
|
15
|
+
| nuqs | URL search params state | `npm install nuqs` |
|
|
16
|
+
|
|
17
|
+
## Style Recommendations by App Type
|
|
18
|
+
|
|
19
|
+
- **SaaS product:** shadcn/ui + custom color tokens in `globals.css`
|
|
20
|
+
- **Marketing site:** shadcn/ui + Tailwind animations + dark mode
|
|
21
|
+
- **Dashboard/admin:** shadcn/ui data table + sidebar layout
|
|
22
|
+
- **E-commerce:** Custom product cards, cart drawer, optimistic updates
|
|
23
|
+
|
|
24
|
+
## Top UX Patterns
|
|
25
|
+
|
|
26
|
+
### 1. Streaming Loading UI with loading.tsx
|
|
27
|
+
```tsx
|
|
28
|
+
// app/dashboard/loading.tsx
|
|
29
|
+
export default function Loading() {
|
|
30
|
+
return <DashboardSkeleton />;
|
|
31
|
+
}
|
|
32
|
+
// Automatically shown while page.tsx's async data resolves
|
|
33
|
+
// No extra code needed in page.tsx
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Server Action with Error Handling
|
|
37
|
+
```tsx
|
|
38
|
+
'use server';
|
|
39
|
+
import { revalidatePath } from 'next/cache';
|
|
40
|
+
|
|
41
|
+
export async function createItem(formData: FormData) {
|
|
42
|
+
const title = formData.get('title') as string;
|
|
43
|
+
try {
|
|
44
|
+
await db.item.create({ data: { title } });
|
|
45
|
+
revalidatePath('/items');
|
|
46
|
+
return { success: true };
|
|
47
|
+
} catch {
|
|
48
|
+
return { error: 'Failed to create item' };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Client component usage
|
|
53
|
+
function ItemForm() {
|
|
54
|
+
const [state, formAction] = useFormState(createItem, null);
|
|
55
|
+
return (
|
|
56
|
+
<form action={formAction}>
|
|
57
|
+
<input name="title" required />
|
|
58
|
+
{state?.error && <p className="text-red-500">{state.error}</p>}
|
|
59
|
+
<button type="submit">Create</button>
|
|
60
|
+
</form>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. Route-Level Data Fetching (Server Component)
|
|
66
|
+
```tsx
|
|
67
|
+
// app/users/[id]/page.tsx
|
|
68
|
+
import { notFound } from 'next/navigation';
|
|
69
|
+
|
|
70
|
+
export default async function UserPage({ params }: { params: { id: string } }) {
|
|
71
|
+
const user = await db.user.findUnique({ where: { id: params.id } });
|
|
72
|
+
if (!user) notFound();
|
|
73
|
+
return <UserProfile user={user} />;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 4. Parallel Data Fetching
|
|
78
|
+
```tsx
|
|
79
|
+
// BAD: sequential — each awaits the previous (slow)
|
|
80
|
+
const user = await getUser(id);
|
|
81
|
+
const posts = await getPosts(id);
|
|
82
|
+
|
|
83
|
+
// GOOD: parallel — both fire simultaneously
|
|
84
|
+
const [user, posts] = await Promise.all([getUser(id), getPosts(id)]);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 5. Intercepting Route Modal Pattern
|
|
88
|
+
```
|
|
89
|
+
app/
|
|
90
|
+
photos/
|
|
91
|
+
page.tsx ← gallery page
|
|
92
|
+
[id]/
|
|
93
|
+
page.tsx ← full photo page (direct navigation)
|
|
94
|
+
@modal/
|
|
95
|
+
(.)photos/[id]/
|
|
96
|
+
page.tsx ← modal (shown when navigating from gallery)
|
|
97
|
+
layout.tsx ← renders both {children} and {modal}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 6. Dynamic Metadata for SEO
|
|
101
|
+
```tsx
|
|
102
|
+
// app/blog/[slug]/page.tsx
|
|
103
|
+
export async function generateMetadata({ params }: { params: { slug: string } }) {
|
|
104
|
+
const post = await getPost(params.slug);
|
|
105
|
+
return {
|
|
106
|
+
title: post.title,
|
|
107
|
+
description: post.excerpt,
|
|
108
|
+
openGraph: {
|
|
109
|
+
title: post.title,
|
|
110
|
+
images: [{ url: post.coverImage }],
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 7. Suspense Boundary for Slow Component
|
|
117
|
+
```tsx
|
|
118
|
+
// app/dashboard/page.tsx
|
|
119
|
+
import { Suspense } from 'react';
|
|
120
|
+
|
|
121
|
+
export default function DashboardPage() {
|
|
122
|
+
return (
|
|
123
|
+
<div>
|
|
124
|
+
<StaticHeader />
|
|
125
|
+
<Suspense fallback={<ChartSkeleton />}>
|
|
126
|
+
<SlowAnalyticsChart /> {/* streams in separately */}
|
|
127
|
+
</Suspense>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 8. Optimistic UI with useOptimistic (React 19 / Next.js 14+)
|
|
134
|
+
```tsx
|
|
135
|
+
'use client';
|
|
136
|
+
import { useOptimistic } from 'react';
|
|
137
|
+
|
|
138
|
+
function LikeButton({ post }) {
|
|
139
|
+
const [optimisticPost, addOptimisticLike] = useOptimistic(
|
|
140
|
+
post,
|
|
141
|
+
(state) => ({ ...state, likes: state.likes + 1 })
|
|
142
|
+
);
|
|
143
|
+
return (
|
|
144
|
+
<form action={async () => {
|
|
145
|
+
addOptimisticLike(null);
|
|
146
|
+
await likePost(post.id);
|
|
147
|
+
}}>
|
|
148
|
+
<button type="submit">Like ({optimisticPost.likes})</button>
|
|
149
|
+
</form>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 9. next/image with Priority and Sizes
|
|
155
|
+
```tsx
|
|
156
|
+
import Image from 'next/image';
|
|
157
|
+
|
|
158
|
+
// Hero image (LCP) — always use priority
|
|
159
|
+
<Image
|
|
160
|
+
src="/hero.jpg"
|
|
161
|
+
alt="Hero"
|
|
162
|
+
width={1200}
|
|
163
|
+
height={600}
|
|
164
|
+
priority
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
// Responsive fill image
|
|
168
|
+
<div className="relative h-64 w-full">
|
|
169
|
+
<Image
|
|
170
|
+
src={product.image}
|
|
171
|
+
alt={product.name}
|
|
172
|
+
fill
|
|
173
|
+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
|
174
|
+
className="object-cover"
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 10. URL State with nuqs
|
|
180
|
+
```tsx
|
|
181
|
+
'use client';
|
|
182
|
+
import { useQueryState } from 'nuqs';
|
|
183
|
+
|
|
184
|
+
function ProductFilter() {
|
|
185
|
+
const [category, setCategory] = useQueryState('category');
|
|
186
|
+
const [sort, setSort] = useQueryState('sort', { defaultValue: 'popular' });
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<div>
|
|
190
|
+
<select value={category ?? ''} onChange={e => setCategory(e.target.value)}>
|
|
191
|
+
<option value="">All</option>
|
|
192
|
+
<option value="shoes">Shoes</option>
|
|
193
|
+
</select>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
// URL: /products?category=shoes&sort=popular — shareable, bookmarkable
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Best Practices by Category
|
|
201
|
+
|
|
202
|
+
### Rendering
|
|
203
|
+
- Server Components by default — no `'use client'` unless you need browser APIs, event handlers, or hooks
|
|
204
|
+
- Push `'use client'` to leaf nodes — not layouts or high-level wrappers
|
|
205
|
+
- Use Suspense for progressive streaming of slow data sections
|
|
206
|
+
- Static generation (`cache: 'force-cache'`) where data does not change per request
|
|
207
|
+
|
|
208
|
+
### Data Fetching
|
|
209
|
+
- Fetch in Server Components — no `useEffect` for initial data load
|
|
210
|
+
- `cache: 'force-cache'` for static data, `cache: 'no-store'` for always-fresh data
|
|
211
|
+
- Server Actions for all mutations (create, update, delete)
|
|
212
|
+
- `revalidatePath` or `revalidateTag` for ISR cache invalidation after mutations
|
|
213
|
+
- `Promise.all` for all parallel fetches — never sequential `await` for independent calls
|
|
214
|
+
|
|
215
|
+
### Routing
|
|
216
|
+
- App Router as default for all new Next.js projects
|
|
217
|
+
- File-based routing: `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx` per segment
|
|
218
|
+
- Parallel routes (`@slot`) for split views and modal overlays
|
|
219
|
+
- Intercepting routes for modal patterns without losing deep-link capability
|
|
220
|
+
|
|
221
|
+
### Images
|
|
222
|
+
- `next/image` on every `<img>` — automatic format optimization and lazy loading
|
|
223
|
+
- `priority` prop on the LCP (largest contentful paint) image
|
|
224
|
+
- Explicit `width`/`height` or `fill` + `sizes` — prevents layout shift
|
|
225
|
+
- `next/font` for all custom fonts — zero layout shift, self-hosted automatically
|
|
226
|
+
|
|
227
|
+
### Performance
|
|
228
|
+
- `next/dynamic` for client-heavy third-party components (maps, editors, charts)
|
|
229
|
+
- Bundle analyzer: `ANALYZE=true npm run build` with `@next/bundle-analyzer`
|
|
230
|
+
- Avoid large `'use client'` subtrees — keep server boundary as low as possible
|
|
231
|
+
- Cache expensive DB queries with `unstable_cache` or React `cache()`
|
|
232
|
+
|
|
233
|
+
### Forms
|
|
234
|
+
- Server Actions as form `action` prop — no API route needed
|
|
235
|
+
- `useFormState` for server action response feedback
|
|
236
|
+
- `useFormStatus` for pending state on submit button
|
|
237
|
+
- Zod for schema validation in Server Actions before DB writes
|
|
238
|
+
|
|
239
|
+
### Accessibility
|
|
240
|
+
- Semantic HTML in all Server Components
|
|
241
|
+
- `loading.tsx` skeleton should mirror the layout of real content — reduces layout shift
|
|
242
|
+
- Focus management after navigation — Next.js handles this automatically
|
|
243
|
+
- Error pages (`error.tsx`) must have a recovery action (retry button)
|
|
244
|
+
|
|
245
|
+
### State
|
|
246
|
+
- URL state (`nuqs`) for filters, pagination, search — shareable and bookmarkable
|
|
247
|
+
- `useState`/`useReducer` for ephemeral UI state (modals, toggles)
|
|
248
|
+
- TanStack Query for client-side caching of server data after initial load
|
|
249
|
+
- Avoid `localStorage` for critical state — use cookies or DB for SSR compatibility
|
|
250
|
+
|
|
251
|
+
## Common Anti-Patterns
|
|
252
|
+
|
|
253
|
+
1. `'use client'` on a layout or high-level wrapper — makes the entire subtree client-side, losing all Server Component benefits
|
|
254
|
+
2. `useEffect` for data that could be fetched in a Server Component — unnecessary client waterfall
|
|
255
|
+
3. Fetching in client components without caching — re-fetches on every mount
|
|
256
|
+
4. Missing `loading.tsx` — no streaming feedback during server data loading
|
|
257
|
+
5. `<img>` instead of `next/image` — no optimization, causes layout shift
|
|
258
|
+
6. Sequential `await` for independent data fetches — use `Promise.all`
|
|
259
|
+
7. No `generateMetadata` on dynamic pages — poor SEO, missing OG tags
|
|
260
|
+
8. Putting secrets in client components — environment variables without `NEXT_PUBLIC_` are server-only
|
|
261
|
+
9. Mutating data without `revalidatePath` — stale cache shown to users after update
|
|
262
|
+
10. Not using `notFound()` — returning null causes blank pages instead of proper 404
|
|
263
|
+
|
|
264
|
+
## Performance Checklist
|
|
265
|
+
|
|
266
|
+
- [ ] Server Components used for all non-interactive UI
|
|
267
|
+
- [ ] `next/image` on every image, with `priority` on LCP image
|
|
268
|
+
- [ ] `next/font` for all custom fonts — no `@font-face` in CSS
|
|
269
|
+
- [ ] `loading.tsx` on every dynamic route segment
|
|
270
|
+
- [ ] `Promise.all` for all parallel data fetches in Server Components
|
|
271
|
+
- [ ] Bundle analyzer run: `ANALYZE=true npm run build`
|
|
272
|
+
- [ ] Suspense boundaries around individually-slow components
|
|
273
|
+
- [ ] `generateStaticParams` for known dynamic routes (static generation)
|
|
274
|
+
- [ ] No large third-party libraries in Server Components (they ship to client via `'use client'` boundary)
|
|
275
|
+
- [ ] `revalidateTag` strategy defined for all cached data
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Nuxt UI (nuxt/ui) UI/UX Guidelines
|
|
2
|
+
|
|
3
|
+
## When to read this
|
|
4
|
+
Read this file when building UI with the `@nuxt/ui` component library in a Nuxt 3 project. Covers component usage, theming, form patterns, toast notifications, modals, and accessibility built into the library.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
```bash
|
|
8
|
+
npx nuxi module add ui
|
|
9
|
+
# Automatically installs and configures:
|
|
10
|
+
# - Tailwind CSS
|
|
11
|
+
# - Headless UI (accessibility primitives)
|
|
12
|
+
# - Heroicons (default icon set)
|
|
13
|
+
# - Color mode support
|
|
14
|
+
# - All components are auto-imported
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Core Components Reference
|
|
18
|
+
|
|
19
|
+
| Component | Selector | Best for |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| UButton | `<UButton>` | Actions — color, variant, icon, size, loading props |
|
|
22
|
+
| UInput | `<UInput>` | Text fields — placeholder, icon, trailing, disabled |
|
|
23
|
+
| UTextarea | `<UTextarea>` | Multi-line text input |
|
|
24
|
+
| USelect | `<USelect>` | Dropdown select with options array |
|
|
25
|
+
| UCard | `<UCard>` | Content containers with header/footer slots |
|
|
26
|
+
| UModal | `<UModal>` | Dialogs — v-model for open/close, accessible by default |
|
|
27
|
+
| USlideover | `<USlideover>` | Side panels / drawers |
|
|
28
|
+
| UDropdown | `<UDropdown>` | Menus — :items array prop |
|
|
29
|
+
| UTable | `<UTable>` | Data tables — :rows, :columns, sorting, pagination |
|
|
30
|
+
| UBadge | `<UBadge>` | Status indicators — color, variant, size |
|
|
31
|
+
| UAlert | `<UAlert>` | Feedback messages — icon, color, description |
|
|
32
|
+
| UCommandPalette | `<UCommandPalette>` | Search / command palette with built-in filtering |
|
|
33
|
+
| UNotifications | `<UNotifications>` | Toast notification stack — pair with useToast() |
|
|
34
|
+
| UFormGroup | `<UFormGroup>` | Form field wrapper with label, hint, and error |
|
|
35
|
+
| UForm | `<UForm>` | Form with schema validation (Zod or Yup) |
|
|
36
|
+
| UTabs | `<UTabs>` | Tab navigation — :items array |
|
|
37
|
+
| UAccordion | `<UAccordion>` | Collapsible sections |
|
|
38
|
+
| UAvatar | `<UAvatar>` | User avatars with fallback initials |
|
|
39
|
+
| UProgress | `<UProgress>` | Progress bar with value and animation |
|
|
40
|
+
| UTooltip | `<UTooltip>` | Hover tooltips with text prop |
|
|
41
|
+
| UPopover | `<UPopover>` | Click-triggered overlay panels |
|
|
42
|
+
| UPagination | `<UPagination>` | Page navigation — v-model, :total, :page-count |
|
|
43
|
+
| URange | `<URange>` | Slider input |
|
|
44
|
+
| UToggle | `<UToggle>` | Boolean on/off switch |
|
|
45
|
+
| UCheckbox | `<UCheckbox>` | Checkbox with label |
|
|
46
|
+
| URadio | `<URadio>` | Radio button |
|
|
47
|
+
|
|
48
|
+
## Top UX Patterns
|
|
49
|
+
|
|
50
|
+
### 1. Button Variants and States
|
|
51
|
+
```vue
|
|
52
|
+
<!-- Primary action -->
|
|
53
|
+
<UButton color="primary" variant="solid" size="md" @click="save">
|
|
54
|
+
Save changes
|
|
55
|
+
</UButton>
|
|
56
|
+
|
|
57
|
+
<!-- With icon and loading state -->
|
|
58
|
+
<UButton
|
|
59
|
+
color="primary"
|
|
60
|
+
variant="solid"
|
|
61
|
+
icon="i-heroicons-check"
|
|
62
|
+
:loading="saving"
|
|
63
|
+
:disabled="saving"
|
|
64
|
+
@click="save"
|
|
65
|
+
>
|
|
66
|
+
Save changes
|
|
67
|
+
</UButton>
|
|
68
|
+
|
|
69
|
+
<!-- Destructive action -->
|
|
70
|
+
<UButton color="red" variant="soft" icon="i-heroicons-trash" @click="remove">
|
|
71
|
+
Delete
|
|
72
|
+
</UButton>
|
|
73
|
+
|
|
74
|
+
<!-- Ghost / subtle -->
|
|
75
|
+
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark" @click="cancel">
|
|
76
|
+
Cancel
|
|
77
|
+
</UButton>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2. Form with Schema Validation
|
|
81
|
+
```vue
|
|
82
|
+
<script setup lang="ts">
|
|
83
|
+
import { z } from 'zod';
|
|
84
|
+
import type { FormSubmitEvent } from '#ui/types';
|
|
85
|
+
|
|
86
|
+
const schema = z.object({
|
|
87
|
+
email: z.string().email('Invalid email'),
|
|
88
|
+
password: z.string().min(8, 'At least 8 characters'),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
type Schema = z.output<typeof schema>;
|
|
92
|
+
|
|
93
|
+
const state = reactive({ email: '', password: '' });
|
|
94
|
+
|
|
95
|
+
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
|
96
|
+
await login(event.data);
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<UForm :schema="schema" :state="state" @submit="onSubmit" class="space-y-4">
|
|
102
|
+
<UFormGroup label="Email" name="email" required>
|
|
103
|
+
<UInput v-model="state.email" type="email" placeholder="you@example.com" />
|
|
104
|
+
</UFormGroup>
|
|
105
|
+
|
|
106
|
+
<UFormGroup label="Password" name="password" required>
|
|
107
|
+
<UInput v-model="state.password" type="password" />
|
|
108
|
+
</UFormGroup>
|
|
109
|
+
|
|
110
|
+
<UButton type="submit" color="primary" block>Sign in</UButton>
|
|
111
|
+
</UForm>
|
|
112
|
+
</template>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 3. Toast Notifications
|
|
116
|
+
```typescript
|
|
117
|
+
const toast = useToast();
|
|
118
|
+
|
|
119
|
+
// Success
|
|
120
|
+
toast.add({
|
|
121
|
+
title: 'Saved!',
|
|
122
|
+
description: 'Your changes have been saved.',
|
|
123
|
+
icon: 'i-heroicons-check-circle',
|
|
124
|
+
color: 'green',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Error
|
|
128
|
+
toast.add({
|
|
129
|
+
title: 'Error',
|
|
130
|
+
description: 'Failed to save. Please try again.',
|
|
131
|
+
icon: 'i-heroicons-x-circle',
|
|
132
|
+
color: 'red',
|
|
133
|
+
timeout: 5000,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Info with action
|
|
137
|
+
toast.add({
|
|
138
|
+
title: 'New version available',
|
|
139
|
+
actions: [{ label: 'Refresh', click: () => window.location.reload() }],
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```vue
|
|
144
|
+
<!-- Place once in app.vue -->
|
|
145
|
+
<template>
|
|
146
|
+
<div>
|
|
147
|
+
<NuxtPage />
|
|
148
|
+
<UNotifications />
|
|
149
|
+
</div>
|
|
150
|
+
</template>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 4. Modal Dialog
|
|
154
|
+
```vue
|
|
155
|
+
<script setup lang="ts">
|
|
156
|
+
const isOpen = ref(false);
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
<template>
|
|
160
|
+
<UButton @click="isOpen = true">Open dialog</UButton>
|
|
161
|
+
|
|
162
|
+
<UModal v-model="isOpen">
|
|
163
|
+
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
|
164
|
+
<template #header>
|
|
165
|
+
<div class="flex items-center justify-between">
|
|
166
|
+
<h3 class="text-base font-semibold">Edit item</h3>
|
|
167
|
+
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark" @click="isOpen = false" />
|
|
168
|
+
</div>
|
|
169
|
+
</template>
|
|
170
|
+
|
|
171
|
+
<p>Modal content goes here.</p>
|
|
172
|
+
|
|
173
|
+
<template #footer>
|
|
174
|
+
<div class="flex justify-end gap-3">
|
|
175
|
+
<UButton color="gray" variant="ghost" @click="isOpen = false">Cancel</UButton>
|
|
176
|
+
<UButton color="primary" @click="save">Save</UButton>
|
|
177
|
+
</div>
|
|
178
|
+
</template>
|
|
179
|
+
</UCard>
|
|
180
|
+
</UModal>
|
|
181
|
+
</template>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 5. Data Table with Columns
|
|
185
|
+
```vue
|
|
186
|
+
<script setup lang="ts">
|
|
187
|
+
const columns = [
|
|
188
|
+
{ key: 'name', label: 'Name', sortable: true },
|
|
189
|
+
{ key: 'email', label: 'Email' },
|
|
190
|
+
{ key: 'role', label: 'Role' },
|
|
191
|
+
{ key: 'actions', label: '' },
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const { data: users } = await useFetch('/api/users');
|
|
195
|
+
</script>
|
|
196
|
+
|
|
197
|
+
<template>
|
|
198
|
+
<UTable
|
|
199
|
+
:rows="users"
|
|
200
|
+
:columns="columns"
|
|
201
|
+
:loading="pending"
|
|
202
|
+
:empty-state="{ icon: 'i-heroicons-users', label: 'No users found.' }"
|
|
203
|
+
>
|
|
204
|
+
<template #actions-data="{ row }">
|
|
205
|
+
<UDropdown :items="[[{ label: 'Edit', click: () => edit(row) }, { label: 'Delete', click: () => remove(row) }]]">
|
|
206
|
+
<UButton icon="i-heroicons-ellipsis-horizontal" color="gray" variant="ghost" />
|
|
207
|
+
</UDropdown>
|
|
208
|
+
</template>
|
|
209
|
+
</UTable>
|
|
210
|
+
</template>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 6. Command Palette
|
|
214
|
+
```vue
|
|
215
|
+
<script setup lang="ts">
|
|
216
|
+
const isOpen = ref(false);
|
|
217
|
+
const { metaSymbol } = useShortcuts();
|
|
218
|
+
|
|
219
|
+
defineShortcuts({
|
|
220
|
+
meta_k: { usingInput: false, handler: () => { isOpen.value = !isOpen.value; } },
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const groups = [
|
|
224
|
+
{
|
|
225
|
+
key: 'pages',
|
|
226
|
+
label: 'Pages',
|
|
227
|
+
commands: [
|
|
228
|
+
{ id: 'home', label: 'Home', icon: 'i-heroicons-home', to: '/' },
|
|
229
|
+
{ id: 'dashboard', label: 'Dashboard', icon: 'i-heroicons-squares-2x2', to: '/dashboard' },
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
</script>
|
|
234
|
+
|
|
235
|
+
<template>
|
|
236
|
+
<UModal v-model="isOpen">
|
|
237
|
+
<UCommandPalette :groups="groups" @update:model-value="isOpen = false" />
|
|
238
|
+
</UModal>
|
|
239
|
+
</template>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 7. Slideover Side Panel
|
|
243
|
+
```vue
|
|
244
|
+
<template>
|
|
245
|
+
<USlideover v-model="isOpen" side="right">
|
|
246
|
+
<UCard class="h-full rounded-none">
|
|
247
|
+
<template #header>
|
|
248
|
+
<div class="flex items-center justify-between">
|
|
249
|
+
<h3>Filter options</h3>
|
|
250
|
+
<UButton icon="i-heroicons-x-mark" color="gray" variant="ghost" @click="isOpen = false" />
|
|
251
|
+
</div>
|
|
252
|
+
</template>
|
|
253
|
+
<FilterForm />
|
|
254
|
+
</UCard>
|
|
255
|
+
</USlideover>
|
|
256
|
+
</template>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 8. Dropdown Menu
|
|
260
|
+
```vue
|
|
261
|
+
<script setup lang="ts">
|
|
262
|
+
const items = [
|
|
263
|
+
[
|
|
264
|
+
{ label: 'Profile', icon: 'i-heroicons-user', to: '/profile' },
|
|
265
|
+
{ label: 'Settings', icon: 'i-heroicons-cog-6-tooth', to: '/settings' },
|
|
266
|
+
],
|
|
267
|
+
[
|
|
268
|
+
{ label: 'Sign out', icon: 'i-heroicons-arrow-right-on-rectangle', click: signOut },
|
|
269
|
+
],
|
|
270
|
+
];
|
|
271
|
+
</script>
|
|
272
|
+
|
|
273
|
+
<template>
|
|
274
|
+
<UDropdown :items="items">
|
|
275
|
+
<UAvatar :src="user.avatar" :alt="user.name" />
|
|
276
|
+
</UDropdown>
|
|
277
|
+
</template>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Theming (app.config.ts)
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// app.config.ts
|
|
284
|
+
export default defineAppConfig({
|
|
285
|
+
ui: {
|
|
286
|
+
primary: 'indigo', // Primary color — any Tailwind color
|
|
287
|
+
gray: 'slate', // Gray scale — slate, cool, zinc, neutral, stone
|
|
288
|
+
|
|
289
|
+
// Component-level defaults
|
|
290
|
+
button: {
|
|
291
|
+
default: {
|
|
292
|
+
size: 'md',
|
|
293
|
+
color: 'primary',
|
|
294
|
+
variant: 'solid',
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
input: {
|
|
299
|
+
default: {
|
|
300
|
+
size: 'md',
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
// Custom variant example
|
|
305
|
+
badge: {
|
|
306
|
+
variant: {
|
|
307
|
+
custom: 'bg-{color}-100 text-{color}-700 ring-1 ring-{color}-300',
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Icons
|
|
315
|
+
|
|
316
|
+
Nuxt UI uses Heroicons by default. Icons are accessed via CSS class names:
|
|
317
|
+
|
|
318
|
+
```vue
|
|
319
|
+
<!-- Outline (default) -->
|
|
320
|
+
<UIcon name="i-heroicons-home" class="w-5 h-5" />
|
|
321
|
+
|
|
322
|
+
<!-- Solid variant -->
|
|
323
|
+
<UIcon name="i-heroicons-home-solid" />
|
|
324
|
+
|
|
325
|
+
<!-- In components -->
|
|
326
|
+
<UButton icon="i-heroicons-plus" />
|
|
327
|
+
<UInput icon="i-heroicons-magnifying-glass" />
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Install additional icon sets:
|
|
331
|
+
```bash
|
|
332
|
+
npm install @iconify-json/lucide # Lucide icons — i-lucide-*
|
|
333
|
+
npm install @iconify-json/ph # Phosphor icons — i-ph-*
|
|
334
|
+
npm install @iconify-json/tabler # Tabler icons — i-tabler-*
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Best Practices by Category
|
|
338
|
+
|
|
339
|
+
### Components
|
|
340
|
+
- Always use `<UFormGroup>` around form inputs — it wires up the label/input association, hint text, and error display automatically
|
|
341
|
+
- Use `<UModal>` and `<USlideover>` instead of building custom overlays — they handle focus trap, Escape key, scroll lock, and ARIA out of the box
|
|
342
|
+
- Use the `color` prop with theme values (`primary`, `gray`, `red`, `green`) — never hardcode hex colors
|
|
343
|
+
- Use `variant` prop to convey hierarchy: `solid` for primary, `soft` or `outline` for secondary, `ghost` for tertiary
|
|
344
|
+
|
|
345
|
+
### Forms
|
|
346
|
+
- `<UForm>` with `:schema` (Zod) handles validation automatically on submit and on change
|
|
347
|
+
- `<UFormGroup name="fieldName">` matches the Zod schema key — errors display automatically
|
|
348
|
+
- Use `:loading` on submit button — never disable the button without visual feedback
|
|
349
|
+
|
|
350
|
+
### Notifications
|
|
351
|
+
- `useToast()` is the only notification system — do not build a custom one
|
|
352
|
+
- Place `<UNotifications />` once in `app.vue`
|
|
353
|
+
- Use `color` prop to convey meaning: `green` for success, `red` for error, `yellow` for warning
|
|
354
|
+
|
|
355
|
+
### Accessibility
|
|
356
|
+
- All Nuxt UI components are built on Headless UI — ARIA roles, keyboard navigation, and focus management are handled automatically
|
|
357
|
+
- Do not suppress the built-in focus ring — it is essential for keyboard users
|
|
358
|
+
- Use `aria-label` on icon-only buttons: `<UButton icon="i-heroicons-x-mark" aria-label="Close" />`
|
|
359
|
+
|
|
360
|
+
### State
|
|
361
|
+
- `v-model` on `<UModal>` and `<USlideover>` for open/close state
|
|
362
|
+
- `useToast()` composable — auto-imported, no import needed
|
|
363
|
+
- `useShortcuts()` for keyboard shortcuts that interact with UI
|
|
364
|
+
|
|
365
|
+
## Common Anti-Patterns
|
|
366
|
+
|
|
367
|
+
1. Not using `<UFormGroup>` — input loses label association, hint text, and auto error display
|
|
368
|
+
2. Building a custom modal — `<UModal>` and `<USlideover>` handle all accessibility automatically (focus trap, Escape key, scroll lock)
|
|
369
|
+
3. Hardcoded colors in `class` or `style` — use `color` prop so theming works correctly
|
|
370
|
+
4. Building a custom toast/notification system — `useToast()` + `<UNotifications />` is already provided
|
|
371
|
+
5. Importing components manually — all Nuxt UI components are auto-imported
|
|
372
|
+
6. Not passing `aria-label` on icon-only buttons — screen readers cannot identify the action
|
|
373
|
+
7. Using `disabled` on form submit without `:loading` — users see no feedback that something is happening
|
|
374
|
+
8. Nested `<UCard>` inside `<UModal>` without removing the ring — creates double border; use `:ui="{ ring: '' }"`
|
|
375
|
+
|
|
376
|
+
## Performance Checklist
|
|
377
|
+
|
|
378
|
+
- [ ] `<UTable>` with `:rows` for all data tables — has built-in empty state and loading state
|
|
379
|
+
- [ ] `<UCommandPalette>` for app-wide search — built-in filtering and keyboard navigation
|
|
380
|
+
- [ ] `<UModal>` content only renders when open — no hidden DOM overhead when closed
|
|
381
|
+
- [ ] `<UNotifications />` placed once at app root — not duplicated per page
|
|
382
|
+
- [ ] Icon sets installed only as needed — each `@iconify-json/*` adds to bundle
|
|
383
|
+
- [ ] Theme configured in `app.config.ts` — avoids runtime style computation
|
|
384
|
+
- [ ] `<UButton :loading="true">` used during async operations — prevents double-submit
|