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.
- 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,1116 @@
|
|
|
1
|
+
# Angular Material 3 + CDK Component Catalog
|
|
2
|
+
|
|
3
|
+
A reference catalog for Angular Material 3 and CDK components. Each entry covers purpose, usage guidance, selector, key inputs/outputs, accessibility notes, and a minimal code example.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Actions
|
|
8
|
+
|
|
9
|
+
### Button Variants (`mat-button`)
|
|
10
|
+
|
|
11
|
+
Angular Material provides six button variants mapped to M3 emphasis levels:
|
|
12
|
+
|
|
13
|
+
| Directive | M3 Variant | Emphasis | Use When |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `mat-flat-button` | Filled | Highest | Primary CTA, one per screen |
|
|
16
|
+
| `mat-button` (with color) | Tonal | High | Secondary actions |
|
|
17
|
+
| `mat-raised-button` | Elevated | Medium | Actions needing separation from background |
|
|
18
|
+
| `mat-stroked-button` | Outlined | Low | Alternative actions, destructive confirmations |
|
|
19
|
+
| `mat-button` | Text | Lowest | Tertiary, inline, cancel buttons |
|
|
20
|
+
| `mat-icon-button` | Icon | — | Toolbar actions, compact spaces |
|
|
21
|
+
| `mat-fab` | FAB | — | Single primary action per view (mobile-first) |
|
|
22
|
+
| `mat-mini-fab` | Mini FAB | — | FAB in constrained spaces |
|
|
23
|
+
|
|
24
|
+
**M3 Emphasis Rule:** Use at most one filled button per screen region. Combine tonal + text for secondary + cancel. Never stack two filled buttons side by side.
|
|
25
|
+
|
|
26
|
+
**Key Inputs:**
|
|
27
|
+
- `color`: `'primary'` | `'accent'` | `'warn'`
|
|
28
|
+
- `disabled`: boolean
|
|
29
|
+
- `disableRipple`: boolean (avoid — ripple is an accessibility affordance)
|
|
30
|
+
|
|
31
|
+
**Accessibility:**
|
|
32
|
+
- All button variants are `<button>` elements — keyboard and screen reader accessible by default.
|
|
33
|
+
- `mat-icon-button` must have `aria-label` since it has no visible text.
|
|
34
|
+
- Never disable without explaining why (use `matTooltip` on the wrapper).
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<!-- Primary action -->
|
|
38
|
+
<button mat-flat-button color="primary" (click)="save()">
|
|
39
|
+
<mat-icon>save</mat-icon> Save
|
|
40
|
+
</button>
|
|
41
|
+
|
|
42
|
+
<!-- Secondary action -->
|
|
43
|
+
<button mat-stroked-button (click)="cancel()">Cancel</button>
|
|
44
|
+
|
|
45
|
+
<!-- Icon-only toolbar button -->
|
|
46
|
+
<button mat-icon-button aria-label="Delete item" (click)="delete()">
|
|
47
|
+
<mat-icon>delete</mat-icon>
|
|
48
|
+
</button>
|
|
49
|
+
|
|
50
|
+
<!-- FAB -->
|
|
51
|
+
<button mat-fab color="primary" aria-label="Add item" (click)="add()">
|
|
52
|
+
<mat-icon>add</mat-icon>
|
|
53
|
+
</button>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
58
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Forms & Inputs
|
|
64
|
+
|
|
65
|
+
### `mat-form-field` + `matInput`
|
|
66
|
+
|
|
67
|
+
The universal wrapper for text inputs, textareas, and select. Always pair with `mat-label` and `mat-error`.
|
|
68
|
+
|
|
69
|
+
**Appearance variants:** `fill` (default M3), `outline`
|
|
70
|
+
|
|
71
|
+
**When to use:** Any text entry. Do not place `mat-select` or `mat-autocomplete` outside a form field.
|
|
72
|
+
|
|
73
|
+
**Key Inputs:**
|
|
74
|
+
- `appearance`: `'fill'` | `'outline'`
|
|
75
|
+
- `floatLabel`: `'always'` | `'auto'` (default)
|
|
76
|
+
- `subscriptSizing`: `'fixed'` | `'dynamic'` (dynamic avoids layout shift when no hint/error)
|
|
77
|
+
- `matInput` directive: standard HTML input attributes (`type`, `placeholder`, `required`, `minlength`)
|
|
78
|
+
|
|
79
|
+
**Accessibility:** `mat-label` is automatically linked via `aria-labelledby`. Always include `mat-error` with meaningful messages — screen readers announce them on blur.
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<mat-form-field appearance="outline">
|
|
83
|
+
<mat-label>Email</mat-label>
|
|
84
|
+
<input matInput type="email" [formControl]="emailCtrl" required />
|
|
85
|
+
<mat-hint>We'll never share your email.</mat-hint>
|
|
86
|
+
<mat-error *ngIf="emailCtrl.hasError('required')">Email is required.</mat-error>
|
|
87
|
+
<mat-error *ngIf="emailCtrl.hasError('email')">Enter a valid email.</mat-error>
|
|
88
|
+
</mat-form-field>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### `mat-select` + `mat-option`
|
|
94
|
+
|
|
95
|
+
Dropdown select. Use for ≤10 options with known values. For search-as-you-type or >10 options, use `mat-autocomplete`.
|
|
96
|
+
|
|
97
|
+
**Key Inputs:**
|
|
98
|
+
- `multiple`: boolean — enables multi-select
|
|
99
|
+
- `[compareWith]`: function — for object values
|
|
100
|
+
- `panelClass`: string — style the dropdown panel
|
|
101
|
+
|
|
102
|
+
**Outputs:** `(selectionChange)`: `MatSelectChange`
|
|
103
|
+
|
|
104
|
+
**Accessibility:** Implements ARIA `listbox`/`option` roles. Always inside `mat-form-field`.
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<mat-form-field appearance="outline">
|
|
108
|
+
<mat-label>Country</mat-label>
|
|
109
|
+
<mat-select [formControl]="countryCtrl">
|
|
110
|
+
<mat-option value="">-- Select --</mat-option>
|
|
111
|
+
<mat-option *ngFor="let c of countries" [value]="c.code">
|
|
112
|
+
{{ c.name }}
|
|
113
|
+
</mat-option>
|
|
114
|
+
</mat-select>
|
|
115
|
+
<mat-error *ngIf="countryCtrl.hasError('required')">Required.</mat-error>
|
|
116
|
+
</mat-form-field>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### `mat-autocomplete`
|
|
122
|
+
|
|
123
|
+
Search-as-you-type dropdown. Requires `[matAutocomplete]` binding on an `matInput` and an `async` pipe or manual subscription to filter options.
|
|
124
|
+
|
|
125
|
+
**When to use:** >10 options, freeform + suggestion combined, or when options depend on search query.
|
|
126
|
+
|
|
127
|
+
**Key Inputs:**
|
|
128
|
+
- `[displayWith]`: function to convert option value to display string
|
|
129
|
+
- `autoSelectActiveOption`: boolean
|
|
130
|
+
- `panelWidth`: `'auto'` | string
|
|
131
|
+
|
|
132
|
+
**Outputs:** `(optionSelected)`: `MatAutocompleteSelectedEvent`
|
|
133
|
+
|
|
134
|
+
```html
|
|
135
|
+
<mat-form-field appearance="outline">
|
|
136
|
+
<mat-label>City</mat-label>
|
|
137
|
+
<input matInput [formControl]="cityCtrl" [matAutocomplete]="auto" />
|
|
138
|
+
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayCity">
|
|
139
|
+
<mat-option *ngFor="let city of filteredCities$ | async" [value]="city">
|
|
140
|
+
{{ city.name }}, {{ city.country }}
|
|
141
|
+
</mat-option>
|
|
142
|
+
</mat-autocomplete>
|
|
143
|
+
</mat-form-field>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
filteredCities$ = this.cityCtrl.valueChanges.pipe(
|
|
148
|
+
startWith(''),
|
|
149
|
+
map(v => this.filter(v ?? ''))
|
|
150
|
+
);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### `mat-checkbox`
|
|
156
|
+
|
|
157
|
+
Boolean form field. Use for independent true/false choices or multi-select lists.
|
|
158
|
+
|
|
159
|
+
**Do not use** for mutually exclusive choices — use `mat-radio-group` instead.
|
|
160
|
+
|
|
161
|
+
**Key Inputs:**
|
|
162
|
+
- `[checked]`: boolean
|
|
163
|
+
- `[indeterminate]`: boolean (for "select all" parent states)
|
|
164
|
+
- `[labelPosition]`: `'before'` | `'after'`
|
|
165
|
+
- `[formControl]`
|
|
166
|
+
|
|
167
|
+
**Outputs:** `(change)`: `MatCheckboxChange`
|
|
168
|
+
|
|
169
|
+
**Accessibility:** Renders as `<input type="checkbox">`. Indeterminate state is conveyed via `aria-checked="mixed"`.
|
|
170
|
+
|
|
171
|
+
```html
|
|
172
|
+
<mat-checkbox [formControl]="agreeCtrl" labelPosition="after">
|
|
173
|
+
I agree to the <a href="/terms">Terms of Service</a>
|
|
174
|
+
</mat-checkbox>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### `mat-radio-group` + `mat-radio-button`
|
|
180
|
+
|
|
181
|
+
Exclusive selection from a small set. Use when choices are ≤5 and all options should be visible simultaneously.
|
|
182
|
+
|
|
183
|
+
**Key Inputs on group:**
|
|
184
|
+
- `[value]`: selected value
|
|
185
|
+
- `[formControl]`
|
|
186
|
+
- `name`: auto-generated but can be overridden
|
|
187
|
+
|
|
188
|
+
**Key Inputs on button:**
|
|
189
|
+
- `[value]`: option value
|
|
190
|
+
- `[disabled]`: boolean per option
|
|
191
|
+
|
|
192
|
+
**Accessibility:** Implements ARIA `radiogroup`/`radio`. Group must have an accessible name via `aria-label` or `aria-labelledby`.
|
|
193
|
+
|
|
194
|
+
```html
|
|
195
|
+
<label id="plan-label">Billing Plan</label>
|
|
196
|
+
<mat-radio-group [formControl]="planCtrl" aria-labelledby="plan-label">
|
|
197
|
+
<mat-radio-button value="monthly">Monthly</mat-radio-button>
|
|
198
|
+
<mat-radio-button value="annual">Annual (save 20%)</mat-radio-button>
|
|
199
|
+
<mat-radio-button value="lifetime">Lifetime</mat-radio-button>
|
|
200
|
+
</mat-radio-group>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### `mat-slide-toggle`
|
|
206
|
+
|
|
207
|
+
Binary setting that takes effect immediately (no form submission needed). Distinguish from checkbox: use slide-toggle for settings panels, checkbox for form fields.
|
|
208
|
+
|
|
209
|
+
**Key Inputs:**
|
|
210
|
+
- `[formControl]`
|
|
211
|
+
- `[checked]`: boolean
|
|
212
|
+
- `color`: `'primary'` | `'accent'` | `'warn'`
|
|
213
|
+
- `labelPosition`: `'before'` | `'after'`
|
|
214
|
+
|
|
215
|
+
**Outputs:** `(change)`: `MatSlideToggleChange`
|
|
216
|
+
|
|
217
|
+
```html
|
|
218
|
+
<mat-slide-toggle [formControl]="notificationsCtrl" color="primary">
|
|
219
|
+
Push notifications
|
|
220
|
+
</mat-slide-toggle>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### `mat-datepicker`
|
|
226
|
+
|
|
227
|
+
Date input with calendar popup. Always set `[min]` and `[max]` to guide users. Always include `mat-datepicker-toggle`.
|
|
228
|
+
|
|
229
|
+
**Key Inputs:**
|
|
230
|
+
- `[min]`: Date
|
|
231
|
+
- `[max]`: Date
|
|
232
|
+
- `[startView]`: `'month'` | `'year'` | `'multi-year'`
|
|
233
|
+
- `[dateFilter]`: function to disable specific dates
|
|
234
|
+
- `touchUi`: boolean (mobile-friendly full-screen picker)
|
|
235
|
+
|
|
236
|
+
**Accessibility:** Requires `MatDatepickerModule` and a `DateAdapter`. Calendar is keyboard navigable with arrow keys.
|
|
237
|
+
|
|
238
|
+
```html
|
|
239
|
+
<mat-form-field appearance="outline">
|
|
240
|
+
<mat-label>Date of Birth</mat-label>
|
|
241
|
+
<input matInput [matDatepicker]="picker" [formControl]="dobCtrl"
|
|
242
|
+
[max]="maxDate" />
|
|
243
|
+
<mat-datepicker-toggle matIconSuffix [for]="picker" />
|
|
244
|
+
<mat-datepicker #picker startView="multi-year" />
|
|
245
|
+
<mat-error *ngIf="dobCtrl.hasError('matDatepickerMax')">
|
|
246
|
+
Date must be in the past.
|
|
247
|
+
</mat-error>
|
|
248
|
+
</mat-form-field>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### `mat-slider`
|
|
254
|
+
|
|
255
|
+
Numeric range input. Use for approximate values where exact input isn't critical (volume, opacity, price range). Always display the current value.
|
|
256
|
+
|
|
257
|
+
**Key Inputs:**
|
|
258
|
+
- `[min]`: number (default 0)
|
|
259
|
+
- `[max]`: number (default 100)
|
|
260
|
+
- `[step]`: number
|
|
261
|
+
- `discrete`: boolean (shows value bubble on drag)
|
|
262
|
+
- `showTickMarks`: boolean
|
|
263
|
+
|
|
264
|
+
**Accessibility:** Renders as `<input type="range">`. Always pair with a visible label.
|
|
265
|
+
|
|
266
|
+
```html
|
|
267
|
+
<label id="vol-label">Volume: {{ volumeCtrl.value }}</label>
|
|
268
|
+
<mat-slider min="0" max="100" step="5" discrete aria-labelledby="vol-label">
|
|
269
|
+
<input matSliderThumb [formControl]="volumeCtrl" />
|
|
270
|
+
</mat-slider>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
### `mat-chips` + `mat-chip-grid`
|
|
276
|
+
|
|
277
|
+
Tag input for multi-select with search. Use `mat-chip-grid` for interactive chip inputs; use `mat-chip-listbox` for selection chips; use `mat-chip-set` for display-only.
|
|
278
|
+
|
|
279
|
+
**Key Outputs:**
|
|
280
|
+
- `(removed)`: `MatChipEvent` on chip grid
|
|
281
|
+
- `(chipEnd)`: `MatChipInputEvent` from `[matChipInputFor]`
|
|
282
|
+
|
|
283
|
+
```html
|
|
284
|
+
<mat-form-field appearance="outline">
|
|
285
|
+
<mat-label>Tags</mat-label>
|
|
286
|
+
<mat-chip-grid #chipGrid>
|
|
287
|
+
<mat-chip-row *ngFor="let tag of tags" (removed)="remove(tag)">
|
|
288
|
+
{{ tag }}
|
|
289
|
+
<button matChipRemove aria-label="Remove {{ tag }}">
|
|
290
|
+
<mat-icon>cancel</mat-icon>
|
|
291
|
+
</button>
|
|
292
|
+
</mat-chip-row>
|
|
293
|
+
</mat-chip-grid>
|
|
294
|
+
<input placeholder="Add tag..." [matChipInputFor]="chipGrid"
|
|
295
|
+
(matChipInputTokenEnd)="add($event)" />
|
|
296
|
+
</mat-form-field>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Navigation
|
|
302
|
+
|
|
303
|
+
### `mat-toolbar`
|
|
304
|
+
|
|
305
|
+
App bar / top navigation bar. For persistent navigation, use `position: sticky; top: 0` in CSS.
|
|
306
|
+
|
|
307
|
+
**Key Inputs:**
|
|
308
|
+
- `color`: `'primary'` | `'accent'` | `'warn'`
|
|
309
|
+
|
|
310
|
+
```html
|
|
311
|
+
<mat-toolbar color="primary" class="app-toolbar">
|
|
312
|
+
<button mat-icon-button aria-label="Open menu" (click)="sidenav.toggle()">
|
|
313
|
+
<mat-icon>menu</mat-icon>
|
|
314
|
+
</button>
|
|
315
|
+
<span class="toolbar-title">My App</span>
|
|
316
|
+
<span class="spacer"></span>
|
|
317
|
+
<button mat-icon-button aria-label="User account">
|
|
318
|
+
<mat-icon>account_circle</mat-icon>
|
|
319
|
+
</button>
|
|
320
|
+
</mat-toolbar>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
```scss
|
|
324
|
+
.app-toolbar { position: sticky; top: 0; z-index: 1000; }
|
|
325
|
+
.spacer { flex: 1; }
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### `mat-sidenav-container` + `mat-sidenav` + `mat-sidenav-content`
|
|
331
|
+
|
|
332
|
+
Side navigation layout. Handles desktop persistent nav and mobile overlay nav.
|
|
333
|
+
|
|
334
|
+
**`mat-sidenav` Key Inputs:**
|
|
335
|
+
- `mode`: `'over'` (overlay, default mobile) | `'side'` (pushes content, desktop) | `'push'`
|
|
336
|
+
- `opened`: boolean
|
|
337
|
+
- `position`: `'start'` | `'end'`
|
|
338
|
+
- `fixedInViewport`: boolean (sticky sidenav regardless of scroll)
|
|
339
|
+
|
|
340
|
+
**Outputs:** `(opened)`, `(closed)`, `(openedChange)`
|
|
341
|
+
|
|
342
|
+
**Responsive pattern — switch mode based on viewport:**
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
// component.ts
|
|
346
|
+
readonly isHandset$ = inject(BreakpointObserver)
|
|
347
|
+
.observe(Breakpoints.Handset)
|
|
348
|
+
.pipe(map(r => r.matches), shareReplay());
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
```html
|
|
352
|
+
<mat-sidenav-container>
|
|
353
|
+
<mat-sidenav #sidenav
|
|
354
|
+
[mode]="(isHandset$ | async) ? 'over' : 'side'"
|
|
355
|
+
[opened]="!(isHandset$ | async)">
|
|
356
|
+
<mat-nav-list>
|
|
357
|
+
<a mat-list-item routerLink="/dashboard" routerLinkActive="active">
|
|
358
|
+
<mat-icon matListItemIcon>dashboard</mat-icon>
|
|
359
|
+
<span matListItemTitle>Dashboard</span>
|
|
360
|
+
</a>
|
|
361
|
+
<a mat-list-item routerLink="/settings" routerLinkActive="active">
|
|
362
|
+
<mat-icon matListItemIcon>settings</mat-icon>
|
|
363
|
+
<span matListItemTitle>Settings</span>
|
|
364
|
+
</a>
|
|
365
|
+
</mat-nav-list>
|
|
366
|
+
</mat-sidenav>
|
|
367
|
+
<mat-sidenav-content>
|
|
368
|
+
<router-outlet />
|
|
369
|
+
</mat-sidenav-content>
|
|
370
|
+
</mat-sidenav-container>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
### `mat-tab-group` + `mat-tab`
|
|
376
|
+
|
|
377
|
+
Horizontal tabs for switching between related views. Keep ≤5 tabs visible; use `mat-tab-nav-bar` for router-linked tabs.
|
|
378
|
+
|
|
379
|
+
**Key Inputs on group:**
|
|
380
|
+
- `[selectedIndex]`
|
|
381
|
+
- `animationDuration`: `'0ms'` to disable animation
|
|
382
|
+
- `mat-stretch-tabs`: boolean (fills full width)
|
|
383
|
+
- `color`, `backgroundColor`
|
|
384
|
+
|
|
385
|
+
**Outputs:** `(selectedTabChange)`: `MatTabChangeEvent`
|
|
386
|
+
|
|
387
|
+
```html
|
|
388
|
+
<mat-tab-group [selectedIndex]="activeTab" (selectedIndexChange)="activeTab = $event">
|
|
389
|
+
<mat-tab label="Overview">
|
|
390
|
+
<ng-template matTabContent> <!-- lazy loaded -->
|
|
391
|
+
<app-overview />
|
|
392
|
+
</ng-template>
|
|
393
|
+
</mat-tab>
|
|
394
|
+
<mat-tab label="Analytics">
|
|
395
|
+
<ng-template matTabContent>
|
|
396
|
+
<app-analytics />
|
|
397
|
+
</ng-template>
|
|
398
|
+
</mat-tab>
|
|
399
|
+
</mat-tab-group>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
### `mat-stepper`
|
|
405
|
+
|
|
406
|
+
Multi-step wizard. Use `linear` for guided flows where each step must be completed before proceeding.
|
|
407
|
+
|
|
408
|
+
**Key Inputs:**
|
|
409
|
+
- `linear`: boolean — validates before allowing next step
|
|
410
|
+
- `orientation`: `'horizontal'` | `'vertical'`
|
|
411
|
+
- `[selectedIndex]`
|
|
412
|
+
|
|
413
|
+
**Step Inputs:**
|
|
414
|
+
- `[stepControl]`: AbstractControl — form group for step validation
|
|
415
|
+
- `label`: string or `matStepLabel` template
|
|
416
|
+
- `optional`: boolean
|
|
417
|
+
- `editable`: boolean
|
|
418
|
+
|
|
419
|
+
```html
|
|
420
|
+
<mat-stepper linear orientation="vertical" #stepper>
|
|
421
|
+
<mat-step [stepControl]="accountForm" label="Account">
|
|
422
|
+
<form [formGroup]="accountForm">
|
|
423
|
+
<mat-form-field appearance="outline">
|
|
424
|
+
<mat-label>Email</mat-label>
|
|
425
|
+
<input matInput formControlName="email" type="email" />
|
|
426
|
+
</mat-form-field>
|
|
427
|
+
<div>
|
|
428
|
+
<button mat-flat-button color="primary" matStepperNext>Next</button>
|
|
429
|
+
</div>
|
|
430
|
+
</form>
|
|
431
|
+
</mat-step>
|
|
432
|
+
<mat-step [stepControl]="profileForm" label="Profile">
|
|
433
|
+
<form [formGroup]="profileForm">
|
|
434
|
+
<!-- fields -->
|
|
435
|
+
<button mat-stroked-button matStepperPrevious>Back</button>
|
|
436
|
+
<button mat-flat-button color="primary" matStepperNext>Next</button>
|
|
437
|
+
</form>
|
|
438
|
+
</mat-step>
|
|
439
|
+
<mat-step label="Confirm">
|
|
440
|
+
<button mat-flat-button color="primary" (click)="submit()">Submit</button>
|
|
441
|
+
</mat-step>
|
|
442
|
+
</mat-stepper>
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
### `mat-menu` + `mat-menu-item`
|
|
448
|
+
|
|
449
|
+
Contextual menus triggered by user action. Never use for primary navigation.
|
|
450
|
+
|
|
451
|
+
**Key Inputs on menu:**
|
|
452
|
+
- `xPosition`: `'before'` | `'after'`
|
|
453
|
+
- `yPosition`: `'above'` | `'below'`
|
|
454
|
+
- `overlapTrigger`: boolean
|
|
455
|
+
|
|
456
|
+
**Trigger directive:** `[matMenuTriggerFor]="menu"` on any element.
|
|
457
|
+
|
|
458
|
+
**Accessibility:** Implements ARIA `menu`/`menuitem`. Keyboard: Enter/Space to open, arrows to navigate, Escape to close.
|
|
459
|
+
|
|
460
|
+
```html
|
|
461
|
+
<button mat-icon-button [matMenuTriggerFor]="actionsMenu" aria-label="More actions">
|
|
462
|
+
<mat-icon>more_vert</mat-icon>
|
|
463
|
+
</button>
|
|
464
|
+
|
|
465
|
+
<mat-menu #actionsMenu>
|
|
466
|
+
<button mat-menu-item (click)="edit()">
|
|
467
|
+
<mat-icon>edit</mat-icon> Edit
|
|
468
|
+
</button>
|
|
469
|
+
<button mat-menu-item (click)="duplicate()">
|
|
470
|
+
<mat-icon>content_copy</mat-icon> Duplicate
|
|
471
|
+
</button>
|
|
472
|
+
<mat-divider />
|
|
473
|
+
<button mat-menu-item class="danger" (click)="delete()">
|
|
474
|
+
<mat-icon>delete</mat-icon> Delete
|
|
475
|
+
</button>
|
|
476
|
+
</mat-menu>
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Data Display
|
|
482
|
+
|
|
483
|
+
### `mat-table` + `matColumnDef`
|
|
484
|
+
|
|
485
|
+
Feature-rich data table. Always implement `trackBy` for performance. Provide loading and empty states.
|
|
486
|
+
|
|
487
|
+
**Directives:**
|
|
488
|
+
- `matColumnDef`: defines a column
|
|
489
|
+
- `matHeaderCellDef`, `matCellDef`, `matFooterCellDef`: cell templates
|
|
490
|
+
- `matHeaderRowDef`, `matRowDef`: row templates
|
|
491
|
+
- `matSort`: add to table for sortable columns
|
|
492
|
+
- `mat-sort-header`: add to `<th>` to make sortable
|
|
493
|
+
|
|
494
|
+
**Key Inputs:**
|
|
495
|
+
- `[dataSource]`: array, Observable, or `MatTableDataSource`
|
|
496
|
+
- `[trackBy]`: function
|
|
497
|
+
- `multiTemplateDataRows`: boolean (expandable rows)
|
|
498
|
+
|
|
499
|
+
**Accessibility:** Renders as `<table>` with proper `<thead>`, `<tbody>`. Sort headers announce direction changes.
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
// component.ts
|
|
503
|
+
dataSource = new MatTableDataSource<User>();
|
|
504
|
+
displayedColumns = ['name', 'email', 'role', 'actions'];
|
|
505
|
+
isLoading = signal(true);
|
|
506
|
+
|
|
507
|
+
@ViewChild(MatSort) sort!: MatSort;
|
|
508
|
+
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
|
509
|
+
|
|
510
|
+
ngAfterViewInit() {
|
|
511
|
+
this.dataSource.sort = this.sort;
|
|
512
|
+
this.dataSource.paginator = this.paginator;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
trackByUserId = (_: number, user: User) => user.id;
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
```html
|
|
519
|
+
<div class="table-container">
|
|
520
|
+
<!-- Loading -->
|
|
521
|
+
<mat-progress-bar *ngIf="isLoading()" mode="indeterminate" />
|
|
522
|
+
|
|
523
|
+
<table mat-table [dataSource]="dataSource" matSort [trackBy]="trackByUserId">
|
|
524
|
+
|
|
525
|
+
<!-- Name Column -->
|
|
526
|
+
<ng-container matColumnDef="name">
|
|
527
|
+
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
|
|
528
|
+
<td mat-cell *matCellDef="let row">{{ row.name }}</td>
|
|
529
|
+
</ng-container>
|
|
530
|
+
|
|
531
|
+
<!-- Email Column -->
|
|
532
|
+
<ng-container matColumnDef="email">
|
|
533
|
+
<th mat-header-cell *matHeaderCellDef mat-sort-header>Email</th>
|
|
534
|
+
<td mat-cell *matCellDef="let row">{{ row.email }}</td>
|
|
535
|
+
</ng-container>
|
|
536
|
+
|
|
537
|
+
<!-- Actions Column -->
|
|
538
|
+
<ng-container matColumnDef="actions" stickyEnd>
|
|
539
|
+
<th mat-header-cell *matHeaderCellDef></th>
|
|
540
|
+
<td mat-cell *matCellDef="let row">
|
|
541
|
+
<button mat-icon-button (click)="edit(row)" aria-label="Edit">
|
|
542
|
+
<mat-icon>edit</mat-icon>
|
|
543
|
+
</button>
|
|
544
|
+
</td>
|
|
545
|
+
</ng-container>
|
|
546
|
+
|
|
547
|
+
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
|
548
|
+
<tr mat-row *matRowDef="let row; columns: displayedColumns;"
|
|
549
|
+
[class.selected]="selectedRow === row"
|
|
550
|
+
(click)="select(row)"></tr>
|
|
551
|
+
|
|
552
|
+
<!-- Empty state -->
|
|
553
|
+
<tr class="mat-row" *matNoDataRow>
|
|
554
|
+
<td class="mat-cell empty-state" [attr.colspan]="displayedColumns.length">
|
|
555
|
+
No records found.
|
|
556
|
+
</td>
|
|
557
|
+
</tr>
|
|
558
|
+
</table>
|
|
559
|
+
|
|
560
|
+
<mat-paginator [pageSizeOptions]="[10, 25, 50]" showFirstLastButtons />
|
|
561
|
+
</div>
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
### `mat-paginator`
|
|
567
|
+
|
|
568
|
+
Pagination controls. Always pair with `MatTableDataSource` or handle `(page)` events manually for server-side pagination.
|
|
569
|
+
|
|
570
|
+
**Key Inputs:**
|
|
571
|
+
- `[length]`: total item count (required for server-side)
|
|
572
|
+
- `[pageSize]`: default page size
|
|
573
|
+
- `[pageSizeOptions]`: number[]
|
|
574
|
+
- `showFirstLastButtons`: boolean
|
|
575
|
+
- `[pageIndex]`: current page (0-based)
|
|
576
|
+
|
|
577
|
+
**Outputs:** `(page)`: `PageEvent`
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
### `mat-list` + `mat-list-item`
|
|
582
|
+
|
|
583
|
+
Simple item lists. Use `mat-nav-list` for lists of links. Use `mat-action-list` for clickable non-link items.
|
|
584
|
+
|
|
585
|
+
**Slot directives on list-item:**
|
|
586
|
+
- `matListItemTitle`: primary text
|
|
587
|
+
- `matListItemLine`: secondary/tertiary text (up to 3)
|
|
588
|
+
- `matListItemIcon`: leading icon
|
|
589
|
+
- `matListItemAvatar`: leading avatar
|
|
590
|
+
- `[matListItemMeta]`: trailing content
|
|
591
|
+
|
|
592
|
+
```html
|
|
593
|
+
<mat-list>
|
|
594
|
+
<mat-list-item *ngFor="let file of files">
|
|
595
|
+
<mat-icon matListItemIcon>insert_drive_file</mat-icon>
|
|
596
|
+
<span matListItemTitle>{{ file.name }}</span>
|
|
597
|
+
<span matListItemLine>{{ file.size | filesize }} · {{ file.modified | date }}</span>
|
|
598
|
+
<button matListItemMeta mat-icon-button aria-label="Download">
|
|
599
|
+
<mat-icon>download</mat-icon>
|
|
600
|
+
</button>
|
|
601
|
+
</mat-list-item>
|
|
602
|
+
</mat-list>
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
### `mat-card`
|
|
608
|
+
|
|
609
|
+
Content container. Use for grouping related information. Do not overuse — cards add visual weight.
|
|
610
|
+
|
|
611
|
+
**Sub-components:**
|
|
612
|
+
- `mat-card-header`: title row (use `mat-card-title`, `mat-card-subtitle`)
|
|
613
|
+
- `mat-card-content`: main body (padded)
|
|
614
|
+
- `mat-card-actions`: button row (use `align="end"`)
|
|
615
|
+
- `mat-card-footer`: bottom metadata
|
|
616
|
+
|
|
617
|
+
**Inputs:** `appearance`: `'raised'` | `'outlined'` | `'filled'`
|
|
618
|
+
|
|
619
|
+
```html
|
|
620
|
+
<mat-card appearance="outlined">
|
|
621
|
+
<mat-card-header>
|
|
622
|
+
<img mat-card-avatar src="{{ user.avatar }}" alt="{{ user.name }}" />
|
|
623
|
+
<mat-card-title>{{ user.name }}</mat-card-title>
|
|
624
|
+
<mat-card-subtitle>{{ user.role }}</mat-card-subtitle>
|
|
625
|
+
</mat-card-header>
|
|
626
|
+
<mat-card-content>
|
|
627
|
+
<p>{{ user.bio }}</p>
|
|
628
|
+
</mat-card-content>
|
|
629
|
+
<mat-card-actions align="end">
|
|
630
|
+
<button mat-stroked-button>View Profile</button>
|
|
631
|
+
<button mat-flat-button color="primary">Message</button>
|
|
632
|
+
</mat-card-actions>
|
|
633
|
+
</mat-card>
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
### `mat-expansion-panel`
|
|
639
|
+
|
|
640
|
+
Accordion panels for progressive disclosure. Group with `mat-accordion` to enforce single-open behavior.
|
|
641
|
+
|
|
642
|
+
**Key Inputs on panel:**
|
|
643
|
+
- `expanded`: boolean
|
|
644
|
+
- `disabled`: boolean
|
|
645
|
+
- `hideToggle`: boolean
|
|
646
|
+
|
|
647
|
+
**Key Inputs on accordion:**
|
|
648
|
+
- `multi`: boolean (allow multiple panels open; default false)
|
|
649
|
+
- `displayMode`: `'default'` | `'flat'`
|
|
650
|
+
|
|
651
|
+
**Outputs:** `(opened)`, `(closed)`, `(expandedChange)`
|
|
652
|
+
|
|
653
|
+
```html
|
|
654
|
+
<mat-accordion>
|
|
655
|
+
<mat-expansion-panel *ngFor="let section of sections">
|
|
656
|
+
<mat-expansion-panel-header>
|
|
657
|
+
<mat-panel-title>{{ section.title }}</mat-panel-title>
|
|
658
|
+
<mat-panel-description>{{ section.summary }}</mat-panel-description>
|
|
659
|
+
</mat-expansion-panel-header>
|
|
660
|
+
<p>{{ section.content }}</p>
|
|
661
|
+
</mat-expansion-panel>
|
|
662
|
+
</mat-accordion>
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
### `mat-badge`
|
|
668
|
+
|
|
669
|
+
Notification count overlaid on icons or buttons. Use for unread counts, cart quantities, or status indicators.
|
|
670
|
+
|
|
671
|
+
**Directive:** `[matBadge]` applied to host element.
|
|
672
|
+
|
|
673
|
+
**Key Inputs:**
|
|
674
|
+
- `[matBadge]`: string | number (the badge content)
|
|
675
|
+
- `matBadgeColor`: `'primary'` | `'accent'` | `'warn'`
|
|
676
|
+
- `matBadgePosition`: `'above after'` | `'above before'` | `'below after'` | `'below before'`
|
|
677
|
+
- `[matBadgeHidden]`: boolean (hide when count is 0)
|
|
678
|
+
- `matBadgeSize`: `'small'` | `'medium'` | `'large'`
|
|
679
|
+
|
|
680
|
+
**Accessibility:** Set `matBadgeDescription` for screen readers (e.g., `"3 unread notifications"`).
|
|
681
|
+
|
|
682
|
+
```html
|
|
683
|
+
<button mat-icon-button
|
|
684
|
+
[matBadge]="unreadCount"
|
|
685
|
+
[matBadgeHidden]="unreadCount === 0"
|
|
686
|
+
matBadgeColor="warn"
|
|
687
|
+
matBadgeDescription="{{ unreadCount }} unread notifications"
|
|
688
|
+
aria-label="Notifications">
|
|
689
|
+
<mat-icon>notifications</mat-icon>
|
|
690
|
+
</button>
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
### `mat-chip` (Display)
|
|
696
|
+
|
|
697
|
+
Read-only tags/categories. For interactive chips, use `mat-chip-listbox`; for input, use `mat-chip-grid`.
|
|
698
|
+
|
|
699
|
+
```html
|
|
700
|
+
<!-- Category tags -->
|
|
701
|
+
<mat-chip-set aria-label="Article categories">
|
|
702
|
+
<mat-chip *ngFor="let tag of article.tags">{{ tag }}</mat-chip>
|
|
703
|
+
</mat-chip-set>
|
|
704
|
+
|
|
705
|
+
<!-- Status chip with icon -->
|
|
706
|
+
<mat-chip color="primary" highlighted>
|
|
707
|
+
<mat-icon matChipAvatar>check_circle</mat-icon>
|
|
708
|
+
Active
|
|
709
|
+
</mat-chip>
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
### `mat-divider`
|
|
715
|
+
|
|
716
|
+
Visual separator. Use sparingly — whitespace is usually preferable. Use `inset` for list dividers that align with text.
|
|
717
|
+
|
|
718
|
+
```html
|
|
719
|
+
<mat-divider /> <!-- full-width -->
|
|
720
|
+
<mat-divider inset /> <!-- inset (aligns with list text) -->
|
|
721
|
+
<mat-divider vertical /> <!-- vertical (in flex containers) -->
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
## Feedback & Overlays
|
|
727
|
+
|
|
728
|
+
### `MatDialog`
|
|
729
|
+
|
|
730
|
+
Modal dialogs for focused user tasks. Inject `MatDialog` service. Pass data via `MAT_DIALOG_DATA` token. Receive results via `afterClosed()`.
|
|
731
|
+
|
|
732
|
+
**`dialog.open()` Config:**
|
|
733
|
+
- `data`: any — passed to dialog component
|
|
734
|
+
- `width`, `maxWidth`, `height`: string
|
|
735
|
+
- `disableClose`: boolean (prevent backdrop click / Escape close)
|
|
736
|
+
- `autoFocus`: `'first-tabbable'` | `'dialog'` | `false`
|
|
737
|
+
- `panelClass`: string | string[] — add CSS classes to overlay
|
|
738
|
+
|
|
739
|
+
**Accessibility:** Focus is trapped inside the dialog. Escape closes by default. Dialog role is `dialog` with `aria-labelledby` pointing to title.
|
|
740
|
+
|
|
741
|
+
```typescript
|
|
742
|
+
// Caller component
|
|
743
|
+
import { MatDialog } from '@angular/material/dialog';
|
|
744
|
+
import { ConfirmDialogComponent } from './confirm-dialog.component';
|
|
745
|
+
|
|
746
|
+
readonly #dialog = inject(MatDialog);
|
|
747
|
+
|
|
748
|
+
openConfirm() {
|
|
749
|
+
const ref = this.#dialog.open(ConfirmDialogComponent, {
|
|
750
|
+
width: '400px',
|
|
751
|
+
data: { message: 'Delete this item permanently?' }
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
ref.afterClosed().subscribe(confirmed => {
|
|
755
|
+
if (confirmed) this.delete();
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
```typescript
|
|
761
|
+
// Dialog component
|
|
762
|
+
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|
763
|
+
|
|
764
|
+
@Component({
|
|
765
|
+
selector: 'app-confirm-dialog',
|
|
766
|
+
template: `
|
|
767
|
+
<h2 mat-dialog-title>Confirm</h2>
|
|
768
|
+
<mat-dialog-content>{{ data.message }}</mat-dialog-content>
|
|
769
|
+
<mat-dialog-actions align="end">
|
|
770
|
+
<button mat-stroked-button mat-dialog-close>Cancel</button>
|
|
771
|
+
<button mat-flat-button color="warn" [mat-dialog-close]="true">Delete</button>
|
|
772
|
+
</mat-dialog-actions>
|
|
773
|
+
`
|
|
774
|
+
})
|
|
775
|
+
export class ConfirmDialogComponent {
|
|
776
|
+
readonly data = inject<{ message: string }>(MAT_DIALOG_DATA);
|
|
777
|
+
}
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
### `MatSnackBar`
|
|
783
|
+
|
|
784
|
+
Brief, non-disruptive notifications. Max 2 lines of text. Use action button for undo patterns.
|
|
785
|
+
|
|
786
|
+
**Duration guidance:**
|
|
787
|
+
- Informational: `3000ms`
|
|
788
|
+
- Success with undo: `5000ms`
|
|
789
|
+
- Errors with action: `10000ms` or `0` (manual dismiss)
|
|
790
|
+
|
|
791
|
+
**`snackBar.open()` Config:**
|
|
792
|
+
- `duration`: ms (0 = no auto-dismiss)
|
|
793
|
+
- `horizontalPosition`: `'start'` | `'center'` | `'end'` | `'left'` | `'right'`
|
|
794
|
+
- `verticalPosition`: `'top'` | `'bottom'`
|
|
795
|
+
- `panelClass`: string[]
|
|
796
|
+
|
|
797
|
+
```typescript
|
|
798
|
+
readonly #snackBar = inject(MatSnackBar);
|
|
799
|
+
|
|
800
|
+
showSuccess(message: string) {
|
|
801
|
+
this.#snackBar.open(message, 'Dismiss', {
|
|
802
|
+
duration: 3000,
|
|
803
|
+
horizontalPosition: 'end',
|
|
804
|
+
verticalPosition: 'bottom',
|
|
805
|
+
panelClass: ['snack-success']
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
showUndo(message: string, undoFn: () => void) {
|
|
810
|
+
const ref = this.#snackBar.open(message, 'Undo', { duration: 5000 });
|
|
811
|
+
ref.onAction().subscribe(undoFn);
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
---
|
|
816
|
+
|
|
817
|
+
### `mat-progress-bar`
|
|
818
|
+
|
|
819
|
+
Linear progress indicator. Use `indeterminate` for unknown duration operations; `determinate` when progress percentage is known.
|
|
820
|
+
|
|
821
|
+
**Key Inputs:**
|
|
822
|
+
- `mode`: `'determinate'` | `'indeterminate'` | `'buffer'` | `'query'`
|
|
823
|
+
- `[value]`: 0–100 (for determinate)
|
|
824
|
+
- `[bufferValue]`: 0–100 (for buffer mode)
|
|
825
|
+
- `color`: `'primary'` | `'accent'` | `'warn'`
|
|
826
|
+
|
|
827
|
+
**Accessibility:** Has `role="progressbar"` with `aria-valuenow` for determinate mode. Add `aria-label`.
|
|
828
|
+
|
|
829
|
+
```html
|
|
830
|
+
<!-- Page-level loading bar (top of content) -->
|
|
831
|
+
<mat-progress-bar *ngIf="isLoading" mode="indeterminate" aria-label="Loading..." />
|
|
832
|
+
|
|
833
|
+
<!-- Upload progress -->
|
|
834
|
+
<mat-progress-bar mode="determinate" [value]="uploadProgress" aria-label="Upload progress" />
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
---
|
|
838
|
+
|
|
839
|
+
### `mat-spinner` / `mat-progress-spinner`
|
|
840
|
+
|
|
841
|
+
Circular progress. `mat-spinner` is shorthand for `mode="indeterminate"`. Use inside buttons for async action feedback.
|
|
842
|
+
|
|
843
|
+
**Key Inputs:**
|
|
844
|
+
- `mode`: `'determinate'` | `'indeterminate'`
|
|
845
|
+
- `[value]`: 0–100
|
|
846
|
+
- `[diameter]`: number (px, default 100)
|
|
847
|
+
- `[strokeWidth]`: number
|
|
848
|
+
|
|
849
|
+
```html
|
|
850
|
+
<!-- Button loading state -->
|
|
851
|
+
<button mat-flat-button color="primary" [disabled]="isSaving" (click)="save()">
|
|
852
|
+
<mat-spinner *ngIf="isSaving" diameter="20" strokeWidth="2" />
|
|
853
|
+
<span *ngIf="!isSaving">Save</span>
|
|
854
|
+
</button>
|
|
855
|
+
|
|
856
|
+
<!-- Full-page spinner -->
|
|
857
|
+
<div class="spinner-overlay" *ngIf="isLoading" role="status" aria-label="Loading">
|
|
858
|
+
<mat-spinner />
|
|
859
|
+
</div>
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
### `mat-tooltip`
|
|
865
|
+
|
|
866
|
+
Hover/focus hints for supplementary information. Never place critical information only in tooltips (not accessible on touch devices).
|
|
867
|
+
|
|
868
|
+
**Key Inputs:**
|
|
869
|
+
- `[matTooltip]`: string — tooltip text
|
|
870
|
+
- `matTooltipPosition`: `'above'` | `'below'` | `'left'` | `'right'` | `'before'` | `'after'`
|
|
871
|
+
- `[matTooltipDisabled]`: boolean
|
|
872
|
+
- `matTooltipShowDelay`, `matTooltipHideDelay`: ms
|
|
873
|
+
|
|
874
|
+
**Accessibility:** Content is exposed via `aria-describedby`. Does not work for touch-only users — always provide alternative text.
|
|
875
|
+
|
|
876
|
+
```html
|
|
877
|
+
<button mat-icon-button
|
|
878
|
+
aria-label="Delete item"
|
|
879
|
+
matTooltip="Delete this item permanently"
|
|
880
|
+
matTooltipPosition="above"
|
|
881
|
+
(click)="delete()">
|
|
882
|
+
<mat-icon>delete</mat-icon>
|
|
883
|
+
</button>
|
|
884
|
+
|
|
885
|
+
<!-- Disabled button with tooltip on wrapper -->
|
|
886
|
+
<span matTooltip="You don't have permission to edit">
|
|
887
|
+
<button mat-flat-button disabled>Edit</button>
|
|
888
|
+
</span>
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
### `MatBottomSheet`
|
|
894
|
+
|
|
895
|
+
Mobile-first action sheets from the bottom of the screen. Use instead of dialogs on mobile for action menus.
|
|
896
|
+
|
|
897
|
+
```typescript
|
|
898
|
+
readonly #bottomSheet = inject(MatBottomSheet);
|
|
899
|
+
|
|
900
|
+
openActions() {
|
|
901
|
+
const ref = this.#bottomSheet.open(ActionsSheetComponent, {
|
|
902
|
+
data: { item: this.selectedItem }
|
|
903
|
+
});
|
|
904
|
+
ref.afterDismissed().subscribe(action => {
|
|
905
|
+
if (action) this.handleAction(action);
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
## Layout (CDK)
|
|
913
|
+
|
|
914
|
+
### `CdkVirtualScrollViewport` — Virtual Scrolling
|
|
915
|
+
|
|
916
|
+
Renders only visible items. Essential for lists exceeding 100 items.
|
|
917
|
+
|
|
918
|
+
**Import:** `ScrollingModule` from `@angular/cdk/scrolling`
|
|
919
|
+
|
|
920
|
+
**Key Inputs:**
|
|
921
|
+
- `itemSize`: number (px, required for fixed-size strategy)
|
|
922
|
+
- `orientation`: `'vertical'` | `'horizontal'`
|
|
923
|
+
- `minBufferPx`, `maxBufferPx`: render buffer sizes
|
|
924
|
+
|
|
925
|
+
```html
|
|
926
|
+
<cdk-virtual-scroll-viewport itemSize="72" class="list-viewport">
|
|
927
|
+
<mat-list-item *cdkVirtualFor="let item of items; trackBy: trackById">
|
|
928
|
+
<span matListItemTitle>{{ item.name }}</span>
|
|
929
|
+
</mat-list-item>
|
|
930
|
+
</cdk-virtual-scroll-viewport>
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
```scss
|
|
934
|
+
.list-viewport {
|
|
935
|
+
height: 400px; /* Must have fixed height */
|
|
936
|
+
width: 100%;
|
|
937
|
+
}
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
---
|
|
941
|
+
|
|
942
|
+
### `CdkDragDrop` — Drag and Drop
|
|
943
|
+
|
|
944
|
+
Reorderable lists and kanban-style boards.
|
|
945
|
+
|
|
946
|
+
**Import:** `DragDropModule` from `@angular/cdk/drag-drop`
|
|
947
|
+
|
|
948
|
+
**Key Directives:**
|
|
949
|
+
- `cdkDropList`: container
|
|
950
|
+
- `cdkDrag`: draggable item
|
|
951
|
+
- `[cdkDropListConnectedTo]`: link multiple lists for cross-list drag
|
|
952
|
+
|
|
953
|
+
**Outputs:** `(cdkDropListDropped)`: `CdkDragDrop<T>`
|
|
954
|
+
|
|
955
|
+
```html
|
|
956
|
+
<div cdkDropList class="task-list" (cdkDropListDropped)="drop($event)">
|
|
957
|
+
<div class="task-card" *ngFor="let task of tasks" cdkDrag>
|
|
958
|
+
<mat-icon cdkDragHandle>drag_indicator</mat-icon>
|
|
959
|
+
{{ task.title }}
|
|
960
|
+
<div *cdkDragPlaceholder class="drag-placeholder"></div>
|
|
961
|
+
</div>
|
|
962
|
+
</div>
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
```typescript
|
|
966
|
+
import { moveItemInArray, CdkDragDrop } from '@angular/cdk/drag-drop';
|
|
967
|
+
|
|
968
|
+
drop(event: CdkDragDrop<Task[]>) {
|
|
969
|
+
moveItemInArray(this.tasks, event.previousIndex, event.currentIndex);
|
|
970
|
+
}
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
---
|
|
974
|
+
|
|
975
|
+
### `BreakpointObserver`
|
|
976
|
+
|
|
977
|
+
Responsive layout decisions in TypeScript. Prefer CSS media queries for styling; use `BreakpointObserver` only when layout changes require component logic.
|
|
978
|
+
|
|
979
|
+
**Import:** `LayoutModule` from `@angular/cdk/layout`
|
|
980
|
+
|
|
981
|
+
**Built-in breakpoints:** `Breakpoints.Handset`, `Breakpoints.Tablet`, `Breakpoints.Web`, `Breakpoints.HandsetPortrait`, etc.
|
|
982
|
+
|
|
983
|
+
```typescript
|
|
984
|
+
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
|
985
|
+
import { map, shareReplay } from 'rxjs/operators';
|
|
986
|
+
|
|
987
|
+
readonly #bp = inject(BreakpointObserver);
|
|
988
|
+
|
|
989
|
+
readonly isHandset$ = this.#bp
|
|
990
|
+
.observe(Breakpoints.Handset)
|
|
991
|
+
.pipe(
|
|
992
|
+
map(result => result.matches),
|
|
993
|
+
shareReplay(1)
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
readonly isMediumUp$ = this.#bp
|
|
997
|
+
.observe(['(min-width: 768px)'])
|
|
998
|
+
.pipe(map(r => r.matches));
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
### `CdkPortal` — Portal / Teleport
|
|
1004
|
+
|
|
1005
|
+
Render a template or component in a different location in the DOM (e.g., inject content into a header from a child route).
|
|
1006
|
+
|
|
1007
|
+
**Import:** `PortalModule` from `@angular/cdk/portal`
|
|
1008
|
+
|
|
1009
|
+
```typescript
|
|
1010
|
+
// Child component provides a portal
|
|
1011
|
+
@ViewChild('actionButtons') actionButtonsPortal!: TemplatePortal;
|
|
1012
|
+
|
|
1013
|
+
ngAfterViewInit() {
|
|
1014
|
+
this.headerService.setPortal(this.actionButtonsPortal);
|
|
1015
|
+
}
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
```html
|
|
1019
|
+
<ng-template #actionButtons>
|
|
1020
|
+
<button mat-flat-button color="primary">Save</button>
|
|
1021
|
+
</ng-template>
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
---
|
|
1025
|
+
|
|
1026
|
+
## Icons
|
|
1027
|
+
|
|
1028
|
+
### `mat-icon`
|
|
1029
|
+
|
|
1030
|
+
Display Material icons. Angular Material v15+ defaults to Material Symbols (variable font), which supports optical size, weight, fill, and grade axes.
|
|
1031
|
+
|
|
1032
|
+
**Setup in `index.html`:**
|
|
1033
|
+
```html
|
|
1034
|
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet" />
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
**Key Inputs:**
|
|
1038
|
+
- `fontSet`: `'material-symbols-outlined'` | `'material-icons'`
|
|
1039
|
+
- `fontIcon`: icon name (alternative to text content)
|
|
1040
|
+
- `[inline]`: boolean (sizes to surrounding text)
|
|
1041
|
+
- `[color]`: `'primary'` | `'accent'` | `'warn'`
|
|
1042
|
+
|
|
1043
|
+
**Custom SVG icons via `MatIconRegistry`:**
|
|
1044
|
+
```typescript
|
|
1045
|
+
// app.config.ts or component
|
|
1046
|
+
import { MatIconRegistry } from '@angular/material/icon';
|
|
1047
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
1048
|
+
|
|
1049
|
+
const iconRegistry = inject(MatIconRegistry);
|
|
1050
|
+
const sanitizer = inject(DomSanitizer);
|
|
1051
|
+
|
|
1052
|
+
iconRegistry.addSvgIcon(
|
|
1053
|
+
'custom-logo',
|
|
1054
|
+
sanitizer.bypassSecurityTrustResourceUrl('assets/icons/logo.svg')
|
|
1055
|
+
);
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
```html
|
|
1059
|
+
<!-- Material Symbol -->
|
|
1060
|
+
<mat-icon fontSet="material-symbols-outlined">rocket_launch</mat-icon>
|
|
1061
|
+
|
|
1062
|
+
<!-- Custom SVG -->
|
|
1063
|
+
<mat-icon svgIcon="custom-logo" aria-hidden="true" />
|
|
1064
|
+
|
|
1065
|
+
<!-- Decorative (hide from screen readers) -->
|
|
1066
|
+
<mat-icon aria-hidden="true">star</mat-icon>
|
|
1067
|
+
|
|
1068
|
+
<!-- Meaningful (describe to screen readers) -->
|
|
1069
|
+
<mat-icon aria-label="Starred">star</mat-icon>
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
**Accessibility:** Set `aria-hidden="true"` on decorative icons. For meaningful standalone icons, add `aria-label`.
|
|
1073
|
+
|
|
1074
|
+
---
|
|
1075
|
+
|
|
1076
|
+
## Import Reference
|
|
1077
|
+
|
|
1078
|
+
```typescript
|
|
1079
|
+
// Commonly needed imports (standalone component style)
|
|
1080
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
1081
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
1082
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
1083
|
+
import { MatInputModule } from '@angular/material/input';
|
|
1084
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
1085
|
+
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
|
1086
|
+
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
1087
|
+
import { MatRadioModule } from '@angular/material/radio';
|
|
1088
|
+
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
1089
|
+
import { MatDatepickerModule } from '@angular/material/datepicker';
|
|
1090
|
+
import { MatNativeDateModule } from '@angular/material/core';
|
|
1091
|
+
import { MatSliderModule } from '@angular/material/slider';
|
|
1092
|
+
import { MatChipsModule } from '@angular/material/chips';
|
|
1093
|
+
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
1094
|
+
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
1095
|
+
import { MatListModule } from '@angular/material/list';
|
|
1096
|
+
import { MatTabsModule } from '@angular/material/tabs';
|
|
1097
|
+
import { MatStepperModule } from '@angular/material/stepper';
|
|
1098
|
+
import { MatMenuModule } from '@angular/material/menu';
|
|
1099
|
+
import { MatTableModule } from '@angular/material/table';
|
|
1100
|
+
import { MatPaginatorModule } from '@angular/material/paginator';
|
|
1101
|
+
import { MatSortModule } from '@angular/material/sort';
|
|
1102
|
+
import { MatCardModule } from '@angular/material/card';
|
|
1103
|
+
import { MatExpansionModule } from '@angular/material/expansion';
|
|
1104
|
+
import { MatBadgeModule } from '@angular/material/badge';
|
|
1105
|
+
import { MatDividerModule } from '@angular/material/divider';
|
|
1106
|
+
import { MatDialogModule } from '@angular/material/dialog';
|
|
1107
|
+
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
|
1108
|
+
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|
1109
|
+
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
1110
|
+
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
1111
|
+
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
|
|
1112
|
+
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
1113
|
+
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
1114
|
+
import { LayoutModule } from '@angular/cdk/layout';
|
|
1115
|
+
import { PortalModule } from '@angular/cdk/portal';
|
|
1116
|
+
```
|