@ericrisco/rsc 3.3.1 → 3.4.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.
Files changed (54) hide show
  1. package/README.md +2 -2
  2. package/manifest.json +43 -22
  3. package/package.json +1 -1
  4. package/scripts/lib/default-skill-floor.js +5 -1
  5. package/scripts/lib/domains.js +2 -2
  6. package/scripts/lib/retired-skills.js +4 -0
  7. package/skills/design/SKILL.md +4 -3
  8. package/skills/design/evals/cases.yaml +2 -2
  9. package/skills/react/SKILL.md +2 -1
  10. package/skills/ux/SKILL.md +149 -0
  11. package/skills/ux/THIRD_PARTY.md +32 -0
  12. package/skills/ux/evals/cases.yaml +40 -0
  13. package/skills/ux/references/flows.md +124 -0
  14. package/skills/ux/upstream/UPSTREAM.json +59 -0
  15. package/skills/ux/upstream/ux-designer/GUIDE.md +180 -0
  16. package/skills/ux/upstream/ux-designer/LICENSE +21 -0
  17. package/skills/ux/upstream/ux-designer/references/01-core-principles.md +251 -0
  18. package/skills/ux/upstream/ux-designer/references/02-laws-of-ux.md +330 -0
  19. package/skills/ux/upstream/ux-designer/references/03-accessibility.md +411 -0
  20. package/skills/ux/upstream/ux-designer/references/04-visual-design.md +433 -0
  21. package/skills/ux/upstream/ux-designer/references/05-information-architecture.md +432 -0
  22. package/skills/ux/upstream/ux-designer/references/06-interaction-design.md +495 -0
  23. package/skills/ux/upstream/ux-designer/references/07-forms-and-inputs.md +491 -0
  24. package/skills/ux/upstream/ux-designer/references/08-mobile-ux.md +539 -0
  25. package/skills/ux/upstream/ux-designer/references/09-ux-writing.md +493 -0
  26. package/skills/ux/upstream/ux-designer/references/10-user-research.md +533 -0
  27. package/skills/ux/upstream/ux-designer/references/11-design-systems.md +618 -0
  28. package/skills/ux/upstream/ux-designer/references/12a-presence-awareness.md +395 -0
  29. package/skills/ux/upstream/ux-designer/references/12b-conflict-resolution-sync.md +420 -0
  30. package/skills/ux/upstream/ux-designer/references/13a-canvas-navigation.md +388 -0
  31. package/skills/ux/upstream/ux-designer/references/13b-canvas-objects-performance.md +559 -0
  32. package/skills/ux/upstream/ux-designer/references/14-ai-ux-patterns.md +511 -0
  33. package/skills/ux/upstream/ux-designer/references/15-ethical-design.md +419 -0
  34. package/skills/ux/upstream/ux-designer/references/16-onboarding.md +430 -0
  35. package/skills/ux/upstream/ux-designer/references/17-notifications.md +461 -0
  36. package/skills/ux/upstream/ux-designer/references/18-data-visualization.md +453 -0
  37. package/skills/ux/upstream/ux-designer/references/19-search-ux.md +479 -0
  38. package/skills/ux/upstream/ux-designer/references/20-emotional-design.md +382 -0
  39. package/skills/ux/upstream/ux-designer/references/21-data-tables.md +629 -0
  40. package/skills/ux/upstream/ux-designer/references/22-performance-ux.md +626 -0
  41. package/skills/ux/upstream/ux-designer/references/23-internationalization.md +347 -0
  42. package/skills/ux/upstream/ux-designer/references/24-voice-and-multimodal.md +308 -0
  43. package/skills/ux/upstream/ux-designer/references/25a-barcode-camera-scanning.md +342 -0
  44. package/skills/ux/upstream/ux-designer/references/25b-barcode-scanner-workflows.md +412 -0
  45. package/skills/ux/upstream/ux-designer/references/25c-barcode-display.md +277 -0
  46. package/skills/ux/upstream/ux-usability-foundations/GUIDE.md +285 -0
  47. package/skills/ux/upstream/ux-usability-foundations/LICENSE +21 -0
  48. package/skills/ux/upstream/ux-usability-foundations/references/anti-patterns.md +267 -0
  49. package/skills/ux/upstream/ux-usability-foundations/references/checklists.md +130 -0
  50. package/skills/ux/upstream/ux-usability-foundations/references/decision-prompts.md +340 -0
  51. package/skills/ux/upstream/ux-usability-foundations/references/principle-cards.md +449 -0
  52. package/targets/clone-bootstrap.mjs +1 -1
  53. package/skills/ui-engineering/SKILL.md +0 -113
  54. package/skills/ui-engineering/evals/cases.yaml +0 -38
@@ -0,0 +1,629 @@
1
+ # Data Tables & Lists
2
+
3
+ Data tables are one of the most common UI patterns in enterprise and admin applications. Well-designed tables help users scan, compare, sort, filter, and act on structured data efficiently.
4
+
5
+ ---
6
+
7
+ ## Contents
8
+
9
+ - Table Layout Patterns
10
+ - Sorting
11
+ - Filtering
12
+ - Pagination vs. Infinite Scroll vs. Virtual Scrolling
13
+ - Bulk Selection & Actions
14
+ - Row Expansion & Detail
15
+ - Empty States & Loading
16
+ - Table Toolbar
17
+ - Accessibility
18
+ - Key Metrics
19
+ - Anti-Patterns
20
+ - Sources
21
+
22
+ ---
23
+
24
+ ## Table Layout Patterns
25
+
26
+ ### Basic Table Structure
27
+
28
+ ```html
29
+ <table class="data-table" role="grid" aria-label="User accounts">
30
+ <thead>
31
+ <tr>
32
+ <th scope="col" aria-sort="ascending">
33
+ <button class="sort-button">
34
+ Name <span class="sort-icon">↑</span>
35
+ </button>
36
+ </th>
37
+ <th scope="col">Email</th>
38
+ <th scope="col">Role</th>
39
+ <th scope="col">Actions</th>
40
+ </tr>
41
+ </thead>
42
+ <tbody>
43
+ <tr>
44
+ <td>Alice Smith</td>
45
+ <td>alice@example.com</td>
46
+ <td><span class="badge badge--admin">Admin</span></td>
47
+ <td>
48
+ <button class="btn-icon" aria-label="Edit Alice Smith">✏️</button>
49
+ <button class="btn-icon" aria-label="Delete Alice Smith">🗑️</button>
50
+ </td>
51
+ </tr>
52
+ </tbody>
53
+ </table>
54
+ ```
55
+
56
+ ### Fixed Headers
57
+
58
+ Keep headers visible while scrolling long tables.
59
+
60
+ ```css
61
+ .data-table-container {
62
+ max-height: 600px;
63
+ overflow-y: auto;
64
+ }
65
+
66
+ .data-table thead th {
67
+ position: sticky;
68
+ top: 0;
69
+ z-index: 1;
70
+ background: white;
71
+ box-shadow: 0 1px 0 #e5e7eb;
72
+ }
73
+ ```
74
+
75
+ ### Fixed Columns
76
+
77
+ Keep identifier columns visible during horizontal scroll.
78
+
79
+ ```css
80
+ .data-table td:first-child,
81
+ .data-table th:first-child {
82
+ position: sticky;
83
+ left: 0;
84
+ z-index: 2;
85
+ background: white;
86
+ box-shadow: 1px 0 0 #e5e7eb;
87
+ }
88
+
89
+ /* Corner cell needs highest z-index */
90
+ .data-table thead th:first-child {
91
+ z-index: 3;
92
+ }
93
+ ```
94
+
95
+ ### Column Resizing
96
+
97
+ ```javascript
98
+ function initColumnResize(table) {
99
+ const headers = table.querySelectorAll('th');
100
+
101
+ headers.forEach(header => {
102
+ const resizer = document.createElement('div');
103
+ resizer.className = 'column-resizer';
104
+ header.appendChild(resizer);
105
+
106
+ let startX, startWidth;
107
+
108
+ resizer.addEventListener('mousedown', (e) => {
109
+ startX = e.clientX;
110
+ startWidth = header.offsetWidth;
111
+ document.addEventListener('mousemove', handleMouseMove);
112
+ document.addEventListener('mouseup', handleMouseUp);
113
+ });
114
+
115
+ function handleMouseMove(e) {
116
+ const newWidth = Math.max(60, startWidth + (e.clientX - startX));
117
+ header.style.width = `${newWidth}px`;
118
+ }
119
+
120
+ function handleMouseUp() {
121
+ document.removeEventListener('mousemove', handleMouseMove);
122
+ document.removeEventListener('mouseup', handleMouseUp);
123
+ }
124
+ });
125
+ }
126
+ ```
127
+
128
+ ```css
129
+ .column-resizer {
130
+ position: absolute;
131
+ right: 0;
132
+ top: 0;
133
+ bottom: 0;
134
+ width: 4px;
135
+ cursor: col-resize;
136
+ background: transparent;
137
+ }
138
+
139
+ .column-resizer:hover,
140
+ .column-resizer:active {
141
+ background: #3b82f6;
142
+ }
143
+ ```
144
+
145
+ ### Responsive Table Strategies
146
+
147
+ | Strategy | When to Use |
148
+ |----------|-------------|
149
+ | Horizontal scroll | Many columns, data must stay tabular |
150
+ | Column hiding | Secondary columns can be hidden on mobile |
151
+ | Card layout | Reflow rows into stacked cards below breakpoint |
152
+ | Priority columns | Show only high-priority columns, expand for more |
153
+
154
+ #### Card Reflow Pattern
155
+
156
+ ```css
157
+ @media (max-width: 768px) {
158
+ .data-table thead {
159
+ display: none;
160
+ }
161
+
162
+ .data-table tr {
163
+ display: block;
164
+ margin-bottom: 12px;
165
+ border: 1px solid #e5e7eb;
166
+ border-radius: 8px;
167
+ padding: 12px;
168
+ }
169
+
170
+ .data-table td {
171
+ display: flex;
172
+ justify-content: space-between;
173
+ padding: 4px 0;
174
+ border: none;
175
+ }
176
+
177
+ .data-table td::before {
178
+ content: attr(data-label);
179
+ font-weight: 600;
180
+ color: #6b7280;
181
+ }
182
+ }
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Sorting
188
+
189
+ ### Sort Interaction
190
+
191
+ ```
192
+ ┌──────────────────────────────────────────────────────────────┐
193
+ │ Name ↑ │ Email │ Created ↕ │ Status │
194
+ ├──────────────────────────────────────────────────────────────┤
195
+ │ Alice Smith │ alice@ex.com │ 2025-01-15 │ Active │
196
+ │ Bob Jones │ bob@ex.com │ 2025-02-01 │ Active │
197
+ │ Carol Lee │ carol@ex.com │ 2024-12-20 │ Inactive │
198
+ └──────────────────────────────────────────────────────────────┘
199
+
200
+ ↑ = Ascending (currently active)
201
+ ↕ = Sortable but not active
202
+ ```
203
+
204
+ #### Sort State Indicators
205
+
206
+ | Symbol | Meaning |
207
+ |--------|---------|
208
+ | ↑ | Sorted ascending |
209
+ | ↓ | Sorted descending |
210
+ | ↕ | Sortable (click to sort) |
211
+ | (none) | Not sortable |
212
+
213
+ #### Best Practices
214
+
215
+ - Click column header to sort ascending, click again for descending, third click to clear
216
+ - Always show sort direction indicator on the active column
217
+ - Use `aria-sort="ascending"` / `aria-sort="descending"` for screen readers
218
+ - Default sort should match the most common use case
219
+ - Support multi-column sort with Shift+Click (show sort priority numbers)
220
+
221
+ ---
222
+
223
+ ## Filtering
224
+
225
+ ### Filter Bar Pattern
226
+
227
+ ```
228
+ ┌──────────────────────────────────────────────────────────────┐
229
+ │ 🔍 Search users... [Status ▼] [Role ▼] [Date range ▼] │
230
+ │ │
231
+ │ Active filters: Status: Active ✕ │ Role: Admin ✕ [Clear] │
232
+ ├──────────────────────────────────────────────────────────────┤
233
+ │ Showing 24 of 156 users │
234
+ ```
235
+
236
+ ### Filter Types by Data
237
+
238
+ | Data Type | Filter UI |
239
+ |-----------|-----------|
240
+ | Text | Search input (debounced, 300ms) |
241
+ | Enum/Category | Dropdown or checkbox group |
242
+ | Boolean | Toggle switch |
243
+ | Date | Date range picker |
244
+ | Number | Range slider or min/max inputs |
245
+ | Tags | Multi-select with typeahead |
246
+
247
+ ### Filter Placement
248
+
249
+ ```
250
+ Filter placement decision:
251
+ ├── Few filters (1-3)?
252
+ │ └── → Inline filter bar above table
253
+ ├── Many filters (4-8)?
254
+ │ └── → Collapsible filter panel (sidebar or above table)
255
+ └── Complex filters with dependencies?
256
+ └── → Dedicated filter modal or side panel
257
+ ```
258
+
259
+ ### Active Filter Display
260
+
261
+ - Show active filters as dismissible chips/tags
262
+ - Include "Clear all filters" action when any filter is active
263
+ - Show result count that updates as filters change
264
+ - Persist filters in URL params for shareable filtered views
265
+
266
+ ---
267
+
268
+ ## Pagination vs. Infinite Scroll vs. Virtual Scrolling
269
+
270
+ ### Decision Tree
271
+
272
+ ```
273
+ How should users navigate through data?
274
+ ├── User needs to reach specific pages or find exact position?
275
+ │ └── → Pagination
276
+ ├── Content is browsable/exploratory (social feed, catalog)?
277
+ │ ├── Total items < 1,000?
278
+ │ │ └── → Infinite scroll
279
+ │ └── Total items > 1,000?
280
+ │ └── → Infinite scroll with "jump to" option
281
+ ├── All data must be in DOM for client-side operations?
282
+ │ └── → Virtual scrolling (windowed rendering)
283
+ └── Data set is very large (10,000+ rows)?
284
+ └── → Server-side pagination OR virtual scrolling
285
+ ```
286
+
287
+ ### Pagination
288
+
289
+ ```
290
+ ┌──────────────────────────────────────────────────────────────┐
291
+ │ Showing 1-25 of 156 Rows per page: [25 ▼] │
292
+ │ │
293
+ │ [← Prev] 1 2 3 ... 6 7 [Next →] │
294
+ └──────────────────────────────────────────────────────────────┘
295
+ ```
296
+
297
+ **Pagination best practices:**
298
+ - Show current range ("Showing 1-25 of 156")
299
+ - Allow page size selection (10, 25, 50, 100)
300
+ - Show first/last page links for long ranges
301
+ - Use ellipsis for large page counts
302
+ - Keyboard accessible (arrow keys between pages)
303
+
304
+ ### Infinite Scroll
305
+
306
+ ```javascript
307
+ const observer = new IntersectionObserver(
308
+ (entries) => {
309
+ if (entries[0].isIntersecting) {
310
+ loadNextPage();
311
+ }
312
+ },
313
+ { rootMargin: '200px' } // Trigger 200px before reaching end
314
+ );
315
+
316
+ observer.observe(document.querySelector('.load-trigger'));
317
+ ```
318
+
319
+ **Infinite scroll best practices:**
320
+ - Show loading indicator at bottom during fetch
321
+ - Preserve scroll position on back navigation
322
+ - Provide "Back to top" button after scrolling
323
+ - Show total count if known ("Showing 75 of ~500")
324
+ - Combine with a "Load more" button as fallback
325
+
326
+ ### Virtual Scrolling
327
+
328
+ Only render rows visible in the viewport.
329
+
330
+ ```javascript
331
+ // Virtual scroll concept
332
+ function getVisibleRows(scrollTop, viewportHeight, rowHeight, totalRows) {
333
+ const startIndex = Math.floor(scrollTop / rowHeight);
334
+ const endIndex = Math.min(
335
+ startIndex + Math.ceil(viewportHeight / rowHeight) + 1,
336
+ totalRows
337
+ );
338
+
339
+ return {
340
+ startIndex,
341
+ endIndex,
342
+ offsetY: startIndex * rowHeight,
343
+ totalHeight: totalRows * rowHeight
344
+ };
345
+ }
346
+ ```
347
+
348
+ **Virtual scrolling best practices:**
349
+ - Keep row height consistent or measure dynamically
350
+ - Render buffer rows above and below viewport (5-10 rows)
351
+ - Show scroll position indicator for long lists
352
+ - Support keyboard navigation (arrow keys, Page Up/Down)
353
+
354
+ ---
355
+
356
+ ## Bulk Selection & Actions
357
+
358
+ ### Selection Patterns
359
+
360
+ ```
361
+ ┌──────────────────────────────────────────────────────────────┐
362
+ │ ☑ Select all (24 on this page) [Select all 156 →] │
363
+ │ 3 selected [Delete] [Export] [Change status ▼] [✕ Clear] │
364
+ ├──────────────────────────────────────────────────────────────┤
365
+ │ ☑ │ Alice Smith │ alice@ex.com │ Active │ │
366
+ │ ☐ │ Bob Jones │ bob@ex.com │ Active │ │
367
+ │ ☑ │ Carol Lee │ carol@ex.com │ Inactive │ │
368
+ │ ☑ │ Dave Park │ dave@ex.com │ Active │ │
369
+ └──────────────────────────────────────────────────────────────┘
370
+ ```
371
+
372
+ ### Selection Interactions
373
+
374
+ | Action | Behavior |
375
+ |--------|----------|
376
+ | Click checkbox | Toggle individual row |
377
+ | Shift + Click | Select range from last selected |
378
+ | Header checkbox | Select/deselect all on current page |
379
+ | "Select all N" | Select across all pages |
380
+
381
+ ### Bulk Action Bar
382
+
383
+ ```css
384
+ .bulk-action-bar {
385
+ position: sticky;
386
+ top: 0;
387
+ z-index: 10;
388
+ display: flex;
389
+ align-items: center;
390
+ gap: 8px;
391
+ padding: 8px 16px;
392
+ background: #eff6ff;
393
+ border-bottom: 1px solid #bfdbfe;
394
+ }
395
+ ```
396
+
397
+ **Best practices:**
398
+ - Show bulk action bar only when items are selected
399
+ - Display count of selected items
400
+ - Require confirmation for destructive bulk actions
401
+ - Show progress indicator for long-running bulk operations
402
+ - Allow cancellation of in-progress bulk operations
403
+
404
+ ---
405
+
406
+ ## Row Expansion & Detail
407
+
408
+ ### Expandable Rows
409
+
410
+ ```
411
+ ┌──────────────────────────────────────────────────────────────┐
412
+ │ ▶ │ Order #1234 │ Alice Smith │ $149.00 │ Shipped │
413
+ │ ▼ │ Order #1235 │ Bob Jones │ $89.50 │ Processing │
414
+ │ ├──────────────────────────────────────────────────────────┤
415
+ │ │ Items: Widget A (×2), Gadget B (×1) │
416
+ │ │ Tracking: 1Z999AA10123456784 │
417
+ │ │ Estimated delivery: Jan 20, 2025 │
418
+ │ ├──────────────────────────────────────────────────────────┤
419
+ │ ▶ │ Order #1236 │ Carol Lee │ $250.00 │ Delivered │
420
+ └──────────────────────────────────────────────────────────────┘
421
+ ```
422
+
423
+ **Best practices:**
424
+ - Use chevron (▶/▼) to indicate expandability
425
+ - Expand only one row at a time (accordion) or allow multiple
426
+ - Keep expanded content visually connected to the parent row
427
+ - Indent or inset expanded content for visual hierarchy
428
+
429
+ ### Inline Editing
430
+
431
+ ```javascript
432
+ function enableInlineEdit(cell) {
433
+ const currentValue = cell.textContent;
434
+ const input = document.createElement('input');
435
+ input.value = currentValue;
436
+ input.className = 'inline-edit-input';
437
+
438
+ cell.replaceChildren(input);
439
+ input.focus();
440
+ input.select();
441
+
442
+ input.addEventListener('blur', () => saveEdit(cell, input.value, currentValue));
443
+ input.addEventListener('keydown', (e) => {
444
+ if (e.key === 'Enter') input.blur();
445
+ if (e.key === 'Escape') {
446
+ input.value = currentValue;
447
+ input.blur();
448
+ }
449
+ });
450
+ }
451
+ ```
452
+
453
+ ```css
454
+ .inline-edit-input {
455
+ width: 100%;
456
+ padding: 4px 8px;
457
+ border: 2px solid #3b82f6;
458
+ border-radius: 4px;
459
+ font: inherit;
460
+ background: #eff6ff;
461
+ }
462
+ ```
463
+
464
+ **Inline edit guidelines:**
465
+ - Double-click or click edit icon to activate
466
+ - Show clear visual change (border, background) when in edit mode
467
+ - Save on blur or Enter, cancel on Escape
468
+ - Validate before saving, show errors inline
469
+ - Show saving indicator (spinner) during async save
470
+
471
+ ---
472
+
473
+ ## Empty States & Loading
474
+
475
+ ### Empty State Patterns
476
+
477
+ ```
478
+ ┌──────────────────────────────────────────────────────────────┐
479
+ │ │
480
+ │ 📋 │
481
+ │ No results found │
482
+ │ │
483
+ │ Your search "xyz" didn't match any users. │
484
+ │ Try adjusting your filters or search terms. │
485
+ │ │
486
+ │ [Clear filters] [Add new user] │
487
+ │ │
488
+ └──────────────────────────────────────────────────────────────┘
489
+ ```
490
+
491
+ | Empty State Type | Message Pattern |
492
+ |-----------------|-----------------|
493
+ | No data yet | Explain what will appear + CTA to create first item |
494
+ | No search results | Acknowledge query + suggest adjustments |
495
+ | Filtered to empty | Show active filters + "Clear filters" button |
496
+ | Error loading | Explain what happened + "Retry" button |
497
+
498
+ ### Loading Skeletons
499
+
500
+ ```css
501
+ .skeleton-row {
502
+ display: flex;
503
+ gap: 16px;
504
+ padding: 12px 16px;
505
+ animation: skeleton-pulse 1.5s ease-in-out infinite;
506
+ }
507
+
508
+ .skeleton-cell {
509
+ height: 16px;
510
+ background: #e5e7eb;
511
+ border-radius: 4px;
512
+ }
513
+
514
+ .skeleton-cell--name { width: 150px; }
515
+ .skeleton-cell--email { width: 200px; }
516
+ .skeleton-cell--status { width: 80px; }
517
+
518
+ @keyframes skeleton-pulse {
519
+ 0%, 100% { opacity: 1; }
520
+ 50% { opacity: 0.4; }
521
+ }
522
+ ```
523
+
524
+ **Loading best practices:**
525
+ - Show skeleton that matches table structure (column widths)
526
+ - Show 5-10 skeleton rows regardless of expected count
527
+ - Transition smoothly from skeleton to real data
528
+ - Keep headers visible during loading
529
+ - Show spinner only for subsequent page loads, not initial
530
+
531
+ ---
532
+
533
+ ## Table Toolbar
534
+
535
+ ### Standard Toolbar Layout
536
+
537
+ ```
538
+ ┌──────────────────────────────────────────────────────────────┐
539
+ │ Users (156) │
540
+ │ │
541
+ │ 🔍 Search... [Status ▼] [Role ▼] │ [Export ▼] [+ Add] │
542
+ │ │
543
+ │ Active filters: Status: Active ✕ │ Showing 1-25 of 156 │
544
+ ├──────────────────────────────────────────────────────────────┤
545
+ │ ☐ │ Name ↕ │ Email │ Role │ Status │ ⋮ │
546
+ ```
547
+
548
+ ### Column Visibility Toggle
549
+
550
+ ```
551
+ ┌─────────────────────────────┐
552
+ │ Columns │
553
+ ├─────────────────────────────┤
554
+ │ ☑ Name │
555
+ │ ☑ Email │
556
+ │ ☑ Role │
557
+ │ ☐ Created date │
558
+ │ ☐ Last login │
559
+ │ ☑ Status │
560
+ ├─────────────────────────────┤
561
+ │ [Reset to default] │
562
+ └─────────────────────────────┘
563
+ ```
564
+
565
+ ---
566
+
567
+ ## Accessibility
568
+
569
+ ### Table Accessibility Checklist
570
+
571
+ - [ ] Use semantic `<table>`, `<thead>`, `<tbody>`, `<th>`, `<td>` elements
572
+ - [ ] Add `scope="col"` to column headers, `scope="row"` to row headers
573
+ - [ ] Use `aria-sort` on sortable column headers
574
+ - [ ] Provide `aria-label` or `aria-labelledby` on the table
575
+ - [ ] Ensure keyboard navigation works (Tab between interactive elements)
576
+ - [ ] Row actions accessible via keyboard (Enter/Space to activate)
577
+ - [ ] Bulk selection announced to screen readers ("3 rows selected")
578
+ - [ ] Pagination controls are keyboard accessible
579
+ - [ ] Sort changes announced via `aria-live` region
580
+
581
+ ### Keyboard Navigation
582
+
583
+ | Key | Action |
584
+ |-----|--------|
585
+ | Tab | Move to next interactive element |
586
+ | Shift + Tab | Move to previous interactive element |
587
+ | Enter / Space | Activate button, toggle checkbox, start inline edit |
588
+ | Escape | Cancel inline edit, close dropdown |
589
+ | Arrow keys | Navigate within dropdowns or between rows (if using `role="grid"`) |
590
+
591
+ ---
592
+
593
+ ## Key Metrics
594
+
595
+ | Metric | Value | Context |
596
+ |--------|-------|---------|
597
+ | Rows per page default | 25 | Balance between scanning and loading |
598
+ | Max visible columns | 7-10 | Before horizontal scroll or hiding |
599
+ | Search debounce | 300ms | Delay before triggering search |
600
+ | Skeleton rows | 5-10 | During initial load |
601
+ | Column min width | 60px | Minimum resizable width |
602
+ | Row height | 40-52px | Comfortable click/tap target |
603
+ | Bulk action confirm | Always | For destructive operations |
604
+
605
+ ---
606
+
607
+ ## Anti-Patterns
608
+
609
+ 1. **No sort indicators** - Users can't tell which column is sorted or in which direction
610
+ 2. **Client-side sort on server-paginated data** - Sorts only the visible page, not the full dataset
611
+ 3. **Pagination without count** - Users don't know how much data exists
612
+ 4. **Tiny action buttons** - Hard to click, especially on touch devices
613
+ 5. **No empty state** - Blank table with headers but no guidance
614
+ 6. **Inline editing without undo** - Users can't recover from accidental changes
615
+ 7. **Checkbox column without bulk actions** - Selection exists but does nothing
616
+ 8. **Filter resets on navigation** - Losing filter state when leaving and returning
617
+ 9. **No loading state between pages** - Content jumps, users lose context
618
+ 10. **Forced horizontal scroll on mobile** - Use card reflow instead
619
+
620
+ ---
621
+
622
+ ## Sources
623
+
624
+ - [Baymard Institute - Data Table Usability](https://baymard.com/blog/data-table-design)
625
+ - [Nielsen Norman Group - Data Tables](https://www.nngroup.com/articles/data-tables/)
626
+ - [Smashing Magazine - Design Patterns for Complex Data Tables](https://www.smashingmagazine.com/2019/02/complex-web-tables/)
627
+ - [TanStack Table](https://tanstack.com/table/) - Headless table library
628
+ - [AG Grid - Best Practices](https://www.ag-grid.com/javascript-data-grid/) - Enterprise grid reference
629
+ - [Ant Design - Table Component](https://ant.design/components/table) - Pattern reference