ui-ux-consultant-cli 1.0.0-beta.1

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.
Files changed (30) hide show
  1. package/assets/ui-ux-consultant/SKILL.md +844 -0
  2. package/assets/ui-ux-consultant/references/accessibility.md +175 -0
  3. package/assets/ui-ux-consultant/references/alt-libraries.md +90 -0
  4. package/assets/ui-ux-consultant/references/animations.md +448 -0
  5. package/assets/ui-ux-consultant/references/catalog/colors.md +91 -0
  6. package/assets/ui-ux-consultant/references/catalog/fonts.md +363 -0
  7. package/assets/ui-ux-consultant/references/catalog/products.md +340 -0
  8. package/assets/ui-ux-consultant/references/catalog/styles.md +165 -0
  9. package/assets/ui-ux-consultant/references/components.md +1116 -0
  10. package/assets/ui-ux-consultant/references/patterns.md +600 -0
  11. package/assets/ui-ux-consultant/references/performance.md +198 -0
  12. package/assets/ui-ux-consultant/references/stacks/astro.md +382 -0
  13. package/assets/ui-ux-consultant/references/stacks/flutter.md +308 -0
  14. package/assets/ui-ux-consultant/references/stacks/html-tailwind.md +415 -0
  15. package/assets/ui-ux-consultant/references/stacks/jetpack-compose.md +333 -0
  16. package/assets/ui-ux-consultant/references/stacks/laravel.md +521 -0
  17. package/assets/ui-ux-consultant/references/stacks/nextjs.md +275 -0
  18. package/assets/ui-ux-consultant/references/stacks/nuxt-ui.md +384 -0
  19. package/assets/ui-ux-consultant/references/stacks/nuxtjs.md +264 -0
  20. package/assets/ui-ux-consultant/references/stacks/react-native.md +346 -0
  21. package/assets/ui-ux-consultant/references/stacks/react.md +268 -0
  22. package/assets/ui-ux-consultant/references/stacks/shadcn.md +485 -0
  23. package/assets/ui-ux-consultant/references/stacks/svelte.md +429 -0
  24. package/assets/ui-ux-consultant/references/stacks/swiftui.md +336 -0
  25. package/assets/ui-ux-consultant/references/stacks/threejs.md +366 -0
  26. package/assets/ui-ux-consultant/references/stacks/vue.md +272 -0
  27. package/assets/ui-ux-consultant/references/theming.md +701 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +130 -0
  30. package/package.json +51 -0
@@ -0,0 +1,844 @@
1
+ ---
2
+ name: ui-ux-pro
3
+ description: >
4
+ Multi-framework UI/UX design intelligence. Use when building UI with any framework or platform.
5
+ Deep Angular coverage (Angular Material 3, signals, CDK, M3 theming, animations, accessibility).
6
+ Also covers: React, Next.js, Vue, Nuxt.js, Nuxt UI, Svelte, Astro, shadcn/ui, HTML+Tailwind,
7
+ Flutter, SwiftUI, React Native, Laravel, Jetpack Compose, Three.js.
8
+ Triggers on: "angular", "react", "next.js", "nextjs", "vue", "nuxt", "svelte", "astro",
9
+ "shadcn", "tailwind", "flutter", "swiftui", "react native", "laravel", "jetpack compose",
10
+ "three.js", "threejs", "r3f", "material", "component", "UI library", "design system",
11
+ "angular material", "ng-zorro", "primeng", "signals UI", "CDK", "hooks", "composable",
12
+ "useState", "useEffect", "Pinia", "Riverpod", "Provider", "StatefulWidget", "@Observable",
13
+ "app router", "server components", "blade", "livewire", "inertia", "webgl", "scene graph".
14
+ user-invokable: true
15
+ argument-hint: "[angular|react|vue|nextjs|svelte|astro|flutter|swiftui|react-native|laravel|jetpack|threejs|shadcn|nuxt|html-tailwind] [component|theme|layout|pattern|audit]"
16
+ ---
17
+
18
+ ## Framework Router
19
+
20
+ Detect the framework from context, then read the matching reference:
21
+
22
+ | Framework | Reference | Triggers |
23
+ |---|---|---|
24
+ | **Angular** | `references/` (7 files — deepest coverage) | angular, angular material, ng-zorro, primeng, signals, CDK, standalone component |
25
+ | **React** | `references/stacks/react.md` | react, jsx, useState, useEffect, vite+react, react hooks |
26
+ | **Next.js** | `references/stacks/nextjs.md` | next.js, nextjs, app router, RSC, server components, server actions |
27
+ | **Vue** | `references/stacks/vue.md` | vue, composition api, script setup, pinia, vue router |
28
+ | **Nuxt.js** | `references/stacks/nuxtjs.md` | nuxt, nuxt 3, auto-imports, useAsyncData, useFetch |
29
+ | **Nuxt UI** | `references/stacks/nuxt-ui.md` | nuxt ui, @nuxt/ui, u-button, u-card |
30
+ | **Svelte** | `references/stacks/svelte.md` | svelte, sveltekit, $state, $derived, $effect, runes |
31
+ | **Astro** | `references/stacks/astro.md` | astro, .astro, islands, content collections, astro components |
32
+ | **shadcn/ui** | `references/stacks/shadcn.md` | shadcn, radix ui, copy-paste components, cn(), cva() |
33
+ | **HTML+Tailwind** | `references/stacks/html-tailwind.md` | tailwind, utility css, no framework, vanilla html |
34
+ | **Flutter** | `references/stacks/flutter.md` | flutter, dart, widget, statelesswidget, provider, riverpod |
35
+ | **SwiftUI** | `references/stacks/swiftui.md` | swiftui, @state, @observable, navigationstack, xcode |
36
+ | **React Native** | `references/stacks/react-native.md` | react native, expo, flatlist, stylesheet, metro |
37
+ | **Laravel** | `references/stacks/laravel.md` | laravel, blade, livewire, inertia, eloquent |
38
+ | **Jetpack Compose** | `references/stacks/jetpack-compose.md` | jetpack compose, composable, remember, kotlin, android |
39
+ | **Three.js** | `references/stacks/threejs.md` | three.js, threejs, r3f, react three fiber, webgl, scene graph |
40
+
41
+ **For Angular requests:** Continue reading this SKILL.md — all Angular content is below.
42
+ **For other frameworks:** Read the matching stack file above, then return here for universal UX rules if needed.
43
+
44
+ ---
45
+
46
+ ## Design Catalog
47
+
48
+ Use these when the user needs aesthetic direction, color, typography, or product-type guidance:
49
+
50
+ | Catalog | File | Use when |
51
+ |---|---|---|
52
+ | **UI Styles** | `references/catalog/styles.md` | User asks for a visual style, aesthetic, or "what style fits X" — 23 named styles |
53
+ | **Color Palettes** | `references/catalog/colors.md` | User needs brand colors, theme colors, or palette recommendations — 35 palettes |
54
+ | **Font Pairings** | `references/catalog/fonts.md` | User needs typography, heading+body font combos — 31 pairings with Google Fonts imports |
55
+ | **Product Types** | `references/catalog/products.md` | User describes what they're building — 41 product types with UI patterns and layout guidance |
56
+
57
+ ---
58
+
59
+ # Angular UI/UX Design Intelligence
60
+
61
+ Angular-specific design guidance for building production-quality UIs. All examples use Angular 17+ standalone component syntax with signals-first patterns.
62
+
63
+ ---
64
+
65
+ ## Section 1: Decision Tree — What Are You Building?
66
+
67
+ > These routes apply to **Angular** projects. For other frameworks see the Framework Router above.
68
+
69
+ Use this routing guide to load the right reference file before implementing:
70
+
71
+ | Building... | Read this |
72
+ |---|---|
73
+ | A new component (card, list, form, dialog, toolbar) | `references/components.md` |
74
+ | Design system / color theming / dark mode / typography | `references/theming.md` |
75
+ | Signals patterns, smart/dumb components, routing UX, state flows | `references/patterns.md` |
76
+ | Motion, page transitions, list animations, micro-interactions | `references/animations.md` |
77
+ | Performance: lazy loading, deferring, image optimization, SSR | `references/performance.md` |
78
+ | Accessibility audit, ARIA, keyboard navigation, screen readers | `references/accessibility.md` |
79
+ | Choosing between Angular Material, NG-ZORRO, PrimeNG, custom | `references/alt-libraries.md` |
80
+
81
+ When in doubt: start with `references/components.md`, then `references/patterns.md`.
82
+
83
+ ---
84
+
85
+ ## Section 2: Design Philosophy
86
+
87
+ ### Component-First Architecture
88
+ Angular's core mental model is composition of focused, testable components with clear `input()` / `output()` signal contracts. Good UI design in Angular means good component decomposition — each component should own a single visual responsibility and surface its API through typed signal inputs. Avoid "god components" that manage multiple concerns (e.g., a dashboard that owns its own HTTP calls, layout, and child state simultaneously). Aim for components under 200 lines of template and under 100 lines of class. When a template grows beyond two levels of conditional nesting, extract the inner content into a sub-component.
89
+
90
+ ### Angular Material 3 as Default
91
+ Angular Material is the default recommendation for virtually all Angular UIs. It is the only Angular component library that is officially maintained by the Angular team, ships with full Material Design 3 spec compliance, and has first-class accessibility built in. Deviations — choosing NG-ZORRO for data-dense enterprise UIs, PrimeNG for e-commerce richness, or a fully custom system — require a deliberate justification. When in doubt, use Material. When the design spec calls for something Material cannot do, layer CDK primitives underneath your own components rather than pulling in a second library.
92
+
93
+ ### Signals-First Reactive UI (Angular 17+)
94
+ `signal()`, `computed()`, and `effect()` replace most `subscribe()` patterns for local UI state. RxJS remains essential for async data pipelines (HTTP, WebSockets, complex event merging) but should terminate at the component boundary via `toSignal()`. Never expose `Observable` subscriptions that outlive component teardown — use `takeUntilDestroyed()` if you must subscribe manually, or prefer `toSignal()` which handles teardown automatically. Template control flow (`@if`, `@for`, `@switch`, `@defer`) replaces `*ngIf`, `*ngFor`, `*ngSwitch` structural directives and is more readable and slightly more efficient.
95
+
96
+ ### OnPush by Default
97
+ Every new component should be created with `changeDetection: ChangeDetectionStrategy.OnPush`. Combined with signals, this eliminates virtually all unnecessary re-renders. Zone.js-based default change detection (the pre-17 default) runs on every async event in the entire application — a click handler, a setTimeout, an HTTP response. OnPush narrows updates to when inputs change or signals emit. This is not a micro-optimization; at scale it is the difference between a snappy UI and a laggy one. Set OnPush at component creation time — retrofitting it later is painful and error-prone.
98
+
99
+ ### Standalone Components and Lazy Loading
100
+ Angular 17+ defaults to standalone components (`standalone: true` is now implicit). No NgModules means the `imports` array on each component is its explicit dependency list — this is a design asset, not boilerplate. It makes lazy-loaded routes trivial (`loadComponent: () => import('./page').then(m => m.PageComponent)`), keeps bundle splitting automatic, and keeps the mental model simple. Never reintroduce NgModules unless integrating a library that still requires them.
101
+
102
+ ### Performance as UX
103
+ `@defer` blocks, `NgOptimizedImage`, and route-level code splitting are UX decisions as much as engineering ones. A dashboard that defers its chart section until it enters the viewport delivers perceived performance that no visual design trick can replicate. `NgOptimizedImage` automatically generates `srcset`, enforces `width`/`height` to prevent layout shift, and lazy-loads non-priority images. SSR hydration (Angular Universal / `provideClientHydration()`) is the right choice for any content-heavy or SEO-sensitive page. These are design choices that should be made in the planning phase, not retrofitted.
104
+
105
+ ---
106
+
107
+ ## Section 3: Top 20 Angular Material 3 Components
108
+
109
+ | Component | Selector | Best Used For | Don't Use When |
110
+ |---|---|---|---|
111
+ | Filled Button | `mat-flat-button` | Primary CTA, form submit, main action | Secondary actions — use `mat-button` or `mat-stroked-button` |
112
+ | Text Button | `mat-button` | Low-emphasis actions, inline links, cancel | The only action on screen — use filled for primary |
113
+ | Icon Button | `mat-icon-button` | Toolbar actions, compact controls, toggles | The action needs a visible label for clarity |
114
+ | FAB | `mat-fab` / `mat-mini-fab` | Single primary action per screen (add, compose) | Multiple competing CTAs exist; use filled button instead |
115
+ | Card | `mat-card` | Grouping related content, entity summaries | Laying out a page — use structural layout, not cards for everything |
116
+ | List | `mat-list` / `mat-nav-list` | Vertical item sequences, navigation drawers | Tabular data with multiple columns — use `mat-table` |
117
+ | Table | `mat-table` | Structured tabular data with sorting/filtering | Simple key-value display — use `mat-list` or definition list |
118
+ | Paginator | `mat-paginator` | Paging large mat-table datasets | Small datasets under 20 items — show all and use filter |
119
+ | Form Field | `mat-form-field` | Wrapping all text inputs, selects, autocompletes | Checkboxes, radios, toggles — those are standalone |
120
+ | Input | `matInput` directive | Text, number, email, search fields inside form field | Multi-line — use `textarea matInput` with `cdkTextareaAutosize` |
121
+ | Select | `mat-select` | Choosing one (or many) from a fixed short list | Lists over ~8 items — use `mat-autocomplete` instead |
122
+ | Autocomplete | `mat-autocomplete` | Searchable select, tag input, typeahead | Fixed short lists — `mat-select` is simpler |
123
+ | Checkbox | `mat-checkbox` | Multi-select, boolean toggles in lists | Confirming destructive actions — use a dialog with buttons |
124
+ | Radio Group | `mat-radio-group` | Mutually exclusive options (3–5 choices) | Binary yes/no — use `mat-slide-toggle` |
125
+ | Slide Toggle | `mat-slide-toggle` | Binary settings that take effect immediately | Actions requiring confirmation — use checkbox + button |
126
+ | Dialog | `MatDialog` | Confirmations, focused sub-tasks, complex forms | Simple alerts or notifications — use `mat-snack-bar` |
127
+ | Snackbar | `MatSnackBar` | Transient success/error feedback, undo prompts | Errors requiring user action — use inline error or dialog |
128
+ | Progress Bar | `mat-progress-bar` | Page-level loading, step progress, file upload | Indeterminate local widget loading — use `mat-spinner` |
129
+ | Spinner | `mat-spinner` | Inline / button loading states, small areas | Full-page loading — use `mat-progress-bar` at top |
130
+ | Sidenav | `mat-sidenav` | Persistent navigation drawer, responsive shell | Simple page layouts — overhead is not worth it |
131
+ | Toolbar | `mat-toolbar` | App header, page-level title + action bar | Section headers inside content — use `<h2>` or card header |
132
+
133
+ ---
134
+
135
+ ## Section 4: Angular-Specific UX Anti-Patterns
136
+
137
+ ### Rule 1: Don't nest `@if` more than 2 levels deep
138
+ **DON'T:**
139
+ ```html
140
+ @if (user()) {
141
+ @if (user()!.isAdmin) {
142
+ @if (user()!.permissions.includes('edit')) {
143
+ <button>Edit</button>
144
+ }
145
+ }
146
+ }
147
+ ```
148
+ **DO:** Extract a `canEdit = computed(() => ...)` signal and use it in a single `@if`, or extract the inner content to a sub-component that receives the resolved data as an input.
149
+
150
+ ---
151
+
152
+ ### Rule 2: Don't `subscribe()` in the component body without cleanup
153
+ **DON'T:**
154
+ ```typescript
155
+ ngOnInit() {
156
+ this.userService.getUser().subscribe(u => this.user = u); // memory leak
157
+ }
158
+ ```
159
+ **DO:** Use `toSignal()` for simple cases, or `takeUntilDestroyed()` for complex pipelines:
160
+ ```typescript
161
+ readonly user = toSignal(this.userService.getUser());
162
+ // OR, when you need the Observable pipeline:
163
+ private destroyRef = inject(DestroyRef);
164
+ ngOnInit() {
165
+ this.userService.getUser()
166
+ .pipe(takeUntilDestroyed(this.destroyRef))
167
+ .subscribe(u => this.user.set(u));
168
+ }
169
+ ```
170
+
171
+ ---
172
+
173
+ ### Rule 3: Don't use `ViewChild` to read state
174
+ **DON'T:**
175
+ ```typescript
176
+ @ViewChild('myInput') inputRef!: ElementRef;
177
+ getInputValue() { return this.inputRef.nativeElement.value; }
178
+ ```
179
+ **DO:** Bind to a `FormControl` or `signal()` — let Angular own the state, not the DOM:
180
+ ```typescript
181
+ readonly inputValue = signal('');
182
+ // In template: <input [value]="inputValue()" (input)="inputValue.set($event.target.value)" />
183
+ // Or with reactive forms: this.form.controls.field.value
184
+ ```
185
+
186
+ ---
187
+
188
+ ### Rule 4: Don't use default (Zone.js) change detection for new components
189
+ **DON'T:**
190
+ ```typescript
191
+ @Component({ selector: 'app-card', ... })
192
+ export class CardComponent { ... } // defaults to CheckAlways
193
+ ```
194
+ **DO:**
195
+ ```typescript
196
+ @Component({
197
+ selector: 'app-card',
198
+ changeDetection: ChangeDetectionStrategy.OnPush,
199
+ ...
200
+ })
201
+ export class CardComponent { ... }
202
+ ```
203
+
204
+ ---
205
+
206
+ ### Rule 5: Don't use inline styles for theming
207
+ **DON'T:**
208
+ ```html
209
+ <button mat-flat-button style="background-color: #2563EB; color: white;">Save</button>
210
+ ```
211
+ **DO:** Use Angular Material's CSS custom properties or the theming API:
212
+ ```scss
213
+ // In component SCSS:
214
+ :host {
215
+ --mat-filled-button-container-color: var(--primary-brand);
216
+ }
217
+ // Or override globally in styles.scss:
218
+ html {
219
+ --mat-toolbar-container-background-color: #1e293b;
220
+ }
221
+ ```
222
+
223
+ ---
224
+
225
+ ### Rule 6: Don't block routing with synchronous guards
226
+ **DON'T:**
227
+ ```typescript
228
+ canActivate(): boolean {
229
+ return this.authService.isLoggedIn; // sync property read — fragile
230
+ }
231
+ ```
232
+ **DO:** Use functional guards with async resolution:
233
+ ```typescript
234
+ export const authGuard = () => {
235
+ const auth = inject(AuthService);
236
+ const router = inject(Router);
237
+ return auth.isAuthenticated$.pipe(
238
+ take(1),
239
+ map(ok => ok ? true : router.createUrlTree(['/login']))
240
+ );
241
+ };
242
+ ```
243
+
244
+ ---
245
+
246
+ ### Rule 7: Don't omit `track` in `@for` with mutable lists
247
+ **DON'T:**
248
+ ```html
249
+ @for (item of items()) {
250
+ <app-item [item]="item" />
251
+ }
252
+ ```
253
+ **DO:**
254
+ ```html
255
+ @for (item of items(); track item.id) {
256
+ <app-item [item]="item" />
257
+ }
258
+ ```
259
+ Without `track`, Angular destroys and recreates DOM nodes on every list mutation, causing flicker and destroying component state (form values, animation state, focus).
260
+
261
+ ---
262
+
263
+ ### Rule 8: Don't use `::ng-deep` for component styling
264
+ **DON'T:**
265
+ ```scss
266
+ ::ng-deep .mat-mdc-form-field-subscript-wrapper { display: none; }
267
+ ```
268
+ **DO:** Use Angular Material's CSS custom properties, the `MAT_FORM_FIELD_DEFAULT_OPTIONS` injection token, or wrap the component in a host element with a class and target that:
269
+ ```scss
270
+ // styles.scss — globally, intentionally:
271
+ html {
272
+ --mat-form-field-subscript-overflow: hidden;
273
+ }
274
+ // Or in component SCSS with a host selector:
275
+ :host {
276
+ --mat-form-field-container-height: 48px;
277
+ }
278
+ ```
279
+ `::ng-deep` is deprecated and will be removed. It also breaks style encapsulation silently.
280
+
281
+ ---
282
+
283
+ ## Section 5: Style Recommendations by App Category
284
+
285
+ | App Type | Primary Style | Secondary Style | Color Focus | Recommended Library |
286
+ |---|---|---|---|---|
287
+ | SaaS / B2B Dashboard | Flat + Material | Glassmorphism cards | Trust blue / Indigo (#2563EB) | Angular Material |
288
+ | Enterprise Admin Panel | Data-Dense Material | Minimal borders | Neutral grey + brand accent | NG-ZORRO or Material |
289
+ | Analytics Dashboard | Dark Material | Data-dense grids | Dark bg (#0F172A) + vivid accents | Material + CDK virtual scroll |
290
+ | E-commerce Storefront | Vibrant + Block | Aurora gradient hero | Brand primary + success green | PrimeNG or Material |
291
+ | Developer Tool / IDE | Minimalist / Monochrome | Dark mode first | Monochrome + single accent | Material or fully custom |
292
+ | Consumer Mobile (PWA) | Material You / Soft UI | Rounded, tactile | M3 dynamic color | Angular Material (M3) |
293
+ | Healthcare / Medical | Clean + Trustworthy | High contrast | Green (#059669) + blue | Angular Material |
294
+ | Fintech / Banking | Formal + Structured | Dark sidebar | Dark navy (#1E3A5F) + teal | Angular Material or custom |
295
+
296
+ ---
297
+
298
+ ## Section 6: Angular Material 3 Color Palettes
299
+
300
+ These palettes are starting points for `mat.define-theme()`. Use the `mat.$*-palette` named palettes or supply a custom tonal palette with `mat.define-palette()`.
301
+
302
+ | App Type | Primary | Secondary | Tertiary | Surface | Notes |
303
+ |---|---|---|---|---|---|
304
+ | SaaS / B2B | #2563EB (Indigo 600) | #6366F1 (Violet) | #EA580C (Orange CTA) | #F8FAFC | Use `mat.$azure-palette` as primary |
305
+ | Healthcare | #059669 (Emerald) | #0891B2 (Cyan) | #7C3AED (Purple) | #F0FDF4 | Conveys calm, trust, health |
306
+ | Fintech / Banking | #1E3A5F (Dark Navy) | #0F766E (Teal 700) | #B45309 (Amber) | #F1F5F9 | Formal, stable, trustworthy |
307
+ | E-commerce | #16A34A (Green) | #EA580C (Orange) | #7C3AED (Purple) | #FFFFFF | Energetic; orange = sale/CTA |
308
+ | Developer Tool | #6B7280 (Grey) | #374151 (Dark Grey) | #3B82F6 (Blue accent) | #111827 | Dark mode first; minimal saturation |
309
+ | Analytics / BI | #6366F1 (Violet) | #06B6D4 (Cyan) | #F59E0B (Amber) | #0F172A | Dark surface; vivid data colors |
310
+ | Consumer / Social | #EC4899 (Pink) | #8B5CF6 (Purple) | #06B6D4 (Cyan) | #FAFAFA | Playful; Material You dynamic color |
311
+ | Education | #2563EB (Blue) | #16A34A (Green) | #F59E0B (Amber) | #EFF6FF | Primary blue = trust; amber = gamification |
312
+
313
+ ---
314
+
315
+ ## Section 7: Angular Material 3 Theming Quick Reference
316
+
317
+ ### Core Theming API
318
+
319
+ ```scss
320
+ // styles.scss
321
+ @use '@angular/material' as mat;
322
+
323
+ // Include core styles once (resets, tokens)
324
+ @include mat.core();
325
+
326
+ // Define the theme
327
+ $theme: mat.define-theme((
328
+ color: (
329
+ theme-type: light,
330
+ primary: mat.$azure-palette, // M3 tonal palette
331
+ tertiary: mat.$orange-palette, // Accent/CTA color role
332
+ ),
333
+ typography: (
334
+ brand-family: 'Inter, system-ui, sans-serif',
335
+ plain-family: 'Inter, system-ui, sans-serif',
336
+ bold-weight: 700,
337
+ medium-weight: 500,
338
+ regular-weight: 400,
339
+ ),
340
+ density: (
341
+ scale: 0, // 0 = default, -1 = compact, -2 = very compact
342
+ ),
343
+ ));
344
+
345
+ // Apply to root
346
+ html {
347
+ @include mat.all-component-themes($theme);
348
+ // Or selectively:
349
+ // @include mat.button-theme($theme);
350
+ // @include mat.form-field-theme($theme);
351
+ }
352
+
353
+ // Dark mode via class toggle
354
+ .dark-theme {
355
+ $dark-theme: mat.define-theme((
356
+ color: (
357
+ theme-type: dark,
358
+ primary: mat.$azure-palette,
359
+ tertiary: mat.$orange-palette,
360
+ ),
361
+ ));
362
+ @include mat.all-component-colors($dark-theme);
363
+ }
364
+ ```
365
+
366
+ ### Available Named M3 Palettes
367
+ `mat.$red-palette`, `mat.$pink-palette`, `mat.$purple-palette`, `mat.$violet-palette`,
368
+ `mat.$indigo-palette`, `mat.$blue-palette`, `mat.$azure-palette`, `mat.$cyan-palette`,
369
+ `mat.$teal-palette`, `mat.$green-palette`, `mat.$olive-palette`, `mat.$yellow-palette`,
370
+ `mat.$orange-palette`, `mat.$brown-palette`, `mat.$rose-palette`, `mat.$chartreuse-palette`
371
+
372
+ ### CSS Custom Property Overrides
373
+ Use `--mat-*` properties for targeted overrides without re-defining the full theme:
374
+
375
+ ```scss
376
+ html {
377
+ // Toolbar
378
+ --mat-toolbar-container-background-color: #1e293b;
379
+ --mat-toolbar-container-text-color: #f8fafc;
380
+
381
+ // Buttons
382
+ --mat-filled-button-container-color: #2563eb;
383
+ --mat-filled-button-label-text-color: #ffffff;
384
+
385
+ // Cards
386
+ --mat-card-elevated-container-color: #ffffff;
387
+ --mat-card-elevated-container-elevation: 0 1px 3px rgba(0,0,0,0.1);
388
+
389
+ // Form fields
390
+ --mat-form-field-container-height: 52px;
391
+
392
+ // Sidenav
393
+ --mat-sidenav-container-width: 260px;
394
+ }
395
+ ```
396
+
397
+ ### Angular Material Typography Scale (M3)
398
+ ```scss
399
+ $theme: mat.define-theme((
400
+ typography: (
401
+ brand-family: 'Geist, Inter, sans-serif',
402
+ plain-family: 'Geist, Inter, sans-serif',
403
+ // M3 type scale roles: display-large → label-small
404
+ // Override individual roles:
405
+ ),
406
+ ));
407
+ ```
408
+ Reference type roles: `display-large`, `display-medium`, `display-small`,
409
+ `headline-large`, `headline-medium`, `headline-small`,
410
+ `title-large`, `title-medium`, `title-small`,
411
+ `body-large`, `body-medium`, `body-small`,
412
+ `label-large`, `label-medium`, `label-small`
413
+
414
+ ---
415
+
416
+ ## Section 8: Signals-First UI Patterns
417
+
418
+ ### Local UI State
419
+ ```typescript
420
+ import { Component, signal, computed, ChangeDetectionStrategy } from '@angular/core';
421
+
422
+ @Component({
423
+ selector: 'app-counter',
424
+ changeDetection: ChangeDetectionStrategy.OnPush,
425
+ template: `
426
+ <p>Count: {{ count() }} — Doubled: {{ doubled() }}</p>
427
+ <button mat-stroked-button (click)="increment()">+1</button>
428
+ <button mat-stroked-button (click)="reset()">Reset</button>
429
+ `,
430
+ })
431
+ export class CounterComponent {
432
+ readonly count = signal(0);
433
+ readonly doubled = computed(() => this.count() * 2);
434
+
435
+ increment() { this.count.update(n => n + 1); }
436
+ reset() { this.count.set(0); }
437
+ }
438
+ ```
439
+
440
+ ### Async Loading State Pattern
441
+ ```typescript
442
+ import { Component, signal, inject, ChangeDetectionStrategy } from '@angular/core';
443
+ import { ItemService } from './item.service';
444
+
445
+ interface Item { id: number; name: string; }
446
+
447
+ @Component({
448
+ selector: 'app-item-list',
449
+ changeDetection: ChangeDetectionStrategy.OnPush,
450
+ template: `
451
+ @if (loading()) {
452
+ <mat-progress-bar mode="indeterminate" />
453
+ } @else if (error()) {
454
+ <p class="error">{{ error() }}</p>
455
+ <button mat-button (click)="load()">Retry</button>
456
+ } @else {
457
+ @for (item of data(); track item.id) {
458
+ <app-item-card [item]="item" />
459
+ } @empty {
460
+ <p class="empty-state">No items found.</p>
461
+ }
462
+ }
463
+ `,
464
+ })
465
+ export class ItemListComponent {
466
+ private service = inject(ItemService);
467
+
468
+ readonly loading = signal(false);
469
+ readonly error = signal<string | null>(null);
470
+ readonly data = signal<Item[]>([]);
471
+
472
+ async load() {
473
+ this.loading.set(true);
474
+ this.error.set(null);
475
+ try {
476
+ this.data.set(await this.service.getItems());
477
+ } catch (e) {
478
+ this.error.set('Failed to load items. Please try again.');
479
+ } finally {
480
+ this.loading.set(false);
481
+ }
482
+ }
483
+ }
484
+ ```
485
+
486
+ ### Converting Observable to Signal (toSignal)
487
+ ```typescript
488
+ import { toSignal } from '@angular/core/rxjs-interop';
489
+ import { inject } from '@angular/core';
490
+
491
+ @Component({ ... })
492
+ export class UserComponent {
493
+ private userService = inject(UserService);
494
+
495
+ // Automatically subscribes and unsubscribes; initial value is undefined
496
+ readonly user = toSignal(this.userService.currentUser$);
497
+
498
+ // With initial value to avoid undefined checks:
499
+ readonly items = toSignal(this.itemService.items$, { initialValue: [] as Item[] });
500
+ }
501
+ ```
502
+
503
+ ### Optimistic UI Updates
504
+ ```typescript
505
+ async toggleItem(item: Item) {
506
+ const previous = this.items(); // snapshot for rollback
507
+
508
+ // Immediately update UI
509
+ this.items.update(list =>
510
+ list.map(i => i.id === item.id ? { ...i, done: !i.done } : i)
511
+ );
512
+
513
+ try {
514
+ await this.api.toggleItem(item.id);
515
+ } catch {
516
+ // Rollback on failure
517
+ this.items.set(previous);
518
+ this.snackBar.open('Update failed — changes reverted', 'Dismiss', { duration: 4000 });
519
+ }
520
+ }
521
+ ```
522
+
523
+ ### Derived State with computed()
524
+ ```typescript
525
+ readonly searchQuery = signal('');
526
+ readonly allItems = signal<Item[]>([]);
527
+ readonly statusFilter = signal<'all' | 'active' | 'done'>('all');
528
+
529
+ readonly filteredItems = computed(() => {
530
+ const q = this.searchQuery().toLowerCase();
531
+ const status = this.statusFilter();
532
+ return this.allItems()
533
+ .filter(item => !q || item.name.toLowerCase().includes(q))
534
+ .filter(item => status === 'all' || (status === 'done' ? item.done : !item.done));
535
+ });
536
+
537
+ readonly resultCount = computed(() => this.filteredItems().length);
538
+ ```
539
+
540
+ ### Input Signals (Angular 17.1+)
541
+ ```typescript
542
+ import { input, output, model } from '@angular/core';
543
+
544
+ @Component({ selector: 'app-item-card', ... })
545
+ export class ItemCardComponent {
546
+ // Required input — type-safe, signal-based
547
+ readonly item = input.required<Item>();
548
+
549
+ // Optional input with default
550
+ readonly variant = input<'compact' | 'full'>('full');
551
+
552
+ // Two-way binding (model signal)
553
+ readonly selected = model(false);
554
+
555
+ // Output
556
+ readonly deleted = output<Item>();
557
+
558
+ delete() { this.deleted.emit(this.item()); }
559
+ }
560
+
561
+ // Usage:
562
+ // <app-item-card [item]="item" [(selected)]="isSelected" (deleted)="onDelete($event)" />
563
+ ```
564
+
565
+ ---
566
+
567
+ ## Section 9: Angular Form UX
568
+
569
+ ### Typed Reactive Forms
570
+ ```typescript
571
+ import { Component, ChangeDetectionStrategy } from '@angular/core';
572
+ import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
573
+ import { MatFormFieldModule } from '@angular/material/form-field';
574
+ import { MatInputModule } from '@angular/material/input';
575
+ import { MatButtonModule } from '@angular/material/button';
576
+
577
+ interface SignupForm {
578
+ email: FormControl<string>;
579
+ password: FormControl<string>;
580
+ displayName: FormControl<string>;
581
+ }
582
+
583
+ @Component({
584
+ selector: 'app-signup-form',
585
+ standalone: true,
586
+ imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatButtonModule],
587
+ changeDetection: ChangeDetectionStrategy.OnPush,
588
+ template: `
589
+ <form [formGroup]="form" (ngSubmit)="submit()">
590
+ <mat-form-field appearance="outline">
591
+ <mat-label>Email</mat-label>
592
+ <input matInput [formControl]="form.controls.email" type="email" autocomplete="email" />
593
+ <mat-error>{{ emailError }}</mat-error>
594
+ </mat-form-field>
595
+
596
+ <mat-form-field appearance="outline">
597
+ <mat-label>Password</mat-label>
598
+ <input matInput [formControl]="form.controls.password" type="password" />
599
+ <mat-hint>At least 8 characters</mat-hint>
600
+ <mat-error>{{ passwordError }}</mat-error>
601
+ </mat-form-field>
602
+
603
+ <mat-form-field appearance="outline">
604
+ <mat-label>Display Name</mat-label>
605
+ <input matInput [formControl]="form.controls.displayName" autocomplete="name" />
606
+ <mat-error *ngIf="form.controls.displayName.hasError('required')">
607
+ Name is required
608
+ </mat-error>
609
+ </mat-form-field>
610
+
611
+ <button mat-flat-button type="submit" [disabled]="form.invalid || submitting()">
612
+ @if (submitting()) {
613
+ <mat-spinner diameter="20" />
614
+ } @else {
615
+ Create Account
616
+ }
617
+ </button>
618
+ </form>
619
+ `,
620
+ })
621
+ export class SignupFormComponent {
622
+ submitting = signal(false);
623
+
624
+ readonly form = new FormGroup<SignupForm>({
625
+ email: new FormControl('', {
626
+ nonNullable: true,
627
+ validators: [Validators.required, Validators.email],
628
+ updateOn: 'blur', // Validate on blur, not on every keystroke
629
+ }),
630
+ password: new FormControl('', {
631
+ nonNullable: true,
632
+ validators: [Validators.required, Validators.minLength(8)],
633
+ updateOn: 'blur',
634
+ }),
635
+ displayName: new FormControl('', {
636
+ nonNullable: true,
637
+ validators: [Validators.required],
638
+ updateOn: 'blur',
639
+ }),
640
+ });
641
+
642
+ get emailError(): string {
643
+ const ctrl = this.form.controls.email;
644
+ if (!ctrl.touched) return '';
645
+ if (ctrl.hasError('required')) return 'Email is required';
646
+ if (ctrl.hasError('email')) return 'Enter a valid email address';
647
+ return '';
648
+ }
649
+
650
+ get passwordError(): string {
651
+ const ctrl = this.form.controls.password;
652
+ if (!ctrl.touched) return '';
653
+ if (ctrl.hasError('required')) return 'Password is required';
654
+ if (ctrl.hasError('minlength')) return 'Password must be at least 8 characters';
655
+ return '';
656
+ }
657
+
658
+ async submit() {
659
+ if (this.form.invalid) { this.form.markAllAsTouched(); return; }
660
+ this.submitting.set(true);
661
+ try {
662
+ await this.authService.signup(this.form.getRawValue());
663
+ } finally {
664
+ this.submitting.set(false);
665
+ }
666
+ }
667
+ }
668
+ ```
669
+
670
+ ### Form UX Rules
671
+ 1. Use `updateOn: 'blur'` — never validate on every keystroke for non-search fields.
672
+ 2. Call `markAllAsTouched()` on submit attempt to surface all errors at once.
673
+ 3. Always show `<mat-hint>` for format expectations before the user types, `<mat-error>` after touched.
674
+ 4. Disable the submit button while `submitting()` is true and show a spinner inside it.
675
+ 5. Use `nonNullable: true` on FormControl to get properly typed `.value` (non-undefined).
676
+ 6. Prefer `FormGroup<T>` typed form groups for type-safe `.controls` access.
677
+ 7. For long multi-step forms, use Angular CDK Stepper (`mat-stepper`) and validate per-step.
678
+ 8. Autocomplete attributes (`autocomplete="email"`, `autocomplete="current-password"`) are UX, not optional.
679
+
680
+ ### Search / Filter UX with Debounce
681
+ ```typescript
682
+ readonly searchControl = new FormControl('', { nonNullable: true });
683
+
684
+ // In constructor or ngOnInit:
685
+ this.searchControl.valueChanges.pipe(
686
+ debounceTime(300),
687
+ distinctUntilChanged(),
688
+ takeUntilDestroyed(),
689
+ ).subscribe(query => this.searchQuery.set(query));
690
+ ```
691
+
692
+ ---
693
+
694
+ ## Section 10: Performance Design Checklist
695
+
696
+ - [ ] **OnPush on every component** — set `changeDetection: ChangeDetectionStrategy.OnPush` at component creation
697
+ - [ ] **`track item.id` in every `@for`** — prevents full DOM reconstruction on list mutation
698
+ - [ ] **`@defer (on viewport)`** for below-fold sections — e.g. charts, secondary panels, comment sections
699
+ - [ ] **`NgOptimizedImage`** on all `<img>` tags — add `priority` attribute to the LCP image
700
+ - [ ] **Lazy-loaded routes** with `loadComponent: () => import('./page').then(m => m.PageComponent)`
701
+ - [ ] **`toSignal()`** instead of `.subscribe()` in components — automatic teardown, no leaks
702
+ - [ ] **SSR-aware code** — no `window`/`document`/`localStorage` in constructors; use `afterRender()` or `isPlatformBrowser()`
703
+ - [ ] **`@defer (on idle)`** for non-critical UI loaded after page is interactive
704
+ - [ ] **`provideClientHydration()`** in app.config.ts for SSR + hydration
705
+ - [ ] **`trackBy` on mat-table** — provide `trackBy` function to `<mat-table [dataSource]>`
706
+ - [ ] **Virtual scrolling** for long lists (500+ items) — use `cdk-virtual-scroll-viewport`
707
+ - [ ] **Bundle size** — check `ng build --stats-json` + `webpack-bundle-analyzer`; no library imported in root if only used in one lazy route
708
+
709
+ ### @defer Patterns
710
+
711
+ ```html
712
+ <!-- Defer chart section until it enters viewport -->
713
+ @defer (on viewport) {
714
+ <app-analytics-chart [data]="chartData()" />
715
+ } @placeholder {
716
+ <div class="chart-skeleton" style="height: 300px; background: #f1f5f9; border-radius: 8px;"></div>
717
+ } @loading (minimum 500ms) {
718
+ <mat-progress-bar mode="indeterminate" />
719
+ } @error {
720
+ <p>Chart failed to load.</p>
721
+ }
722
+
723
+ <!-- Defer non-critical panel until browser is idle -->
724
+ @defer (on idle) {
725
+ <app-recommendations-panel />
726
+ }
727
+
728
+ <!-- Conditional defer — only load when user triggers it -->
729
+ @defer (on interaction(triggerEl)) {
730
+ <app-heavy-modal />
731
+ }
732
+ <button #triggerEl mat-button>Show Details</button>
733
+ ```
734
+
735
+ ### NgOptimizedImage
736
+ ```typescript
737
+ // In component imports:
738
+ import { NgOptimizedImage } from '@angular/common';
739
+
740
+ // In template:
741
+ // LCP image — add priority:
742
+ <img ngSrc="/hero.webp" width="1200" height="600" priority alt="Hero banner" />
743
+
744
+ // Standard lazy-loaded image:
745
+ <img ngSrc="/thumbnail.webp" width="300" height="200" alt="Product photo" />
746
+
747
+ // Dynamic from CDN with loader:
748
+ <img [ngSrc]="product.imageUrl" width="400" height="400" alt="{{ product.name }}" />
749
+ ```
750
+
751
+ ---
752
+
753
+ ## Section 11: Reference Files
754
+
755
+ These files contain expanded catalogs, code patterns, and decision matrices. Instruct Claude to read them when the task requires deeper detail:
756
+
757
+ | File | When to Read |
758
+ |---|---|
759
+ | `references/components.md` | Full Angular Material 3 + CDK component catalog with annotated code examples, inputs/outputs, and common patterns for each component |
760
+ | `references/theming.md` | Complete M3 theming system: custom tonal palettes, dark/light switching, per-component theme overrides, CSS custom property reference, typography scale, density |
761
+ | `references/patterns.md` | Signals patterns (effect, resource, linkedSignal), smart/dumb component split, routing UX (skeleton screens, route transitions, breadcrumbs), state management patterns |
762
+ | `references/animations.md` | Angular Animations module, route transition animations, list stagger/reorder, micro-interactions, `@keyframes` vs Angular `animate()`, reduced-motion media query |
763
+ | `references/performance.md` | `@defer` reference, `NgOptimizedImage` loader setup, SSR hydration patterns, virtual scrolling, bundle splitting, Core Web Vitals measurement in Angular |
764
+ | `references/accessibility.md` | CDK `A11yModule` (LiveAnnouncer, FocusTrap, FocusMonitor), ARIA roles with Angular Material, keyboard navigation patterns, color contrast in M3, screen reader testing |
765
+ | `references/alt-libraries.md` | NG-ZORRO vs PrimeNG vs Angular Material decision matrix: when each wins, migration notes, bundle size comparison, Angular version compatibility |
766
+
767
+ ### Quick Access Prompts
768
+ When helping with a specific task, pre-load context with:
769
+ - `Read references/components.md for the mat-table section` — for sortable/filterable table UX
770
+ - `Read references/theming.md for the dark mode section` — for dark/light toggle implementation
771
+ - `Read references/patterns.md for the smart/dumb split` — for component decomposition guidance
772
+ - `Read references/animations.md for route transitions` — for page-to-page motion design
773
+ - `Read references/accessibility.md for keyboard nav` — for ARIA and CDK FocusTrap patterns
774
+
775
+ ---
776
+
777
+ ## Quick Reference: Angular 17+ Standalone Component Template
778
+
779
+ ```typescript
780
+ import {
781
+ Component,
782
+ ChangeDetectionStrategy,
783
+ signal,
784
+ computed,
785
+ input,
786
+ output,
787
+ inject,
788
+ } from '@angular/core';
789
+ import { MatButtonModule } from '@angular/material/button';
790
+ import { MatCardModule } from '@angular/material/card';
791
+
792
+ @Component({
793
+ selector: 'app-my-component',
794
+ standalone: true,
795
+ imports: [MatButtonModule, MatCardModule],
796
+ changeDetection: ChangeDetectionStrategy.OnPush,
797
+ host: { class: 'app-my-component' },
798
+ styles: `
799
+ :host {
800
+ display: block;
801
+ }
802
+ `,
803
+ template: `
804
+ <mat-card>
805
+ <mat-card-header>
806
+ <mat-card-title>{{ title() }}</mat-card-title>
807
+ </mat-card-header>
808
+ <mat-card-content>
809
+ @if (loading()) {
810
+ <mat-progress-bar mode="indeterminate" />
811
+ } @else {
812
+ <p>{{ content() }}</p>
813
+ }
814
+ </mat-card-content>
815
+ <mat-card-actions>
816
+ <button mat-flat-button (click)="confirm.emit()">Confirm</button>
817
+ <button mat-button (click)="cancel.emit()">Cancel</button>
818
+ </mat-card-actions>
819
+ </mat-card>
820
+ `,
821
+ })
822
+ export class MyComponent {
823
+ // Inputs
824
+ readonly title = input.required<string>();
825
+ readonly content = input<string>('');
826
+
827
+ // Outputs
828
+ readonly confirm = output<void>();
829
+ readonly cancel = output<void>();
830
+
831
+ // Local state
832
+ readonly loading = signal(false);
833
+
834
+ // Derived state
835
+ readonly hasContent = computed(() => this.content().length > 0);
836
+
837
+ // Services
838
+ private myService = inject(MyService);
839
+ }
840
+ ```
841
+
842
+ ---
843
+
844
+ *This skill covers Angular 17+ with standalone components, signals, and Angular Material 3. For Angular 14–16 NgModule-based patterns, note that the core UX guidance remains valid but syntax differs (use `ngOnDestroy` + `Subject` for cleanup, `*ngIf`/`*ngFor` directives, and `@NgModule` imports arrays).*