@cdx-ui/components 0.2.3 → 0.2.4

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 (248) hide show
  1. package/AGENTS.md +24 -0
  2. package/README.md +2 -0
  3. package/coverage.svg +1 -1
  4. package/docs/components/accordion/content.md +538 -0
  5. package/docs/components/alert/content.md +203 -0
  6. package/docs/components/alert-dialog/content.md +319 -0
  7. package/docs/components/avatar/content.md +368 -0
  8. package/docs/components/avatar/design.md +202 -0
  9. package/docs/components/badge/content.md +235 -0
  10. package/docs/components/bottom-sheet/content.md +351 -0
  11. package/docs/components/bottom-sheet/design.md +249 -0
  12. package/docs/components/breadcrumbs/content.md +169 -0
  13. package/docs/components/button/content.md +398 -0
  14. package/docs/components/button/design.md +258 -0
  15. package/docs/components/calendar/content.md +137 -0
  16. package/docs/components/card/content.md +283 -0
  17. package/docs/components/carousel/content.md +416 -0
  18. package/docs/components/checkbox/content.md +236 -0
  19. package/docs/components/checkbox/design.md +214 -0
  20. package/docs/components/chip/content.md +439 -0
  21. package/docs/components/chip/design.md +418 -0
  22. package/docs/components/color-area/content.md +86 -0
  23. package/docs/components/color-field/content.md +134 -0
  24. package/docs/components/color-picker/content.md +208 -0
  25. package/docs/components/color-slider/content.md +120 -0
  26. package/docs/components/color-swatch/content.md +64 -0
  27. package/docs/components/color-swatch-picker/content.md +126 -0
  28. package/docs/components/content.md +97 -0
  29. package/docs/components/currency-input/content.md +205 -0
  30. package/docs/components/data-grid/content.md +522 -0
  31. package/docs/components/date-field/content.md +147 -0
  32. package/docs/components/date-picker/content.md +360 -0
  33. package/docs/components/date-range-picker/content.md +301 -0
  34. package/docs/components/date-time-field/content.md +157 -0
  35. package/docs/components/date-time-list/content.md +105 -0
  36. package/docs/components/date-time-picker/content.md +272 -0
  37. package/docs/components/dialog/content.md +436 -0
  38. package/docs/components/dialog/design.md +192 -0
  39. package/docs/components/divider/content.md +112 -0
  40. package/docs/components/drawer/content.md +436 -0
  41. package/docs/components/field/content.md +277 -0
  42. package/docs/components/form/content.md +62 -0
  43. package/docs/components/full-screen-dialog/content.md +881 -0
  44. package/docs/components/heading/content.md +55 -0
  45. package/docs/components/icon/content.md +190 -0
  46. package/docs/components/icon-button/content.md +200 -0
  47. package/docs/components/icon-button/design.md +165 -0
  48. package/docs/components/image/content.md +104 -0
  49. package/docs/components/input/content.md +281 -0
  50. package/docs/components/input/design.md +389 -0
  51. package/docs/components/insurance-disclosure/content.md +103 -0
  52. package/docs/components/link/content.md +180 -0
  53. package/docs/components/link/design.md +160 -0
  54. package/docs/components/list-item/content.md +695 -0
  55. package/docs/components/menu/content.md +1247 -0
  56. package/docs/components/month-calendar/content.md +84 -0
  57. package/docs/components/month-picker/content.md +273 -0
  58. package/docs/components/otp-input/content.md +327 -0
  59. package/docs/components/progress-bar/content.md +87 -0
  60. package/docs/components/progress-circle/content.md +176 -0
  61. package/docs/components/progress-segmented/content.md +119 -0
  62. package/docs/components/quick-action-button/content.md +154 -0
  63. package/docs/components/quick-action-button/design.md +170 -0
  64. package/docs/components/radio/content.md +224 -0
  65. package/docs/components/range-calendar/content.md +123 -0
  66. package/docs/components/reorderable-list/content.md +309 -0
  67. package/docs/components/scoped-theme/content.md +47 -0
  68. package/docs/components/scroll-shadow/content.md +686 -0
  69. package/docs/components/select/content.md +400 -0
  70. package/docs/components/select/design.md +301 -0
  71. package/docs/components/skeleton/content.md +103 -0
  72. package/docs/components/slider/content.md +423 -0
  73. package/docs/components/stack/content.md +161 -0
  74. package/docs/components/switch/content.md +150 -0
  75. package/docs/components/switch/design.md +208 -0
  76. package/docs/components/table/content.md +548 -0
  77. package/docs/components/table-pagination/content.md +177 -0
  78. package/docs/components/tabs/content.md +292 -0
  79. package/docs/components/text/content.md +142 -0
  80. package/docs/components/textarea/content.md +222 -0
  81. package/docs/components/tile/content.md +703 -0
  82. package/docs/components/toast/content.md +456 -0
  83. package/docs/components/toggle-button/content.md +505 -0
  84. package/docs/components/tooltip/content.md +350 -0
  85. package/docs/components/virtualized-list/content.md +250 -0
  86. package/docs/foundations/content.md +9 -0
  87. package/docs/foundations/expressions/icons/content.md +19 -0
  88. package/docs/foundations/styles/tokens/all-tokens/content.md +192 -0
  89. package/docs/foundations/styles/tokens/what-are-tokens/content.md +33 -0
  90. package/docs/foundations/styles/tokens/what-are-tokens/design.md +33 -0
  91. package/docs/getting-started/content.md +4 -0
  92. package/docs/getting-started/designers/content.md +13 -0
  93. package/docs/getting-started/designers/design.md +13 -0
  94. package/docs/getting-started/developers/accessibility/content.md +31 -0
  95. package/docs/getting-started/developers/content.md +612 -0
  96. package/docs/getting-started/developers/styling/content.md +193 -0
  97. package/docs/llms.txt +193 -0
  98. package/docs/patterns/account-management/content.md +75 -0
  99. package/docs/patterns/authentication/content.md +52 -0
  100. package/docs/patterns/content.md +10 -0
  101. package/docs/patterns/dashboard/content.md +39 -0
  102. package/docs/patterns/empty-states/content.md +31 -0
  103. package/docs/patterns/forms/content.md +109 -0
  104. package/docs/patterns/onboarding/content.md +69 -0
  105. package/docs/patterns/payments/content.md +35 -0
  106. package/docs/patterns/settings/content.md +72 -0
  107. package/docs/resources/content.md +9 -0
  108. package/docs/resources/figma-libraries/content.md +42 -0
  109. package/docs/resources/release-notes/0.0.1-beta.118/content.md +27 -0
  110. package/docs/resources/release-notes/0.0.1-beta.122/content.md +21 -0
  111. package/docs/resources/release-notes/0.0.1-beta.124/content.md +16 -0
  112. package/docs/resources/release-notes/0.0.1-beta.126/content.md +10 -0
  113. package/docs/resources/release-notes/0.0.1-beta.127/content.md +14 -0
  114. package/docs/resources/release-notes/0.0.1-beta.128/content.md +9 -0
  115. package/docs/resources/release-notes/0.0.1-beta.129/content.md +18 -0
  116. package/docs/resources/release-notes/0.0.1-beta.130/content.md +18 -0
  117. package/docs/resources/release-notes/0.0.1-beta.133/content.md +7 -0
  118. package/docs/resources/release-notes/0.0.1-beta.134/content.md +17 -0
  119. package/docs/resources/release-notes/0.0.1-beta.136/content.md +7 -0
  120. package/docs/resources/release-notes/0.0.1-beta.137/content.md +10 -0
  121. package/docs/resources/release-notes/0.0.1-beta.138/content.md +7 -0
  122. package/docs/resources/release-notes/0.0.1-beta.139/content.md +13 -0
  123. package/docs/resources/release-notes/0.0.1-beta.140/content.md +26 -0
  124. package/docs/resources/release-notes/0.0.1-beta.141/content.md +63 -0
  125. package/docs/resources/release-notes/0.0.1-beta.142/content.md +7 -0
  126. package/docs/resources/release-notes/0.0.1-beta.143/content.md +12 -0
  127. package/docs/resources/release-notes/0.0.1-beta.144/content.md +7 -0
  128. package/docs/resources/release-notes/0.0.1-beta.145/content.md +7 -0
  129. package/docs/resources/release-notes/0.0.1-beta.147/content.md +10 -0
  130. package/docs/resources/release-notes/0.0.1-beta.149/content.md +10 -0
  131. package/docs/resources/release-notes/0.0.1-beta.150/content.md +13 -0
  132. package/docs/resources/release-notes/0.0.1-beta.152/content.md +7 -0
  133. package/docs/resources/release-notes/0.0.1-beta.154/content.md +15 -0
  134. package/docs/resources/release-notes/0.0.1-beta.156/content.md +7 -0
  135. package/docs/resources/release-notes/0.0.1-beta.158/content.md +9 -0
  136. package/docs/resources/release-notes/0.0.1-beta.159/content.md +11 -0
  137. package/docs/resources/release-notes/0.0.1-beta.160/content.md +7 -0
  138. package/docs/resources/release-notes/0.0.1-beta.161/content.md +7 -0
  139. package/docs/resources/release-notes/0.0.1-beta.163/content.md +7 -0
  140. package/docs/resources/release-notes/0.0.1-beta.164/content.md +9 -0
  141. package/docs/resources/release-notes/0.0.1-beta.166/content.md +17 -0
  142. package/docs/resources/release-notes/0.0.1-beta.167/content.md +7 -0
  143. package/docs/resources/release-notes/0.0.1-beta.168/content.md +7 -0
  144. package/docs/resources/release-notes/0.0.1-beta.169/content.md +7 -0
  145. package/docs/resources/release-notes/0.0.1-beta.171/content.md +16 -0
  146. package/docs/resources/release-notes/0.0.1-beta.172/content.md +24 -0
  147. package/docs/resources/release-notes/0.0.1-beta.173/content.md +15 -0
  148. package/docs/resources/release-notes/0.0.1-beta.174/content.md +7 -0
  149. package/docs/resources/release-notes/0.0.1-beta.176/content.md +32 -0
  150. package/docs/resources/release-notes/0.0.1-beta.179/content.md +11 -0
  151. package/docs/resources/release-notes/0.0.1-beta.182/content.md +13 -0
  152. package/docs/resources/release-notes/0.0.1-beta.185/content.md +7 -0
  153. package/docs/resources/release-notes/0.0.1-beta.187/content.md +27 -0
  154. package/docs/resources/release-notes/0.1.1/content.md +23 -0
  155. package/docs/resources/release-notes/0.1.2/content.md +7 -0
  156. package/docs/resources/release-notes/0.1.4/content.md +19 -0
  157. package/docs/resources/release-notes/0.1.5/content.md +7 -0
  158. package/docs/resources/release-notes/0.1.6/content.md +7 -0
  159. package/docs/resources/release-notes/0.2.0/content.md +31 -0
  160. package/docs/resources/release-notes/0.2.1/content.md +17 -0
  161. package/docs/resources/release-notes/0.2.2/content.md +11 -0
  162. package/docs/resources/release-notes/0.2.3/content.md +15 -0
  163. package/docs/resources/release-notes/content.md +64 -0
  164. package/lib/commonjs/components/DateTimeList/index.js +11 -26
  165. package/lib/commonjs/components/DateTimeList/index.js.map +1 -1
  166. package/lib/commonjs/components/DateTimeList/styles.js +2 -12
  167. package/lib/commonjs/components/DateTimeList/styles.js.map +1 -1
  168. package/lib/commonjs/components/ListItem/index.js.map +1 -1
  169. package/lib/commonjs/components/ListItem/styles.js +2 -1
  170. package/lib/commonjs/components/ListItem/styles.js.map +1 -1
  171. package/lib/commonjs/components/Menu/index.js +11 -5
  172. package/lib/commonjs/components/Menu/index.js.map +1 -1
  173. package/lib/commonjs/components/ScrollShadow/gradient.js +29 -0
  174. package/lib/commonjs/components/ScrollShadow/gradient.js.map +1 -0
  175. package/lib/commonjs/components/ScrollShadow/index.js +171 -44
  176. package/lib/commonjs/components/ScrollShadow/index.js.map +1 -1
  177. package/lib/commonjs/components/ScrollShadow/styles.js +58 -14
  178. package/lib/commonjs/components/ScrollShadow/styles.js.map +1 -1
  179. package/lib/commonjs/components/ScrollShadow/useComposedScrollHandler.js +63 -0
  180. package/lib/commonjs/components/ScrollShadow/useComposedScrollHandler.js.map +1 -0
  181. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js +48 -8
  182. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js.map +1 -1
  183. package/lib/commonjs/components/Tabs/index.js +16 -23
  184. package/lib/commonjs/components/Tabs/index.js.map +1 -1
  185. package/lib/commonjs/components/Tabs/styles.js +1 -10
  186. package/lib/commonjs/components/Tabs/styles.js.map +1 -1
  187. package/lib/commonjs/figma/ListItem.SectionHeader.figma.js +20 -5
  188. package/lib/commonjs/figma/ListItem.SectionHeader.figma.js.map +1 -1
  189. package/lib/module/components/DateTimeList/index.js +12 -27
  190. package/lib/module/components/DateTimeList/index.js.map +1 -1
  191. package/lib/module/components/DateTimeList/styles.js +1 -11
  192. package/lib/module/components/DateTimeList/styles.js.map +1 -1
  193. package/lib/module/components/ListItem/index.js.map +1 -1
  194. package/lib/module/components/ListItem/styles.js +2 -1
  195. package/lib/module/components/ListItem/styles.js.map +1 -1
  196. package/lib/module/components/Menu/index.js +11 -5
  197. package/lib/module/components/Menu/index.js.map +1 -1
  198. package/lib/module/components/ScrollShadow/gradient.js +24 -0
  199. package/lib/module/components/ScrollShadow/gradient.js.map +1 -0
  200. package/lib/module/components/ScrollShadow/index.js +170 -48
  201. package/lib/module/components/ScrollShadow/index.js.map +1 -1
  202. package/lib/module/components/ScrollShadow/styles.js +57 -13
  203. package/lib/module/components/ScrollShadow/styles.js.map +1 -1
  204. package/lib/module/components/ScrollShadow/useComposedScrollHandler.js +57 -0
  205. package/lib/module/components/ScrollShadow/useComposedScrollHandler.js.map +1 -0
  206. package/lib/module/components/ScrollShadow/useScrollShadow.js +46 -8
  207. package/lib/module/components/ScrollShadow/useScrollShadow.js.map +1 -1
  208. package/lib/module/components/Tabs/index.js +17 -24
  209. package/lib/module/components/Tabs/index.js.map +1 -1
  210. package/lib/module/components/Tabs/styles.js +0 -9
  211. package/lib/module/components/Tabs/styles.js.map +1 -1
  212. package/lib/module/figma/ListItem.SectionHeader.figma.js +20 -5
  213. package/lib/module/figma/ListItem.SectionHeader.figma.js.map +1 -1
  214. package/lib/typescript/components/DateTimeList/index.d.ts.map +1 -1
  215. package/lib/typescript/components/DateTimeList/styles.d.ts +1 -4
  216. package/lib/typescript/components/DateTimeList/styles.d.ts.map +1 -1
  217. package/lib/typescript/components/ListItem/index.d.ts +5 -4
  218. package/lib/typescript/components/ListItem/index.d.ts.map +1 -1
  219. package/lib/typescript/components/ListItem/styles.d.ts +1 -1
  220. package/lib/typescript/components/ListItem/styles.d.ts.map +1 -1
  221. package/lib/typescript/components/Menu/index.d.ts.map +1 -1
  222. package/lib/typescript/components/ScrollShadow/gradient.d.ts +7 -0
  223. package/lib/typescript/components/ScrollShadow/gradient.d.ts.map +1 -0
  224. package/lib/typescript/components/ScrollShadow/index.d.ts +57 -15
  225. package/lib/typescript/components/ScrollShadow/index.d.ts.map +1 -1
  226. package/lib/typescript/components/ScrollShadow/styles.d.ts +8 -2
  227. package/lib/typescript/components/ScrollShadow/styles.d.ts.map +1 -1
  228. package/lib/typescript/components/ScrollShadow/useComposedScrollHandler.d.ts +13 -0
  229. package/lib/typescript/components/ScrollShadow/useComposedScrollHandler.d.ts.map +1 -0
  230. package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts +15 -2
  231. package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts.map +1 -1
  232. package/lib/typescript/components/Tabs/index.d.ts.map +1 -1
  233. package/lib/typescript/components/Tabs/styles.d.ts +0 -7
  234. package/lib/typescript/components/Tabs/styles.d.ts.map +1 -1
  235. package/lib/typescript/figma/ListItem.SectionHeader.figma.d.ts.map +1 -1
  236. package/package.json +8 -6
  237. package/src/components/DateTimeList/index.tsx +13 -26
  238. package/src/components/DateTimeList/styles.ts +1 -10
  239. package/src/components/ListItem/index.tsx +5 -4
  240. package/src/components/ListItem/styles.ts +1 -0
  241. package/src/components/Menu/index.tsx +11 -8
  242. package/src/components/ScrollShadow/gradient.ts +48 -0
  243. package/src/components/ScrollShadow/index.tsx +300 -69
  244. package/src/components/ScrollShadow/styles.ts +45 -28
  245. package/src/components/ScrollShadow/useComposedScrollHandler.ts +58 -0
  246. package/src/components/ScrollShadow/useScrollShadow.ts +72 -10
  247. package/src/components/Tabs/index.tsx +25 -35
  248. package/src/components/Tabs/styles.ts +0 -9
@@ -0,0 +1,703 @@
1
+ # Tile (https://design.candescent.com/docs/components/tile)
2
+
3
+ Resources: [Figma](https://www.figma.com/design/0lEVMVlLj8ZS4fgItr3pmd/%E2%AD%90-Forge-DS--Components?node-id=8843-7101), [Storybook](https://victorious-wave-0daadf00f.6.azurestaticapps.net/?path=/story/surfaces-tile--default)
4
+
5
+ A compound component with sub-components for leading content, title, description, selection indicator, trailing content, and grouping. Selection state lives in `Tile.Group`; individual tiles are pressable.
6
+
7
+ ```tsx
8
+ import { Tile } from '@cdx-ui/components';
9
+ ```
10
+
11
+ ## Shapes
12
+
13
+ `shape` sets how tiles look and how they sit together. Set it on `Tile.Group` so every child inherits it, or override per `Tile`.
14
+
15
+ ```tsx
16
+ import { useState } from 'react';
17
+ import { Tile, View } from '@cdx-ui/components';
18
+
19
+ export default function TileShapesDemo() {
20
+ const [card, setCard] = useState('checking');
21
+ const [flat, setFlat] = useState('checking');
22
+
23
+ return (
24
+ <View className="w-full max-w-md gap-6">
25
+ <Tile.Group
26
+ type="single"
27
+ shape="card"
28
+ value={card}
29
+ onValueChange={setCard}
30
+ aria-label="Card shape"
31
+ >
32
+ <Tile value="checking">
33
+ <Tile.Content>
34
+ <Tile.Title>Card</Tile.Title>
35
+ <Tile.Description>Bordered and rounded, with gaps between tiles.</Tile.Description>
36
+ </Tile.Content>
37
+ <Tile.Indicator />
38
+ </Tile>
39
+ <Tile value="savings">
40
+ <Tile.Content>
41
+ <Tile.Title>Card</Tile.Title>
42
+ <Tile.Description>Each tile stands alone.</Tile.Description>
43
+ </Tile.Content>
44
+ <Tile.Indicator />
45
+ </Tile>
46
+ </Tile.Group>
47
+ <Tile.Group
48
+ type="single"
49
+ shape="flat"
50
+ spacing="none"
51
+ value={flat}
52
+ onValueChange={setFlat}
53
+ aria-label="Flat shape"
54
+ >
55
+ <Tile value="checking" showSeparator>
56
+ <Tile.Content>
57
+ <Tile.Title>Flat</Tile.Title>
58
+ <Tile.Description>Borderless rows that touch, split by separators.</Tile.Description>
59
+ </Tile.Content>
60
+ <Tile.Indicator />
61
+ </Tile>
62
+ <Tile value="savings">
63
+ <Tile.Content>
64
+ <Tile.Title>Flat</Tile.Title>
65
+ <Tile.Description>Suited to lists and cards.</Tile.Description>
66
+ </Tile.Content>
67
+ <Tile.Indicator />
68
+ </Tile>
69
+ </Tile.Group>
70
+ </View>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ### Card
76
+
77
+ The default. Each tile is a bordered, rounded surface with gaps between tiles. Use `type="single"` for radio semantics — one value at a time.
78
+
79
+ ```tsx
80
+ import { useState } from 'react';
81
+ import { Avatar, Tile } from '@cdx-ui/components';
82
+ import { Person } from '@cdx-ui/icons';
83
+
84
+ export default function TileCardDemo() {
85
+ const [selected, setSelected] = useState('checking');
86
+
87
+ return (
88
+ <Tile.Group
89
+ type="single"
90
+ shape="card"
91
+ value={selected}
92
+ onValueChange={setSelected}
93
+ aria-label="Choose an account"
94
+ className="w-full max-w-md"
95
+ >
96
+ <Tile value="checking">
97
+ <Tile.LeadingSlot>
98
+ <Avatar size="md">
99
+ <Avatar.Icon as={Person} />
100
+ </Avatar>
101
+ </Tile.LeadingSlot>
102
+ <Tile.Content>
103
+ <Tile.Title>Joint Checking · 6643</Tile.Title>
104
+ <Tile.Description>$11,308.34 available</Tile.Description>
105
+ </Tile.Content>
106
+ <Tile.Indicator />
107
+ </Tile>
108
+ <Tile value="savings">
109
+ <Tile.LeadingSlot>
110
+ <Avatar size="md">
111
+ <Avatar.Icon as={Person} />
112
+ </Avatar>
113
+ </Tile.LeadingSlot>
114
+ <Tile.Content>
115
+ <Tile.Title>High Yield Savings · 7891</Tile.Title>
116
+ <Tile.Description>$25,430.00 available</Tile.Description>
117
+ </Tile.Content>
118
+ <Tile.Indicator />
119
+ </Tile>
120
+ </Tile.Group>
121
+ );
122
+ }
123
+ ```
124
+
125
+ ### Flat
126
+
127
+ Borderless rows with no radius. Pair with `spacing="none"` and `showSeparator` for tight lists, and see [In card](#in-card) and [List](#list) for common placements.
128
+
129
+ ```tsx
130
+ import { useState } from 'react';
131
+ import { Tile } from '@cdx-ui/components';
132
+
133
+ export default function TileFlatDemo() {
134
+ const [selected, setSelected] = useState('checking');
135
+
136
+ return (
137
+ <Tile.Group
138
+ type="single"
139
+ shape="flat"
140
+ spacing="none"
141
+ value={selected}
142
+ onValueChange={setSelected}
143
+ aria-label="Choose an account"
144
+ className="w-full max-w-md"
145
+ >
146
+ <Tile value="checking" showSeparator>
147
+ <Tile.Content>
148
+ <Tile.Title>Joint Checking · 6643</Tile.Title>
149
+ <Tile.Description>$11,308.34 available</Tile.Description>
150
+ </Tile.Content>
151
+ <Tile.Indicator />
152
+ </Tile>
153
+ <Tile value="savings">
154
+ <Tile.Content>
155
+ <Tile.Title>High Yield Savings · 7891</Tile.Title>
156
+ <Tile.Description>$25,430.00 available</Tile.Description>
157
+ </Tile.Content>
158
+ <Tile.Indicator />
159
+ </Tile>
160
+ </Tile.Group>
161
+ );
162
+ }
163
+ ```
164
+
165
+ ## Without indicator
166
+
167
+ Omit `Tile.Indicator` when the selected tile styling provides enough visual affordance for the interaction.
168
+
169
+ ```tsx
170
+ import { useState } from 'react';
171
+ import { Tile } from '@cdx-ui/components';
172
+
173
+ export default function TileWithoutIndicatorDemo() {
174
+ const [selected, setSelected] = useState('standard');
175
+
176
+ return (
177
+ <Tile.Group
178
+ type="single"
179
+ shape="card"
180
+ value={selected}
181
+ onValueChange={setSelected}
182
+ aria-label="Choose a delivery speed"
183
+ className="w-full max-w-md"
184
+ >
185
+ <Tile value="standard">
186
+ <Tile.Content>
187
+ <Tile.Title>Standard delivery</Tile.Title>
188
+ <Tile.Description>Arrives in 3–5 business days</Tile.Description>
189
+ </Tile.Content>
190
+ </Tile>
191
+ <Tile value="express">
192
+ <Tile.Content>
193
+ <Tile.Title>Express delivery</Tile.Title>
194
+ <Tile.Description>Arrives the next business day</Tile.Description>
195
+ </Tile.Content>
196
+ </Tile>
197
+ </Tile.Group>
198
+ );
199
+ }
200
+ ```
201
+
202
+ ## Multiple
203
+
204
+ Use `type="multiple"` for checkbox semantics. Optional `max` caps how many tiles can be selected; unselected tiles disable when the cap is reached.
205
+
206
+ ```tsx
207
+ import { useState } from 'react';
208
+ import { Tile } from '@cdx-ui/components';
209
+
210
+ const CATEGORY_OPTIONS = [
211
+ ['dining', 'Dining out'],
212
+ ['groceries', 'Groceries'],
213
+ ['transport', 'Transportation'],
214
+ ['travel', 'Travel'],
215
+ ] as const;
216
+
217
+ export default function TileMultipleDemo() {
218
+ const [categories, setCategories] = useState<string[]>(['dining']);
219
+
220
+ return (
221
+ <Tile.Group
222
+ type="multiple"
223
+ shape="card"
224
+ max={3}
225
+ value={categories}
226
+ onValueChange={setCategories}
227
+ aria-label="Spending categories"
228
+ className="w-full max-w-md"
229
+ >
230
+ {CATEGORY_OPTIONS.map(([id, label]) => (
231
+ <Tile key={id} value={id}>
232
+ <Tile.Content>
233
+ <Tile.Title>{label}</Tile.Title>
234
+ </Tile.Content>
235
+ <Tile.Indicator />
236
+ </Tile>
237
+ ))}
238
+ </Tile.Group>
239
+ );
240
+ }
241
+ ```
242
+
243
+ ## Horizontal
244
+
245
+ Set `direction="row"` for side-by-side segment-style choosers. Give each tile `className="flex-1"` so widths split evenly.
246
+
247
+ ```tsx
248
+ import { useState } from 'react';
249
+ import { Tile } from '@cdx-ui/components';
250
+
251
+ export default function TileHorizontalDemo() {
252
+ const [selected, setSelected] = useState('expense');
253
+
254
+ return (
255
+ <Tile.Group
256
+ type="single"
257
+ direction="row"
258
+ shape="card"
259
+ value={selected}
260
+ onValueChange={setSelected}
261
+ aria-label="Transaction type"
262
+ className="w-full max-w-md"
263
+ >
264
+ <Tile value="expense" className="flex-1">
265
+ <Tile.Content>
266
+ <Tile.Title>Expense</Tile.Title>
267
+ </Tile.Content>
268
+ <Tile.Indicator />
269
+ </Tile>
270
+ <Tile value="income" className="flex-1">
271
+ <Tile.Content>
272
+ <Tile.Title>Income</Tile.Title>
273
+ </Tile.Content>
274
+ <Tile.Indicator />
275
+ </Tile>
276
+ </Tile.Group>
277
+ );
278
+ }
279
+ ```
280
+
281
+ ## In card
282
+
283
+ Use the [flat shape](#flat) for tight rows inside a `Card`. Use `Card.Content fullBleed` so tiles stretch edge-to-edge.
284
+
285
+ ```tsx
286
+ import { useState } from 'react';
287
+ import { Card, Tile } from '@cdx-ui/components';
288
+
289
+ export default function TileInCardDemo() {
290
+ const [selected, setSelected] = useState<string[]>([]);
291
+
292
+ return (
293
+ <Card className="w-full max-w-md">
294
+ <Card.Content fullBleed>
295
+ <Tile.Group
296
+ type="multiple"
297
+ spacing="none"
298
+ shape="flat"
299
+ value={selected}
300
+ onValueChange={setSelected}
301
+ aria-label="Statement preferences"
302
+ >
303
+ <Tile value="transactions" showSeparator>
304
+ <Tile.Content>
305
+ <Tile.Title>Transactions</Tile.Title>
306
+ </Tile.Content>
307
+ <Tile.Indicator />
308
+ </Tile>
309
+ <Tile value="running" showSeparator>
310
+ <Tile.Content>
311
+ <Tile.Title>Running balance</Tile.Title>
312
+ </Tile.Content>
313
+ <Tile.Indicator />
314
+ </Tile>
315
+ <Tile value="daily">
316
+ <Tile.Content>
317
+ <Tile.Title>Daily balances</Tile.Title>
318
+ </Tile.Content>
319
+ <Tile.Indicator />
320
+ </Tile>
321
+ </Tile.Group>
322
+ </Card.Content>
323
+ </Card>
324
+ );
325
+ }
326
+ ```
327
+
328
+ ## List
329
+
330
+ Use the same [flat](#flat) rows without a `Card` for a plain list. Omit `showSeparator` on the last row.
331
+
332
+ ```tsx
333
+ import { useState } from 'react';
334
+ import { Text, Tile } from '@cdx-ui/components';
335
+
336
+ const PAYMENT_OPTIONS = [
337
+ { value: 'regular', title: 'Regular payment', description: 'Make your scheduled loan payment.' },
338
+ {
339
+ value: 'principal',
340
+ title: 'Principal only',
341
+ description: 'Apply your payment directly to the loan principal.',
342
+ },
343
+ { value: 'interest', title: 'Interest only', description: 'Pay only the accrued interest.' },
344
+ {
345
+ value: 'escrow',
346
+ title: 'Escrow only',
347
+ description: 'Pay only the escrow portion of your loan.',
348
+ },
349
+ {
350
+ value: 'fees',
351
+ title: 'Fees only',
352
+ description: 'Pay outstanding fees only.',
353
+ amount: '$109.30',
354
+ },
355
+ {
356
+ value: 'payoff',
357
+ title: 'Pay off balance',
358
+ description: 'Pay the loan in full and keep the account open.',
359
+ },
360
+ ];
361
+
362
+ export default function TileListDemo() {
363
+ const [selected, setSelected] = useState<string[]>(['fees']);
364
+
365
+ return (
366
+ <Tile.Group
367
+ type="multiple"
368
+ spacing="none"
369
+ shape="flat"
370
+ value={selected}
371
+ onValueChange={setSelected}
372
+ aria-label="Payment type"
373
+ className="w-full max-w-md"
374
+ >
375
+ {PAYMENT_OPTIONS.map((option, index) => (
376
+ <Tile
377
+ key={option.value}
378
+ value={option.value}
379
+ showSeparator={index < PAYMENT_OPTIONS.length - 1}
380
+ >
381
+ <Tile.Content>
382
+ <Tile.Title>{option.title}</Tile.Title>
383
+ <Tile.Description>{option.description}</Tile.Description>
384
+ </Tile.Content>
385
+ {option.amount ? (
386
+ <Tile.TrailingSlot>
387
+ <Text>{option.amount}</Text>
388
+ </Tile.TrailingSlot>
389
+ ) : null}
390
+ <Tile.Indicator />
391
+ </Tile>
392
+ ))}
393
+ </Tile.Group>
394
+ );
395
+ }
396
+ ```
397
+
398
+ ## Standalone
399
+
400
+ A `Tile` without a parent `Tile.Group` manages its own selection via `isSelected` / `defaultSelected` / `onSelectedChange`. Standalone tiles use checkbox semantics.
401
+
402
+ ```tsx
403
+ import { useState } from 'react';
404
+ import { Tile } from '@cdx-ui/components';
405
+
406
+ export default function TileStandaloneDemo() {
407
+ const [agreed, setAgreed] = useState(false);
408
+
409
+ return (
410
+ <Tile
411
+ value="terms"
412
+ shape="card"
413
+ isSelected={agreed}
414
+ onSelectedChange={setAgreed}
415
+ className="w-full max-w-md"
416
+ >
417
+ <Tile.Content>
418
+ <Tile.Title>I agree to the terms and conditions</Tile.Title>
419
+ <Tile.Description>By selecting this, you accept our privacy policy.</Tile.Description>
420
+ </Tile.Content>
421
+ <Tile.Indicator indicatorType="checkbox" />
422
+ </Tile>
423
+ );
424
+ }
425
+ ```
426
+
427
+ ## With trailing
428
+
429
+ `Tile.TrailingSlot` carries amounts, chips, or status text — separate from `Tile.Indicator`, which is reserved for the selection affordance.
430
+
431
+ ```tsx
432
+ import { useState } from 'react';
433
+ import { Avatar, Text, Tile } from '@cdx-ui/components';
434
+ import { Person } from '@cdx-ui/icons';
435
+
436
+ export default function TileWithTrailingDemo() {
437
+ const [selected, setSelected] = useState<string[]>(['checking']);
438
+
439
+ return (
440
+ <Tile.Group
441
+ type="multiple"
442
+ shape="card"
443
+ value={selected}
444
+ onValueChange={setSelected}
445
+ aria-label="Accounts"
446
+ className="w-full max-w-md"
447
+ >
448
+ <Tile value="checking">
449
+ <Tile.LeadingSlot>
450
+ <Avatar size="md">
451
+ <Avatar.Icon as={Person} />
452
+ </Avatar>
453
+ </Tile.LeadingSlot>
454
+ <Tile.Content>
455
+ <Tile.Title>Joint Checking · 6643</Tile.Title>
456
+ <Tile.Description>Available balance</Tile.Description>
457
+ </Tile.Content>
458
+ <Tile.TrailingSlot>
459
+ <Text>$11,308.34</Text>
460
+ </Tile.TrailingSlot>
461
+ <Tile.Indicator />
462
+ </Tile>
463
+ <Tile value="savings">
464
+ <Tile.LeadingSlot>
465
+ <Avatar size="md">
466
+ <Avatar.Icon as={Person} />
467
+ </Avatar>
468
+ </Tile.LeadingSlot>
469
+ <Tile.Content>
470
+ <Tile.Title>High Yield Savings · 7891</Tile.Title>
471
+ <Tile.Description>Available balance</Tile.Description>
472
+ </Tile.Content>
473
+ <Tile.TrailingSlot>
474
+ <Text>$25,430.00</Text>
475
+ </Tile.TrailingSlot>
476
+ <Tile.Indicator />
477
+ </Tile>
478
+ </Tile.Group>
479
+ );
480
+ }
481
+ ```
482
+
483
+ ## Disabled
484
+
485
+ Set `disabled` on a single tile, or `isDisabled` on `Tile.Group` to cascade across the whole group.
486
+
487
+ ```tsx
488
+ import { Avatar, Tile } from '@cdx-ui/components';
489
+ import { Person } from '@cdx-ui/icons';
490
+
491
+ export default function TileDisabledDemo() {
492
+ return (
493
+ <Tile.Group
494
+ type="multiple"
495
+ shape="card"
496
+ defaultValue={['open']}
497
+ aria-label="Accounts"
498
+ className="w-full max-w-md"
499
+ >
500
+ <Tile value="open">
501
+ <Tile.LeadingSlot>
502
+ <Avatar size="md">
503
+ <Avatar.Icon as={Person} />
504
+ </Avatar>
505
+ </Tile.LeadingSlot>
506
+ <Tile.Content>
507
+ <Tile.Title>Joint Checking · 6643</Tile.Title>
508
+ <Tile.Description>$11,308.34 available</Tile.Description>
509
+ </Tile.Content>
510
+ <Tile.Indicator />
511
+ </Tile>
512
+ <Tile value="closed" disabled>
513
+ <Tile.LeadingSlot>
514
+ <Avatar size="md">
515
+ <Avatar.Icon as={Person} />
516
+ </Avatar>
517
+ </Tile.LeadingSlot>
518
+ <Tile.Content>
519
+ <Tile.Title>Closed account</Tile.Title>
520
+ <Tile.Description>Press is no-op; opacity reduced</Tile.Description>
521
+ </Tile.Content>
522
+ <Tile.Indicator />
523
+ </Tile>
524
+ </Tile.Group>
525
+ );
526
+ }
527
+ ```
528
+
529
+ ## API
530
+
531
+ ### Tile
532
+
533
+ Pressable root that toggles selection on press. Requires a unique `value`. Inside a `Tile.Group`, selection is owned by the group; standalone usage supports `isSelected` / `defaultSelected` / `onSelectedChange`.
534
+
535
+ | Prop | Type | Default | Description |
536
+ | --- | --- | --- | --- |
537
+ | `className?` | `string` | — | — |
538
+ | `children?` | `ReactNode \| ((state: PressableStateCallbackType) => React.ReactNode)` | — | Either children or a render prop that receives a boolean reflecting whether the component is currently pressed. |
539
+ | `shape?` | `'card' \| 'flat'` | `'card'` | Visual shape. Overrides the shape inherited from `Tile.Group`. `card` is a bordered surface; `flat` is a borderless row. |
540
+ | `showSeparator?` | `boolean` | `false` | Renders a bottom border. Omit it on the last row of a tight list. |
541
+ | `asChild?` | `boolean` | — | Merge tile props onto a single child instead of rendering the default Pressable. |
542
+ | `value` | `string` | — | Identifies this tile within `Tile.Group`. Must be unique within the group. Standalone tiles still require a value (useful for form submission, analytics, etc.). |
543
+ | `isSelected?` | `boolean` | — | Controlled selected state for **standalone** usage (no parent `Tile.Group`). Ignored when the tile is rendered inside a `Tile.Group` — selection is owned by the group. |
544
+ | `defaultSelected?` | `boolean` | — | Initial selected state for **standalone**, uncontrolled usage. |
545
+ | `onSelectedChange?` | `((isSelected: boolean) => void)` | — | Called when standalone selection state changes. Group context overrides this — when inside a `Tile.Group`, use the group's `onValueChange` instead. |
546
+
547
+ Inherits all [`PressableProps`](https://reactnative.dev/docs/pressable#props) (including `onPress`).
548
+
549
+ | Attribute | Values | Description |
550
+ | --------------- | -------------------------- | --------------- |
551
+ | `data-slot` | `tile` | Root pressable |
552
+ | `data-state` | `selected` \| `unselected` | Selection state |
553
+ | `data-disabled` | `true` \| `false` | Disabled state |
554
+
555
+ ## Sub-components
556
+
557
+ ### Tile.Group
558
+
559
+ Selection state container and layout. `type="single"` uses radio semantics and does not clear the current value when that tile is pressed again; `type="multiple"` (optional `max`) uses checkbox semantics. `direction` and `spacing` constrain layout to the three supported patterns. `shape` cascades to child tiles.
560
+
561
+ | Prop | Type | Default | Description |
562
+ | --- | --- | --- | --- |
563
+ | `className?` | `string` | — | — |
564
+ | `children?` | `ReactNode` | — | — |
565
+ | `direction?` | `'column' \| 'row' \| null` | — | — |
566
+ | `spacing?` | `'default' \| 'none' \| null` | — | — |
567
+ | `shape?` | `'flat' \| 'card' \| null` | — | — |
568
+ | `type?` | `'single' \| 'multiple'` | — | — |
569
+ | `value?` | `string \| string[]` | — | — |
570
+ | `defaultValue?` | `string \| string[]` | — | — |
571
+ | `onValueChange?` | `((value: string) => void) \| ((value: string[]) => void)` | — | — |
572
+ | `asChild?` | `boolean` | — | — |
573
+ | `isDisabled?` | `boolean` | — | — |
574
+ | `max?` | `number` | — | — |
575
+
576
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props). `Tile.Group` is a discriminated union on `type`; `max` applies only when `type="multiple"`.
577
+
578
+ | `direction` | `spacing` | Use case |
579
+ | ------------------ | ------------------- | --------------------------------- |
580
+ | `column` (default) | `default` (default) | Vertical stack with gaps |
581
+ | `column` | `none` | Tight vertical stack (rows touch) |
582
+ | `row` | `default` | Side-by-side horizontal layout |
583
+
584
+ ### Tile.LeadingSlot
585
+
586
+ Optional leading adornment (Avatar, Icon). Decorative and `aria-hidden` by default — override for meaningful leading content.
587
+
588
+ | Prop | Type | Default | Description |
589
+ | --- | --- | --- | --- |
590
+ | `className?` | `string` | — | — |
591
+ | `children?` | `ReactNode` | — | — |
592
+ | `asChild?` | `boolean` | — | — |
593
+
594
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
595
+
596
+ ### Tile.Content
597
+
598
+ Flex column for the text stack (`Tile.Title` and `Tile.Description`).
599
+
600
+ | Prop | Type | Default | Description |
601
+ | --- | --- | --- | --- |
602
+ | `className?` | `string` | — | — |
603
+ | `children?` | `ReactNode` | — | — |
604
+ | `asChild?` | `boolean` | — | — |
605
+
606
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
607
+
608
+ ### Tile.Title
609
+
610
+ Primary label. Truncates to one line by default (`numberOfLines={1}`).
611
+
612
+ | Prop | Type | Default | Description |
613
+ | --- | --- | --- | --- |
614
+ | `className?` | `string` | — | — |
615
+ | `children?` | `ReactNode` | — | — |
616
+ | `asChild?` | `boolean` | — | — |
617
+
618
+ Inherits all [`TextProps`](https://reactnative.dev/docs/text#props).
619
+
620
+ ### Tile.Description
621
+
622
+ Secondary text below the title. Wraps freely by default.
623
+
624
+ | Prop | Type | Default | Description |
625
+ | --- | --- | --- | --- |
626
+ | `className?` | `string` | — | — |
627
+ | `children?` | `ReactNode` | — | — |
628
+ | `asChild?` | `boolean` | — | — |
629
+
630
+ Inherits all [`TextProps`](https://reactnative.dev/docs/text#props).
631
+
632
+ ### Tile.Indicator
633
+
634
+ Decorative selection affordance — radio for `type="single"`, checkbox for `type="multiple"`. Always `aria-hidden`; the tile root owns the press target and ARIA role. Use `indicatorType` to override (required for radio styling when standalone).
635
+
636
+ | Prop | Type | Default | Description |
637
+ | --- | --- | --- | --- |
638
+ | `className?` | `string` | — | — |
639
+ | `children?` | `ReactNode` | — | Replaces the default radio or checkbox visual when provided. |
640
+ | `asChild?` | `boolean` | — | — |
641
+ | `indicatorType?` | `'radio' \| 'checkbox'` | — | Explicit indicator visual. When inside a `Tile.Group`, auto-infers from `type` (`single` → radio, `multiple` → checkbox). For standalone tiles, defaults to `'checkbox'` to match the standalone `checkbox` ARIA role; override with `indicatorType="radio"` if a radio-style visual is needed. |
642
+
643
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
644
+
645
+ ### Tile.TrailingSlot
646
+
647
+ Optional trailing content (amount, chip, status text) — distinct from `Tile.Indicator`.
648
+
649
+ | Prop | Type | Default | Description |
650
+ | --- | --- | --- | --- |
651
+ | `className?` | `string` | — | — |
652
+ | `children?` | `ReactNode` | — | — |
653
+ | `asChild?` | `boolean` | — | — |
654
+
655
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
656
+
657
+ ## Anatomy
658
+
659
+ | `data-slot` | Element |
660
+ | ------------------ | -------------------- |
661
+ | `tile` | Root pressable |
662
+ | `tile-group` | Selection group |
663
+ | `tile-leading` | Leading adornment |
664
+ | `tile-content` | Text stack |
665
+ | `tile-title` | Primary label |
666
+ | `tile-description` | Secondary text |
667
+ | `tile-indicator` | Selection affordance |
668
+ | `tile-trailing` | Trailing content |
669
+
670
+ ## Hook
671
+
672
+ ### useTileContext
673
+
674
+ Reads this tile's selection state from a child, for example to swap a leading icon when the tile is selected. Call it inside a `Tile`; it throws otherwise.
675
+
676
+ ```tsx
677
+ const { isSelected } = useTileContext();
678
+ ```
679
+
680
+ | Return | Type | Description |
681
+ | --------------- | ------------------------ | -------------------------------------------------------------------------------------- |
682
+ | `value` | `string` | This tile's `value`. |
683
+ | `isSelected` | `boolean` | Whether the tile is selected. |
684
+ | `isDisabled` | `boolean` | Effective disabled state from the tile's `disabled` prop or group rules such as `max`. |
685
+ | `selectionType` | `'single' \| 'multiple'` | The group's `type`, or `'multiple'` when the tile is standalone. |
686
+
687
+ ## Accessibility
688
+
689
+ | Attribute | Behavior |
690
+ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
691
+ | **Role** | `Tile.Group` is `radiogroup` for `single`, `group` for `multiple`. Each `Tile` is `radio` or `checkbox` accordingly. Standalone tiles are `checkbox`. |
692
+ | **`aria-checked`** | Reflects per-tile selection state. |
693
+ | **`aria-disabled`** | Set on disabled tiles and on unselected tiles when `max` is reached. |
694
+ | **Keyboard** | Focusable via Tab; activated with Enter or Space. Focus-visible ring on web. |
695
+ | **`Tile.LeadingSlot`** | `aria-hidden` by default — override for meaningful leading content. |
696
+ | **`Tile.Indicator`** | Always `aria-hidden`. The tile root is the accessible interactive element. |
697
+
698
+ ## When to use
699
+
700
+ | Use `Tile` when… | Use `ListItem` when… |
701
+ | -------------------------------------------------------------- | -------------------------------------------------------------- |
702
+ | The user is choosing one or many items | The row displays information, navigates, or triggers an action |
703
+ | The row should be a `radio` or `checkbox` with selection state | The row should not carry built-in selection state |