@discourser/design-system 0.3.1 → 0.5.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 (36) hide show
  1. package/README.md +12 -4
  2. package/dist/styles.css +5126 -0
  3. package/guidelines/Guidelines.md +92 -41
  4. package/guidelines/components/accordion.md +732 -0
  5. package/guidelines/components/avatar.md +1015 -0
  6. package/guidelines/components/badge.md +728 -0
  7. package/guidelines/components/button.md +75 -40
  8. package/guidelines/components/card.md +84 -25
  9. package/guidelines/components/checkbox.md +671 -0
  10. package/guidelines/components/dialog.md +619 -31
  11. package/guidelines/components/drawer.md +1616 -0
  12. package/guidelines/components/heading.md +576 -0
  13. package/guidelines/components/icon-button.md +92 -37
  14. package/guidelines/components/input-addon.md +685 -0
  15. package/guidelines/components/input-group.md +830 -0
  16. package/guidelines/components/input.md +92 -37
  17. package/guidelines/components/popover.md +1271 -0
  18. package/guidelines/components/progress.md +836 -0
  19. package/guidelines/components/radio-group.md +852 -0
  20. package/guidelines/components/select.md +1662 -0
  21. package/guidelines/components/skeleton.md +802 -0
  22. package/guidelines/components/slider.md +911 -0
  23. package/guidelines/components/spinner.md +783 -0
  24. package/guidelines/components/switch.md +105 -38
  25. package/guidelines/components/tabs.md +1488 -0
  26. package/guidelines/components/textarea.md +495 -0
  27. package/guidelines/components/toast.md +784 -0
  28. package/guidelines/components/tooltip.md +912 -0
  29. package/guidelines/design-tokens/colors.md +309 -72
  30. package/guidelines/design-tokens/elevation.md +615 -45
  31. package/guidelines/design-tokens/spacing.md +654 -74
  32. package/guidelines/design-tokens/typography.md +432 -50
  33. package/guidelines/overview-components.md +60 -8
  34. package/guidelines/overview-imports.md +314 -0
  35. package/guidelines/overview-patterns.md +3852 -0
  36. package/package.json +4 -2
@@ -0,0 +1,1015 @@
1
+ # Avatar
2
+
3
+ **Purpose:** Visual representation of users or entities through images, initials, or icons, providing identity context throughout the application.
4
+
5
+ ## When to Use This Component
6
+
7
+ Use Avatar when you need to **visually represent a user, person, or entity** with an image, initials, or icon to provide identity context.
8
+
9
+ ### Decision Tree
10
+
11
+ | Scenario | Use Avatar? | Alternative | Reasoning |
12
+ | ------------------------- | ----------- | --------------------------- | ------------------------------------------------ |
13
+ | User profile pictures | ✅ Yes | - | Avatar is designed for user representation |
14
+ | Comment author indicators | ✅ Yes | - | Shows who created the content |
15
+ | Team member lists | ✅ Yes | - | Visual identification of people |
16
+ | Status labels or tags | ❌ No | Badge | Badge is for status, not identity |
17
+ | Decorative icons | ❌ No | Icon component | Use semantic icons for UI elements |
18
+ | Company logos (non-user) | ⚠️ Maybe | Image with `shape="square"` | Avatar with square shape works for organizations |
19
+
20
+ ### Component Comparison
21
+
22
+ ```typescript
23
+ // ✅ Avatar - User profile representation
24
+ <Avatar.Root colorPalette="primary" size="md">
25
+ <Avatar.Fallback name="John Doe" />
26
+ <Avatar.Image src="/avatars/john.jpg" alt="John Doe" />
27
+ </Avatar.Root>
28
+
29
+ // ❌ Don't use Avatar for status indicators - Use Badge
30
+ <Avatar.Root colorPalette="success" size="sm">
31
+ <Avatar.Fallback>✓</Avatar.Fallback>
32
+ </Avatar.Root>
33
+
34
+ // ✅ Better: Use Badge for status
35
+ <Badge colorPalette="success">Active</Badge>
36
+
37
+ // ❌ Don't use Avatar for UI icons - Use Icon
38
+ <Avatar.Root size="sm">
39
+ <Avatar.Fallback>
40
+ <SettingsIcon />
41
+ </Avatar.Fallback>
42
+ </Avatar.Root>
43
+
44
+ // ✅ Better: Use IconButton for UI actions
45
+ <IconButton aria-label="Settings">
46
+ <SettingsIcon />
47
+ </IconButton>
48
+
49
+ // ✅ Avatar - Comment author with status
50
+ <HStack gap="3">
51
+ <Box position="relative">
52
+ <Avatar.Root colorPalette="primary" size="md">
53
+ <Avatar.Fallback name="Jane Smith" />
54
+ <Avatar.Image src="/avatars/jane.jpg" alt="Jane Smith" />
55
+ </Avatar.Root>
56
+ {/* Online status indicator */}
57
+ <Box
58
+ position="absolute"
59
+ bottom="0"
60
+ right="0"
61
+ width="3"
62
+ height="3"
63
+ borderRadius="full"
64
+ bg="success.solid"
65
+ border="2px solid white"
66
+ />
67
+ </Box>
68
+ <Stack gap="1">
69
+ <Text fontWeight="semibold">Jane Smith</Text>
70
+ <Text fontSize="sm" color="fg.muted">2 hours ago</Text>
71
+ </Stack>
72
+ </HStack>
73
+ ```
74
+
75
+ ## Import
76
+
77
+ ```typescript
78
+ import * as Avatar from '@discourser/design-system';
79
+ ```
80
+
81
+ ## Component Structure
82
+
83
+ Avatar uses a compound component pattern with these parts:
84
+
85
+ - `Avatar.Root` - Container for the avatar
86
+ - `Avatar.Image` - Image element for avatar photo
87
+ - `Avatar.Fallback` - Fallback content when image fails or is loading
88
+ - `Avatar.Context` - Context hook for accessing avatar state
89
+ - `Avatar.RootProvider` - Root provider for advanced context usage
90
+
91
+ ## Variants
92
+
93
+ The Avatar component supports 4 visual variants, each with specific use cases:
94
+
95
+ | Variant | Visual Style | Usage | When to Use |
96
+ | --------- | -------------------------------------------- | ------------------------- | --------------------------------------------------- |
97
+ | `subtle` | Light background with colored text | Default avatars | General user representations, comments, posts |
98
+ | `solid` | Solid color background with contrasting text | High emphasis identifiers | Important users, featured profiles, emphasis needed |
99
+ | `surface` | Surface background with border | Outlined avatars | Cards, lists, secondary emphasis |
100
+ | `outline` | Transparent background with border | Minimal emphasis | Subtle user indicators, ghost profiles |
101
+
102
+ ### Visual Characteristics
103
+
104
+ - **subtle**: Uses `colorPalette.subtle.bg` background with `colorPalette.subtle.fg` text (default)
105
+ - **solid**: Uses `colorPalette.solid.bg` background with `colorPalette.solid.fg` text (highest contrast)
106
+ - **surface**: Uses `colorPalette.surface.bg` background with 1px border and `colorPalette.surface.fg` text
107
+ - **outline**: Transparent background with 1px `colorPalette.outline.border` and `colorPalette.outline.fg` text
108
+
109
+ ## Shapes
110
+
111
+ | Shape | Visual Style | Usage | When to Use |
112
+ | --------- | --------------------------- | ----------------- | ------------------------------------------------ |
113
+ | `full` | Fully circular avatar | User profiles | Default, most use cases, follows common patterns |
114
+ | `rounded` | Rounded corners (l3 radius) | Alternative style | Brand consistency, modern UI |
115
+ | `square` | Sharp corners | Non-user entities | Organizations, groups, system accounts |
116
+
117
+ **Recommendation:** Use `full` for individual users. Use `square` for organizations or system entities.
118
+
119
+ ## Sizes
120
+
121
+ | Size | Dimensions | Font Size | Icon Size | Usage |
122
+ | ------ | ---------- | --------- | ---------- | ----------------------------------------------- |
123
+ | `2xs` | 24px (6) | 2xs | 12px (3) | Tiny indicators, inline mentions, compact lists |
124
+ | `xs` | 32px (8) | xs | 16px (4) | Dense layouts, small avatars in groups |
125
+ | `sm` | 36px (9) | sm | 18px (4.5) | Comments, compact user lists, secondary areas |
126
+ | `md` | 40px (10) | md | 20px (5) | Default, most use cases, navigation bars |
127
+ | `lg` | 44px (11) | md | 22px (5.5) | User cards, prominent displays |
128
+ | `xl` | 48px (12) | lg | 24px (6) | Profile headers, featured users |
129
+ | `2xl` | 64px (16) | xl | 32px (8) | Large profile displays, hero sections |
130
+ | `full` | 100% | 100% | Responsive | Container-sized avatars, flexible layouts |
131
+
132
+ **Recommendation:** Use `md` for most cases. Use `sm` or `xs` for compact lists. Use `lg` or larger for profile pages and emphasis.
133
+
134
+ ## Props
135
+
136
+ ### Root Props
137
+
138
+ | Prop | Type | Default | Description |
139
+ | -------------- | ------------------------------------------------------------------ | ---------- | --------------------------------------------------------- |
140
+ | `size` | `'2xs' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl' \| 'full'` | `'md'` | Avatar size |
141
+ | `variant` | `'subtle' \| 'solid' \| 'surface' \| 'outline'` | `'subtle'` | Visual style variant |
142
+ | `shape` | `'full' \| 'rounded' \| 'square'` | `'full'` | Avatar shape |
143
+ | `colorPalette` | `string` | - | Color palette for the avatar (e.g., 'primary', 'neutral') |
144
+ | `className` | `string` | - | Additional CSS classes (use sparingly) |
145
+
146
+ ### Image Props
147
+
148
+ | Prop | Type | Default | Description |
149
+ | ---------------- | --------- | --------------- | ------------------------------------------- |
150
+ | `src` | `string` | Required | Image source URL |
151
+ | `alt` | `string` | Required | Alt text for accessibility |
152
+ | `draggable` | `boolean` | `false` | Whether image is draggable (default: false) |
153
+ | `referrerPolicy` | `string` | `'no-referrer'` | Referrer policy for image loading |
154
+
155
+ ### Fallback Props
156
+
157
+ | Prop | Type | Default | Description |
158
+ | ---------- | ----------- | ------- | ------------------------------------------------------- |
159
+ | `name` | `string` | - | Name to derive initials from (e.g., "John Doe" → "JD") |
160
+ | `children` | `ReactNode` | - | Custom fallback content (overrides name-based initials) |
161
+
162
+ **Note:** Avatar components extend their respective HTML element attributes, so all standard attributes are supported.
163
+
164
+ ## Automatic Initials Generation
165
+
166
+ The `Avatar.Fallback` component automatically generates initials from the `name` prop:
167
+
168
+ - **Full name**: "John Doe" → "JD" (first letter of first and last name)
169
+ - **Single name**: "John" → "J" (first letter only)
170
+ - **Multiple names**: "Mary Jane Watson" → "MW" (first and last name only)
171
+ - **No name**: Displays default user icon
172
+
173
+ ## Examples
174
+
175
+ ### Basic Usage
176
+
177
+ ```typescript
178
+ import * as Avatar from '@discourser/design-system';
179
+
180
+ // Avatar with image
181
+ <Avatar.Root colorPalette="primary">
182
+ <Avatar.Fallback name="John Doe" />
183
+ <Avatar.Image src="https://example.com/avatar.jpg" alt="John Doe" />
184
+ </Avatar.Root>
185
+
186
+ // Avatar with initials only
187
+ <Avatar.Root colorPalette="primary">
188
+ <Avatar.Fallback name="Sarah Williams" />
189
+ </Avatar.Root>
190
+
191
+ // Avatar with default icon (no name)
192
+ <Avatar.Root colorPalette="primary">
193
+ <Avatar.Fallback />
194
+ </Avatar.Root>
195
+ ```
196
+
197
+ ### Image Avatars with Fallback
198
+
199
+ ```typescript
200
+ // Image with automatic fallback
201
+ // If image fails to load, shows initials
202
+ <Avatar.Root colorPalette="primary">
203
+ <Avatar.Fallback name="Michael Chen" />
204
+ <Avatar.Image
205
+ src="https://api.example.com/users/123/avatar"
206
+ alt="Michael Chen"
207
+ />
208
+ </Avatar.Root>
209
+
210
+ // Multiple avatars with consistent fallback
211
+ const users = [
212
+ { name: "Alice Johnson", avatar: "https://i.pravatar.cc/150?img=1" },
213
+ { name: "Bob Smith", avatar: "https://i.pravatar.cc/150?img=2" },
214
+ { name: "Carol White", avatar: "https://i.pravatar.cc/150?img=3" },
215
+ ];
216
+
217
+ {users.map(user => (
218
+ <Avatar.Root key={user.name} colorPalette="primary">
219
+ <Avatar.Fallback name={user.name} />
220
+ <Avatar.Image src={user.avatar} alt={user.name} />
221
+ </Avatar.Root>
222
+ ))}
223
+ ```
224
+
225
+ ### Initials-Only Avatars
226
+
227
+ ```typescript
228
+ // Default variant with initials
229
+ <Avatar.Root colorPalette="primary">
230
+ <Avatar.Fallback name="Emily Rodriguez" />
231
+ </Avatar.Root>
232
+
233
+ // Different variants
234
+ <Avatar.Root colorPalette="primary" variant="subtle">
235
+ <Avatar.Fallback name="David Kim" />
236
+ </Avatar.Root>
237
+
238
+ <Avatar.Root colorPalette="primary" variant="solid">
239
+ <Avatar.Fallback name="Lisa Anderson" />
240
+ </Avatar.Root>
241
+
242
+ <Avatar.Root colorPalette="primary" variant="surface">
243
+ <Avatar.Fallback name="Tom Baker" />
244
+ </Avatar.Root>
245
+
246
+ <Avatar.Root colorPalette="primary" variant="outline">
247
+ <Avatar.Fallback name="Nina Patel" />
248
+ </Avatar.Root>
249
+ ```
250
+
251
+ ### Custom Fallback Content
252
+
253
+ ```typescript
254
+ // Custom icon fallback
255
+ <Avatar.Root colorPalette="primary">
256
+ <Avatar.Fallback>
257
+ <CustomUserIcon />
258
+ </Avatar.Fallback>
259
+ </Avatar.Root>
260
+
261
+ // Custom text fallback
262
+ <Avatar.Root colorPalette="primary">
263
+ <Avatar.Fallback>?</Avatar.Fallback>
264
+ </Avatar.Root>
265
+
266
+ // Badge-style fallback
267
+ <Avatar.Root colorPalette="success">
268
+ <Avatar.Fallback>✓</Avatar.Fallback>
269
+ </Avatar.Root>
270
+ ```
271
+
272
+ ### Different Sizes
273
+
274
+ ```typescript
275
+ // Extra small - for inline mentions
276
+ <Avatar.Root colorPalette="primary" size="2xs">
277
+ <Avatar.Fallback name="John Doe" />
278
+ </Avatar.Root>
279
+
280
+ // Small - for comments and compact lists
281
+ <Avatar.Root colorPalette="primary" size="sm">
282
+ <Avatar.Fallback name="Jane Smith" />
283
+ <Avatar.Image src="/avatars/jane.jpg" alt="Jane Smith" />
284
+ </Avatar.Root>
285
+
286
+ // Default - general use
287
+ <Avatar.Root colorPalette="primary" size="md">
288
+ <Avatar.Fallback name="Mike Johnson" />
289
+ <Avatar.Image src="/avatars/mike.jpg" alt="Mike Johnson" />
290
+ </Avatar.Root>
291
+
292
+ // Large - profile displays
293
+ <Avatar.Root colorPalette="primary" size="lg">
294
+ <Avatar.Fallback name="Sarah Williams" />
295
+ <Avatar.Image src="/avatars/sarah.jpg" alt="Sarah Williams" />
296
+ </Avatar.Root>
297
+
298
+ // Extra large - profile headers
299
+ <Avatar.Root colorPalette="primary" size="2xl">
300
+ <Avatar.Fallback name="David Chen" />
301
+ <Avatar.Image src="/avatars/david.jpg" alt="David Chen" />
302
+ </Avatar.Root>
303
+ ```
304
+
305
+ ### Different Shapes
306
+
307
+ ```typescript
308
+ // Circular (default) - for users
309
+ <Avatar.Root colorPalette="primary" shape="full">
310
+ <Avatar.Fallback name="John Doe" />
311
+ </Avatar.Root>
312
+
313
+ // Rounded - modern style
314
+ <Avatar.Root colorPalette="primary" shape="rounded">
315
+ <Avatar.Fallback name="Jane Smith" />
316
+ </Avatar.Root>
317
+
318
+ // Square - for organizations
319
+ <Avatar.Root colorPalette="neutral" shape="square">
320
+ <Avatar.Fallback name="Acme Corp" />
321
+ <Avatar.Image src="/logos/acme.png" alt="Acme Corp" />
322
+ </Avatar.Root>
323
+ ```
324
+
325
+ ### Color Palettes
326
+
327
+ ```typescript
328
+ // Different color palettes for variety
329
+ <Avatar.Root colorPalette="primary">
330
+ <Avatar.Fallback name="User One" />
331
+ </Avatar.Root>
332
+
333
+ <Avatar.Root colorPalette="neutral">
334
+ <Avatar.Fallback name="User Two" />
335
+ </Avatar.Root>
336
+
337
+ <Avatar.Root colorPalette="error">
338
+ <Avatar.Fallback name="User Three" />
339
+ </Avatar.Root>
340
+
341
+ <Avatar.Root colorPalette="success">
342
+ <Avatar.Fallback name="User Four" />
343
+ </Avatar.Root>
344
+
345
+ // Use color palette to represent different user types
346
+ const getColorForRole = (role: string) => {
347
+ const colors = {
348
+ admin: 'error',
349
+ moderator: 'warning',
350
+ member: 'primary',
351
+ guest: 'neutral',
352
+ };
353
+ return colors[role] || 'primary';
354
+ };
355
+
356
+ <Avatar.Root colorPalette={getColorForRole(user.role)}>
357
+ <Avatar.Fallback name={user.name} />
358
+ <Avatar.Image src={user.avatar} alt={user.name} />
359
+ </Avatar.Root>
360
+ ```
361
+
362
+ ## Common Patterns
363
+
364
+ ### Avatar with Status Indicator
365
+
366
+ ```typescript
367
+ import { css } from 'styled-system/css';
368
+
369
+ // Avatar with online status
370
+ <div className={css({ position: 'relative', display: 'inline-flex' })}>
371
+ <Avatar.Root colorPalette="primary" size="md">
372
+ <Avatar.Fallback name="John Doe" />
373
+ <Avatar.Image src="/avatars/john.jpg" alt="John Doe" />
374
+ </Avatar.Root>
375
+
376
+ {/* Status indicator */}
377
+ <div className={css({
378
+ position: 'absolute',
379
+ bottom: '0',
380
+ right: '0',
381
+ width: '3',
382
+ height: '3',
383
+ borderRadius: 'full',
384
+ bg: 'success.solid',
385
+ border: '2px solid',
386
+ borderColor: 'bg.canvas',
387
+ })} />
388
+ </div>
389
+
390
+ // Avatar with availability status
391
+ const StatusAvatar = ({ name, src, status }: Props) => {
392
+ const statusColors = {
393
+ online: 'success.solid',
394
+ away: 'warning.solid',
395
+ busy: 'error.solid',
396
+ offline: 'neutral.subtle',
397
+ };
398
+
399
+ return (
400
+ <div className={css({ position: 'relative', display: 'inline-flex' })}>
401
+ <Avatar.Root colorPalette="primary" size="md">
402
+ <Avatar.Fallback name={name} />
403
+ <Avatar.Image src={src} alt={name} />
404
+ </Avatar.Root>
405
+
406
+ <div className={css({
407
+ position: 'absolute',
408
+ bottom: '0',
409
+ right: '0',
410
+ width: '3',
411
+ height: '3',
412
+ borderRadius: 'full',
413
+ bg: statusColors[status],
414
+ border: '2px solid',
415
+ borderColor: 'bg.canvas',
416
+ })} />
417
+ </div>
418
+ );
419
+ };
420
+ ```
421
+
422
+ ### Avatar Group (Stacked)
423
+
424
+ ```typescript
425
+ import { HStack } from 'styled-system/jsx';
426
+
427
+ // Overlapping avatar group
428
+ <HStack gap="-3">
429
+ <Avatar.Root colorPalette="primary" size="md">
430
+ <Avatar.Fallback name="Alice Johnson" />
431
+ <Avatar.Image src="/avatars/alice.jpg" alt="Alice" />
432
+ </Avatar.Root>
433
+
434
+ <Avatar.Root colorPalette="primary" size="md">
435
+ <Avatar.Fallback name="Bob Smith" />
436
+ <Avatar.Image src="/avatars/bob.jpg" alt="Bob" />
437
+ </Avatar.Root>
438
+
439
+ <Avatar.Root colorPalette="primary" size="md">
440
+ <Avatar.Fallback name="Carol White" />
441
+ <Avatar.Image src="/avatars/carol.jpg" alt="Carol" />
442
+ </Avatar.Root>
443
+
444
+ {/* Count indicator for additional users */}
445
+ <Avatar.Root colorPalette="neutral" size="md">
446
+ <Avatar.Fallback>+5</Avatar.Fallback>
447
+ </Avatar.Root>
448
+ </HStack>
449
+
450
+ // Reusable avatar group component
451
+ const AvatarGroup = ({ users, max = 3, size = 'md' }: Props) => {
452
+ const visibleUsers = users.slice(0, max);
453
+ const remainingCount = users.length - max;
454
+
455
+ return (
456
+ <HStack gap={size === 'sm' ? '-2' : '-3'}>
457
+ {visibleUsers.map(user => (
458
+ <Avatar.Root key={user.id} colorPalette="primary" size={size}>
459
+ <Avatar.Fallback name={user.name} />
460
+ <Avatar.Image src={user.avatar} alt={user.name} />
461
+ </Avatar.Root>
462
+ ))}
463
+
464
+ {remainingCount > 0 && (
465
+ <Avatar.Root colorPalette="neutral" size={size}>
466
+ <Avatar.Fallback>+{remainingCount}</Avatar.Fallback>
467
+ </Avatar.Root>
468
+ )}
469
+ </HStack>
470
+ );
471
+ };
472
+
473
+ <AvatarGroup users={teamMembers} max={4} size="md" />
474
+ ```
475
+
476
+ ### Avatar with Tooltip
477
+
478
+ ```typescript
479
+ import * as Tooltip from '@discourser/design-system';
480
+
481
+ // Avatar with hover tooltip
482
+ <Tooltip.Root>
483
+ <Tooltip.Trigger asChild>
484
+ <Avatar.Root colorPalette="primary" size="md">
485
+ <Avatar.Fallback name="John Doe" />
486
+ <Avatar.Image src="/avatars/john.jpg" alt="John Doe" />
487
+ </Avatar.Root>
488
+ </Tooltip.Trigger>
489
+
490
+ <Tooltip.Positioner>
491
+ <Tooltip.Content>
492
+ <Tooltip.Arrow />
493
+ John Doe - Senior Developer
494
+ </Tooltip.Content>
495
+ </Tooltip.Positioner>
496
+ </Tooltip.Root>
497
+
498
+ // Avatar group with individual tooltips
499
+ {users.map(user => (
500
+ <Tooltip.Root key={user.id}>
501
+ <Tooltip.Trigger asChild>
502
+ <Avatar.Root colorPalette="primary" size="md">
503
+ <Avatar.Fallback name={user.name} />
504
+ <Avatar.Image src={user.avatar} alt={user.name} />
505
+ </Avatar.Root>
506
+ </Tooltip.Trigger>
507
+
508
+ <Tooltip.Positioner>
509
+ <Tooltip.Content>
510
+ <Tooltip.Arrow />
511
+ {user.name} - {user.role}
512
+ </Tooltip.Content>
513
+ </Tooltip.Positioner>
514
+ </Tooltip.Root>
515
+ ))}
516
+ ```
517
+
518
+ ### Avatar in User Card
519
+
520
+ ```typescript
521
+ import { VStack, HStack } from 'styled-system/jsx';
522
+ import { css } from 'styled-system/css';
523
+
524
+ const UserCard = ({ user }: Props) => (
525
+ <div className={css({
526
+ p: '4',
527
+ borderRadius: 'l2',
528
+ bg: 'bg.surface',
529
+ borderWidth: '1px',
530
+ borderColor: 'border.default',
531
+ })}>
532
+ <HStack gap="3" align="center">
533
+ <Avatar.Root colorPalette="primary" size="lg">
534
+ <Avatar.Fallback name={user.name} />
535
+ <Avatar.Image src={user.avatar} alt={user.name} />
536
+ </Avatar.Root>
537
+
538
+ <VStack gap="1" align="start">
539
+ <div className={css({ fontWeight: 'semibold' })}>
540
+ {user.name}
541
+ </div>
542
+ <div className={css({ fontSize: 'sm', color: 'fg.subtle' })}>
543
+ {user.email}
544
+ </div>
545
+ </VStack>
546
+ </HStack>
547
+ </div>
548
+ );
549
+ ```
550
+
551
+ ### Avatar with Upload/Edit
552
+
553
+ ```typescript
554
+ import { css } from 'styled-system/css';
555
+
556
+ // Avatar with edit overlay
557
+ const EditableAvatar = ({ name, src, onEdit }: Props) => (
558
+ <div className={css({ position: 'relative', display: 'inline-flex' })}>
559
+ <Avatar.Root colorPalette="primary" size="2xl">
560
+ <Avatar.Fallback name={name} />
561
+ <Avatar.Image src={src} alt={name} />
562
+ </Avatar.Root>
563
+
564
+ {/* Edit overlay */}
565
+ <button
566
+ onClick={onEdit}
567
+ className={css({
568
+ position: 'absolute',
569
+ inset: '0',
570
+ borderRadius: 'full',
571
+ bg: 'blackAlpha.600',
572
+ color: 'white',
573
+ display: 'flex',
574
+ alignItems: 'center',
575
+ justifyContent: 'center',
576
+ opacity: '0',
577
+ transition: 'opacity 0.2s',
578
+ cursor: 'pointer',
579
+ _hover: { opacity: '1' },
580
+ })}
581
+ >
582
+ <CameraIcon />
583
+ </button>
584
+ </div>
585
+ );
586
+ ```
587
+
588
+ ### Loading State
589
+
590
+ ```typescript
591
+ import { Skeleton } from '@discourser/design-system';
592
+
593
+ // Avatar loading skeleton
594
+ <Skeleton.Root>
595
+ <Skeleton.Circle size="10" />
596
+ </Skeleton.Root>
597
+
598
+ // Conditional loading
599
+ const UserAvatar = ({ user, isLoading }: Props) => {
600
+ if (isLoading) {
601
+ return <Skeleton.Circle size="10" />;
602
+ }
603
+
604
+ return (
605
+ <Avatar.Root colorPalette="primary" size="md">
606
+ <Avatar.Fallback name={user.name} />
607
+ <Avatar.Image src={user.avatar} alt={user.name} />
608
+ </Avatar.Root>
609
+ );
610
+ };
611
+ ```
612
+
613
+ ### Comment/Post Avatar
614
+
615
+ ```typescript
616
+ import { HStack, VStack } from 'styled-system/jsx';
617
+ import { css } from 'styled-system/css';
618
+
619
+ const Comment = ({ comment }: Props) => (
620
+ <HStack gap="3" align="start">
621
+ <Avatar.Root colorPalette="primary" size="sm">
622
+ <Avatar.Fallback name={comment.author.name} />
623
+ <Avatar.Image src={comment.author.avatar} alt={comment.author.name} />
624
+ </Avatar.Root>
625
+
626
+ <VStack gap="1" align="start" flex="1">
627
+ <HStack gap="2" align="center">
628
+ <span className={css({ fontWeight: 'semibold', fontSize: 'sm' })}>
629
+ {comment.author.name}
630
+ </span>
631
+ <span className={css({ fontSize: 'xs', color: 'fg.subtle' })}>
632
+ {comment.timestamp}
633
+ </span>
634
+ </HStack>
635
+
636
+ <p className={css({ fontSize: 'sm' })}>
637
+ {comment.content}
638
+ </p>
639
+ </VStack>
640
+ </HStack>
641
+ );
642
+ ```
643
+
644
+ ### Navigation User Menu
645
+
646
+ ```typescript
647
+ import { HStack } from 'styled-system/jsx';
648
+ import { css } from 'styled-system/css';
649
+
650
+ const UserMenu = ({ user }: Props) => (
651
+ <HStack gap="2" align="center">
652
+ <Avatar.Root colorPalette="primary" size="sm">
653
+ <Avatar.Fallback name={user.name} />
654
+ <Avatar.Image src={user.avatar} alt={user.name} />
655
+ </Avatar.Root>
656
+
657
+ <span className={css({ fontSize: 'sm', fontWeight: 'medium' })}>
658
+ {user.name}
659
+ </span>
660
+
661
+ <ChevronDownIcon />
662
+ </HStack>
663
+ );
664
+ ```
665
+
666
+ ## DO NOT
667
+
668
+ ```typescript
669
+ // ❌ Don't forget the fallback (image might fail to load)
670
+ <Avatar.Root colorPalette="primary">
671
+ <Avatar.Image src={user.avatar} alt={user.name} />
672
+ </Avatar.Root>
673
+
674
+ // ✅ Always provide a fallback
675
+ <Avatar.Root colorPalette="primary">
676
+ <Avatar.Fallback name={user.name} />
677
+ <Avatar.Image src={user.avatar} alt={user.name} />
678
+ </Avatar.Root>
679
+
680
+ // ❌ Don't forget alt text for accessibility
681
+ <Avatar.Image src={user.avatar} />
682
+
683
+ // ✅ Always provide meaningful alt text
684
+ <Avatar.Image src={user.avatar} alt={user.name} />
685
+
686
+ // ❌ Don't use inline styles
687
+ <Avatar.Root style={{ width: '60px' }}>
688
+ <Avatar.Fallback name="User" />
689
+ </Avatar.Root>
690
+
691
+ // ✅ Use size prop
692
+ <Avatar.Root size="xl">
693
+ <Avatar.Fallback name="User" />
694
+ </Avatar.Root>
695
+
696
+ // ❌ Don't use Image without Root
697
+ <Avatar.Image src={user.avatar} alt={user.name} />
698
+
699
+ // ✅ Always wrap with Root
700
+ <Avatar.Root colorPalette="primary">
701
+ <Avatar.Fallback name={user.name} />
702
+ <Avatar.Image src={user.avatar} alt={user.name} />
703
+ </Avatar.Root>
704
+
705
+ // ❌ Don't hardcode initials
706
+ <Avatar.Root colorPalette="primary">
707
+ <Avatar.Fallback>JD</Avatar.Fallback>
708
+ </Avatar.Root>
709
+
710
+ // ✅ Let the component generate initials
711
+ <Avatar.Root colorPalette="primary">
712
+ <Avatar.Fallback name="John Doe" />
713
+ </Avatar.Root>
714
+
715
+ // ❌ Don't use different sizes in avatar groups
716
+ <HStack gap="-3">
717
+ <Avatar.Root size="md"><Avatar.Fallback name="User 1" /></Avatar.Root>
718
+ <Avatar.Root size="lg"><Avatar.Fallback name="User 2" /></Avatar.Root>
719
+ </HStack>
720
+
721
+ // ✅ Keep consistent sizes in groups
722
+ <HStack gap="-3">
723
+ <Avatar.Root size="md"><Avatar.Fallback name="User 1" /></Avatar.Root>
724
+ <Avatar.Root size="md"><Avatar.Fallback name="User 2" /></Avatar.Root>
725
+ </HStack>
726
+
727
+ // ❌ Don't mix shapes in the same context
728
+ <HStack gap="2">
729
+ <Avatar.Root shape="full"><Avatar.Fallback name="User 1" /></Avatar.Root>
730
+ <Avatar.Root shape="square"><Avatar.Fallback name="User 2" /></Avatar.Root>
731
+ </HStack>
732
+
733
+ // ✅ Use consistent shapes in the same context
734
+ <HStack gap="2">
735
+ <Avatar.Root shape="full"><Avatar.Fallback name="User 1" /></Avatar.Root>
736
+ <Avatar.Root shape="full"><Avatar.Fallback name="User 2" /></Avatar.Root>
737
+ </HStack>
738
+ ```
739
+
740
+ ## Accessibility
741
+
742
+ The Avatar component follows WCAG 2.1 Level AA standards:
743
+
744
+ - **Alt Text**: Always provide descriptive alt text for Avatar.Image
745
+ - **Semantic HTML**: Uses proper img elements with alt attributes
746
+ - **Color Contrast**: All variants meet 4.5:1 contrast ratio for text/initials
747
+ - **Non-decorative**: Images convey user identity and should have meaningful alt text
748
+ - **Keyboard Navigation**: If interactive (e.g., clickable), ensure proper keyboard support
749
+
750
+ ### Accessibility Best Practices
751
+
752
+ ```typescript
753
+ // ✅ Provide meaningful alt text
754
+ <Avatar.Root colorPalette="primary">
755
+ <Avatar.Fallback name="Sarah Johnson" />
756
+ <Avatar.Image
757
+ src="/avatars/sarah.jpg"
758
+ alt="Sarah Johnson's profile picture"
759
+ />
760
+ </Avatar.Root>
761
+
762
+ // ✅ For clickable avatars, add proper labels
763
+ <button aria-label="View John Doe's profile">
764
+ <Avatar.Root colorPalette="primary">
765
+ <Avatar.Fallback name="John Doe" />
766
+ <Avatar.Image src="/avatars/john.jpg" alt="John Doe" />
767
+ </Avatar.Root>
768
+ </button>
769
+
770
+ // ✅ For decorative avatars in groups (when text is nearby)
771
+ <HStack gap="2" align="center">
772
+ <Avatar.Root colorPalette="primary" size="sm">
773
+ <Avatar.Fallback name="Jane Smith" />
774
+ <Avatar.Image src="/avatars/jane.jpg" alt="" role="presentation" />
775
+ </Avatar.Root>
776
+ <span>Jane Smith</span>
777
+ </HStack>
778
+
779
+ // ✅ Provide context for avatar groups
780
+ <div role="group" aria-label="Team members">
781
+ <HStack gap="-3">
782
+ {members.map(member => (
783
+ <Avatar.Root key={member.id} colorPalette="primary">
784
+ <Avatar.Fallback name={member.name} />
785
+ <Avatar.Image src={member.avatar} alt={member.name} />
786
+ </Avatar.Root>
787
+ ))}
788
+ </HStack>
789
+ </div>
790
+
791
+ // ✅ Status indicators should be announced
792
+ <div role="img" aria-label="Sarah Johnson, online">
793
+ <Avatar.Root colorPalette="primary">
794
+ <Avatar.Fallback name="Sarah Johnson" />
795
+ <Avatar.Image src="/avatars/sarah.jpg" alt="" />
796
+ </Avatar.Root>
797
+ <span className={css({ srOnly: true })}>Online</span>
798
+ <div className={css({ /* status indicator styles */ })} />
799
+ </div>
800
+ ```
801
+
802
+ ## Size Selection Guide
803
+
804
+ | Scenario | Recommended Size | Reasoning |
805
+ | --------------- | ---------------- | --------------------------------------- |
806
+ | Inline mentions | `2xs` | Minimal disruption to text flow |
807
+ | Comment threads | `sm` | Compact, doesn't overwhelm content |
808
+ | User lists | `sm` or `md` | Balance between recognition and density |
809
+ | Navigation bar | `sm` or `md` | Subtle presence, space-efficient |
810
+ | User cards | `lg` | Prominent, easy to recognize |
811
+ | Profile headers | `xl` or `2xl` | Hero display, maximum recognition |
812
+ | Avatar groups | `sm` or `md` | Better overlap appearance |
813
+ | Sidebar users | `md` | Standard size for side navigation |
814
+ | Chat messages | `sm` | Space-efficient for message threads |
815
+ | Settings pages | `lg` | Clear identity context |
816
+
817
+ ## Variant Selection Guide
818
+
819
+ | Scenario | Recommended Variant | Reasoning |
820
+ | ----------------- | -------------------- | ------------------------------------------------ |
821
+ | Default users | `subtle` | Soft, unobtrusive, works everywhere |
822
+ | Featured users | `solid` | High contrast, draws attention |
823
+ | User cards | `surface` | Defined boundaries, works on colored backgrounds |
824
+ | Minimal UI | `outline` | Lightweight, modern appearance |
825
+ | Dark backgrounds | `solid` or `surface` | Better contrast and visibility |
826
+ | Light backgrounds | `subtle` | Subtle, doesn't compete with content |
827
+
828
+ ## Shape Selection Guide
829
+
830
+ | Scenario | Recommended Shape | Reasoning |
831
+ | ---------------- | --------------------- | ---------------------------------------- |
832
+ | Individual users | `full` | Standard convention, friendly appearance |
833
+ | Organizations | `square` | Distinct from users, formal |
834
+ | Brand logos | `square` or `rounded` | Preserves logo shape and design |
835
+ | Bot accounts | `rounded` | Different from users, still approachable |
836
+ | System users | `square` | Indicates non-human entity |
837
+
838
+ ## State Behaviors
839
+
840
+ | State | Visual Change | Behavior |
841
+ | -------------------------- | ------------------------ | ---------------------------------------------- |
842
+ | **Loading** | Shows fallback | Fallback displays while image loads |
843
+ | **Error** | Shows fallback | Fallback displays if image fails to load |
844
+ | **No Image** | Shows fallback | Displays initials or default icon |
845
+ | **Hover** (if interactive) | Optional visual feedback | Add cursor: pointer and hover styles as needed |
846
+
847
+ ## Responsive Considerations
848
+
849
+ ```typescript
850
+ // Responsive avatar sizes
851
+ import { css } from 'styled-system/css';
852
+
853
+ <Avatar.Root
854
+ colorPalette="primary"
855
+ className={css({
856
+ // Use size tokens for responsive sizing
857
+ size: { base: '8', md: '10', lg: '12' }
858
+ })}
859
+ >
860
+ <Avatar.Fallback name="John Doe" />
861
+ <Avatar.Image src="/avatars/john.jpg" alt="John Doe" />
862
+ </Avatar.Root>
863
+
864
+ // Responsive avatar group
865
+ <HStack
866
+ gap={{ base: '-2', md: '-3' }}
867
+ className={css({
868
+ '& > *': {
869
+ size: { base: 'sm', md: 'md' }
870
+ }
871
+ })}
872
+ >
873
+ {users.map(user => (
874
+ <Avatar.Root key={user.id} colorPalette="primary">
875
+ <Avatar.Fallback name={user.name} />
876
+ <Avatar.Image src={user.avatar} alt={user.name} />
877
+ </Avatar.Root>
878
+ ))}
879
+ </HStack>
880
+
881
+ // Responsive profile header
882
+ <VStack gap={{ base: '3', md: '4' }} align="center">
883
+ <Avatar.Root
884
+ colorPalette="primary"
885
+ size={{ base: 'xl', md: '2xl' }}
886
+ >
887
+ <Avatar.Fallback name={user.name} />
888
+ <Avatar.Image src={user.avatar} alt={user.name} />
889
+ </Avatar.Root>
890
+
891
+ <h1 className={css({
892
+ fontSize: { base: 'xl', md: '2xl' },
893
+ fontWeight: 'bold'
894
+ })}>
895
+ {user.name}
896
+ </h1>
897
+ </VStack>
898
+ ```
899
+
900
+ ## Testing
901
+
902
+ When testing Avatar components:
903
+
904
+ ```typescript
905
+ import { render, screen, waitFor } from '@testing-library/react';
906
+ import * as Avatar from '@discourser/design-system';
907
+
908
+ test('displays image when loaded', async () => {
909
+ render(
910
+ <Avatar.Root colorPalette="primary">
911
+ <Avatar.Fallback name="John Doe" />
912
+ <Avatar.Image src="https://example.com/avatar.jpg" alt="John Doe" />
913
+ </Avatar.Root>
914
+ );
915
+
916
+ const image = await screen.findByAltText('John Doe');
917
+ expect(image).toBeInTheDocument();
918
+ expect(image).toHaveAttribute('src', 'https://example.com/avatar.jpg');
919
+ });
920
+
921
+ test('displays fallback when image fails', async () => {
922
+ render(
923
+ <Avatar.Root colorPalette="primary">
924
+ <Avatar.Fallback name="John Doe" />
925
+ <Avatar.Image src="invalid-url.jpg" alt="John Doe" />
926
+ </Avatar.Root>
927
+ );
928
+
929
+ // Initially shows fallback
930
+ expect(screen.getByText('JD')).toBeInTheDocument();
931
+ });
932
+
933
+ test('generates correct initials from name', () => {
934
+ render(
935
+ <Avatar.Root colorPalette="primary">
936
+ <Avatar.Fallback name="Sarah Jane Williams" />
937
+ </Avatar.Root>
938
+ );
939
+
940
+ // Should show first and last name initials only
941
+ expect(screen.getByText('SW')).toBeInTheDocument();
942
+ });
943
+
944
+ test('displays default icon when no name provided', () => {
945
+ const { container } = render(
946
+ <Avatar.Root colorPalette="primary">
947
+ <Avatar.Fallback />
948
+ </Avatar.Root>
949
+ );
950
+
951
+ // Should render the default user icon SVG
952
+ expect(container.querySelector('svg')).toBeInTheDocument();
953
+ });
954
+
955
+ test('supports custom fallback content', () => {
956
+ render(
957
+ <Avatar.Root colorPalette="primary">
958
+ <Avatar.Fallback>🎉</Avatar.Fallback>
959
+ </Avatar.Root>
960
+ );
961
+
962
+ expect(screen.getByText('🎉')).toBeInTheDocument();
963
+ });
964
+ ```
965
+
966
+ ## Color Palette System
967
+
968
+ Avatar supports the design system's color palette:
969
+
970
+ ```typescript
971
+ // Semantic colors
972
+ <Avatar.Root colorPalette="primary">
973
+ <Avatar.Fallback name="Primary User" />
974
+ </Avatar.Root>
975
+
976
+ <Avatar.Root colorPalette="neutral">
977
+ <Avatar.Fallback name="Neutral User" />
978
+ </Avatar.Root>
979
+
980
+ <Avatar.Root colorPalette="success">
981
+ <Avatar.Fallback name="Success" />
982
+ </Avatar.Root>
983
+
984
+ <Avatar.Root colorPalette="warning">
985
+ <Avatar.Fallback name="Warning" />
986
+ </Avatar.Root>
987
+
988
+ <Avatar.Root colorPalette="error">
989
+ <Avatar.Fallback name="Error" />
990
+ </Avatar.Root>
991
+
992
+ // Use different colors to categorize users
993
+ const getUserColor = (role: string) => {
994
+ const roleColors = {
995
+ admin: 'error',
996
+ moderator: 'warning',
997
+ premium: 'primary',
998
+ free: 'neutral',
999
+ };
1000
+ return roleColors[role] || 'neutral';
1001
+ };
1002
+
1003
+ <Avatar.Root colorPalette={getUserColor(user.role)}>
1004
+ <Avatar.Fallback name={user.name} />
1005
+ <Avatar.Image src={user.avatar} alt={user.name} />
1006
+ </Avatar.Root>
1007
+ ```
1008
+
1009
+ ## Related Components
1010
+
1011
+ - **Badge**: For displaying status or role labels near avatars
1012
+ - **Tooltip**: For showing additional user information on hover
1013
+ - **Dialog**: For displaying full user profiles
1014
+ - **IconButton**: For edit actions on avatars
1015
+ - **Skeleton**: For loading states