@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.
- package/README.md +12 -4
- package/dist/styles.css +5126 -0
- package/guidelines/Guidelines.md +92 -41
- package/guidelines/components/accordion.md +732 -0
- package/guidelines/components/avatar.md +1015 -0
- package/guidelines/components/badge.md +728 -0
- package/guidelines/components/button.md +75 -40
- package/guidelines/components/card.md +84 -25
- package/guidelines/components/checkbox.md +671 -0
- package/guidelines/components/dialog.md +619 -31
- package/guidelines/components/drawer.md +1616 -0
- package/guidelines/components/heading.md +576 -0
- package/guidelines/components/icon-button.md +92 -37
- package/guidelines/components/input-addon.md +685 -0
- package/guidelines/components/input-group.md +830 -0
- package/guidelines/components/input.md +92 -37
- package/guidelines/components/popover.md +1271 -0
- package/guidelines/components/progress.md +836 -0
- package/guidelines/components/radio-group.md +852 -0
- package/guidelines/components/select.md +1662 -0
- package/guidelines/components/skeleton.md +802 -0
- package/guidelines/components/slider.md +911 -0
- package/guidelines/components/spinner.md +783 -0
- package/guidelines/components/switch.md +105 -38
- package/guidelines/components/tabs.md +1488 -0
- package/guidelines/components/textarea.md +495 -0
- package/guidelines/components/toast.md +784 -0
- package/guidelines/components/tooltip.md +912 -0
- package/guidelines/design-tokens/colors.md +309 -72
- package/guidelines/design-tokens/elevation.md +615 -45
- package/guidelines/design-tokens/spacing.md +654 -74
- package/guidelines/design-tokens/typography.md +432 -50
- package/guidelines/overview-components.md +60 -8
- package/guidelines/overview-imports.md +314 -0
- package/guidelines/overview-patterns.md +3852 -0
- package/package.json +4 -2
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
# Badge
|
|
2
|
+
|
|
3
|
+
**Purpose:** Compact visual indicator for displaying status, labels, counts, or categories following Material Design 3 patterns.
|
|
4
|
+
|
|
5
|
+
## When to Use This Component
|
|
6
|
+
|
|
7
|
+
Use Badge when you need to **display compact visual indicators for status, counts, labels, or categories** as non-interactive elements.
|
|
8
|
+
|
|
9
|
+
### Decision Tree
|
|
10
|
+
|
|
11
|
+
| Scenario | Use Badge? | Alternative | Reasoning |
|
|
12
|
+
| ------------------------------------------------ | ---------- | -------------- | ---------------------------------------------- |
|
|
13
|
+
| Displaying status labels (Active, Pending, etc.) | ✅ Yes | - | Badge provides clear visual categorization |
|
|
14
|
+
| Notification counts on icons or buttons | ✅ Yes | - | Perfect for unread messages, alerts |
|
|
15
|
+
| Category tags or labels | ✅ Yes | - | Compact visual grouping |
|
|
16
|
+
| Clickable filters or selections | ❌ No | Button or Chip | Badge is non-interactive |
|
|
17
|
+
| Primary call-to-action | ❌ No | Button | Button is designed for actions |
|
|
18
|
+
| User profile images | ❌ No | Avatar | Avatar is specifically for user representation |
|
|
19
|
+
|
|
20
|
+
### Component Comparison
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// ✅ Badge - Status indicator
|
|
24
|
+
<Stack gap="2" direction="row">
|
|
25
|
+
<Badge colorPalette="success">Active</Badge>
|
|
26
|
+
<Badge colorPalette="warning">Pending</Badge>
|
|
27
|
+
<Badge colorPalette="error">Expired</Badge>
|
|
28
|
+
</Stack>
|
|
29
|
+
|
|
30
|
+
// ❌ Don't use Badge for interactive elements - Use Button
|
|
31
|
+
<Badge onClick={handleClick} colorPalette="primary">
|
|
32
|
+
Click me
|
|
33
|
+
</Badge>
|
|
34
|
+
|
|
35
|
+
// ✅ Better: Use Button for clickable actions
|
|
36
|
+
<Button size="sm" variant="outlined">
|
|
37
|
+
Click me
|
|
38
|
+
</Button>
|
|
39
|
+
|
|
40
|
+
// ❌ Don't use Badge for user avatars - Use Avatar
|
|
41
|
+
<Badge colorPalette="primary" size="2xl">
|
|
42
|
+
JD
|
|
43
|
+
</Badge>
|
|
44
|
+
|
|
45
|
+
// ✅ Better: Use Avatar for user representation
|
|
46
|
+
<Avatar.Root colorPalette="primary" size="md">
|
|
47
|
+
<Avatar.Fallback name="John Doe" />
|
|
48
|
+
<Avatar.Image src="/avatar.jpg" alt="John Doe" />
|
|
49
|
+
</Avatar.Root>
|
|
50
|
+
|
|
51
|
+
// ✅ Badge - Notification count on icon
|
|
52
|
+
<Box position="relative">
|
|
53
|
+
<IconButton><BellIcon /></IconButton>
|
|
54
|
+
<Badge
|
|
55
|
+
position="absolute"
|
|
56
|
+
top="-1"
|
|
57
|
+
right="-1"
|
|
58
|
+
colorPalette="error"
|
|
59
|
+
size="sm"
|
|
60
|
+
>
|
|
61
|
+
5
|
|
62
|
+
</Badge>
|
|
63
|
+
</Box>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Import
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { Badge } from '@discourser/design-system';
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Variants
|
|
73
|
+
|
|
74
|
+
The Badge component supports 4 visual variants, each with specific use cases:
|
|
75
|
+
|
|
76
|
+
| Variant | Visual Style | Usage | When to Use |
|
|
77
|
+
| --------- | -------------------------------------------- | ------------------------- | -------------------------------------------------- |
|
|
78
|
+
| `subtle` | Light background with colored text | Default status indicators | General labels, categories, non-critical status |
|
|
79
|
+
| `solid` | Solid color background with contrasting text | High emphasis indicators | Important status, featured items, primary labels |
|
|
80
|
+
| `surface` | Surface background with border | Outlined status | Secondary emphasis, grouped badges, neutral labels |
|
|
81
|
+
| `outline` | Transparent background with border | Minimal emphasis | Tertiary labels, tags, filters |
|
|
82
|
+
|
|
83
|
+
### Visual Characteristics
|
|
84
|
+
|
|
85
|
+
- **subtle**: Uses `colorPalette.subtle.bg` background with `colorPalette.subtle.fg` text
|
|
86
|
+
- **solid**: Uses `colorPalette.solid.bg` background with `colorPalette.solid.fg` text (highest contrast)
|
|
87
|
+
- **surface**: Uses `colorPalette.surface.bg` background with 1px border and `colorPalette.surface.fg` text
|
|
88
|
+
- **outline**: Transparent background with 1px `colorPalette.outline.border` and `colorPalette.outline.fg` text
|
|
89
|
+
|
|
90
|
+
## Sizes
|
|
91
|
+
|
|
92
|
+
| Size | Height | Padding (Horizontal) | Font Size | Icon Size | Gap | Usage |
|
|
93
|
+
| ----- | ---------- | -------------------- | --------- | ---------- | --------- | ------------------------------------- |
|
|
94
|
+
| `sm` | 18px (4.5) | 6px (1.5) | xs | 10px (2.5) | 2px (0.5) | Compact UI, dense tables, inline text |
|
|
95
|
+
| `md` | 20px (5) | 8px (2) | xs | 12px (3) | 4px (1) | Default, most use cases |
|
|
96
|
+
| `lg` | 22px (5.5) | 10px (2.5) | xs | 14px (3.5) | 4px (1) | Prominent labels, touch targets |
|
|
97
|
+
| `xl` | 24px (6) | 10px (2.5) | sm | 16px (4) | 6px (1.5) | Large displays, featured items |
|
|
98
|
+
| `2xl` | 28px (7) | 12px (3) | md | 18px (4.5) | 6px (1.5) | Hero sections, marketing emphasis |
|
|
99
|
+
|
|
100
|
+
**Recommendation:** Use `md` for most cases. Use `sm` for dense layouts or inline badges. Use `lg` or larger for touch interfaces.
|
|
101
|
+
|
|
102
|
+
## Props
|
|
103
|
+
|
|
104
|
+
| Prop | Type | Default | Description |
|
|
105
|
+
| ----------- | ----------------------------------------------- | ---------- | ------------------------------------------- |
|
|
106
|
+
| `variant` | `'subtle' \| 'solid' \| 'surface' \| 'outline'` | `'subtle'` | Visual style variant |
|
|
107
|
+
| `size` | `'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` | Badge size |
|
|
108
|
+
| `children` | `ReactNode` | Required | Badge content (text, icons, or combination) |
|
|
109
|
+
| `className` | `string` | - | Additional CSS classes (use sparingly) |
|
|
110
|
+
|
|
111
|
+
**Note:** Badge extends `HTMLAttributes<HTMLDivElement>`, so all standard HTML div attributes are supported.
|
|
112
|
+
|
|
113
|
+
## Color Palettes
|
|
114
|
+
|
|
115
|
+
Badges support dynamic color palettes using Panda CSS color palette system:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Use colorPalette prop to change badge colors
|
|
119
|
+
<Badge colorPalette="primary">Primary</Badge>
|
|
120
|
+
<Badge colorPalette="success">Success</Badge>
|
|
121
|
+
<Badge colorPalette="warning">Warning</Badge>
|
|
122
|
+
<Badge colorPalette="danger">Danger</Badge>
|
|
123
|
+
<Badge colorPalette="info">Info</Badge>
|
|
124
|
+
<Badge colorPalette="neutral">Neutral</Badge>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Examples
|
|
128
|
+
|
|
129
|
+
### Basic Usage
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// Default badge (subtle variant)
|
|
133
|
+
<Badge>New</Badge>
|
|
134
|
+
|
|
135
|
+
// Different variants
|
|
136
|
+
<Badge variant="subtle">Pending</Badge>
|
|
137
|
+
<Badge variant="solid">Active</Badge>
|
|
138
|
+
<Badge variant="surface">Draft</Badge>
|
|
139
|
+
<Badge variant="outline">Optional</Badge>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Semantic Status Badges
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Success status
|
|
146
|
+
<Badge colorPalette="success" variant="subtle">
|
|
147
|
+
Completed
|
|
148
|
+
</Badge>
|
|
149
|
+
|
|
150
|
+
// Warning status
|
|
151
|
+
<Badge colorPalette="warning" variant="solid">
|
|
152
|
+
Attention Required
|
|
153
|
+
</Badge>
|
|
154
|
+
|
|
155
|
+
// Error status
|
|
156
|
+
<Badge colorPalette="danger" variant="surface">
|
|
157
|
+
Failed
|
|
158
|
+
</Badge>
|
|
159
|
+
|
|
160
|
+
// Info status
|
|
161
|
+
<Badge colorPalette="info" variant="outline">
|
|
162
|
+
Information
|
|
163
|
+
</Badge>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Different Sizes
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Small badges for compact layouts
|
|
170
|
+
<Badge size="sm">Small</Badge>
|
|
171
|
+
|
|
172
|
+
// Default size
|
|
173
|
+
<Badge size="md">Medium</Badge>
|
|
174
|
+
|
|
175
|
+
// Large badges for emphasis
|
|
176
|
+
<Badge size="lg">Large</Badge>
|
|
177
|
+
|
|
178
|
+
// Extra large for hero sections
|
|
179
|
+
<Badge size="xl">Extra Large</Badge>
|
|
180
|
+
|
|
181
|
+
// Maximum size
|
|
182
|
+
<Badge size="2xl">Huge</Badge>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### With Icons
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { CheckIcon, ClockIcon, XIcon, InfoIcon } from 'your-icon-library';
|
|
189
|
+
|
|
190
|
+
// Icon before text
|
|
191
|
+
<Badge>
|
|
192
|
+
<CheckIcon /> Verified
|
|
193
|
+
</Badge>
|
|
194
|
+
|
|
195
|
+
// Icon after text
|
|
196
|
+
<Badge>
|
|
197
|
+
In Progress <ClockIcon />
|
|
198
|
+
</Badge>
|
|
199
|
+
|
|
200
|
+
// Icon only (ensure accessible label)
|
|
201
|
+
<Badge aria-label="Completed">
|
|
202
|
+
<CheckIcon />
|
|
203
|
+
</Badge>
|
|
204
|
+
|
|
205
|
+
// Multiple elements
|
|
206
|
+
<Badge colorPalette="success">
|
|
207
|
+
<CheckIcon />
|
|
208
|
+
<span>Success</span>
|
|
209
|
+
</Badge>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Counts and Numbers
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Notification count
|
|
216
|
+
<Badge variant="solid" colorPalette="danger">
|
|
217
|
+
5
|
|
218
|
+
</Badge>
|
|
219
|
+
|
|
220
|
+
// Quantity indicator
|
|
221
|
+
<Badge variant="subtle">
|
|
222
|
+
12 items
|
|
223
|
+
</Badge>
|
|
224
|
+
|
|
225
|
+
// Numeric status
|
|
226
|
+
<Badge colorPalette="primary">
|
|
227
|
+
+99
|
|
228
|
+
</Badge>
|
|
229
|
+
|
|
230
|
+
// Percentage
|
|
231
|
+
<Badge variant="surface" colorPalette="success">
|
|
232
|
+
+15%
|
|
233
|
+
</Badge>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Category Labels
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// Product categories
|
|
240
|
+
<Badge colorPalette="primary">Technology</Badge>
|
|
241
|
+
<Badge colorPalette="purple">Design</Badge>
|
|
242
|
+
<Badge colorPalette="green">Marketing</Badge>
|
|
243
|
+
|
|
244
|
+
// Priority levels
|
|
245
|
+
<Badge variant="solid" colorPalette="danger">High Priority</Badge>
|
|
246
|
+
<Badge variant="subtle" colorPalette="warning">Medium Priority</Badge>
|
|
247
|
+
<Badge variant="outline" colorPalette="neutral">Low Priority</Badge>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Tag Groups
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// Multiple tags
|
|
254
|
+
<div className={css({ display: 'flex', gap: '2', flexWrap: 'wrap' })}>
|
|
255
|
+
<Badge variant="outline">JavaScript</Badge>
|
|
256
|
+
<Badge variant="outline">React</Badge>
|
|
257
|
+
<Badge variant="outline">TypeScript</Badge>
|
|
258
|
+
<Badge variant="outline">Node.js</Badge>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
// Removable tags
|
|
262
|
+
<div className={css({ display: 'flex', gap: '2' })}>
|
|
263
|
+
<Badge variant="surface">
|
|
264
|
+
Design
|
|
265
|
+
<button aria-label="Remove Design tag">
|
|
266
|
+
<XIcon />
|
|
267
|
+
</button>
|
|
268
|
+
</Badge>
|
|
269
|
+
<Badge variant="surface">
|
|
270
|
+
Development
|
|
271
|
+
<button aria-label="Remove Development tag">
|
|
272
|
+
<XIcon />
|
|
273
|
+
</button>
|
|
274
|
+
</Badge>
|
|
275
|
+
</div>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Common Patterns
|
|
279
|
+
|
|
280
|
+
### Status Indicators in Lists
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// List with status badges
|
|
284
|
+
<ul>
|
|
285
|
+
{items.map(item => (
|
|
286
|
+
<li key={item.id} className={css({ display: 'flex', alignItems: 'center', gap: '3' })}>
|
|
287
|
+
<span>{item.name}</span>
|
|
288
|
+
<Badge
|
|
289
|
+
colorPalette={item.status === 'active' ? 'success' : 'neutral'}
|
|
290
|
+
variant="subtle"
|
|
291
|
+
>
|
|
292
|
+
{item.status}
|
|
293
|
+
</Badge>
|
|
294
|
+
</li>
|
|
295
|
+
))}
|
|
296
|
+
</ul>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Notification Badge
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// Icon with notification count
|
|
303
|
+
<div className={css({ position: 'relative', display: 'inline-block' })}>
|
|
304
|
+
<IconButton aria-label="Notifications">
|
|
305
|
+
<BellIcon />
|
|
306
|
+
</IconButton>
|
|
307
|
+
<Badge
|
|
308
|
+
variant="solid"
|
|
309
|
+
colorPalette="danger"
|
|
310
|
+
size="sm"
|
|
311
|
+
className={css({ position: 'absolute', top: '-1', right: '-1' })}
|
|
312
|
+
>
|
|
313
|
+
3
|
|
314
|
+
</Badge>
|
|
315
|
+
</div>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Card with Badge
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// Card featuring a badge
|
|
322
|
+
<Card>
|
|
323
|
+
<div className={css({ display: 'flex', justifyContent: 'space-between', alignItems: 'start' })}>
|
|
324
|
+
<Heading as="h3" size="md">Premium Plan</Heading>
|
|
325
|
+
<Badge variant="solid" colorPalette="primary">
|
|
326
|
+
Popular
|
|
327
|
+
</Badge>
|
|
328
|
+
</div>
|
|
329
|
+
<p>Best value for growing teams</p>
|
|
330
|
+
<Button>Choose Plan</Button>
|
|
331
|
+
</Card>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Table Cell Badges
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
// Status column in table
|
|
338
|
+
<table>
|
|
339
|
+
<thead>
|
|
340
|
+
<tr>
|
|
341
|
+
<th>Name</th>
|
|
342
|
+
<th>Status</th>
|
|
343
|
+
<th>Priority</th>
|
|
344
|
+
</tr>
|
|
345
|
+
</thead>
|
|
346
|
+
<tbody>
|
|
347
|
+
{rows.map(row => (
|
|
348
|
+
<tr key={row.id}>
|
|
349
|
+
<td>{row.name}</td>
|
|
350
|
+
<td>
|
|
351
|
+
<Badge
|
|
352
|
+
variant="subtle"
|
|
353
|
+
colorPalette={row.status === 'complete' ? 'success' : 'warning'}
|
|
354
|
+
size="sm"
|
|
355
|
+
>
|
|
356
|
+
{row.status}
|
|
357
|
+
</Badge>
|
|
358
|
+
</td>
|
|
359
|
+
<td>
|
|
360
|
+
<Badge size="sm" variant="outline">
|
|
361
|
+
{row.priority}
|
|
362
|
+
</Badge>
|
|
363
|
+
</td>
|
|
364
|
+
</tr>
|
|
365
|
+
))}
|
|
366
|
+
</tbody>
|
|
367
|
+
</table>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### User Profile Badges
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
// Profile with role badges
|
|
374
|
+
<div className={css({ display: 'flex', alignItems: 'center', gap: '3' })}>
|
|
375
|
+
<Avatar src={user.avatar} />
|
|
376
|
+
<div>
|
|
377
|
+
<div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
|
|
378
|
+
<span className={css({ fontWeight: 'bold' })}>{user.name}</span>
|
|
379
|
+
<Badge variant="solid" colorPalette="primary" size="sm">
|
|
380
|
+
Admin
|
|
381
|
+
</Badge>
|
|
382
|
+
{user.isVerified && (
|
|
383
|
+
<Badge variant="subtle" colorPalette="success" size="sm">
|
|
384
|
+
<CheckIcon /> Verified
|
|
385
|
+
</Badge>
|
|
386
|
+
)}
|
|
387
|
+
</div>
|
|
388
|
+
<p className={css({ fontSize: 'sm', opacity: 0.7 })}>{user.email}</p>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Filter Tags
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
// Active filters with badges
|
|
397
|
+
<div>
|
|
398
|
+
<span className={css({ fontWeight: 'medium', mr: '3' })}>Active Filters:</span>
|
|
399
|
+
<div className={css({ display: 'inline-flex', gap: '2', flexWrap: 'wrap' })}>
|
|
400
|
+
{activeFilters.map(filter => (
|
|
401
|
+
<Badge
|
|
402
|
+
key={filter.id}
|
|
403
|
+
variant="surface"
|
|
404
|
+
colorPalette="primary"
|
|
405
|
+
>
|
|
406
|
+
{filter.label}
|
|
407
|
+
<button
|
|
408
|
+
onClick={() => removeFilter(filter.id)}
|
|
409
|
+
aria-label={`Remove ${filter.label} filter`}
|
|
410
|
+
className={css({ ml: '1', cursor: 'pointer' })}
|
|
411
|
+
>
|
|
412
|
+
<XIcon />
|
|
413
|
+
</button>
|
|
414
|
+
</Badge>
|
|
415
|
+
))}
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Feature Badges
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
// Product features with badges
|
|
424
|
+
<div className={css({ display: 'grid', gridTemplateColumns: '2', gap: '4' })}>
|
|
425
|
+
<div>
|
|
426
|
+
<Badge variant="solid" colorPalette="success" size="sm">
|
|
427
|
+
New
|
|
428
|
+
</Badge>
|
|
429
|
+
<Heading as="h3" size="md" className={css({ mt: '2' })}>
|
|
430
|
+
Dark Mode
|
|
431
|
+
</Heading>
|
|
432
|
+
<p>Beautiful dark theme for reduced eye strain</p>
|
|
433
|
+
</div>
|
|
434
|
+
<div>
|
|
435
|
+
<Badge variant="subtle" colorPalette="warning" size="sm">
|
|
436
|
+
Beta
|
|
437
|
+
</Badge>
|
|
438
|
+
<Heading as="h3" size="md" className={css({ mt: '2' })}>
|
|
439
|
+
AI Assistant
|
|
440
|
+
</Heading>
|
|
441
|
+
<p>Intelligent help powered by machine learning</p>
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## DO NOT
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
// ❌ Don't use badges for interactive buttons
|
|
450
|
+
<Badge onClick={handleClick}>Click Me</Badge> // Use Button instead
|
|
451
|
+
|
|
452
|
+
// ❌ Don't overuse solid variant (reduces emphasis)
|
|
453
|
+
<div>
|
|
454
|
+
<Badge variant="solid">Tag 1</Badge>
|
|
455
|
+
<Badge variant="solid">Tag 2</Badge>
|
|
456
|
+
<Badge variant="solid">Tag 3</Badge> // Too much emphasis
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
// ❌ Don't use long text in badges
|
|
460
|
+
<Badge>This is a very long label that doesn't fit well in a badge</Badge>
|
|
461
|
+
|
|
462
|
+
// ❌ Don't mix too many color palettes
|
|
463
|
+
<div>
|
|
464
|
+
<Badge colorPalette="red">A</Badge>
|
|
465
|
+
<Badge colorPalette="blue">B</Badge>
|
|
466
|
+
<Badge colorPalette="green">C</Badge>
|
|
467
|
+
<Badge colorPalette="yellow">D</Badge> // Too chaotic
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
// ❌ Don't use badges as primary navigation
|
|
471
|
+
<Badge onClick={() => navigate('/page')}>Go to Page</Badge> // Use Link
|
|
472
|
+
|
|
473
|
+
// ❌ Don't forget accessible labels for icon-only badges
|
|
474
|
+
<Badge><CheckIcon /></Badge> // No text alternative
|
|
475
|
+
|
|
476
|
+
// ❌ Don't override semantic colors inappropriately
|
|
477
|
+
<Badge colorPalette="success">Error</Badge> // Misleading color
|
|
478
|
+
|
|
479
|
+
// ✅ Use appropriate variants for emphasis
|
|
480
|
+
<div className={css({ display: 'flex', gap: '2' })}>
|
|
481
|
+
<Badge variant="solid">Featured</Badge>
|
|
482
|
+
<Badge variant="subtle">Tag 1</Badge>
|
|
483
|
+
<Badge variant="subtle">Tag 2</Badge>
|
|
484
|
+
<Badge variant="outline">Optional</Badge>
|
|
485
|
+
</div>
|
|
486
|
+
|
|
487
|
+
// ✅ Keep badge text concise
|
|
488
|
+
<Badge>New</Badge>
|
|
489
|
+
<Badge>Beta</Badge>
|
|
490
|
+
<Badge>Coming Soon</Badge>
|
|
491
|
+
|
|
492
|
+
// ✅ Use consistent color palette
|
|
493
|
+
<div className={css({ display: 'flex', gap: '2' })}>
|
|
494
|
+
<Badge colorPalette="primary" variant="outline">JavaScript</Badge>
|
|
495
|
+
<Badge colorPalette="primary" variant="outline">React</Badge>
|
|
496
|
+
<Badge colorPalette="primary" variant="outline">TypeScript</Badge>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
// ✅ Match colors to meaning
|
|
500
|
+
<Badge colorPalette="success">Completed</Badge>
|
|
501
|
+
<Badge colorPalette="danger">Failed</Badge>
|
|
502
|
+
<Badge colorPalette="warning">Pending</Badge>
|
|
503
|
+
|
|
504
|
+
// ✅ Always provide accessible labels
|
|
505
|
+
<Badge aria-label="Verified user">
|
|
506
|
+
<CheckIcon />
|
|
507
|
+
</Badge>
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
## Accessibility
|
|
511
|
+
|
|
512
|
+
The Badge component follows WCAG 2.1 Level AA standards:
|
|
513
|
+
|
|
514
|
+
- **Color Independence**: Don't rely solely on color to convey meaning
|
|
515
|
+
- **Text Alternative**: Provide text or aria-label for icon-only badges
|
|
516
|
+
- **Color Contrast**: All variants meet 4.5:1 contrast ratio
|
|
517
|
+
- **Non-interactive**: Badges are display elements, not interactive controls
|
|
518
|
+
- **Readable Text**: Minimum font size ensures legibility
|
|
519
|
+
|
|
520
|
+
### Accessibility Best Practices
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
// ✅ Include text with icons
|
|
524
|
+
<Badge colorPalette="success">
|
|
525
|
+
<CheckIcon /> Verified
|
|
526
|
+
</Badge>
|
|
527
|
+
|
|
528
|
+
// ✅ Provide aria-label for icon-only badges
|
|
529
|
+
<Badge colorPalette="success" aria-label="Verified">
|
|
530
|
+
<CheckIcon />
|
|
531
|
+
</Badge>
|
|
532
|
+
|
|
533
|
+
// ✅ Use semantic meaning beyond color
|
|
534
|
+
<Badge colorPalette="danger">Failed - Try Again</Badge> // Text clarifies status
|
|
535
|
+
|
|
536
|
+
// ✅ Screen reader friendly counts
|
|
537
|
+
<Badge aria-label="5 unread notifications">5</Badge>
|
|
538
|
+
|
|
539
|
+
// ✅ Accessible removable badges
|
|
540
|
+
<Badge variant="surface">
|
|
541
|
+
Design
|
|
542
|
+
<button
|
|
543
|
+
aria-label="Remove Design tag"
|
|
544
|
+
onClick={() => removeTag('design')}
|
|
545
|
+
>
|
|
546
|
+
<XIcon />
|
|
547
|
+
</button>
|
|
548
|
+
</Badge>
|
|
549
|
+
|
|
550
|
+
// ✅ Status badges with clear meaning
|
|
551
|
+
<Badge
|
|
552
|
+
colorPalette="warning"
|
|
553
|
+
aria-label="Status: Pending approval"
|
|
554
|
+
>
|
|
555
|
+
Pending
|
|
556
|
+
</Badge>
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Screen Reader Considerations
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
// Badge content is read by screen readers
|
|
563
|
+
<Badge>New</Badge> // Announces: "New"
|
|
564
|
+
|
|
565
|
+
// Combine with context for clarity
|
|
566
|
+
<article aria-label="New feature: Dark mode">
|
|
567
|
+
<Badge colorPalette="primary">New</Badge>
|
|
568
|
+
<Heading as="h3" size="md">Dark Mode</Heading>
|
|
569
|
+
</article>
|
|
570
|
+
|
|
571
|
+
// Use aria-hidden for decorative badges
|
|
572
|
+
<Badge aria-hidden="true" size="sm">
|
|
573
|
+
<StarIcon />
|
|
574
|
+
</Badge>
|
|
575
|
+
<span className="sr-only">Featured item</span>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
## Variant Selection Guide
|
|
579
|
+
|
|
580
|
+
| Scenario | Recommended Variant | Color Palette | Reasoning |
|
|
581
|
+
| ------------------ | ------------------- | --------------------------------- | ---------------------------------------- |
|
|
582
|
+
| Status indicator | `subtle` | Semantic (success/warning/danger) | Clear status without overwhelming |
|
|
583
|
+
| Featured item | `solid` | `primary` | Maximum emphasis for important items |
|
|
584
|
+
| Category tags | `outline` | `neutral` or `primary` | Minimal emphasis, clean grouping |
|
|
585
|
+
| Notification count | `solid` | `danger` | High visibility for urgent items |
|
|
586
|
+
| Filter tags | `surface` | `primary` | Medium emphasis, distinct from content |
|
|
587
|
+
| Priority labels | `subtle` or `solid` | Semantic | Matches urgency level |
|
|
588
|
+
| Beta/New labels | `solid` or `subtle` | `primary` or `warning` | Draws attention to new features |
|
|
589
|
+
| Role badges | `subtle` | `primary` or `neutral` | Clear identification without distraction |
|
|
590
|
+
|
|
591
|
+
## State Behaviors
|
|
592
|
+
|
|
593
|
+
| State | Visual Change | Behavior |
|
|
594
|
+
| -------------------------- | ------------------------- | ----------------------------------------------- |
|
|
595
|
+
| **Default** | Standard appearance | Non-interactive, pure visual indicator |
|
|
596
|
+
| **With interactive child** | Child element interactive | Button or link within badge can be clicked |
|
|
597
|
+
| **Disabled** | N/A | Badges don't have disabled state (hide instead) |
|
|
598
|
+
|
|
599
|
+
**Note:** Badges themselves are not interactive. If you need an interactive badge-like element, consider using a Button with badge styling or wrap the badge in a clickable element.
|
|
600
|
+
|
|
601
|
+
## Size Selection Guide
|
|
602
|
+
|
|
603
|
+
| Context | Recommended Size | Reasoning |
|
|
604
|
+
| --------------------- | ---------------- | ----------------------------------------- |
|
|
605
|
+
| Inline with text | `sm` | Matches text baseline, minimal disruption |
|
|
606
|
+
| Table cells | `sm` | Compact display in dense layouts |
|
|
607
|
+
| Card headers | `md` | Balanced emphasis with card content |
|
|
608
|
+
| List items | `md` | Clear visibility without overwhelming |
|
|
609
|
+
| Feature highlights | `lg` or `xl` | Prominent display for marketing |
|
|
610
|
+
| Navigation indicators | `sm` or `md` | Visible but not distracting |
|
|
611
|
+
| Notification bubbles | `sm` | Compact count display |
|
|
612
|
+
| Hero sections | `xl` or `2xl` | Large format for emphasis |
|
|
613
|
+
|
|
614
|
+
## Responsive Considerations
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
// Responsive badge sizes
|
|
618
|
+
<Badge size={{ base: 'sm', md: 'md', lg: 'lg' }}>
|
|
619
|
+
Responsive
|
|
620
|
+
</Badge>
|
|
621
|
+
|
|
622
|
+
// Hide badges on mobile for cleaner layout
|
|
623
|
+
<Badge className={css({ display: { base: 'none', md: 'inline-flex' } })}>
|
|
624
|
+
Desktop Only
|
|
625
|
+
</Badge>
|
|
626
|
+
|
|
627
|
+
// Responsive badge groups
|
|
628
|
+
<div className={css({
|
|
629
|
+
display: 'flex',
|
|
630
|
+
gap: { base: '1', md: '2' },
|
|
631
|
+
flexWrap: 'wrap'
|
|
632
|
+
})}>
|
|
633
|
+
<Badge size={{ base: 'sm', md: 'md' }}>Tag 1</Badge>
|
|
634
|
+
<Badge size={{ base: 'sm', md: 'md' }}>Tag 2</Badge>
|
|
635
|
+
<Badge size={{ base: 'sm', md: 'md' }}>Tag 3</Badge>
|
|
636
|
+
</div>
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
## Testing
|
|
640
|
+
|
|
641
|
+
When testing Badge components:
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
import { render, screen } from '@testing-library/react';
|
|
645
|
+
|
|
646
|
+
test('renders badge with text content', () => {
|
|
647
|
+
render(<Badge>New</Badge>);
|
|
648
|
+
|
|
649
|
+
const badge = screen.getByText('New');
|
|
650
|
+
expect(badge).toBeInTheDocument();
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
test('applies correct variant styles', () => {
|
|
654
|
+
const { container } = render(<Badge variant="solid">Featured</Badge>);
|
|
655
|
+
|
|
656
|
+
const badge = container.firstChild;
|
|
657
|
+
expect(badge).toHaveClass('badge');
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
test('applies correct size', () => {
|
|
661
|
+
render(<Badge size="lg">Large Badge</Badge>);
|
|
662
|
+
|
|
663
|
+
const badge = screen.getByText('Large Badge');
|
|
664
|
+
expect(badge).toBeInTheDocument();
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
test('applies color palette', () => {
|
|
668
|
+
render(<Badge colorPalette="success">Success</Badge>);
|
|
669
|
+
|
|
670
|
+
const badge = screen.getByText('Success');
|
|
671
|
+
expect(badge).toBeInTheDocument();
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
test('renders icon-only badge with aria-label', () => {
|
|
675
|
+
const { container } = render(
|
|
676
|
+
<Badge aria-label="Verified">
|
|
677
|
+
<svg data-testid="check-icon" />
|
|
678
|
+
</Badge>
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
const badge = container.firstChild;
|
|
682
|
+
expect(badge).toHaveAttribute('aria-label', 'Verified');
|
|
683
|
+
expect(screen.getByTestId('check-icon')).toBeInTheDocument();
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
test('renders badge with icon and text', () => {
|
|
687
|
+
render(
|
|
688
|
+
<Badge>
|
|
689
|
+
<svg data-testid="icon" />
|
|
690
|
+
<span>Verified</span>
|
|
691
|
+
</Badge>
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
expect(screen.getByTestId('icon')).toBeInTheDocument();
|
|
695
|
+
expect(screen.getByText('Verified')).toBeInTheDocument();
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
test('accepts custom className', () => {
|
|
699
|
+
const { container } = render(
|
|
700
|
+
<Badge className="custom-badge">Custom</Badge>
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
const badge = container.firstChild;
|
|
704
|
+
expect(badge).toHaveClass('custom-badge');
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test('badge is not interactive by default', () => {
|
|
708
|
+
const handleClick = vi.fn();
|
|
709
|
+
const { container } = render(
|
|
710
|
+
<Badge onClick={handleClick}>Click Me</Badge>
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
const badge = container.firstChild as HTMLElement;
|
|
714
|
+
badge.click();
|
|
715
|
+
|
|
716
|
+
expect(handleClick).toHaveBeenCalled(); // onClick is passed through but not recommended
|
|
717
|
+
});
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
## Related Components
|
|
721
|
+
|
|
722
|
+
- **Button**: For interactive badge-like elements
|
|
723
|
+
- **Chip**: For interactive, removable tags (if implemented)
|
|
724
|
+
- **Tag**: Alternative name for similar component patterns
|
|
725
|
+
- **IconButton**: For interactive icon elements
|
|
726
|
+
- **Heading**: Often paired with badges in headers
|
|
727
|
+
- **Card**: Frequently contains status badges
|
|
728
|
+
- **Avatar**: Often displayed with role or status badges
|