@frosted-ui/react-native 0.0.1-canary.91 → 0.0.1-canary.94

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 (172) hide show
  1. package/README.md +72 -14
  2. package/dist/components/accordion.js +2 -2
  3. package/dist/components/accordion.js.map +1 -1
  4. package/dist/components/alert-dialog.js +2 -2
  5. package/dist/components/alert-dialog.js.map +1 -1
  6. package/dist/components/avatar.d.ts.map +1 -1
  7. package/dist/components/avatar.js +5 -20
  8. package/dist/components/avatar.js.map +1 -1
  9. package/dist/components/badge.d.ts +2 -4
  10. package/dist/components/badge.d.ts.map +1 -1
  11. package/dist/components/badge.js +8 -22
  12. package/dist/components/badge.js.map +1 -1
  13. package/dist/components/button.d.ts +1 -1
  14. package/dist/components/button.d.ts.map +1 -1
  15. package/dist/components/button.js +13 -17
  16. package/dist/components/button.js.map +1 -1
  17. package/dist/components/callout.d.ts +2 -4
  18. package/dist/components/callout.d.ts.map +1 -1
  19. package/dist/components/callout.js +6 -24
  20. package/dist/components/callout.js.map +1 -1
  21. package/dist/components/card.d.ts +1 -2
  22. package/dist/components/card.d.ts.map +1 -1
  23. package/dist/components/card.js +4 -4
  24. package/dist/components/card.js.map +1 -1
  25. package/dist/components/checkbox.d.ts +1 -2
  26. package/dist/components/checkbox.d.ts.map +1 -1
  27. package/dist/components/checkbox.js +5 -21
  28. package/dist/components/checkbox.js.map +1 -1
  29. package/dist/components/code.d.ts +6 -8
  30. package/dist/components/code.d.ts.map +1 -1
  31. package/dist/components/code.js +7 -7
  32. package/dist/components/code.js.map +1 -1
  33. package/dist/components/context-menu.js +9 -9
  34. package/dist/components/context-menu.js.map +1 -1
  35. package/dist/components/dialog.js +2 -2
  36. package/dist/components/dialog.js.map +1 -1
  37. package/dist/components/dropdown-menu.js +9 -9
  38. package/dist/components/dropdown-menu.js.map +1 -1
  39. package/dist/components/heading.d.ts +6 -6
  40. package/dist/components/heading.d.ts.map +1 -1
  41. package/dist/components/heading.js +2 -2
  42. package/dist/components/heading.js.map +1 -1
  43. package/dist/components/hover-card.js +2 -2
  44. package/dist/components/hover-card.js.map +1 -1
  45. package/dist/components/icon-button.d.ts +2 -2
  46. package/dist/components/icon-button.d.ts.map +1 -1
  47. package/dist/components/icon-button.js +13 -17
  48. package/dist/components/icon-button.js.map +1 -1
  49. package/dist/components/icon.d.ts +6 -7
  50. package/dist/components/icon.d.ts.map +1 -1
  51. package/dist/components/icon.js +9 -24
  52. package/dist/components/icon.js.map +1 -1
  53. package/dist/components/index.d.ts +2 -0
  54. package/dist/components/index.d.ts.map +1 -1
  55. package/dist/components/index.js +2 -0
  56. package/dist/components/index.js.map +1 -1
  57. package/dist/components/label.js +2 -2
  58. package/dist/components/label.js.map +1 -1
  59. package/dist/components/link.d.ts +19 -0
  60. package/dist/components/link.d.ts.map +1 -0
  61. package/dist/components/link.js +68 -0
  62. package/dist/components/link.js.map +1 -0
  63. package/dist/components/list.d.ts +37 -0
  64. package/dist/components/list.d.ts.map +1 -0
  65. package/dist/components/list.js +112 -0
  66. package/dist/components/list.js.map +1 -0
  67. package/dist/components/native-only-animated-view.d.ts +0 -4
  68. package/dist/components/native-only-animated-view.d.ts.map +1 -1
  69. package/dist/components/popover.js +2 -2
  70. package/dist/components/popover.js.map +1 -1
  71. package/dist/components/progress.d.ts +1 -2
  72. package/dist/components/progress.d.ts.map +1 -1
  73. package/dist/components/progress.js +5 -21
  74. package/dist/components/progress.js.map +1 -1
  75. package/dist/components/radio-group.d.ts +1 -2
  76. package/dist/components/radio-group.d.ts.map +1 -1
  77. package/dist/components/radio-group.js +7 -23
  78. package/dist/components/radio-group.js.map +1 -1
  79. package/dist/components/segmented-control.js +3 -3
  80. package/dist/components/segmented-control.js.map +1 -1
  81. package/dist/components/select.d.ts.map +1 -1
  82. package/dist/components/select.js +11 -13
  83. package/dist/components/select.js.map +1 -1
  84. package/dist/components/separator.d.ts +1 -1
  85. package/dist/components/separator.d.ts.map +1 -1
  86. package/dist/components/separator.js +4 -21
  87. package/dist/components/separator.js.map +1 -1
  88. package/dist/components/skeleton.d.ts.map +1 -1
  89. package/dist/components/skeleton.js +10 -26
  90. package/dist/components/skeleton.js.map +1 -1
  91. package/dist/components/spinner.js +2 -2
  92. package/dist/components/spinner.js.map +1 -1
  93. package/dist/components/switch.d.ts +1 -2
  94. package/dist/components/switch.d.ts.map +1 -1
  95. package/dist/components/switch.js +5 -21
  96. package/dist/components/switch.js.map +1 -1
  97. package/dist/components/tabs.d.ts +4 -3
  98. package/dist/components/tabs.d.ts.map +1 -1
  99. package/dist/components/tabs.js +10 -9
  100. package/dist/components/tabs.js.map +1 -1
  101. package/dist/components/text-area.d.ts.map +1 -1
  102. package/dist/components/text-area.js +13 -9
  103. package/dist/components/text-area.js.map +1 -1
  104. package/dist/components/text-field.d.ts.map +1 -1
  105. package/dist/components/text-field.js +62 -18
  106. package/dist/components/text-field.js.map +1 -1
  107. package/dist/components/text.d.ts +6 -6
  108. package/dist/components/text.d.ts.map +1 -1
  109. package/dist/components/text.js +22 -11
  110. package/dist/components/text.js.map +1 -1
  111. package/dist/components/tooltip.js +2 -2
  112. package/dist/components/tooltip.js.map +1 -1
  113. package/dist/index.d.ts +6 -1
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +11 -1
  116. package/dist/index.js.map +1 -1
  117. package/dist/lib/button-styles.d.ts +6 -6
  118. package/dist/lib/button-styles.d.ts.map +1 -1
  119. package/dist/lib/button-styles.js +1 -17
  120. package/dist/lib/button-styles.js.map +1 -1
  121. package/dist/lib/color-utils.d.ts +19 -14
  122. package/dist/lib/color-utils.d.ts.map +1 -1
  123. package/dist/lib/color-utils.js +37 -73
  124. package/dist/lib/color-utils.js.map +1 -1
  125. package/dist/lib/full-window-overlay.d.ts +11 -0
  126. package/dist/lib/full-window-overlay.d.ts.map +1 -0
  127. package/dist/lib/full-window-overlay.js +16 -0
  128. package/dist/lib/full-window-overlay.js.map +1 -0
  129. package/dist/lib/text-input-styles.d.ts +9 -8
  130. package/dist/lib/text-input-styles.d.ts.map +1 -1
  131. package/dist/lib/text-input-styles.js +4 -23
  132. package/dist/lib/text-input-styles.js.map +1 -1
  133. package/dist/lib/theme-context.d.ts +80 -0
  134. package/dist/lib/theme-context.d.ts.map +1 -0
  135. package/dist/lib/theme-context.js +97 -0
  136. package/dist/lib/theme-context.js.map +1 -0
  137. package/dist/lib/{theme-vars.d.ts → theme-tokens.d.ts} +2 -2
  138. package/dist/lib/theme-tokens.d.ts.map +1 -0
  139. package/dist/lib/{theme-vars.js → theme-tokens.js} +4 -19
  140. package/dist/lib/theme-tokens.js.map +1 -0
  141. package/dist/lib/theme.d.ts +14 -54
  142. package/dist/lib/theme.d.ts.map +1 -1
  143. package/dist/lib/theme.js +98 -66
  144. package/dist/lib/theme.js.map +1 -1
  145. package/dist/lib/types.d.ts +6 -2
  146. package/dist/lib/types.d.ts.map +1 -1
  147. package/dist/lib/use-theme-tokens.d.ts +593 -0
  148. package/dist/lib/use-theme-tokens.d.ts.map +1 -0
  149. package/dist/lib/use-theme-tokens.js +44 -0
  150. package/dist/lib/use-theme-tokens.js.map +1 -0
  151. package/docs/llm/COLOR_SYSTEM.md +799 -0
  152. package/docs/llm/COMPONENTS.md +1183 -0
  153. package/docs/llm/DESIGN_PATTERNS.md +2466 -0
  154. package/docs/llm/README.md +117 -0
  155. package/docs/llm/TYPOGRAPHY.md +516 -0
  156. package/package.json +11 -21
  157. package/dist/lib/native-colors.d.ts +0 -8
  158. package/dist/lib/native-colors.d.ts.map +0 -1
  159. package/dist/lib/native-colors.js +0 -67
  160. package/dist/lib/native-colors.js.map +0 -1
  161. package/dist/lib/theme-vars.d.ts.map +0 -1
  162. package/dist/lib/theme-vars.js.map +0 -1
  163. package/dist/lib/use-theme-vars.d.ts +0 -325
  164. package/dist/lib/use-theme-vars.d.ts.map +0 -1
  165. package/dist/lib/use-theme-vars.js +0 -17
  166. package/dist/lib/use-theme-vars.js.map +0 -1
  167. package/dist/lib/utils.d.ts +0 -3
  168. package/dist/lib/utils.d.ts.map +0 -1
  169. package/dist/lib/utils.js +0 -17
  170. package/dist/lib/utils.js.map +0 -1
  171. package/global.css +0 -1813
  172. package/tailwind-preset.js +0 -310
@@ -0,0 +1,2466 @@
1
+ # Frosted UI Design Patterns Guide
2
+
3
+ > **For Design Engineer AI Agents**: This guide covers UX patterns, layout composition, and visual design principles for building polished, user-friendly apps with Frosted UI.
4
+
5
+ ---
6
+
7
+ ## Core Design Principles
8
+
9
+ ### 1. Consistency Over Creativity
10
+
11
+ Use Frosted UI's built-in variants, sizes, and colors. Don't override component styles unless absolutely necessary. The design system exists to ensure visual consistency.
12
+
13
+ ### 2. Hierarchy Through Size & Weight
14
+
15
+ Establish clear visual hierarchy using:
16
+
17
+ - **Typography scale** (`size="1"` to `size="9"`) for text importance
18
+ - **Font weight** (`weight="bold"` for headings, `weight="medium"` for labels, `weight="regular"` for body)
19
+ - **Component sizes** — larger sizes draw more attention
20
+
21
+ ### 3. Color With Purpose
22
+
23
+ - Use **accent color** for interactive elements and primary actions
24
+ - Use **gray** for secondary/supporting UI
25
+ - Use **semantic colors** (`danger`, `warning`, `success`, `info`) only for status and feedback
26
+ - Don't use color decoratively — every color should communicate something
27
+
28
+ ### 4. Colored Sections
29
+
30
+ When creating a themed section (e.g., promotional banner, newsletter signup), use the palette's alpha shades for a cohesive look:
31
+
32
+ | Element | Token | Example |
33
+ | -------------- | ---------------------------- | ----------------------- |
34
+ | Background | `palette.a2` | Card/section background |
35
+ | Border | `palette.a5` | Subtle themed border |
36
+ | Text (body) | `palette.a11` | Body text, descriptions |
37
+ | Text (heading) | `palette.a12` | High-contrast headings |
38
+ | Form inputs | `variant="soft" color="..."` | Match the section color |
39
+
40
+ ### 5. Whitespace Is Your Friend
41
+
42
+ Generous spacing improves readability and touch targets. Use consistent gaps:
43
+
44
+ - `4px` — tight (related items)
45
+ - `8px` — standard (within groups)
46
+ - `12-16px` — comfortable (between groups)
47
+ - `24-32px` — sections
48
+
49
+ ---
50
+
51
+ ## Spacing Scale
52
+
53
+ Use these values for `gap`, `padding`, and `margin`:
54
+
55
+ | Value | Use Case |
56
+ | ----- | -------------------------------------------------- |
57
+ | `4` | Tight spacing: icon + text, badge content |
58
+ | `8` | Standard spacing: form fields, list items internal |
59
+ | `12` | Comfortable: between related groups |
60
+ | `16` | Section padding, card padding |
61
+ | `24` | Between distinct sections |
62
+ | `32` | Major section breaks, screen padding |
63
+
64
+ ---
65
+
66
+ ## Responsive Design
67
+
68
+ Apps built with Frosted UI should be mobile-first but work well on web/desktop. Choose the right layout strategy based on app complexity.
69
+
70
+ ### When to Use Each Layout Strategy
71
+
72
+ | App Type | Layout Strategy | Example |
73
+ | ----------------------------------- | ----------------- | ------------------------------- |
74
+ | Landing pages, forms, settings | **Centered** | Max-width container, centered |
75
+ | E-commerce, marketplace, dashboards | **Adaptive Grid** | Single column → multi-column |
76
+ | Chat, feed, detail views | **Centered** | Content-focused, easy reading |
77
+ | File browsers, admin panels | **Adaptive Grid** | Utilize full screen real estate |
78
+
79
+ ---
80
+
81
+ ### Strategy 1: Centered Layout (Simple Apps)
82
+
83
+ Best for: landing pages, forms, articles, settings, detail views.
84
+
85
+ ```tsx
86
+ import { useWindowDimensions } from 'react-native';
87
+
88
+ const MAX_CONTENT_WIDTH = 600;
89
+ const BREAKPOINT = 768;
90
+
91
+ function useResponsiveLayout() {
92
+ const { width } = useWindowDimensions();
93
+ const isWide = width >= BREAKPOINT;
94
+ const horizontalPadding = isWide ? Math.max(24, (width - MAX_CONTENT_WIDTH) / 2) : 16;
95
+
96
+ return { isWide, horizontalPadding, screenWidth: width };
97
+ }
98
+
99
+ function SimpleScreen() {
100
+ const { colors } = useThemeTokens();
101
+ const { horizontalPadding, isWide } = useResponsiveLayout();
102
+
103
+ return (
104
+ <ScrollView
105
+ style={{ flex: 1, backgroundColor: colors.background }}
106
+ contentContainerStyle={{
107
+ paddingHorizontal: horizontalPadding,
108
+ paddingVertical: 16,
109
+ gap: 24,
110
+ maxWidth: isWide ? MAX_CONTENT_WIDTH + horizontalPadding * 2 : undefined,
111
+ alignSelf: isWide ? 'center' : undefined,
112
+ width: '100%',
113
+ }}>
114
+ {/* Content */}
115
+ </ScrollView>
116
+ );
117
+ }
118
+ ```
119
+
120
+ ---
121
+
122
+ ### Strategy 2: Adaptive Grid (Complex Apps)
123
+
124
+ Best for: e-commerce, marketplaces, dashboards, productivity apps, file browsers.
125
+
126
+ ```tsx
127
+ import { useWindowDimensions } from 'react-native';
128
+
129
+ // Breakpoints
130
+ const TABLET = 768;
131
+ const DESKTOP = 1024;
132
+ const WIDE = 1280;
133
+
134
+ function useAdaptiveLayout() {
135
+ const { width } = useWindowDimensions();
136
+
137
+ // Calculate columns based on screen width
138
+ const getColumns = (minItemWidth: number, maxColumns: number = 4) => {
139
+ const availableWidth = width - 32; // Account for padding
140
+ const columns = Math.floor(availableWidth / minItemWidth);
141
+ return Math.max(1, Math.min(columns, maxColumns));
142
+ };
143
+
144
+ return {
145
+ screenWidth: width,
146
+ isTablet: width >= TABLET,
147
+ isDesktop: width >= DESKTOP,
148
+ isWide: width >= WIDE,
149
+ getColumns,
150
+ padding: width >= TABLET ? 24 : 16,
151
+ };
152
+ }
153
+ ```
154
+
155
+ #### Product Grid Example
156
+
157
+ ```tsx
158
+ function ProductGridScreen() {
159
+ const { colors } = useThemeTokens();
160
+ const { getColumns, padding, isDesktop } = useAdaptiveLayout();
161
+
162
+ // Min 200px per item for comfortable cards, max 3 columns
163
+ const columns = getColumns(200, 3);
164
+ const gap = 16;
165
+
166
+ const products = [...]; // Your product data
167
+
168
+ return (
169
+ <ScrollView
170
+ style={{ flex: 1, backgroundColor: colors.background }}
171
+ contentContainerStyle={{
172
+ padding,
173
+ gap: 24,
174
+ maxWidth: isDesktop ? 1200 : undefined,
175
+ alignSelf: isDesktop ? 'center' : undefined,
176
+ width: '100%',
177
+ }}>
178
+ {/* Header */}
179
+ <View style={{ gap: 4 }}>
180
+ <Heading size="5">Products</Heading>
181
+ <Text color="gray">{products.length} items</Text>
182
+ </View>
183
+
184
+ {/* Responsive Grid */}
185
+ <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap }}>
186
+ {products.map((product) => (
187
+ <View
188
+ key={product.id}
189
+ style={{
190
+ flexGrow: 1,
191
+ flexBasis: columns === 1 ? '100%' : `${Math.floor(100 / columns) - 2}%`,
192
+ maxWidth: columns === 1 ? '100%' : `${Math.floor(100 / columns) - 1}%`,
193
+ }}>
194
+ <ProductCard product={product} />
195
+ </View>
196
+ ))}
197
+ </View>
198
+ </ScrollView>
199
+ );
200
+ }
201
+ ```
202
+
203
+ #### Simpler Grid with Fixed Breakpoints
204
+
205
+ ```tsx
206
+ function SimpleGrid({ items, renderItem }) {
207
+ const { width } = useWindowDimensions();
208
+
209
+ // Simple breakpoint-based columns
210
+ const columns = width >= 1024 ? 3 : width >= 600 ? 2 : 1;
211
+ const gap = 16;
212
+
213
+ return (
214
+ <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap }}>
215
+ {items.map((item, index) => (
216
+ <View
217
+ key={item.id ?? index}
218
+ style={{
219
+ flexGrow: 1,
220
+ flexBasis: columns === 1 ? '100%' : `${Math.floor(100 / columns) - 2}%`,
221
+ maxWidth: columns === 1 ? '100%' : `${Math.floor(100 / columns) - 1}%`,
222
+ }}>
223
+ {renderItem(item)}
224
+ </View>
225
+ ))}
226
+ </View>
227
+ );
228
+ }
229
+ ```
230
+
231
+ ---
232
+
233
+ ### Strategy 3: Hybrid Layout (Mixed Content)
234
+
235
+ Best for: screens with both full-width and constrained sections.
236
+
237
+ ```tsx
238
+ function HybridScreen() {
239
+ const { colors } = useThemeTokens();
240
+ const { isDesktop, padding, getColumns } = useAdaptiveLayout();
241
+
242
+ const maxContentWidth = 800;
243
+
244
+ return (
245
+ <ScrollView style={{ flex: 1, backgroundColor: colors.background }}>
246
+ {/* Full-width hero/banner */}
247
+ <View style={{ padding, backgroundColor: colors.palettes.accent.a2 }}>
248
+ <View
249
+ style={{
250
+ maxWidth: maxContentWidth,
251
+ alignSelf: 'center',
252
+ width: '100%',
253
+ }}>
254
+ <Heading size="6">Welcome Back</Heading>
255
+ <Text color="gray">Your dashboard overview</Text>
256
+ </View>
257
+ </View>
258
+
259
+ {/* Constrained content area */}
260
+ <View
261
+ style={{
262
+ padding,
263
+ gap: 24,
264
+ maxWidth: isDesktop ? 1200 : undefined,
265
+ alignSelf: 'center',
266
+ width: '100%',
267
+ }}>
268
+ {/* Stats Grid - adapts columns */}
269
+ <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 12 }}>
270
+ {stats.map((stat) => (
271
+ <View key={stat.label} style={{ flex: 1, minWidth: 150 }}>
272
+ <StatCard {...stat} />
273
+ </View>
274
+ ))}
275
+ </View>
276
+
277
+ {/* Two-column layout on desktop */}
278
+ <View
279
+ style={{
280
+ flexDirection: isDesktop ? 'row' : 'column',
281
+ gap: 16,
282
+ }}>
283
+ <View style={{ flex: isDesktop ? 2 : 1 }}>
284
+ <Card>
285
+ <Heading size="4">Recent Activity</Heading>
286
+ {/* Activity list */}
287
+ </Card>
288
+ </View>
289
+ <View style={{ flex: 1 }}>
290
+ <Card>
291
+ <Heading size="4">Quick Actions</Heading>
292
+ {/* Actions */}
293
+ </Card>
294
+ </View>
295
+ </View>
296
+ </View>
297
+ </ScrollView>
298
+ );
299
+ }
300
+ ```
301
+
302
+ ---
303
+
304
+ ### Responsive Design Principles
305
+
306
+ | Principle | Mobile | Tablet (768px+) | Desktop (1024px+) |
307
+ | --------------------- | --------------- | --------------- | ----------------- |
308
+ | **Grid columns** | 1-2 | 2-3 | 3-4 |
309
+ | **Content max-width** | Full | Full or 800px | 1200px |
310
+ | **Side padding** | 16px | 24px | 24-48px |
311
+ | **Card arrangement** | Stacked | Side-by-side | Multi-column |
312
+ | **Touch targets** | 44px+ (size 3+) | Same | Same |
313
+ | **Component sizes** | Don't change | Don't change | Don't change |
314
+
315
+ ### Do's and Don'ts
316
+
317
+ **Do:**
318
+
319
+ - Use `flexWrap: 'wrap'` for responsive grids
320
+ - Set `minWidth` on grid items to control breakpoints
321
+ - Use `flex: 1` for equal-width columns
322
+ - Constrain max-width on very wide screens (1200-1400px)
323
+ - Keep consistent gap/padding at each breakpoint
324
+
325
+ **Don't:**
326
+
327
+ - Change button sizes based on screen width
328
+ - Use different font sizes for mobile vs desktop
329
+ - Create completely different layouts (keep hierarchy similar)
330
+ - Forget touch targets — desktop users may have touchscreens
331
+
332
+ ---
333
+
334
+ ## Layout Patterns
335
+
336
+ ### Screen Structure
337
+
338
+ ```tsx
339
+ <View style={{ flex: 1, backgroundColor: colors.background }}>
340
+ {/* Header */}
341
+ <View style={{ paddingHorizontal: 16, paddingVertical: 12 }}>
342
+ <Heading size="6">Screen Title</Heading>
343
+ </View>
344
+
345
+ {/* Content */}
346
+ <ScrollView contentContainerStyle={{ padding: 16, gap: 24 }}>{/* Sections go here */}</ScrollView>
347
+
348
+ {/* Footer (optional - for primary actions) */}
349
+ <View style={{ padding: 16, borderTopWidth: 1, borderTopColor: colors.stroke }}>
350
+ <Button variant="solid">
351
+ <Text>Primary Action</Text>
352
+ </Button>
353
+ </View>
354
+ </View>
355
+ ```
356
+
357
+ ### Section with Header
358
+
359
+ ```tsx
360
+ <View style={{ gap: 12 }}>
361
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
362
+ <Heading size="4">Section Title</Heading>
363
+ <Button variant="ghost" size="1">
364
+ <Text>See All</Text>
365
+ </Button>
366
+ </View>
367
+ <Card>{/* Section content */}</Card>
368
+ </View>
369
+ ```
370
+
371
+ ### Title + Description Pattern
372
+
373
+ Use this pattern for headings with supporting text:
374
+
375
+ ```tsx
376
+ <View style={{ gap: 4 }}>
377
+ <Heading size="5">Welcome back</Heading>
378
+ <Text size="3" color="gray">
379
+ Here's what's happening today
380
+ </Text>
381
+ </View>
382
+ ```
383
+
384
+ ### Form Field Pattern
385
+
386
+ ```tsx
387
+ <View style={{ gap: 6 }}>
388
+ <Label nativeID="field-id">Field Label</Label>
389
+ <TextField.Input placeholder="Placeholder..." aria-labelledby="field-id" />
390
+ <Text size="1" color="gray">
391
+ Helper text explaining the field
392
+ </Text>
393
+ </View>
394
+ ```
395
+
396
+ ### Form Section Pattern
397
+
398
+ Group related fields together:
399
+
400
+ ```tsx
401
+ <View style={{ gap: 16 }}>
402
+ <Heading size="3">Personal Information</Heading>
403
+
404
+ <View style={{ gap: 12 }}>
405
+ {/* First Name */}
406
+ <View style={{ gap: 6 }}>
407
+ <Label nativeID="first-name">First Name</Label>
408
+ <TextField.Input placeholder="John" aria-labelledby="first-name" />
409
+ </View>
410
+
411
+ {/* Last Name */}
412
+ <View style={{ gap: 6 }}>
413
+ <Label nativeID="last-name">Last Name</Label>
414
+ <TextField.Input placeholder="Doe" aria-labelledby="last-name" />
415
+ </View>
416
+
417
+ {/* Email */}
418
+ <View style={{ gap: 6 }}>
419
+ <Label nativeID="email">Email</Label>
420
+ <TextField.Input
421
+ placeholder="john@example.com"
422
+ aria-labelledby="email"
423
+ keyboardType="email-address"
424
+ />
425
+ </View>
426
+ </View>
427
+ </View>
428
+ ```
429
+
430
+ ---
431
+
432
+ ## List Patterns
433
+
434
+ > **Use the `List` component** for structured lists with items, slots, and separators. It renders a `Card` internally with proper padding and handles press states automatically.
435
+
436
+ ### Basic List
437
+
438
+ ```tsx
439
+ <List.Root>
440
+ {users.map((user, index) => (
441
+ <React.Fragment key={user.id}>
442
+ {index > 0 && <List.Separator />}
443
+ <List.Item onPress={() => {}}>
444
+ <List.ItemSlot>
445
+ <Avatar fallback={user.name} size="3" />
446
+ </List.ItemSlot>
447
+ <List.ItemContent>
448
+ <List.ItemTitle>{user.name}</List.ItemTitle>
449
+ <List.ItemDescription>{user.email}</List.ItemDescription>
450
+ </List.ItemContent>
451
+ <List.ItemSlot>
452
+ <Badge color={user.status === 'Active' ? 'success' : 'warning'} size="1">
453
+ <Text>{user.status}</Text>
454
+ </Badge>
455
+ </List.ItemSlot>
456
+ <List.ItemSlot>
457
+ <Icon as={ChevronRight} size={16} color={colors.palettes.gray.a8} />
458
+ </List.ItemSlot>
459
+ </List.Item>
460
+ </React.Fragment>
461
+ ))}
462
+ </List.Root>
463
+ ```
464
+
465
+ ### Settings List
466
+
467
+ ```tsx
468
+ <List.Root>
469
+ {/* Switch setting */}
470
+ <List.Item>
471
+ <List.ItemSlot>
472
+ <View style={iconBoxStyle}>
473
+ <Icon as={Bell} size={20} color={colors.palettes.blue.a11} />
474
+ </View>
475
+ </List.ItemSlot>
476
+ <List.ItemContent>
477
+ <List.ItemTitle>Notifications</List.ItemTitle>
478
+ </List.ItemContent>
479
+ <List.ItemSlot>
480
+ <Switch checked={enabled} onCheckedChange={setEnabled} />
481
+ </List.ItemSlot>
482
+ </List.Item>
483
+
484
+ <List.Separator />
485
+
486
+ {/* Pressable setting with checkbox */}
487
+ <List.Item onPress={() => setDarkMode(!darkMode)}>
488
+ <List.ItemSlot>
489
+ <View style={iconBoxStyle}>
490
+ <Icon as={Settings} size={20} color={colors.palettes.purple.a11} />
491
+ </View>
492
+ </List.ItemSlot>
493
+ <List.ItemContent>
494
+ <List.ItemTitle>Dark Mode</List.ItemTitle>
495
+ </List.ItemContent>
496
+ <List.ItemSlot>
497
+ <Checkbox checked={darkMode} onCheckedChange={setDarkMode} />
498
+ </List.ItemSlot>
499
+ </List.Item>
500
+ </List.Root>
501
+ ```
502
+
503
+ ### List with RadioGroup (Shipping Options)
504
+
505
+ ```tsx
506
+ <RadioGroup.Root value={selected} onValueChange={setSelected}>
507
+ <List.Root>
508
+ {options.map((option, index) => (
509
+ <React.Fragment key={option.id}>
510
+ {index > 0 && <List.Separator />}
511
+ <List.Item onPress={() => setSelected(option.id)}>
512
+ <List.ItemSlot>
513
+ <RadioGroup.Item value={option.id} />
514
+ </List.ItemSlot>
515
+ <List.ItemSlot>
516
+ <View style={iconBoxStyle}>
517
+ <Icon as={option.icon} size={20} />
518
+ </View>
519
+ </List.ItemSlot>
520
+ <List.ItemContent>
521
+ <List.ItemTitle>{option.name}</List.ItemTitle>
522
+ <List.ItemDescription>{option.time}</List.ItemDescription>
523
+ </List.ItemContent>
524
+ <List.ItemSlot>
525
+ <Text weight="medium" color={option.price === 'Free' ? 'success' : undefined}>
526
+ {option.price}
527
+ </Text>
528
+ </List.ItemSlot>
529
+ </List.Item>
530
+ </React.Fragment>
531
+ ))}
532
+ </List.Root>
533
+ </RadioGroup.Root>
534
+ ```
535
+
536
+ ### List Variants
537
+
538
+ Use the `variant` prop on `List.Root` to match different contexts:
539
+
540
+ ```tsx
541
+ // Default surface style (bordered, elevated)
542
+ <List.Root variant="surface">...</List.Root>
543
+
544
+ // Soft background for highlighted lists
545
+ <List.Root variant="soft">...</List.Root>
546
+
547
+ // Ghost for minimal style
548
+ <List.Root variant="ghost">...</List.Root>
549
+ ```
550
+
551
+ ### Leaderboard
552
+
553
+ ```tsx
554
+ <List.Root variant="soft">
555
+ {entries.map((entry, index) => (
556
+ <React.Fragment key={entry.rank}>
557
+ {index > 0 && <List.Separator />}
558
+ <List.Item style={entry.isUser ? { backgroundColor: colors.palettes.gray.a3 } : undefined}>
559
+ <List.ItemSlot>
560
+ <Text size="2" weight="bold" style={{ width: 24, textAlign: 'center' }}>
561
+ {entry.rank}
562
+ </Text>
563
+ </List.ItemSlot>
564
+ <List.ItemSlot>
565
+ <Avatar fallback={entry.avatar} size="2" color={entry.color} />
566
+ </List.ItemSlot>
567
+ <List.ItemContent>
568
+ <Text weight={entry.isUser ? 'bold' : 'medium'}>{entry.name}</Text>
569
+ </List.ItemContent>
570
+ <List.ItemSlot>
571
+ <Text weight="medium" color={entry.color}>
572
+ {entry.points.toLocaleString()} pts
573
+ </Text>
574
+ </List.ItemSlot>
575
+ </List.Item>
576
+ </React.Fragment>
577
+ ))}
578
+ </List.Root>
579
+ ```
580
+
581
+ ---
582
+
583
+ ## Card Patterns
584
+
585
+ ### Card Variants
586
+
587
+ Cards come in three variants. Choose based on visual weight needed:
588
+
589
+ | Variant | Visual Style | When to Use |
590
+ | --------- | --------------------------------------- | -------------------------------------------------------- |
591
+ | `surface` | Solid background, border, subtle shadow | **Default** — Elevated content like messages, profiles |
592
+ | `soft` | Translucent tinted background | Highlighted sections, tips, promotions, feature callouts |
593
+ | `ghost` | No background or border (just padding) | Section grouping, layout containers, minimal UI |
594
+
595
+ #### Surface — Message Card (default)
596
+
597
+ ```tsx
598
+ <Card variant="surface">
599
+ <View style={{ flexDirection: 'row', gap: 12 }}>
600
+ <Avatar fallback="SJ" color="blue" size="3" />
601
+ <View style={{ flex: 1, gap: 4 }}>
602
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
603
+ <Text weight="medium">Sarah Johnson</Text>
604
+ <Text size="1" color="gray">
605
+ 2m ago
606
+ </Text>
607
+ </View>
608
+ <Text size="3" color="gray">
609
+ Hey! Just finished the design review. The new dashboard looks amazing! 🎉
610
+ </Text>
611
+ </View>
612
+ </View>
613
+ </Card>
614
+ ```
615
+
616
+ #### Soft — Pro Tip / Feature Highlight
617
+
618
+ ```tsx
619
+ <Card variant="soft">
620
+ <View style={{ flexDirection: 'row', gap: 12, alignItems: 'flex-start' }}>
621
+ <View
622
+ style={{
623
+ width: 40,
624
+ height: 40,
625
+ borderRadius: 10,
626
+ backgroundColor: colors.palettes.amber.a3,
627
+ alignItems: 'center',
628
+ justifyContent: 'center',
629
+ }}>
630
+ <Icon as={Lightbulb} size={20} color={colors.palettes.amber.a11} />
631
+ </View>
632
+ <View style={{ flex: 1, gap: 4 }}>
633
+ <Text weight="medium">Pro Tip</Text>
634
+ <Text size="3" color="gray">
635
+ Enable notifications to stay updated on new messages and activity from your team.
636
+ </Text>
637
+ <Button variant="ghost" size="2" style={{ alignSelf: 'flex-start', marginTop: 4 }}>
638
+ <Text>Enable Notifications</Text>
639
+ <Icon as={ChevronRight} size={16} />
640
+ </Button>
641
+ </View>
642
+ </View>
643
+ </Card>
644
+ ```
645
+
646
+ #### Ghost — Section Container
647
+
648
+ Use `ghost` when you need layout grouping but no visual container:
649
+
650
+ ```tsx
651
+ <Card variant="ghost" style={{ padding: 0 }}>
652
+ <View style={{ gap: 12 }}>
653
+ {/* Section header */}
654
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
655
+ <Heading size="4">Recent Activity</Heading>
656
+ <Button variant="ghost" size="2">
657
+ <Text>See All</Text>
658
+ </Button>
659
+ </View>
660
+
661
+ {/* Content in a surface card (has overflow: hidden for separators) */}
662
+ <Card variant="surface" style={{ padding: 0 }}>
663
+ {items.map((item, index, arr) => (
664
+ <View key={index}>
665
+ <Pressable
666
+ style={({ pressed }) => ({
667
+ flexDirection: 'row',
668
+ alignItems: 'center',
669
+ gap: 12,
670
+ paddingHorizontal: 16,
671
+ paddingVertical: 14,
672
+ backgroundColor: pressed ? colors.palettes.gray.a3 : 'transparent',
673
+ })}>
674
+ <Avatar fallback={item.initials} size="3" color={item.color} />
675
+ <View style={{ flex: 1, gap: 2 }}>
676
+ <Text size="2" weight="medium" numberOfLines={1}>
677
+ {item.name}
678
+ </Text>
679
+ <Text size="2" color="gray" numberOfLines={1}>
680
+ {item.action}
681
+ </Text>
682
+ </View>
683
+ <Text size="1" color="gray">
684
+ {item.time}
685
+ </Text>
686
+ </Pressable>
687
+ {index < arr.length - 1 && <Separator size="4" />}
688
+ </View>
689
+ ))}
690
+ </Card>
691
+ </View>
692
+ </Card>
693
+ ```
694
+
695
+ > **Tip**: Card has `overflow: 'hidden'` by default, so full-width separators won't overflow the rounded corners.
696
+
697
+ ### Info Card
698
+
699
+ ```tsx
700
+ <Card>
701
+ <View style={{ gap: 12 }}>
702
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
703
+ <Icon as={Info} size={16} />
704
+ <Text weight="medium">Card Title</Text>
705
+ </View>
706
+ <Text size="3" color="gray">
707
+ Supporting description text that provides more context.
708
+ </Text>
709
+ <View style={{ flexDirection: 'row', gap: 8 }}>
710
+ <Button variant="soft" color="gray" size="2">
711
+ <Text>Dismiss</Text>
712
+ </Button>
713
+ <Button variant="solid" size="2">
714
+ <Text>Action</Text>
715
+ </Button>
716
+ </View>
717
+ </View>
718
+ </Card>
719
+ ```
720
+
721
+ ### Stat Card
722
+
723
+ ```tsx
724
+ <Card>
725
+ <View style={{ gap: 4 }}>
726
+ <Text size="1" color="gray">
727
+ Total Revenue
728
+ </Text>
729
+ <Heading size="6">$12,345</Heading>
730
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
731
+ <Badge color="success" size="1">
732
+ <Text>+12%</Text>
733
+ </Badge>
734
+ <Text size="1" color="gray">
735
+ vs last month
736
+ </Text>
737
+ </View>
738
+ </View>
739
+ </Card>
740
+ ```
741
+
742
+ ### Buy Box (E-commerce)
743
+
744
+ Use `size="4"` buttons for prominent CTAs in e-commerce and conversion-focused screens:
745
+
746
+ ```tsx
747
+ <Card>
748
+ <View style={{ gap: 16 }}>
749
+ {/* Product Image */}
750
+ <View
751
+ style={{
752
+ height: 200,
753
+ backgroundColor: colors.palettes.gray.a3,
754
+ borderRadius: 8,
755
+ alignItems: 'center',
756
+ justifyContent: 'center',
757
+ }}>
758
+ <Text color="gray">Product Image</Text>
759
+ </View>
760
+
761
+ {/* Product Info */}
762
+ <View style={{ gap: 8 }}>
763
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
764
+ <Badge color="success" size="1">
765
+ <Text>In Stock</Text>
766
+ </Badge>
767
+ <Badge variant="soft" color="gray" size="1">
768
+ <Text>Free Shipping</Text>
769
+ </Badge>
770
+ </View>
771
+ <Heading size="5">Premium Wireless Headphones</Heading>
772
+ <Text size="3" color="gray">
773
+ High-fidelity audio with active noise cancellation and 30-hour battery life.
774
+ </Text>
775
+ </View>
776
+
777
+ {/* Price */}
778
+ <View style={{ gap: 4 }}>
779
+ <View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 8 }}>
780
+ <Heading size="6">$299</Heading>
781
+ <Text size="2" color="gray" style={{ textDecorationLine: 'line-through' }}>
782
+ $349
783
+ </Text>
784
+ </View>
785
+ <Text size="1" color="success">
786
+ Save $50 (14% off)
787
+ </Text>
788
+ </View>
789
+
790
+ <Separator size="4" />
791
+
792
+ {/* CTA Buttons - Use size="4" for prominent actions */}
793
+ <View style={{ gap: 12 }}>
794
+ <Button variant="solid" size="4">
795
+ <Text>Add to Cart</Text>
796
+ </Button>
797
+ <Button variant="soft" color="gray" size="4">
798
+ <Icon as={Heart} size={18} />
799
+ <Text>Add to Wishlist</Text>
800
+ </Button>
801
+ </View>
802
+ </View>
803
+ </Card>
804
+ ```
805
+
806
+ > **Tip**: Use `size="4"` buttons for important conversion actions like "Add to Cart", "Buy Now", "Subscribe", or "Sign Up". The larger touch target and visual weight helps drive conversions.
807
+
808
+ ### Promotional Banner (Apple-like)
809
+
810
+ Create clean, premium promotional sections with structured layout:
811
+
812
+ ```tsx
813
+ <Card
814
+ style={{
815
+ padding: 0,
816
+ backgroundColor: colors.palettes.pink.a2,
817
+ borderWidth: 1,
818
+ borderColor: colors.palettes.pink.a4,
819
+ }}>
820
+ <View style={{ padding: 20, gap: 16 }}>
821
+ {/* Header row with badge + timer */}
822
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
823
+ <Badge color="pink" variant="soft" size="1">
824
+ <Icon as={Zap} size={10} />
825
+ <Text>Limited Time</Text>
826
+ </Badge>
827
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
828
+ <View
829
+ style={{
830
+ width: 6,
831
+ height: 6,
832
+ borderRadius: 3,
833
+ backgroundColor: colors.palettes.pink['9'],
834
+ }}
835
+ />
836
+ <Text size="1" weight="medium" style={{ color: colors.palettes.pink.a11 }}>
837
+ Ends in 02:34:56
838
+ </Text>
839
+ </View>
840
+ </View>
841
+
842
+ {/* Main content */}
843
+ <View style={{ gap: 4 }}>
844
+ <Text size="5" weight="bold" style={{ color: colors.palettes.pink.a12 }}>
845
+ Flash Sale
846
+ </Text>
847
+ <Text size="3" style={{ color: colors.palettes.pink.a11 }}>
848
+ Up to 50% off on selected items. Don't miss out.
849
+ </Text>
850
+ </View>
851
+
852
+ {/* CTA */}
853
+ <Button variant="solid" color="pink" size="3">
854
+ <Text>Shop Now</Text>
855
+ <Icon as={ChevronRight} size={16} />
856
+ </Button>
857
+ </View>
858
+ </Card>
859
+ ```
860
+
861
+ > **Key Pattern**: Use a consistent color palette (e.g., pink) across background (`a2`), border (`a4`), text (`a11`, `a12`), and button for a cohesive, premium feel.
862
+
863
+ ### Achievement Card (Apple-like)
864
+
865
+ For gamification elements like achievements, badges, or milestones:
866
+
867
+ ```tsx
868
+ <Card style={{ padding: 0, borderWidth: 1, borderColor: colors.stroke }}>
869
+ {/* Header section with award */}
870
+ <View
871
+ style={{
872
+ paddingVertical: 24,
873
+ paddingHorizontal: 20,
874
+ alignItems: 'center',
875
+ gap: 16,
876
+ backgroundColor: colors.palettes.gray.a2,
877
+ borderBottomWidth: 1,
878
+ borderBottomColor: colors.stroke,
879
+ }}>
880
+ {/* Centered badge with glow effect */}
881
+ <Badge
882
+ size="2"
883
+ color="amber"
884
+ variant="soft"
885
+ style={{
886
+ alignSelf: 'center',
887
+ shadowColor: colors.palettes.amber['9'],
888
+ shadowOffset: { width: 0, height: 0 },
889
+ shadowOpacity: 0.3,
890
+ shadowRadius: 12,
891
+ elevation: 4,
892
+ }}>
893
+ <Icon as={Award} size={14} />
894
+ <Text>First Purchase</Text>
895
+ </Badge>
896
+
897
+ <View style={{ gap: 4, alignItems: 'center' }}>
898
+ <Text size="4" weight="bold">
899
+ Achievement Unlocked!
900
+ </Text>
901
+ <Text size="2" color="gray" style={{ textAlign: 'center' }}>
902
+ You've made your first purchase and earned 100 bonus points.
903
+ </Text>
904
+ </View>
905
+ </View>
906
+
907
+ {/* Stats row */}
908
+ <View style={{ flexDirection: 'row', paddingVertical: 16, paddingHorizontal: 20 }}>
909
+ <View style={{ flex: 1, alignItems: 'center', gap: 2 }}>
910
+ <Text size="4" weight="bold">
911
+ 100
912
+ </Text>
913
+ <Text size="1" color="gray">
914
+ Points Earned
915
+ </Text>
916
+ </View>
917
+ <View style={{ width: 1, backgroundColor: colors.stroke }} />
918
+ <View style={{ flex: 1, alignItems: 'center', gap: 2 }}>
919
+ <Text size="4" weight="bold">
920
+ 3
921
+ </Text>
922
+ <Text size="1" color="gray">
923
+ Achievements
924
+ </Text>
925
+ </View>
926
+ <View style={{ width: 1, backgroundColor: colors.stroke }} />
927
+ <View style={{ flex: 1, alignItems: 'center', gap: 2 }}>
928
+ <Text size="4" weight="bold">
929
+ Gold
930
+ </Text>
931
+ <Text size="1" color="gray">
932
+ Next Tier
933
+ </Text>
934
+ </View>
935
+ </View>
936
+ </Card>
937
+ ```
938
+
939
+ > **Key Pattern**: Use `colors.stroke` for subtle borders between sections. Center important elements using `alignSelf: 'center'`. Add shadow to badges for a "glow" effect.
940
+
941
+ ### Newsletter Signup (Themed)
942
+
943
+ Use the palette's alpha shades to create cohesive themed sections:
944
+
945
+ ```tsx
946
+ <Card
947
+ style={{
948
+ backgroundColor: colors.palettes.accent.a2,
949
+ borderWidth: 1,
950
+ borderColor: colors.palettes.accent.a5,
951
+ }}>
952
+ <View style={{ gap: 16 }}>
953
+ <View style={{ gap: 4 }}>
954
+ <Text size="4" weight="bold" style={{ color: colors.palettes.accent.a12 }}>
955
+ Stay in the loop
956
+ </Text>
957
+ <Text size="3" style={{ color: colors.palettes.accent.a11 }}>
958
+ Get weekly updates on new features and tips.
959
+ </Text>
960
+ </View>
961
+
962
+ <TextField.Root variant="soft" color="accent">
963
+ <TextField.Input placeholder="Enter your email" keyboardType="email-address" />
964
+ </TextField.Root>
965
+
966
+ <Button variant="solid" size="3">
967
+ <Text>Subscribe</Text>
968
+ </Button>
969
+
970
+ <Text size="1" style={{ color: colors.palettes.accent.a11 }}>
971
+ No spam, unsubscribe anytime.
972
+ </Text>
973
+ </View>
974
+ </Card>
975
+ ```
976
+
977
+ ---
978
+
979
+ ## Button Placement
980
+
981
+ ### Primary Action at Bottom
982
+
983
+ For screens with a clear primary action, place it at the bottom:
984
+
985
+ ```tsx
986
+ <View style={{ flex: 1 }}>
987
+ <ScrollView style={{ flex: 1 }}>{/* Content */}</ScrollView>
988
+ <View style={{ padding: 16, gap: 8 }}>
989
+ <Button variant="solid" size="3">
990
+ <Text>Continue</Text>
991
+ </Button>
992
+ </View>
993
+ </View>
994
+ ```
995
+
996
+ ### Action Pairs
997
+
998
+ When you have two actions (primary + secondary):
999
+
1000
+ ```tsx
1001
+ <View style={{ flexDirection: 'row', gap: 8 }}>
1002
+ <Button variant="soft" color="gray" style={{ flex: 1 }}>
1003
+ <Text>Cancel</Text>
1004
+ </Button>
1005
+ <Button variant="solid" style={{ flex: 1 }}>
1006
+ <Text>Confirm</Text>
1007
+ </Button>
1008
+ </View>
1009
+ ```
1010
+
1011
+ ### Inline Actions
1012
+
1013
+ For less prominent actions within content:
1014
+
1015
+ ```tsx
1016
+ <View style={{ flexDirection: 'row', gap: 8 }}>
1017
+ <Button variant="ghost" size="2">
1018
+ <Icon as={Heart} size={16} />
1019
+ <Text>Like</Text>
1020
+ </Button>
1021
+ <Button variant="ghost" size="2">
1022
+ <Icon as={MessageCircle} size={16} />
1023
+ <Text>Comment</Text>
1024
+ </Button>
1025
+ <Button variant="ghost" size="2">
1026
+ <Icon as={Share} size={16} />
1027
+ <Text>Share</Text>
1028
+ </Button>
1029
+ </View>
1030
+ ```
1031
+
1032
+ ---
1033
+
1034
+ ## Empty States
1035
+
1036
+ Always design for empty states:
1037
+
1038
+ ```tsx
1039
+ <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', padding: 32, gap: 16 }}>
1040
+ <View
1041
+ style={{
1042
+ width: 64,
1043
+ height: 64,
1044
+ borderRadius: 32,
1045
+ backgroundColor: colors.palettes.gray.a3,
1046
+ alignItems: 'center',
1047
+ justifyContent: 'center',
1048
+ }}>
1049
+ <Icon as={Inbox} size={32} color={colors.palettes.gray.a11} />
1050
+ </View>
1051
+ <View style={{ gap: 4, alignItems: 'center' }}>
1052
+ <Heading size="4">No messages yet</Heading>
1053
+ <Text size="3" color="gray" style={{ textAlign: 'center' }}>
1054
+ When you receive messages, they'll appear here
1055
+ </Text>
1056
+ </View>
1057
+ <Button variant="solid">
1058
+ <Text>Start a conversation</Text>
1059
+ </Button>
1060
+ </View>
1061
+ ```
1062
+
1063
+ ---
1064
+
1065
+ ## Loading States
1066
+
1067
+ ### Skeleton Loading
1068
+
1069
+ Match skeleton dimensions to actual content:
1070
+
1071
+ ```tsx
1072
+ {
1073
+ isLoading ? (
1074
+ <View style={{ gap: 12 }}>
1075
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
1076
+ <Skeleton.Avatar size="2" />
1077
+ <View style={{ flex: 1, gap: 4 }}>
1078
+ <Skeleton.Text size="2" style={{ width: '60%' }} />
1079
+ <Skeleton.Text size="1" style={{ width: '40%' }} />
1080
+ </View>
1081
+ </View>
1082
+ </View>
1083
+ ) : (
1084
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
1085
+ <Avatar fallback={user.name} size="2" />
1086
+ <View style={{ gap: 4 }}>
1087
+ <Text weight="medium">{user.name}</Text>
1088
+ <Text size="1" color="gray">
1089
+ {user.email}
1090
+ </Text>
1091
+ </View>
1092
+ </View>
1093
+ );
1094
+ }
1095
+ ```
1096
+
1097
+ ### Button Loading
1098
+
1099
+ The Spinner component wraps content and automatically shows/hides based on the `loading` prop:
1100
+
1101
+ ```tsx
1102
+ <Button variant="solid" disabled={isLoading} onPress={handleSubmit}>
1103
+ <Spinner loading={isLoading} size="1">
1104
+ <Text>Submit</Text>
1105
+ </Spinner>
1106
+ </Button>
1107
+ ```
1108
+
1109
+ ### Page Loading
1110
+
1111
+ ```tsx
1112
+ <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
1113
+ <Spinner size="4" />
1114
+ </View>
1115
+ ```
1116
+
1117
+ ---
1118
+
1119
+ ## Feedback & Status
1120
+
1121
+ ### Success State
1122
+
1123
+ ```tsx
1124
+ <Callout.Root color="success">
1125
+ <Callout.Icon>
1126
+ <Icon as={CheckCircle} size={16} />
1127
+ </Callout.Icon>
1128
+ <Callout.Text>
1129
+ <Text>Your changes have been saved successfully.</Text>
1130
+ </Callout.Text>
1131
+ </Callout.Root>
1132
+ ```
1133
+
1134
+ ### Error State
1135
+
1136
+ ```tsx
1137
+ <Callout.Root color="danger">
1138
+ <Callout.Icon>
1139
+ <Icon as={AlertCircle} size={16} />
1140
+ </Callout.Icon>
1141
+ <Callout.Text>
1142
+ <Text>Something went wrong. Please try again.</Text>
1143
+ </Callout.Text>
1144
+ </Callout.Root>
1145
+ ```
1146
+
1147
+ ### Inline Validation Error
1148
+
1149
+ ```tsx
1150
+ <View style={{ gap: 6 }}>
1151
+ <Label nativeID="email">Email</Label>
1152
+ <TextField.Input
1153
+ placeholder="you@example.com"
1154
+ aria-labelledby="email"
1155
+ style={{ borderColor: colors.palettes.danger['7'] }}
1156
+ />
1157
+ <Text size="1" color="danger">
1158
+ Please enter a valid email address
1159
+ </Text>
1160
+ </View>
1161
+ ```
1162
+
1163
+ ---
1164
+
1165
+ ## Modal & Dialog Best Practices
1166
+
1167
+ ### Dialog Content Structure
1168
+
1169
+ ```tsx
1170
+ <Dialog.Content>
1171
+ {/* Title + Description */}
1172
+ <Dialog.Title>Confirm Action</Dialog.Title>
1173
+ <Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
1174
+
1175
+ {/* Optional: Form or additional content */}
1176
+ <View style={{ gap: 12, marginVertical: 16 }}>{/* Content */}</View>
1177
+
1178
+ {/* Actions - always at bottom, right-aligned */}
1179
+ <View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
1180
+ <Dialog.Close>
1181
+ <Button variant="soft" color="gray">
1182
+ <Text>Cancel</Text>
1183
+ </Button>
1184
+ </Dialog.Close>
1185
+ <Dialog.Close>
1186
+ <Button variant="solid">
1187
+ <Text>Confirm</Text>
1188
+ </Button>
1189
+ </Dialog.Close>
1190
+ </View>
1191
+ </Dialog.Content>
1192
+ ```
1193
+
1194
+ ### Destructive Dialog
1195
+
1196
+ ```tsx
1197
+ <AlertDialog.Content>
1198
+ <AlertDialog.Header>
1199
+ <AlertDialog.Title>Delete Account</AlertDialog.Title>
1200
+ <AlertDialog.Description>
1201
+ This will permanently delete your account and all associated data. This action cannot be
1202
+ undone.
1203
+ </AlertDialog.Description>
1204
+ </AlertDialog.Header>
1205
+ <AlertDialog.Footer>
1206
+ <AlertDialog.Cancel>
1207
+ <Button variant="soft" color="gray">
1208
+ <Text>Cancel</Text>
1209
+ </Button>
1210
+ </AlertDialog.Cancel>
1211
+ <AlertDialog.Action>
1212
+ <Button variant="solid" color="danger">
1213
+ <Text>Delete Account</Text>
1214
+ </Button>
1215
+ </AlertDialog.Action>
1216
+ </AlertDialog.Footer>
1217
+ </AlertDialog.Content>
1218
+ ```
1219
+
1220
+ ---
1221
+
1222
+ ## Navigation Patterns
1223
+
1224
+ ### Header with Back Button
1225
+
1226
+ ```tsx
1227
+ <View
1228
+ style={{
1229
+ flexDirection: 'row',
1230
+ alignItems: 'center',
1231
+ paddingHorizontal: 8,
1232
+ paddingVertical: 12,
1233
+ gap: 8,
1234
+ }}>
1235
+ <IconButton variant="ghost" onPress={goBack}>
1236
+ <Icon as={ArrowLeft} size={20} />
1237
+ </IconButton>
1238
+ <Heading size="4" style={{ flex: 1 }}>
1239
+ Page Title
1240
+ </Heading>
1241
+ <IconButton variant="ghost">
1242
+ <Icon as={MoreHorizontal} size={20} />
1243
+ </IconButton>
1244
+ </View>
1245
+ ```
1246
+
1247
+ ### Tab Navigation
1248
+
1249
+ ```tsx
1250
+ <Tabs.Root value={activeTab} onValueChange={setActiveTab}>
1251
+ <Tabs.List>
1252
+ <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
1253
+ <Tabs.Trigger value="activity">Activity</Tabs.Trigger>
1254
+ <Tabs.Trigger value="settings">Settings</Tabs.Trigger>
1255
+ </Tabs.List>
1256
+
1257
+ <Tabs.Content value="overview">{/* Overview content */}</Tabs.Content>
1258
+ <Tabs.Content value="activity">{/* Activity content */}</Tabs.Content>
1259
+ <Tabs.Content value="settings">{/* Settings content */}</Tabs.Content>
1260
+ </Tabs.Root>
1261
+ ```
1262
+
1263
+ ### Segmented Control
1264
+
1265
+ For switching between mutually exclusive views:
1266
+
1267
+ ```tsx
1268
+ const [view, setView] = React.useState('list');
1269
+
1270
+ <SegmentedControl.Root value={view} onValueChange={setView}>
1271
+ <SegmentedControl.List>
1272
+ <SegmentedControl.Trigger value="list">List</SegmentedControl.Trigger>
1273
+ <SegmentedControl.Trigger value="grid">Grid</SegmentedControl.Trigger>
1274
+ <SegmentedControl.Trigger value="table">Table</SegmentedControl.Trigger>
1275
+ </SegmentedControl.List>
1276
+ </SegmentedControl.Root>;
1277
+ ```
1278
+
1279
+ ### Radio Group
1280
+
1281
+ For selecting one option from a list:
1282
+
1283
+ ```tsx
1284
+ const [selected, setSelected] = React.useState('option1');
1285
+
1286
+ <RadioGroup.Root value={selected} onValueChange={setSelected}>
1287
+ <View style={{ gap: 8 }}>
1288
+ <Pressable
1289
+ style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}
1290
+ onPress={() => setSelected('option1')}>
1291
+ <RadioGroup.Item value="option1" />
1292
+ <Text>Option 1</Text>
1293
+ </Pressable>
1294
+ <Pressable
1295
+ style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}
1296
+ onPress={() => setSelected('option2')}>
1297
+ <RadioGroup.Item value="option2" />
1298
+ <Text>Option 2</Text>
1299
+ </Pressable>
1300
+ </View>
1301
+ </RadioGroup.Root>;
1302
+ ```
1303
+
1304
+ ### Search Field
1305
+
1306
+ ```tsx
1307
+ <TextField.Root>
1308
+ <TextField.Slot>
1309
+ <Icon as={Search} size={16} />
1310
+ </TextField.Slot>
1311
+ <TextField.Input placeholder="Search..." />
1312
+ <TextField.Slot>
1313
+ <IconButton variant="ghost" size="1">
1314
+ <Icon as={X} size={14} />
1315
+ </IconButton>
1316
+ </TextField.Slot>
1317
+ </TextField.Root>
1318
+ ```
1319
+
1320
+ ---
1321
+
1322
+ ## Accessibility Checklist
1323
+
1324
+ - [ ] All interactive elements have sufficient touch targets (minimum 44×44px, use `size="3"` or larger)
1325
+ - [ ] Form inputs have associated `<Label>` with `nativeID` and `aria-labelledby`
1326
+ - [ ] Color is not the only way to convey information (add icons or text)
1327
+ - [ ] Text has sufficient contrast (Frosted UI handles this automatically)
1328
+ - [ ] Loading states are announced (use `aria-busy`)
1329
+ - [ ] Error messages are associated with inputs (use `aria-describedby`)
1330
+
1331
+ ---
1332
+
1333
+ ## Visual Polish Tips
1334
+
1335
+ ### 1. Align Everything
1336
+
1337
+ Use consistent padding and alignment. If your screen padding is 16px, all content should align to that grid.
1338
+
1339
+ ### 2. Group Related Items
1340
+
1341
+ Use `gap` to show relationships:
1342
+
1343
+ - Tighter gaps (4-8px) = closely related
1344
+ - Larger gaps (16-24px) = separate groups
1345
+
1346
+ ### 3. Use Cards to Elevate
1347
+
1348
+ Wrap distinct content sections in `<Card>` to create visual separation and hierarchy.
1349
+
1350
+ ### 4. Consistent Icon Sizes
1351
+
1352
+ - `14-16px` — inline with text, buttons
1353
+ - `18-20px` — list items, navigation
1354
+ - `24-32px` — feature icons, empty states
1355
+
1356
+ ### 5. Icon Colors
1357
+
1358
+ When icons are standalone (not inside Frosted UI components):
1359
+
1360
+ ```tsx
1361
+ // Gray icons (neutral)
1362
+ <Icon as={Settings} size={20} color={colors.palettes.gray.a11} />
1363
+
1364
+ // Colored icons (e.g., in icon boxes)
1365
+ <View style={{ backgroundColor: colors.palettes.blue.a3 }}>
1366
+ <Icon as={Bell} size={20} color={colors.palettes.blue.a11} />
1367
+ </View>
1368
+
1369
+ // Semantic icons
1370
+ <Icon as={AlertCircle} size={20} color={colors.palettes.danger['9']} />
1371
+ ```
1372
+
1373
+ > **Important**: Use `palette.a11` for colored icons, not `palette['9']`. The alpha shades adapt better to light/dark mode.
1374
+
1375
+ ### 5. Balance Whitespace
1376
+
1377
+ If something feels cramped, add padding. If something feels disconnected, reduce gaps. Trust your visual instincts.
1378
+
1379
+ ---
1380
+
1381
+ ## Store & Marketing Patterns
1382
+
1383
+ ### Pricing Tier
1384
+
1385
+ ```tsx
1386
+ <Card>
1387
+ <View style={{ gap: 16 }}>
1388
+ <Badge color="accent" size="1" style={{ alignSelf: 'flex-start' }}>
1389
+ <Text>MOST POPULAR</Text>
1390
+ </Badge>
1391
+
1392
+ <View style={{ gap: 4 }}>
1393
+ <Text size="3" weight="bold">
1394
+ Pro Plan
1395
+ </Text>
1396
+ <View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 4 }}>
1397
+ <Text size="7" weight="bold">
1398
+ $19
1399
+ </Text>
1400
+ <Text size="2" color="gray">
1401
+ /month
1402
+ </Text>
1403
+ </View>
1404
+ </View>
1405
+
1406
+ <Separator size="4" />
1407
+
1408
+ <View style={{ gap: 12 }}>
1409
+ {['Unlimited projects', 'Advanced analytics', 'Priority support', 'Custom integrations'].map(
1410
+ (feature) => (
1411
+ <View key={feature} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
1412
+ <View
1413
+ style={{
1414
+ width: 20,
1415
+ height: 20,
1416
+ borderRadius: 10,
1417
+ backgroundColor: colors.palettes.success.a3,
1418
+ alignItems: 'center',
1419
+ justifyContent: 'center',
1420
+ }}>
1421
+ <Icon as={Check} size={12} color={colors.palettes.success['9']} />
1422
+ </View>
1423
+ <Text size="2">{feature}</Text>
1424
+ </View>
1425
+ )
1426
+ )}
1427
+ </View>
1428
+
1429
+ <Button variant="solid" size="4">
1430
+ <Text>Get Started</Text>
1431
+ </Button>
1432
+ </View>
1433
+ </Card>
1434
+ ```
1435
+
1436
+ ### Testimonial
1437
+
1438
+ ```tsx
1439
+ <Card>
1440
+ <View style={{ gap: 16 }}>
1441
+ <Icon as={Quote} size={32} color={colors.palettes.gray.a6} />
1442
+
1443
+ <Text size="3" style={{ fontStyle: 'italic' }}>
1444
+ "This product has completely transformed how our team works. We've seen a 40% increase in
1445
+ productivity and the support team is incredibly responsive."
1446
+ </Text>
1447
+
1448
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
1449
+ {[1, 2, 3, 4, 5].map((star) => (
1450
+ <Icon
1451
+ key={star}
1452
+ as={Star}
1453
+ size={16}
1454
+ color={colors.palettes.amber['9']}
1455
+ fill={colors.palettes.amber['9']}
1456
+ />
1457
+ ))}
1458
+ </View>
1459
+
1460
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
1461
+ <Avatar fallback="JD" size="3" color="blue" />
1462
+ <View style={{ gap: 2 }}>
1463
+ <Text weight="medium">Jennifer Davis</Text>
1464
+ <Text size="1" color="gray">
1465
+ CTO at TechCorp
1466
+ </Text>
1467
+ </View>
1468
+ </View>
1469
+ </View>
1470
+ </Card>
1471
+ ```
1472
+
1473
+ ### Feature Showcase
1474
+
1475
+ ```tsx
1476
+ const features = [
1477
+ { icon: Zap, title: 'Lightning Fast', description: 'Sub-100ms response times', color: 'amber' },
1478
+ { icon: Users, title: 'Team Collaboration', description: 'Real-time sync', color: 'blue' },
1479
+ { icon: Sparkles, title: 'AI Powered', description: 'Smart suggestions', color: 'purple' },
1480
+ ];
1481
+
1482
+ <View style={{ gap: 12 }}>
1483
+ {features.map((feature) => (
1484
+ <Card key={feature.title} variant="soft">
1485
+ <View style={{ flexDirection: 'row', gap: 16 }}>
1486
+ <View
1487
+ style={{
1488
+ width: 48,
1489
+ height: 48,
1490
+ borderRadius: 12,
1491
+ backgroundColor: colors.palettes[feature.color].a3,
1492
+ alignItems: 'center',
1493
+ justifyContent: 'center',
1494
+ }}>
1495
+ <Icon as={feature.icon} size={24} color={colors.palettes[feature.color].a11} />
1496
+ </View>
1497
+ <View style={{ flex: 1, gap: 4 }}>
1498
+ <Text weight="medium">{feature.title}</Text>
1499
+ <Text size="2" color="gray">
1500
+ {feature.description}
1501
+ </Text>
1502
+ </View>
1503
+ </View>
1504
+ </Card>
1505
+ ))}
1506
+ </View>;
1507
+ ```
1508
+
1509
+ ### App Stats
1510
+
1511
+ ```tsx
1512
+ const stats = [
1513
+ { value: '10M+', label: 'Downloads', icon: Download },
1514
+ { value: '4.8★', label: 'Rating', icon: Star },
1515
+ { value: '#1', label: 'Top Charts', icon: Trophy },
1516
+ ];
1517
+
1518
+ <View style={{ flexDirection: 'row', gap: 12 }}>
1519
+ {stats.map((stat) => (
1520
+ <Card key={stat.label} style={{ flex: 1, alignItems: 'center' }}>
1521
+ <View style={{ alignItems: 'center', gap: 8 }}>
1522
+ <Icon as={stat.icon} size={24} color={colors.palettes.accent.a11} />
1523
+ <Text size="4" weight="bold">
1524
+ {stat.value}
1525
+ </Text>
1526
+ <Text size="1" color="gray">
1527
+ {stat.label}
1528
+ </Text>
1529
+ </View>
1530
+ </Card>
1531
+ ))}
1532
+ </View>;
1533
+ ```
1534
+
1535
+ ---
1536
+
1537
+ ## Apple-like Design Principles
1538
+
1539
+ When creating premium, polished interfaces, follow these principles:
1540
+
1541
+ ### 1. Structure Over Decoration
1542
+
1543
+ - Use clear sections with subtle borders (`colors.stroke`)
1544
+ - Separate header areas with different background shades (`gray.a2`)
1545
+ - Avoid gratuitous gradients or shadows
1546
+
1547
+ ### 2. Consistent Color Theming
1548
+
1549
+ When theming a section:
1550
+
1551
+ ```tsx
1552
+ // Use a single palette consistently
1553
+ backgroundColor: colors.palettes.pink.a2, // Subtle background
1554
+ borderColor: colors.palettes.pink.a4, // Border
1555
+ color: colors.palettes.pink.a11, // Body text
1556
+ color: colors.palettes.pink.a12, // Heading
1557
+ ```
1558
+
1559
+ ### 3. Subtle Emphasis
1560
+
1561
+ - Use `shadowRadius: 12` with `shadowOpacity: 0.3` for a soft "glow"
1562
+ - Add small indicator dots (6×6) for live/active states
1563
+ - Use `weight="medium"` more than `weight="bold"` for cleaner text
1564
+
1565
+ ### 4. Centered Focal Points
1566
+
1567
+ For achievements, promotions, or highlights:
1568
+
1569
+ ```tsx
1570
+ <Badge style={{ alignSelf: 'center' }}>
1571
+ <Icon as={Award} size={14} />
1572
+ <Text>Achievement Name</Text>
1573
+ </Badge>
1574
+ ```
1575
+
1576
+ ### 5. Stats Rows
1577
+
1578
+ Display multiple metrics in a clean horizontal layout:
1579
+
1580
+ ```tsx
1581
+ <View style={{ flexDirection: 'row' }}>
1582
+ <View style={{ flex: 1, alignItems: 'center', gap: 2 }}>
1583
+ <Text size="4" weight="bold">
1584
+ 100
1585
+ </Text>
1586
+ <Text size="1" color="gray">
1587
+ Points
1588
+ </Text>
1589
+ </View>
1590
+ <View style={{ width: 1, backgroundColor: colors.stroke }} />
1591
+ {/* More stats... */}
1592
+ </View>
1593
+ ```
1594
+
1595
+ ---
1596
+
1597
+ ## Common Mistakes to Avoid
1598
+
1599
+ | Mistake | Better Approach |
1600
+ | --------------------------- | ------------------------------------------------------ |
1601
+ | Using many different colors | Stick to accent + gray + semantic colors |
1602
+ | Inconsistent spacing | Use the spacing scale (4, 8, 12, 16, 24, 32) |
1603
+ | Too many primary buttons | One `solid` button per view/section |
1604
+ | Overriding component styles | Use built-in variants and props |
1605
+ | Missing loading states | Always show feedback during async operations |
1606
+ | Missing empty states | Design what users see with no data |
1607
+ | Text without hierarchy | Use Heading for titles, Text with size/weight for body |
1608
+ | Cramped touch targets | Use `size="2"` or `size="3"` for interactive elements |
1609
+
1610
+ ---
1611
+
1612
+ ## Quick Reference: Common Compositions
1613
+
1614
+ | Pattern | Components Used |
1615
+ | ------------ | ------------------------------------------------- |
1616
+ | Page header | `<Heading>` + optional `<Text>` description |
1617
+ | Form field | `<Label>` + `<TextField.Input>` + helper `<Text>` |
1618
+ | List item | `<Avatar>` + `<Text>` stack + `<Badge>` or action |
1619
+ | Card action | `<Card>` + content + `<Button>` row |
1620
+ | Empty state | Icon + `<Heading>` + `<Text>` + `<Button>` |
1621
+ | Settings row | Label + `<Switch>` or `<Select>` |
1622
+ | Dialog | Title + Description + content + button row |
1623
+ | Feedback | `<Callout>` with semantic color |
1624
+
1625
+ ---
1626
+
1627
+ ## E-commerce Patterns
1628
+
1629
+ ### Product Card
1630
+
1631
+ ```tsx
1632
+ <Card style={{ padding: 0 }}>
1633
+ {/* Product Image */}
1634
+ <View style={{ height: 200, backgroundColor: colors.palettes.gray.a3 }} />
1635
+
1636
+ <View style={{ padding: 16, gap: 12 }}>
1637
+ {/* Category + Rating */}
1638
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
1639
+ <Badge variant="soft" color="gray" size="1">
1640
+ <Text>Electronics</Text>
1641
+ </Badge>
1642
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
1643
+ <Icon
1644
+ as={Star}
1645
+ size={14}
1646
+ color={colors.palettes.amber['9']}
1647
+ fill={colors.palettes.amber['9']}
1648
+ />
1649
+ <Text size="1" weight="medium">
1650
+ 4.8
1651
+ </Text>
1652
+ <Text size="1" color="gray">
1653
+ (128)
1654
+ </Text>
1655
+ </View>
1656
+ </View>
1657
+
1658
+ {/* Title */}
1659
+ <Text size="3" weight="medium" numberOfLines={2}>
1660
+ Wireless Noise-Cancelling Headphones
1661
+ </Text>
1662
+
1663
+ {/* Price */}
1664
+ <View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 8 }}>
1665
+ <Text size="4" weight="bold">
1666
+ $299
1667
+ </Text>
1668
+ <Text size="2" color="gray" style={{ textDecorationLine: 'line-through' }}>
1669
+ $349
1670
+ </Text>
1671
+ </View>
1672
+
1673
+ {/* Quick action */}
1674
+ <Button variant="solid" size="3">
1675
+ <Text>Add to Cart</Text>
1676
+ </Button>
1677
+ </View>
1678
+ </Card>
1679
+ ```
1680
+
1681
+ ### Cart Item
1682
+
1683
+ ```tsx
1684
+ <View
1685
+ style={{
1686
+ flexDirection: 'row',
1687
+ gap: 12,
1688
+ paddingVertical: 16,
1689
+ borderBottomWidth: 1,
1690
+ borderBottomColor: colors.stroke,
1691
+ }}>
1692
+ {/* Thumbnail */}
1693
+ <View
1694
+ style={{
1695
+ width: 80,
1696
+ height: 80,
1697
+ borderRadius: 8,
1698
+ backgroundColor: colors.palettes.gray.a3,
1699
+ }}
1700
+ />
1701
+
1702
+ {/* Details */}
1703
+ <View style={{ flex: 1, gap: 4 }}>
1704
+ <Text size="2" weight="medium" numberOfLines={2}>
1705
+ Premium Wireless Headphones
1706
+ </Text>
1707
+ <Text size="1" color="gray">
1708
+ Black · Qty: 1
1709
+ </Text>
1710
+ <Text size="3" weight="bold">
1711
+ $299
1712
+ </Text>
1713
+ </View>
1714
+
1715
+ {/* Remove button */}
1716
+ <IconButton variant="ghost" size="2" color="gray">
1717
+ <Icon as={Trash2} size={16} />
1718
+ </IconButton>
1719
+ </View>
1720
+ ```
1721
+
1722
+ ### Order Summary
1723
+
1724
+ ```tsx
1725
+ <Card>
1726
+ <View style={{ gap: 16 }}>
1727
+ <Heading size="4">Order Summary</Heading>
1728
+
1729
+ <View style={{ gap: 12 }}>
1730
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
1731
+ <Text color="gray">Subtotal (3 items)</Text>
1732
+ <Text weight="medium">$239.97</Text>
1733
+ </View>
1734
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
1735
+ <Text color="gray">Shipping</Text>
1736
+ <Text weight="medium" color="success">
1737
+ Free
1738
+ </Text>
1739
+ </View>
1740
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
1741
+ <Text color="gray">Tax</Text>
1742
+ <Text weight="medium">$19.20</Text>
1743
+ </View>
1744
+ </View>
1745
+
1746
+ <Separator size="4" />
1747
+
1748
+ {/* Discount Code */}
1749
+ <View style={{ flexDirection: 'row', gap: 8 }}>
1750
+ <View style={{ flex: 1 }}>
1751
+ <TextField.Input placeholder="Discount code" />
1752
+ </View>
1753
+ <Button variant="surface">
1754
+ <Text>Apply</Text>
1755
+ </Button>
1756
+ </View>
1757
+
1758
+ <Separator size="4" />
1759
+
1760
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
1761
+ <Text size="4" weight="bold">
1762
+ Total
1763
+ </Text>
1764
+ <Text size="5" weight="bold">
1765
+ $259.17
1766
+ </Text>
1767
+ </View>
1768
+
1769
+ <Button variant="solid" size="4">
1770
+ <Text>Checkout</Text>
1771
+ </Button>
1772
+ </View>
1773
+ </Card>
1774
+ ```
1775
+
1776
+ ### Shipping Options
1777
+
1778
+ Use `List` with `RadioGroup` for selection lists like shipping methods:
1779
+
1780
+ ```tsx
1781
+ const [selected, setSelected] = React.useState('standard');
1782
+
1783
+ const options = [
1784
+ {
1785
+ id: 'standard',
1786
+ name: 'Standard Shipping',
1787
+ price: 'Free',
1788
+ time: '5-7 business days',
1789
+ icon: Truck,
1790
+ },
1791
+ { id: 'express', name: 'Express Shipping', price: '$9.99', time: '2-3 business days', icon: Zap },
1792
+ { id: 'overnight', name: 'Overnight', price: '$24.99', time: 'Next business day', icon: Clock },
1793
+ ];
1794
+
1795
+ <RadioGroup.Root value={selected} onValueChange={setSelected}>
1796
+ <List.Root>
1797
+ {options.map((option, index) => (
1798
+ <React.Fragment key={option.id}>
1799
+ {index > 0 && <List.Separator />}
1800
+ <List.Item onPress={() => setSelected(option.id)}>
1801
+ <List.ItemSlot>
1802
+ <RadioGroup.Item value={option.id} />
1803
+ </List.ItemSlot>
1804
+ <List.ItemSlot>
1805
+ <View style={iconBoxStyle}>
1806
+ <Icon as={option.icon} size={20} />
1807
+ </View>
1808
+ </List.ItemSlot>
1809
+ <List.ItemContent>
1810
+ <List.ItemTitle>{option.name}</List.ItemTitle>
1811
+ <List.ItemDescription>{option.time}</List.ItemDescription>
1812
+ </List.ItemContent>
1813
+ <List.ItemSlot>
1814
+ <Text weight="medium" color={option.price === 'Free' ? 'success' : undefined}>
1815
+ {option.price}
1816
+ </Text>
1817
+ </List.ItemSlot>
1818
+ </List.Item>
1819
+ </React.Fragment>
1820
+ ))}
1821
+ </List.Root>
1822
+ </RadioGroup.Root>;
1823
+ ```
1824
+
1825
+ ### Payment Method
1826
+
1827
+ ```tsx
1828
+ const [selected, setSelected] = React.useState('visa');
1829
+
1830
+ <RadioGroup.Root value={selected} onValueChange={setSelected}>
1831
+ <List.Root>
1832
+ {[
1833
+ { id: 'visa', name: 'Visa', last4: '4242', expiry: '12/25' },
1834
+ { id: 'mastercard', name: 'Mastercard', last4: '8888', expiry: '03/26' },
1835
+ ].map((card, index) => (
1836
+ <React.Fragment key={card.id}>
1837
+ {index > 0 && <List.Separator />}
1838
+ <List.Item onPress={() => setSelected(card.id)}>
1839
+ <List.ItemSlot>
1840
+ <RadioGroup.Item value={card.id} />
1841
+ </List.ItemSlot>
1842
+ <List.ItemSlot>
1843
+ <View style={cardIconStyle}>
1844
+ <Icon as={CreditCard} size={20} color={colors.palettes.gray.a11} />
1845
+ </View>
1846
+ </List.ItemSlot>
1847
+ <List.ItemContent>
1848
+ <List.ItemTitle>
1849
+ {card.name} •••• {card.last4}
1850
+ </List.ItemTitle>
1851
+ <List.ItemDescription>Expires {card.expiry}</List.ItemDescription>
1852
+ </List.ItemContent>
1853
+ </List.Item>
1854
+ </React.Fragment>
1855
+ ))}
1856
+
1857
+ <List.Separator />
1858
+
1859
+ {/* Add new card option */}
1860
+ <List.Item onPress={() => {}}>
1861
+ <List.ItemSlot>
1862
+ <View style={addButtonStyle}>
1863
+ <Icon as={Plus} size={14} color={colors.palettes.accent.a11} />
1864
+ </View>
1865
+ </List.ItemSlot>
1866
+ <List.ItemContent>
1867
+ <Text weight="medium" color="accent">
1868
+ Add new card
1869
+ </Text>
1870
+ </List.ItemContent>
1871
+ </List.Item>
1872
+ </List.Root>
1873
+ </RadioGroup.Root>;
1874
+ ```
1875
+
1876
+ ### Order Status (Timeline)
1877
+
1878
+ ```tsx
1879
+ const steps = [
1880
+ { id: 'ordered', label: 'Ordered', date: 'Dec 15', completed: true },
1881
+ { id: 'shipped', label: 'Shipped', date: 'Dec 16', completed: true },
1882
+ { id: 'transit', label: 'In Transit', date: 'Dec 17', completed: true, active: true },
1883
+ { id: 'delivered', label: 'Delivered', date: 'Dec 19', completed: false },
1884
+ ];
1885
+
1886
+ <Card>
1887
+ <View style={{ gap: 16 }}>
1888
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
1889
+ <Heading size="4">Order Status</Heading>
1890
+ <Badge color="info" size="1">
1891
+ <Text>In Transit</Text>
1892
+ </Badge>
1893
+ </View>
1894
+
1895
+ <View style={{ gap: 0 }}>
1896
+ {steps.map((step, index) => (
1897
+ <View key={step.id} style={{ flexDirection: 'row', gap: 12 }}>
1898
+ {/* Timeline */}
1899
+ <View style={{ alignItems: 'center', width: 24 }}>
1900
+ <View
1901
+ style={{
1902
+ width: 24,
1903
+ height: 24,
1904
+ borderRadius: 12,
1905
+ backgroundColor: step.completed
1906
+ ? colors.palettes.success['9']
1907
+ : colors.palettes.gray.a4,
1908
+ alignItems: 'center',
1909
+ justifyContent: 'center',
1910
+ }}>
1911
+ {step.completed && <Icon as={Check} size={14} color="white" />}
1912
+ </View>
1913
+ {index < steps.length - 1 && (
1914
+ <View
1915
+ style={{
1916
+ width: 2,
1917
+ height: 32,
1918
+ backgroundColor: steps[index + 1].completed
1919
+ ? colors.palettes.success['9']
1920
+ : colors.palettes.gray.a4,
1921
+ }}
1922
+ />
1923
+ )}
1924
+ </View>
1925
+ {/* Content */}
1926
+ <View style={{ flex: 1, paddingBottom: index < steps.length - 1 ? 16 : 0 }}>
1927
+ <Text weight={step.active ? 'bold' : 'medium'}>{step.label}</Text>
1928
+ <Text size="1" color="gray">
1929
+ {step.date}
1930
+ </Text>
1931
+ </View>
1932
+ </View>
1933
+ ))}
1934
+ </View>
1935
+ </View>
1936
+ </Card>;
1937
+ ```
1938
+
1939
+ ### Product Review
1940
+
1941
+ ```tsx
1942
+ <Card>
1943
+ <View style={{ gap: 12 }}>
1944
+ <View
1945
+ style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start' }}>
1946
+ <View style={{ flexDirection: 'row', gap: 12 }}>
1947
+ <Avatar fallback="MJ" size="3" color="blue" />
1948
+ <View style={{ gap: 2 }}>
1949
+ <Text weight="medium">Michael Johnson</Text>
1950
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
1951
+ {[1, 2, 3, 4, 5].map((star) => (
1952
+ <Icon
1953
+ key={star}
1954
+ as={Star}
1955
+ size={12}
1956
+ color={star <= 5 ? colors.palettes.amber['9'] : colors.palettes.gray.a6}
1957
+ fill={star <= 5 ? colors.palettes.amber['9'] : 'transparent'}
1958
+ />
1959
+ ))}
1960
+ </View>
1961
+ </View>
1962
+ </View>
1963
+ <Text size="1" color="gray">
1964
+ 2 days ago
1965
+ </Text>
1966
+ </View>
1967
+
1968
+ <Text size="3" color="gray">
1969
+ Absolutely love these headphones! The noise cancellation is incredible and the battery life
1970
+ exceeds expectations. Highly recommend for anyone looking for premium audio quality.
1971
+ </Text>
1972
+
1973
+ <Button variant="ghost" size="2" style={{ alignSelf: 'flex-start' }}>
1974
+ <Icon as={ThumbsUp} size={14} />
1975
+ <Text>Helpful (24)</Text>
1976
+ </Button>
1977
+ </View>
1978
+ </Card>
1979
+ ```
1980
+
1981
+ ### Wishlist Item
1982
+
1983
+ ```tsx
1984
+ <Card style={{ padding: 0 }}>
1985
+ <View style={{ flexDirection: 'row', padding: 16, gap: 12 }}>
1986
+ <View
1987
+ style={{
1988
+ width: 80,
1989
+ height: 80,
1990
+ backgroundColor: colors.palettes.gray.a3,
1991
+ borderRadius: 8,
1992
+ alignItems: 'center',
1993
+ justifyContent: 'center',
1994
+ }}>
1995
+ <Icon as={ShoppingBag} size={32} color={colors.palettes.gray.a8} />
1996
+ </View>
1997
+ <View style={{ flex: 1, gap: 4 }}>
1998
+ <Text size="2" weight="medium" numberOfLines={2}>
1999
+ Premium Leather Wallet
2000
+ </Text>
2001
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
2002
+ {[1, 2, 3, 4, 5].map((star) => (
2003
+ <Icon
2004
+ key={star}
2005
+ as={Star}
2006
+ size={10}
2007
+ color={star <= 4 ? colors.palettes.amber['9'] : colors.palettes.gray.a6}
2008
+ fill={star <= 4 ? colors.palettes.amber['9'] : 'transparent'}
2009
+ />
2010
+ ))}
2011
+ <Text size="0" color="gray">
2012
+ (89)
2013
+ </Text>
2014
+ </View>
2015
+ <Text size="3" weight="bold">
2016
+ $49.99
2017
+ </Text>
2018
+ </View>
2019
+ </View>
2020
+ <Separator size="4" />
2021
+ <View style={{ flexDirection: 'row', paddingHorizontal: 16, paddingVertical: 12, gap: 8 }}>
2022
+ <Button variant="solid" size="2" style={{ flex: 1 }}>
2023
+ <Icon as={ShoppingCart} size={14} />
2024
+ <Text>Move to Cart</Text>
2025
+ </Button>
2026
+ <IconButton variant="surface" size="2" color="danger">
2027
+ <Icon as={Trash2} size={16} />
2028
+ </IconButton>
2029
+ </View>
2030
+ </Card>
2031
+ ```
2032
+
2033
+ ---
2034
+
2035
+ ## Profile & Social Patterns
2036
+
2037
+ ### Team Member Card
2038
+
2039
+ ```tsx
2040
+ <Card>
2041
+ <View style={{ alignItems: 'center', gap: 12 }}>
2042
+ <Avatar fallback="AK" size="7" color="blue" />
2043
+ <View style={{ alignItems: 'center', gap: 4 }}>
2044
+ <Text size="3" weight="bold">
2045
+ Alex Kim
2046
+ </Text>
2047
+ <Text size="2" color="gray">
2048
+ Senior Designer
2049
+ </Text>
2050
+ </View>
2051
+ <View style={{ flexDirection: 'row', gap: 8 }}>
2052
+ <IconButton variant="soft" size="2" color="gray">
2053
+ <Icon as={Twitter} size={16} />
2054
+ </IconButton>
2055
+ <IconButton variant="soft" size="2" color="gray">
2056
+ <Icon as={Linkedin} size={16} />
2057
+ </IconButton>
2058
+ <IconButton variant="soft" size="2" color="gray">
2059
+ <Icon as={Send} size={16} />
2060
+ </IconButton>
2061
+ </View>
2062
+ </View>
2063
+ </Card>
2064
+ ```
2065
+
2066
+ ### User Stats Row
2067
+
2068
+ ```tsx
2069
+ <View style={{ flexDirection: 'row' }}>
2070
+ <View style={{ flex: 1, alignItems: 'center', gap: 2 }}>
2071
+ <Text size="5" weight="bold">
2072
+ 2.4k
2073
+ </Text>
2074
+ <Text size="1" color="gray">
2075
+ Followers
2076
+ </Text>
2077
+ </View>
2078
+ <View style={{ width: 1, backgroundColor: colors.stroke }} />
2079
+ <View style={{ flex: 1, alignItems: 'center', gap: 2 }}>
2080
+ <Text size="5" weight="bold">
2081
+ 486
2082
+ </Text>
2083
+ <Text size="1" color="gray">
2084
+ Following
2085
+ </Text>
2086
+ </View>
2087
+ <View style={{ width: 1, backgroundColor: colors.stroke }} />
2088
+ <View style={{ flex: 1, alignItems: 'center', gap: 2 }}>
2089
+ <Text size="5" weight="bold">
2090
+ 12
2091
+ </Text>
2092
+ <Text size="1" color="gray">
2093
+ Projects
2094
+ </Text>
2095
+ </View>
2096
+ </View>
2097
+ ```
2098
+
2099
+ ### Social Post
2100
+
2101
+ ```tsx
2102
+ const [liked, setLiked] = React.useState(false);
2103
+ const [likes, setLikes] = React.useState(42);
2104
+
2105
+ <Card style={{ padding: 0 }}>
2106
+ {/* Header */}
2107
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, padding: 16 }}>
2108
+ <Avatar fallback="EW" size="3" color="purple" />
2109
+ <View style={{ flex: 1, gap: 2 }}>
2110
+ <Text weight="medium">Emma Wilson</Text>
2111
+ <Text size="1" color="gray">
2112
+ 2 hours ago
2113
+ </Text>
2114
+ </View>
2115
+ <IconButton variant="ghost" size="2">
2116
+ <Icon as={MoreHorizontal} size={18} />
2117
+ </IconButton>
2118
+ </View>
2119
+
2120
+ {/* Content */}
2121
+ <View style={{ paddingHorizontal: 16, paddingBottom: 12 }}>
2122
+ <Text size="3">
2123
+ Just finished my morning run! 🏃‍♀️ Nothing beats starting the day with some exercise.
2124
+ </Text>
2125
+ </View>
2126
+
2127
+ {/* Image placeholder */}
2128
+ <View
2129
+ style={{
2130
+ height: 200,
2131
+ backgroundColor: colors.palettes.gray.a3,
2132
+ alignItems: 'center',
2133
+ justifyContent: 'center',
2134
+ }}>
2135
+ <Icon as={MapPin} size={48} color={colors.palettes.gray.a8} />
2136
+ </View>
2137
+
2138
+ {/* Actions */}
2139
+ <View style={{ flexDirection: 'row', padding: 12, gap: 16 }}>
2140
+ <Button
2141
+ variant="ghost"
2142
+ size="2"
2143
+ color={liked ? 'danger' : 'gray'}
2144
+ onPress={() => {
2145
+ setLiked(!liked);
2146
+ setLikes(liked ? likes - 1 : likes + 1);
2147
+ }}>
2148
+ <Icon as={Heart} size={18} />
2149
+ <Text>{likes}</Text>
2150
+ </Button>
2151
+ <Button variant="ghost" size="2" color="gray">
2152
+ <Icon as={MessageCircle} size={18} />
2153
+ <Text>12</Text>
2154
+ </Button>
2155
+ <Button variant="ghost" size="2" color="gray">
2156
+ <Icon as={Share} size={18} />
2157
+ </Button>
2158
+ </View>
2159
+ </Card>;
2160
+ ```
2161
+
2162
+ ---
2163
+
2164
+ ## Gamification Patterns
2165
+
2166
+ ### Streak Counter
2167
+
2168
+ ```tsx
2169
+ <Card>
2170
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 16 }}>
2171
+ <View
2172
+ style={{
2173
+ width: 56,
2174
+ height: 56,
2175
+ borderRadius: 14,
2176
+ backgroundColor: colors.palettes.orange.a3,
2177
+ alignItems: 'center',
2178
+ justifyContent: 'center',
2179
+ }}>
2180
+ <Icon as={Flame} size={28} color={colors.palettes.orange['9']} />
2181
+ </View>
2182
+ <View style={{ flex: 1, gap: 4 }}>
2183
+ <View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 4 }}>
2184
+ <Text size="6" weight="bold">
2185
+ 7
2186
+ </Text>
2187
+ <Text size="3" weight="medium">
2188
+ Day Streak
2189
+ </Text>
2190
+ </View>
2191
+ <Text size="2" color="gray">
2192
+ Keep it up! You're on fire 🔥
2193
+ </Text>
2194
+ </View>
2195
+ </View>
2196
+ </Card>
2197
+ ```
2198
+
2199
+ ### Leaderboard
2200
+
2201
+ ```tsx
2202
+ const entries = [
2203
+ { rank: 1, name: 'Sarah Chen', points: 12450, avatar: 'SC', color: 'pink' },
2204
+ { rank: 2, name: 'Alex Kim', points: 11200, avatar: 'AK', color: 'blue' },
2205
+ { rank: 3, name: 'Jordan Lee', points: 10890, avatar: 'JL', color: 'green' },
2206
+ { rank: 4, name: 'You', points: 9540, avatar: 'ME', color: 'accent', isUser: true },
2207
+ ];
2208
+
2209
+ <List.Root variant="soft">
2210
+ {entries.map((entry, index) => (
2211
+ <React.Fragment key={entry.rank}>
2212
+ {index > 0 && <List.Separator />}
2213
+ <List.Item style={entry.isUser ? { backgroundColor: colors.palettes.gray.a3 } : undefined}>
2214
+ <List.ItemSlot>
2215
+ <Text
2216
+ size="2"
2217
+ weight="bold"
2218
+ style={{ width: 24, textAlign: 'center' }}
2219
+ color={entry.rank <= 3 ? undefined : 'gray'}>
2220
+ {entry.rank}
2221
+ </Text>
2222
+ </List.ItemSlot>
2223
+ <List.ItemSlot>
2224
+ <Avatar fallback={entry.avatar} size="2" color={entry.color} />
2225
+ </List.ItemSlot>
2226
+ <List.ItemContent>
2227
+ <Text weight={entry.isUser ? 'bold' : 'medium'}>{entry.name}</Text>
2228
+ </List.ItemContent>
2229
+ <List.ItemSlot>
2230
+ <Text weight="medium" color={entry.color}>
2231
+ {entry.points.toLocaleString()} pts
2232
+ </Text>
2233
+ </List.ItemSlot>
2234
+ </List.Item>
2235
+ </React.Fragment>
2236
+ ))}
2237
+ </List.Root>;
2238
+ ```
2239
+
2240
+ ### XP Progress
2241
+
2242
+ ```tsx
2243
+ <Card>
2244
+ <View style={{ gap: 12 }}>
2245
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
2246
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
2247
+ <View
2248
+ style={{
2249
+ width: 32,
2250
+ height: 32,
2251
+ borderRadius: 8,
2252
+ backgroundColor: colors.palettes.purple['9'],
2253
+ alignItems: 'center',
2254
+ justifyContent: 'center',
2255
+ }}>
2256
+ <Text size="2" weight="bold" style={{ color: 'white' }}>
2257
+ 12
2258
+ </Text>
2259
+ </View>
2260
+ <Text weight="medium">Level 12</Text>
2261
+ </View>
2262
+ <Badge color="purple" size="1">
2263
+ <Icon as={Sparkles} size={10} />
2264
+ <Text>250 XP to go</Text>
2265
+ </Badge>
2266
+ </View>
2267
+ <Progress value={75} size="2" color="purple" />
2268
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
2269
+ <Text size="1" color="gray">
2270
+ 750 / 1,000 XP
2271
+ </Text>
2272
+ <Text size="1" color="gray">
2273
+ Next: Level 13
2274
+ </Text>
2275
+ </View>
2276
+ </View>
2277
+ </Card>
2278
+ ```
2279
+
2280
+ ### Daily Challenge
2281
+
2282
+ ```tsx
2283
+ <Card>
2284
+ <View style={{ gap: 12 }}>
2285
+ <View
2286
+ style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start' }}>
2287
+ <View style={{ flexDirection: 'row', gap: 12 }}>
2288
+ <View
2289
+ style={{
2290
+ width: 48,
2291
+ height: 48,
2292
+ borderRadius: 12,
2293
+ backgroundColor: colors.palettes.cyan.a3,
2294
+ alignItems: 'center',
2295
+ justifyContent: 'center',
2296
+ }}>
2297
+ <Icon as={Timer} size={24} color={colors.palettes.cyan.a11} />
2298
+ </View>
2299
+ <View style={{ gap: 2 }}>
2300
+ <Text size="1" color="gray" weight="medium">
2301
+ DAILY CHALLENGE
2302
+ </Text>
2303
+ <Text weight="medium">Complete 5 lessons</Text>
2304
+ </View>
2305
+ </View>
2306
+ <Badge color="amber" size="1">
2307
+ <Icon as={Gift} size={10} />
2308
+ <Text>+50 XP</Text>
2309
+ </Badge>
2310
+ </View>
2311
+ <Progress value={60} size="2" color="cyan" />
2312
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
2313
+ <Text size="1" color="gray">
2314
+ 3 of 5 completed
2315
+ </Text>
2316
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
2317
+ <Icon as={Clock} size={12} color={colors.palettes.gray.a11} />
2318
+ <Text size="1" color="gray">
2319
+ 8h remaining
2320
+ </Text>
2321
+ </View>
2322
+ </View>
2323
+ </View>
2324
+ </Card>
2325
+ ```
2326
+
2327
+ ---
2328
+
2329
+ ## Media Patterns
2330
+
2331
+ ### Now Playing (Music Player)
2332
+
2333
+ ```tsx
2334
+ const [isPlaying, setIsPlaying] = React.useState(true);
2335
+
2336
+ <Card>
2337
+ <View style={{ gap: 16 }}>
2338
+ {/* Album Art + Info */}
2339
+ <View style={{ flexDirection: 'row', gap: 16 }}>
2340
+ <View
2341
+ style={{
2342
+ width: 80,
2343
+ height: 80,
2344
+ borderRadius: 8,
2345
+ backgroundColor: colors.palettes.pink.a3,
2346
+ alignItems: 'center',
2347
+ justifyContent: 'center',
2348
+ }}>
2349
+ <Icon as={Music} size={32} color={colors.palettes.pink.a11} />
2350
+ </View>
2351
+ <View style={{ flex: 1, justifyContent: 'center', gap: 4 }}>
2352
+ <Text size="3" weight="bold" numberOfLines={1}>
2353
+ Midnight Dreams
2354
+ </Text>
2355
+ <Text size="2" color="gray" numberOfLines={1}>
2356
+ Aurora Synth
2357
+ </Text>
2358
+ <Text size="1" color="gray">
2359
+ Neon Horizons • 2024
2360
+ </Text>
2361
+ </View>
2362
+ <IconButton variant="ghost" size="2">
2363
+ <Icon as={Heart} size={20} />
2364
+ </IconButton>
2365
+ </View>
2366
+
2367
+ {/* Progress */}
2368
+ <View style={{ gap: 8 }}>
2369
+ <Progress value={35} size="1" />
2370
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
2371
+ <Text size="0" color="gray">
2372
+ 1:24
2373
+ </Text>
2374
+ <Text size="0" color="gray">
2375
+ 3:45
2376
+ </Text>
2377
+ </View>
2378
+ </View>
2379
+
2380
+ {/* Controls */}
2381
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 24 }}>
2382
+ <IconButton variant="ghost" size="3">
2383
+ <Icon as={SkipBack} size={24} />
2384
+ </IconButton>
2385
+ <IconButton variant="solid" size="4" onPress={() => setIsPlaying(!isPlaying)}>
2386
+ <Icon as={isPlaying ? Pause : Play} size={24} />
2387
+ </IconButton>
2388
+ <IconButton variant="ghost" size="3">
2389
+ <Icon as={SkipForward} size={24} />
2390
+ </IconButton>
2391
+ </View>
2392
+ </View>
2393
+ </Card>;
2394
+ ```
2395
+
2396
+ ### Poll Card
2397
+
2398
+ ```tsx
2399
+ const [voted, setVoted] = React.useState(null);
2400
+
2401
+ const options = [
2402
+ { id: 'react', label: 'React Native', votes: 156 },
2403
+ { id: 'flutter', label: 'Flutter', votes: 89 },
2404
+ { id: 'native', label: 'Native (Swift/Kotlin)', votes: 67 },
2405
+ ];
2406
+
2407
+ const totalVotes = options.reduce((sum, opt) => sum + opt.votes, 0);
2408
+
2409
+ <Card>
2410
+ <View style={{ gap: 16 }}>
2411
+ <View style={{ gap: 8 }}>
2412
+ <Text size="3" weight="medium">
2413
+ What's your preferred mobile framework?
2414
+ </Text>
2415
+ <Text size="1" color="gray">
2416
+ {totalVotes} votes • 2 days left
2417
+ </Text>
2418
+ </View>
2419
+
2420
+ <View style={{ gap: 8 }}>
2421
+ {options.map((option) => {
2422
+ const percentage = Math.round((option.votes / totalVotes) * 100);
2423
+ const isSelected = voted === option.id;
2424
+
2425
+ return (
2426
+ <Pressable
2427
+ key={option.id}
2428
+ onPress={() => !voted && setVoted(option.id)}
2429
+ style={{
2430
+ borderRadius: 8,
2431
+ overflow: 'hidden',
2432
+ borderWidth: 1,
2433
+ borderColor: isSelected ? colors.palettes.accent['8'] : colors.stroke,
2434
+ }}>
2435
+ {/* Progress background */}
2436
+ <View
2437
+ style={{
2438
+ position: 'absolute',
2439
+ left: 0,
2440
+ top: 0,
2441
+ bottom: 0,
2442
+ width: voted ? `${percentage}%` : '0%',
2443
+ backgroundColor: isSelected ? colors.palettes.accent.a3 : colors.palettes.gray.a3,
2444
+ }}
2445
+ />
2446
+ <View
2447
+ style={{
2448
+ flexDirection: 'row',
2449
+ alignItems: 'center',
2450
+ justifyContent: 'space-between',
2451
+ padding: 12,
2452
+ }}>
2453
+ <Text weight={isSelected ? 'medium' : 'regular'}>{option.label}</Text>
2454
+ {voted && (
2455
+ <Text size="2" weight="medium">
2456
+ {percentage}%
2457
+ </Text>
2458
+ )}
2459
+ </View>
2460
+ </Pressable>
2461
+ );
2462
+ })}
2463
+ </View>
2464
+ </View>
2465
+ </Card>;
2466
+ ```