@deriv-com/dds-marketing 1.0.2 → 1.1.1

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 (315) hide show
  1. package/.llm/components/awards.md +209 -0
  2. package/.llm/components/background.md +89 -40
  3. package/.llm/components/bottomsheet.md +5 -0
  4. package/.llm/components/comparisoncards.md +257 -0
  5. package/.llm/components/comparisontable.md +231 -0
  6. package/.llm/components/ctabanner.md +9 -12
  7. package/.llm/components/ctabannerslider.md +15 -2
  8. package/.llm/components/datatable.md +273 -0
  9. package/.llm/components/downloadbanner.md +283 -0
  10. package/.llm/components/featurecards.md +41 -3
  11. package/.llm/components/featurecardscarousel.md +311 -0
  12. package/.llm/components/featuresplit.md +5 -0
  13. package/.llm/components/featurestack.md +227 -0
  14. package/.llm/components/illustrationcard.md +2 -2
  15. package/.llm/components/illustrationscene.md +6 -10
  16. package/.llm/components/imagebanner.md +278 -0
  17. package/.llm/components/navbar.md +1 -1
  18. package/.llm/components/navbaritem.md +4 -0
  19. package/.llm/components/pageheader.md +4 -3
  20. package/.llm/components/paginationdots.md +6 -1
  21. package/.llm/components/producthero.md +35 -11
  22. package/.llm/components/resourcebanner.md +247 -0
  23. package/.llm/components/scenescroll.md +222 -0
  24. package/.llm/components/select.md +17 -0
  25. package/.llm/components/stats.md +219 -0
  26. package/.llm/components/steps.md +286 -0
  27. package/.llm/components/stickycards.md +251 -0
  28. package/.llm/components/tableheadercell.md +19 -0
  29. package/.llm/components/teamgrid.md +243 -0
  30. package/.llm/components/testimonialrow.md +5 -0
  31. package/.llm/components/testimonialslider.md +14 -1
  32. package/.llm/components/textarea.md +13 -0
  33. package/.llm/components/textinput.md +13 -0
  34. package/.llm/components/textscroll.md +187 -0
  35. package/.llm/components/tiers.md +227 -0
  36. package/.llm/digest/awards.md +45 -0
  37. package/.llm/digest/background.md +11 -10
  38. package/.llm/digest/bottomsheet.md +2 -0
  39. package/.llm/digest/comparisoncards.md +51 -0
  40. package/.llm/digest/comparisontable.md +53 -0
  41. package/.llm/digest/ctabanner.md +4 -4
  42. package/.llm/digest/ctabannerslider.md +4 -0
  43. package/.llm/digest/datatable.md +65 -0
  44. package/.llm/digest/downloadbanner.md +65 -0
  45. package/.llm/digest/featurecards.md +1 -1
  46. package/.llm/digest/featurecardscarousel.md +54 -0
  47. package/.llm/digest/featuresplit.md +1 -0
  48. package/.llm/digest/featurestack.md +48 -0
  49. package/.llm/digest/illustrationscene.md +2 -3
  50. package/.llm/digest/imagebanner.md +64 -0
  51. package/.llm/digest/navbaritem.md +1 -0
  52. package/.llm/digest/pageheader.md +2 -1
  53. package/.llm/digest/paginationdots.md +3 -1
  54. package/.llm/digest/producthero.md +4 -2
  55. package/.llm/digest/resourcebanner.md +53 -0
  56. package/.llm/digest/scenescroll.md +49 -0
  57. package/.llm/digest/select.md +5 -0
  58. package/.llm/digest/stats.md +46 -0
  59. package/.llm/digest/steps.md +55 -0
  60. package/.llm/digest/stickycards.md +52 -0
  61. package/.llm/digest/tableheadercell.md +4 -0
  62. package/.llm/digest/teamgrid.md +53 -0
  63. package/.llm/digest/testimonialrow.md +2 -0
  64. package/.llm/digest/testimonialslider.md +4 -0
  65. package/.llm/digest/textarea.md +4 -0
  66. package/.llm/digest/textinput.md +4 -0
  67. package/.llm/digest/textscroll.md +43 -0
  68. package/.llm/digest/tiers.md +50 -0
  69. package/.llm/index.md +19 -3
  70. package/.llm/tokens.md +26 -1
  71. package/AGENTS.md +17 -1
  72. package/dist/components/awards-data.d.ts +24 -0
  73. package/dist/components/awards-data.js +11 -0
  74. package/dist/components/block-image-data.d.ts +24 -0
  75. package/dist/components/block-image-data.js +9 -0
  76. package/dist/components/blocks/awards/Awards.css +134 -0
  77. package/dist/components/blocks/awards/Awards.d.ts +24 -0
  78. package/dist/components/blocks/awards/Awards.js +114 -0
  79. package/dist/components/blocks/awards/Awards.types.d.ts +31 -0
  80. package/dist/components/blocks/awards/Awards.types.js +16 -0
  81. package/dist/components/blocks/awards/awards-impl.css +222 -0
  82. package/dist/components/blocks/cards-of.d.ts +16 -0
  83. package/dist/components/blocks/cards-of.js +23 -0
  84. package/dist/components/blocks/comparison-cards/ComparisonCards.css +195 -0
  85. package/dist/components/blocks/comparison-cards/ComparisonCards.d.ts +24 -0
  86. package/dist/components/blocks/comparison-cards/ComparisonCards.js +75 -0
  87. package/dist/components/blocks/comparison-cards/ComparisonCards.types.d.ts +49 -0
  88. package/dist/components/blocks/comparison-cards/ComparisonCards.types.js +26 -0
  89. package/dist/components/blocks/comparison-cards/comparison-cards-impl.css +262 -0
  90. package/dist/components/blocks/comparison-table/ComparisonTable.css +111 -0
  91. package/dist/components/blocks/comparison-table/ComparisonTable.d.ts +25 -0
  92. package/dist/components/blocks/comparison-table/ComparisonTable.js +100 -0
  93. package/dist/components/blocks/comparison-table/ComparisonTable.types.d.ts +36 -0
  94. package/dist/components/blocks/comparison-table/ComparisonTable.types.js +15 -0
  95. package/dist/components/blocks/comparison-table/comparison-table-impl.css +143 -0
  96. package/dist/components/blocks/cta-banner/CtaBanner.css +1 -0
  97. package/dist/components/blocks/cta-banner/CtaBanner.js +2 -2
  98. package/dist/components/blocks/cta-banner/CtaBanner.types.d.ts +4 -3
  99. package/dist/components/blocks/cta-banner/CtaBanner.types.js +1 -1
  100. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.css +1 -0
  101. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.d.ts +1 -1
  102. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.js +6 -2
  103. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.d.ts +8 -0
  104. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.js +1 -1
  105. package/dist/components/blocks/data-table/DataTable.css +170 -0
  106. package/dist/components/blocks/data-table/DataTable.d.ts +29 -0
  107. package/dist/components/blocks/data-table/DataTable.js +84 -0
  108. package/dist/components/blocks/data-table/DataTable.types.d.ts +69 -0
  109. package/dist/components/blocks/data-table/DataTable.types.js +19 -0
  110. package/dist/components/blocks/data-table/data-table-impl.css +155 -0
  111. package/dist/components/blocks/download-banner/DownloadBanner.css +228 -0
  112. package/dist/components/blocks/download-banner/DownloadBanner.d.ts +28 -0
  113. package/dist/components/blocks/download-banner/DownloadBanner.js +75 -0
  114. package/dist/components/blocks/download-banner/DownloadBanner.types.d.ts +56 -0
  115. package/dist/components/blocks/download-banner/DownloadBanner.types.js +23 -0
  116. package/dist/components/blocks/download-banner/download-banner-impl.css +216 -0
  117. package/dist/components/blocks/faq-accordion/FaqAccordion.css +1 -0
  118. package/dist/components/blocks/faq-accordion/faq-accordion-impl.css +3 -7
  119. package/dist/components/blocks/feature-bullets/FeatureBullets.css +1 -0
  120. package/dist/components/blocks/feature-bullets/feature-bullets-impl.css +14 -1
  121. package/dist/components/blocks/feature-cards/FeatureCards.css +1 -0
  122. package/dist/components/blocks/feature-cards/FeatureCards.d.ts +2 -1
  123. package/dist/components/blocks/feature-cards/FeatureCards.js +4 -21
  124. package/dist/components/blocks/feature-cards/feature-cards-impl.css +10 -9
  125. package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.css +115 -0
  126. package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.d.ts +30 -0
  127. package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.js +160 -0
  128. package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.types.d.ts +52 -0
  129. package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.types.js +17 -0
  130. package/dist/components/blocks/feature-cards-carousel/feature-cards-carousel-impl.css +207 -0
  131. package/dist/components/blocks/feature-scroll/FeatureScroll.css +1 -0
  132. package/dist/components/blocks/feature-split/FeatureSplit.css +1 -0
  133. package/dist/components/blocks/feature-split/feature-split-impl.css +14 -1
  134. package/dist/components/blocks/feature-stack/FeatureStack.css +86 -0
  135. package/dist/components/blocks/feature-stack/FeatureStack.d.ts +19 -0
  136. package/dist/components/blocks/feature-stack/FeatureStack.js +27 -0
  137. package/dist/components/blocks/feature-stack/FeatureStack.types.d.ts +34 -0
  138. package/dist/components/blocks/feature-stack/FeatureStack.types.js +14 -0
  139. package/dist/components/blocks/feature-stack/feature-stack-impl.css +87 -0
  140. package/dist/components/blocks/image-banner/ImageBanner.css +133 -0
  141. package/dist/components/blocks/image-banner/ImageBanner.d.ts +32 -0
  142. package/dist/components/blocks/image-banner/ImageBanner.js +41 -0
  143. package/dist/components/blocks/image-banner/ImageBanner.types.d.ts +54 -0
  144. package/dist/components/blocks/image-banner/ImageBanner.types.js +14 -0
  145. package/dist/components/blocks/image-banner/image-banner-impl.css +281 -0
  146. package/dist/components/blocks/landing-hero/LandingHero.css +1 -0
  147. package/dist/components/blocks/logo-columns/LogoColumns.css +1 -0
  148. package/dist/components/blocks/logo-rows/LogoRows.css +1 -0
  149. package/dist/components/blocks/page-header/PageHeader.css +1 -0
  150. package/dist/components/blocks/page-header/PageHeader.js +2 -2
  151. package/dist/components/blocks/page-header/PageHeader.types.d.ts +4 -2
  152. package/dist/components/blocks/page-header/PageHeader.types.js +1 -1
  153. package/dist/components/blocks/page-header/page-header-impl.css +5 -3
  154. package/dist/components/blocks/product-hero/ProductHero.css +8 -3
  155. package/dist/components/blocks/product-hero/ProductHero.d.ts +1 -1
  156. package/dist/components/blocks/product-hero/ProductHero.js +5 -2
  157. package/dist/components/blocks/product-hero/ProductHero.types.d.ts +5 -1
  158. package/dist/components/blocks/product-hero/ProductHero.types.js +1 -0
  159. package/dist/components/blocks/product-hero/product-hero-impl.css +43 -12
  160. package/dist/components/blocks/resource-banner/ResourceBanner.css +106 -0
  161. package/dist/components/blocks/resource-banner/ResourceBanner.d.ts +32 -0
  162. package/dist/components/blocks/resource-banner/ResourceBanner.js +51 -0
  163. package/dist/components/blocks/resource-banner/ResourceBanner.types.d.ts +44 -0
  164. package/dist/components/blocks/resource-banner/ResourceBanner.types.js +14 -0
  165. package/dist/components/blocks/resource-banner/resource-banner-impl.css +69 -0
  166. package/dist/components/blocks/scene-scroll/SceneScroll.css +93 -0
  167. package/dist/components/blocks/scene-scroll/SceneScroll.d.ts +36 -0
  168. package/dist/components/blocks/scene-scroll/SceneScroll.js +54 -0
  169. package/dist/components/blocks/scene-scroll/SceneScroll.types.d.ts +32 -0
  170. package/dist/components/blocks/scene-scroll/SceneScroll.types.js +15 -0
  171. package/dist/components/blocks/scene-scroll/scene-scroll-impl.css +275 -0
  172. package/dist/components/blocks/stats/Stats.css +139 -0
  173. package/dist/components/blocks/stats/Stats.d.ts +24 -0
  174. package/dist/components/blocks/stats/Stats.js +120 -0
  175. package/dist/components/blocks/stats/Stats.types.d.ts +36 -0
  176. package/dist/components/blocks/stats/Stats.types.js +19 -0
  177. package/dist/components/blocks/stats/stats-count.d.ts +34 -0
  178. package/dist/components/blocks/stats/stats-count.js +61 -0
  179. package/dist/components/blocks/stats/stats-impl.css +284 -0
  180. package/dist/components/blocks/stats/use-wheel.d.ts +37 -0
  181. package/dist/components/blocks/stats/use-wheel.js +126 -0
  182. package/dist/components/blocks/steps/Steps.css +202 -0
  183. package/dist/components/blocks/steps/Steps.d.ts +32 -0
  184. package/dist/components/blocks/steps/Steps.js +65 -0
  185. package/dist/components/blocks/steps/Steps.types.d.ts +48 -0
  186. package/dist/components/blocks/steps/Steps.types.js +21 -0
  187. package/dist/components/blocks/steps/steps-impl.css +216 -0
  188. package/dist/components/blocks/steps/use-turn.d.ts +33 -0
  189. package/dist/components/blocks/steps/use-turn.js +75 -0
  190. package/dist/components/blocks/sticky-cards/StickyCards.css +154 -0
  191. package/dist/components/blocks/sticky-cards/StickyCards.d.ts +35 -0
  192. package/dist/components/blocks/sticky-cards/StickyCards.js +153 -0
  193. package/dist/components/blocks/sticky-cards/StickyCards.types.d.ts +43 -0
  194. package/dist/components/blocks/sticky-cards/StickyCards.types.js +18 -0
  195. package/dist/components/blocks/sticky-cards/sticky-cards-impl.css +393 -0
  196. package/dist/components/blocks/team-grid/TeamGrid.css +159 -0
  197. package/dist/components/blocks/team-grid/TeamGrid.d.ts +28 -0
  198. package/dist/components/blocks/team-grid/TeamGrid.js +74 -0
  199. package/dist/components/blocks/team-grid/TeamGrid.types.d.ts +51 -0
  200. package/dist/components/blocks/team-grid/TeamGrid.types.js +20 -0
  201. package/dist/components/blocks/team-grid/team-grid-impl.css +129 -0
  202. package/dist/components/blocks/testimonial-row/TestimonialRow.css +1 -0
  203. package/dist/components/blocks/testimonial-row/TestimonialRow.d.ts +1 -1
  204. package/dist/components/blocks/testimonial-row/TestimonialRow.js +4 -3
  205. package/dist/components/blocks/testimonial-row/TestimonialRow.types.d.ts +3 -0
  206. package/dist/components/blocks/testimonial-row/TestimonialRow.types.js +1 -1
  207. package/dist/components/blocks/testimonial-row/testimonial-row-impl.css +8 -0
  208. package/dist/components/blocks/testimonial-slider/TestimonialSlider.css +1 -0
  209. package/dist/components/blocks/testimonial-slider/TestimonialSlider.d.ts +1 -1
  210. package/dist/components/blocks/testimonial-slider/TestimonialSlider.js +2 -2
  211. package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.d.ts +8 -0
  212. package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.js +1 -1
  213. package/dist/components/blocks/text-scroll/TextScroll.css +53 -0
  214. package/dist/components/blocks/text-scroll/TextScroll.d.ts +5 -0
  215. package/dist/components/blocks/text-scroll/TextScroll.js +71 -0
  216. package/dist/components/blocks/text-scroll/TextScroll.types.d.ts +22 -0
  217. package/dist/components/blocks/text-scroll/TextScroll.types.js +10 -0
  218. package/dist/components/blocks/text-scroll/text-scroll-impl.css +115 -0
  219. package/dist/components/blocks/tiers/Tiers.css +233 -0
  220. package/dist/components/blocks/tiers/Tiers.d.ts +32 -0
  221. package/dist/components/blocks/tiers/Tiers.js +68 -0
  222. package/dist/components/blocks/tiers/Tiers.types.d.ts +45 -0
  223. package/dist/components/blocks/tiers/Tiers.types.js +25 -0
  224. package/dist/components/blocks/tiers/tiers-impl.css +185 -0
  225. package/dist/components/blocks/use-rotation.d.ts +12 -2
  226. package/dist/components/blocks/use-rotation.js +6 -28
  227. package/dist/components/comparison-cards-data.d.ts +59 -0
  228. package/dist/components/comparison-cards-data.js +12 -0
  229. package/dist/components/comparison-table-data.d.ts +39 -0
  230. package/dist/components/comparison-table-data.js +11 -0
  231. package/dist/components/core/accordion-item/AccordionItem.css +1 -5
  232. package/dist/components/core/background/Background.css +23 -0
  233. package/dist/components/core/background/Background.d.ts +10 -4
  234. package/dist/components/core/background/Background.js +16 -7
  235. package/dist/components/core/background/Background.types.d.ts +11 -8
  236. package/dist/components/core/background/Background.types.js +3 -1
  237. package/dist/components/core/background/background-impl.css +334 -1
  238. package/dist/components/core/background/halftone.d.ts +11 -0
  239. package/dist/components/core/background/halftone.js +43 -3
  240. package/dist/components/core/background/patterns.d.ts +66 -0
  241. package/dist/components/core/background/patterns.js +244 -0
  242. package/dist/components/core/background/shader.d.ts +44 -0
  243. package/dist/components/core/background/shader.js +307 -0
  244. package/dist/components/core/background/use-shader.d.ts +11 -0
  245. package/dist/components/core/background/use-shader.js +25 -0
  246. package/dist/components/core/background/use-spotlight.d.ts +2 -1
  247. package/dist/components/core/background/use-spotlight.js +9 -1
  248. package/dist/components/core/bottom-sheet/BottomSheet.js +2 -2
  249. package/dist/components/core/bottom-sheet/BottomSheet.types.d.ts +3 -1
  250. package/dist/components/core/breadcrumb/Breadcrumb.d.ts +1 -0
  251. package/dist/components/core/breadcrumb/Breadcrumb.js +1 -0
  252. package/dist/components/core/breadcrumb/breadcrumb-impl.css +11 -0
  253. package/dist/components/core/button/button-impl.css +14 -1
  254. package/dist/components/core/illustration-card/IllustrationCard.js +1 -1
  255. package/dist/components/core/illustration-card/illustration-card-impl.css +20 -0
  256. package/dist/components/core/illustration-scene/IllustrationScene.css +4 -7
  257. package/dist/components/core/illustration-scene/IllustrationScene.types.d.ts +2 -3
  258. package/dist/components/core/illustration-scene/illustration-scene-impl.css +22 -1
  259. package/dist/components/core/link/Link.js +7 -3
  260. package/dist/components/core/link/link-impl.css +29 -2
  261. package/dist/components/core/navbar/Navbar.css +2 -2
  262. package/dist/components/core/navbar/Navbar.types.d.ts +1 -1
  263. package/dist/components/core/navbar-item/NavbarItem.d.ts +2 -1
  264. package/dist/components/core/navbar-item/NavbarItem.js +3 -10
  265. package/dist/components/core/navbar-item/NavbarItem.types.d.ts +4 -1
  266. package/dist/components/core/navbar-item/NavbarItem.types.js +1 -1
  267. package/dist/components/core/navbar-item/navbar-item-impl.css +10 -0
  268. package/dist/components/core/navbar-menu/navbar-menu-impl.css +9 -0
  269. package/dist/components/core/navbar-switcher/navbar-switcher-impl.css +19 -0
  270. package/dist/components/core/pagination/Pagination.js +1 -1
  271. package/dist/components/core/pagination/pagination-impl.css +9 -0
  272. package/dist/components/core/pagination-dots/PaginationDots.d.ts +1 -1
  273. package/dist/components/core/pagination-dots/PaginationDots.js +3 -3
  274. package/dist/components/core/pagination-dots/PaginationDots.types.d.ts +3 -1
  275. package/dist/components/core/segmented-control/SegmentedControl.css +4 -3
  276. package/dist/components/core/segmented-control/SegmentedControl.js +17 -3
  277. package/dist/components/core/segmented-control-item/SegmentedControlItem.css +2 -1
  278. package/dist/components/core/select/Select.js +3 -3
  279. package/dist/components/core/select/Select.types.d.ts +11 -1
  280. package/dist/components/core/select/Select.types.js +1 -1
  281. package/dist/components/core/tab-list/TabList.js +9 -1
  282. package/dist/components/core/table-header-cell/TableHeaderCell.d.ts +1 -1
  283. package/dist/components/core/table-header-cell/TableHeaderCell.js +5 -2
  284. package/dist/components/core/table-header-cell/TableHeaderCell.types.d.ts +7 -1
  285. package/dist/components/core/table-header-cell/TableHeaderCell.types.js +1 -1
  286. package/dist/components/core/text-area/TextArea.d.ts +1 -1
  287. package/dist/components/core/text-area/TextArea.js +2 -2
  288. package/dist/components/core/text-area/TextArea.types.d.ts +9 -1
  289. package/dist/components/core/text-area/TextArea.types.js +1 -1
  290. package/dist/components/core/text-input/TextInput.d.ts +1 -1
  291. package/dist/components/core/text-input/TextInput.js +2 -2
  292. package/dist/components/core/text-input/TextInput.types.d.ts +9 -1
  293. package/dist/components/core/text-input/TextInput.types.js +1 -1
  294. package/dist/components/data-table-data.d.ts +33 -0
  295. package/dist/components/data-table-data.js +12 -0
  296. package/dist/components/download-banner-data.d.ts +52 -0
  297. package/dist/components/download-banner-data.js +11 -0
  298. package/dist/components/index.d.ts +45 -0
  299. package/dist/components/index.js +32 -0
  300. package/dist/components/resource-banner-data.d.ts +34 -0
  301. package/dist/components/resource-banner-data.js +13 -0
  302. package/dist/components/scene-scroll-data.d.ts +24 -0
  303. package/dist/components/scene-scroll-data.js +9 -0
  304. package/dist/components/stats-data.d.ts +28 -0
  305. package/dist/components/stats-data.js +11 -0
  306. package/dist/components/steps-data.d.ts +38 -0
  307. package/dist/components/steps-data.js +13 -0
  308. package/dist/components/sticky-cards-data.d.ts +43 -0
  309. package/dist/components/sticky-cards-data.js +12 -0
  310. package/dist/components/team-grid-data.d.ts +38 -0
  311. package/dist/components/team-grid-data.js +11 -0
  312. package/dist/components/tiers-data.d.ts +47 -0
  313. package/dist/components/tiers-data.js +13 -0
  314. package/dist/marketing.css +77 -28
  315. package/package.json +1 -1
@@ -0,0 +1,251 @@
1
+ <!-- GENERATED from sticky-cards.contract.json. Do not edit. -->
2
+
3
+ # StickyCards
4
+
5
+ A section whose cards pile up as the page scrolls: a heading, and under it a set of cards that each stop near the top of the window while the next one slides up over them, so the cards are read one at a time and the edges of the ones already read stay showing above the one being read. A covered card draws back a little and darkens as the next one crosses it, which is what makes the pile read as depth.
6
+
7
+ Two layouts. `compact` is the reference design's StickyMarketCards: text-first cards in a column held to the heading's measure, any number of them, a picture or an animation beside the words where a card has one, and at most four edges showing however long the set is. `feature` is its StickyStackedCards: a few large cards the width of the page, each its words beside a picture filling its half from the fold.
8
+
9
+ The pile is CSS — `position: sticky`, and a scroll timeline for the drawing back — so nothing waits for a script. Where the browser has no scroll timeline the cards still pile, without drawing back. A reader who asked for less motion, or a window too short to hold a card, gets the cards as a plain column, every one in full.
10
+
11
+ The reference design's StickyMarketCards, StickyMarketCardsLottie and StickyStackedCards, as one block.
12
+
13
+ ## Use when
14
+
15
+ - A set of markets, products or benefits a page wants read one at a time as the reader scrolls — the reference design's "What we offer" — where each is a title and a sentence, and a picture or an animation is optional
16
+ - Three steps or benefits that each need a picture of their own — Sign up, Deposit, Trade — told as a sequence the reader scrolls through rather than a row they compare
17
+
18
+ ## Do not use
19
+
20
+ - Three or four things a reader should compare side by side, at a glance → `FeatureCards`
21
+ - Several things of the same kind, each with a picture worth a screen and room for more than a sentence → `FeatureScroll`
22
+ - One or two items → nothing here does this
23
+ - Anything the reader must see at once — terms, prices compared across plans, a risk warning. A covered card is out of sight until the reader scrolls back to it
24
+ - Cards a reader presses. The pile covers each card as the next arrives, and a control under another card is one the eye cannot find; a card's way on belongs in the section after it
25
+ - The page's opening band. This sits under the page's heading, never in place of it
26
+
27
+ ## Anatomy
28
+
29
+ StickyCards renders 12 elements. A token binding names the part it applies to.
30
+
31
+ - `root` — `<section>`, class `dds-stickycards`. The section. It paints the ground and the ink to go with it — see `ground` — and asks its own width, so the cards fold on the section's measure rather than the window's. A region named by its heading. Nothing between it and the page may clip with `overflow: hidden`: that makes a box a scroll container, which unpins the cards and freezes their timelines.
32
+ - `inner` — `<div>`, class `dds-stickycards__inner`. The measure and the gutter, the same column every band holds its content in, and the step from the heading to the cards: space.heading.content.
33
+ - `header` — `<div>`, class `dds-stickycards__header`. The heading and the line under it. Ranged to the start on a phone and centred from the fold in both layouts — both references turn at 768px — FeatureCards' header and its reason: the cards are stacked under it. It sets no gap: the title's own margin is the step, so a header without a line closes up.
34
+ - `title` — `<h2>`, class `dds-stickycards__title`. What the set is. `h2` because a section sits under the page's own heading; each card's title comes below it in the outline as an `h3`.
35
+ - `description` — `<p>`, class `dds-stickycards__description`. The line under the heading, at the description measure. Rendered only when `description` is set.
36
+ - `list` — `<ul>`, class `dds-stickycards__list`. The cards, a list a screen reader counts — `role="list"` said outright, because Safari drops a list's role once its markers are styled away. One column, every row as tall as the tallest card, so each card hides the one under it completely and only the edges show. Held to size.sticky-cards.compact-max and centred in `compact`, the page's measure in `feature`. Its rows are space.grid.gutter apart, the room each card is read in before the next one reaches it.
37
+ - `item` — `<li>`, class `dds-stickycards__item`. One card's place in the pile. Sticky where the pile runs: it stops under the room a navigation bar takes, space.pile.peek lower than the card before it, so the edge of each covered card shows. Past four cards the earliest share the first card's place, so no more than four edges show. Once the next card starts across it, it draws back by motion.scale.pile-covered from its top edge, scrubbed by the scroll. While anything inside it has focus it comes to the top of the pile and stands at full size, so focus is never behind another card. It holds a Card at the variant the record names, filling it.
38
+ - `backdrop` — `<span>`, class `dds-stickycards__backdrop`. A `compact` card's background photograph, behind the whole card under an 72% wash of the card's own fill, so the words keep their contrast and the picture reads as texture — the reference design's market cards. Rounded with the card. Drawn only for a card with a `background`, and only in `compact`.
39
+ - `body` — `<div>`, class `dds-stickycards__body`. Inside the Card, its one child: a CardBody holding the title (`h3`) and the sentence, and after it the picture. The words come first in reading order on every screen. Stacked on a phone; from the fold the words and the picture sit side by side — the words at the start — and a `compact` card with no picture sets its title beside its sentence instead, the reference design's two columns.
40
+ - `media` — `<div>`, class `dds-stickycards__media`. Where a card's picture goes, or the player the `media` callback returns for it. In `compact` it is a landscape container, aspect.landscape, inset from the card's edges and rounded at radius.surface-compact — beside the words from the fold, under them on a phone — and the picture fills it, cropped to fill; a player stretches to it. In `feature` it reaches the card's edges and fills its half, cropped to fill — landscape on a phone. Drawn only for a card with a picture or a player.
41
+ - `image` — `<img>`, class `dds-stickycards__image`. The card's picture, with the record's `alt`, its width and height so its shape is held before it loads, and loaded lazily. Rounded with the media in `compact`, square-cornered in `feature`, where the card's own corner clips it. Not drawn when `media` returns a player for the card.
42
+ - `scrim` — `<span>`, class `dds-stickycards__scrim`. The darkening a covered card takes: color.bg.overlay laid over the whole card at the card's corner, faded in from nothing as the next card crosses it. Drawn only where the pile animates — no scroll timeline, or a reader who asked for less motion, and it is not shown at all. Empty, so it says nothing to a screen reader, and never in the way of a pointer.
43
+
44
+ Apply these classes from the generated `stickyCardsParts` constant rather than writing the strings by hand.
45
+
46
+ ## Composition
47
+
48
+ - Takes no children. Everything it renders comes from props
49
+ - Never contains: `StickyCards`, `LandingHero`, `ProductHero`, `PageHeader`, `Footer`, `Navbar` — in children or in any prop that takes a node
50
+
51
+ ## Props
52
+
53
+ ### ground — `page` | `subtle` | `strong` (default `page`)
54
+
55
+ - `page` — The default, and the page's own white. The reference design's sticky stacked cards sit on it
56
+ - `subtle` — A quiet step off the page — the reference design's slate band, which its market cards sit on. Painted as the band's tint laid over the page's own ground; the light and dark cards are raised a step so their edges do not sink into it, FeatureCards' answer
57
+ - `strong` — `color.bg.brand-strong`, the dark band, for a section a reader should arrive at. The heading turns white; a dark card on it is told apart by its edge in the pile more than by its fill, so alternate the cards
58
+
59
+ Use when: Which of the page's three grounds this section paints, and with it the ink of the heading. The cards keep their own fills on every ground. A page builds its rhythm from these, and the section above knows nothing about the one below it, so the page is the only thing that can decide.
60
+ Never for: Marking importance. Every section on a page painted a ground is a page with no emphasis left. And never the brand's coral, `color.bg.brand`, which is not offered: that ground belongs to the band that asks a reader to do something.
61
+
62
+ ### layout — `compact` | `feature` (default `compact`)
63
+
64
+ - `compact` — The default — the reference design's StickyMarketCards and their animation sibling. Cards held to the heading's measure, at least size.sticky-cards.compact-min tall, the title at the card's own step: a photograph behind the card where it has a `background`, and a landscape container beside the words from the fold — under them on a phone — for its `image` or the page's player. With neither, the title beside its sentence. Any number of cards, three or more; past four, the earliest share an edge so no more than four ever show
65
+ - `feature` — The reference design's StickyStackedCards — about three large cards, each the width of the page and at least size.sticky-cards.feature-min tall, the title at the h3 step, its words beside a picture that runs to the card's edges and fills its half from the fold, under them on a phone. Give every card an `image` or a `media`: a card here without one is words beside an empty half
66
+
67
+ Use when: Which pile this is: text-first cards in a narrow column, or a few large cards the width of the page with a picture each.
68
+
69
+ ### title — `string` **Required.**
70
+
71
+ Use when: Always. What the set is, in the fewest words — "What we offer", "Start trading in minutes". It names the section as a region, so a screen reader can jump to it and past it.
72
+ Never for: A sentence. The line under it is for the sentence.
73
+
74
+ ### description — `string`
75
+
76
+ Use when: A line saying what the cards have in common, when the title alone does not.
77
+ Never for: A paragraph, or an introduction to each card. The cards say what each one is.
78
+
79
+ ### cards — `data` `StickyCardsItem[]` **Required.**
80
+
81
+ Use when: Always. The cards, in the order the reader meets them — the first is the first one covered, the last ends the pile on top. Each has a title, a sentence, a fill (`variant`), and where it has them an `image` — the card's container in `compact`, its half in `feature` — and in `compact` a `background` photograph under the card's wash. Every card is drawn the height of the tallest, so the pile covers cleanly: keep the descriptions about the same length. An empty list draws the heading alone. A picture on every card or on none: a pile is one set, and it takes one arrangement — words beside the picture, or, where no card has one, each title beside its sentence. A card left without a picture in a pile of pictured cards keeps its words stacked at the top like the rest, beside an empty half.
82
+ Never for: A card a reader has to press, or one or two cards. See the block's own guidance. Mixing cards with and without a picture in one pile. Give every card one, or none.
83
+
84
+ ### media — `callback`
85
+
86
+ Use when: A card shows something the page plays itself — a Lottie animation, a video — in the place its picture would take, the reference design's StickyMarketCardsLottie. Given each card and its place in the list, return the player for that card, or nothing to fall back to the card's `image`. The block ships no player of its own and imports none: the page mounts its own. The slot is the picture's place — beside the words from the fold, under them on a phone — and the player should fill it: landscape in `compact`, the whole half in `feature`.
87
+ Never for: A control that only works while its card is on top, or anything a reader must press to read the card. Whatever this returns is covered when the next card arrives; a focusable control in it brings its card to the top of the pile while it has focus, but a reader scrolling past never sees it. Start an animation on its own, paused under reduced motion, rather than behind a play button.
88
+
89
+ ## Accessibility
90
+
91
+ Guaranteed by the component. Write none of it by hand: the ids these attributes point at are generated per instance, so an attribute set from outside either says the same thing twice or points at nothing.
92
+
93
+ - Announced as: `region`
94
+ - `Tab` — The block has no controls of its own and adds no tab stop. Anything focusable a page renders through `media` is reached in reading order, card by card, and the card holding it comes to the top of the pile while it has focus.
95
+ - accessible name from title, so the section is a named region a screen reader can jump to and skip
96
+ - The cards are a list with role="list", each card's title an h3 under the section's h2, so the set is counted and a reader can move card to card.
97
+ - Reading order is the order the cards are drawn in, words before the picture in every card, and no card is hidden from assistive technology while it is covered: the pile is a picture of the list, not a reveal of it.
98
+ - A card holding focus is never covered: it comes to the top of the pile and stands at full size until focus leaves it.
99
+ - A reader who asked for less motion gets no pile and no drawing back: the cards as a plain column, every one in full.
100
+
101
+ ## Rules
102
+
103
+ - **On a phone a section's actions stack and each fills the width, including when there is only one** — On a phone a page's actions stack, and each one fills the width. Not most of it, and not the width its label happens to need — the whole column.
104
+
105
+ Two side by side is what this replaces. On the narrowest screen each gets half a column minus the gap, which is rarely enough for a real label: the words wrap inside the pills, the two buttons come out different heights, and the pair reads as a broken row rather than as a choice. Stacked, each has the whole width and neither has to shorten its words to fit.
106
+
107
+ A single action fills too, and that is the part worth saying, because it is the one that looks unnecessary. A lone button has the room to sit at its natural width and still should not: a full-width control is the phone's convention for the thing the screen is asking you to do, and a pill hugging its label reads as one option among others that are not there. It also makes the target predictable — the whole width, wherever the thumb lands — which a centred pill does not.
108
+
109
+ Filling is the row's job rather than the button's. A button has no width of its own and is not given one; the row it sits in becomes a column and stretches its children, so nothing about the control changes and the same button is full width here and its own size on a desktop.
110
+
111
+ This is the phone's breakpoint, size.breakpoint.md, and not the wider one that decides whether a heading is centred. Those are different questions and they break at different widths: a heading stops being centred when the measure is the screen, while a button stops being full width much sooner than that — stretched across a tablet it is a long way from its own label, and a label centred in a metre of pill is harder to hit than one in a pill the size of the words.
112
+
113
+ What this is not about is every row of controls. A set of filter chips, a pagination strip, a toolbar, a link sitting after a sentence — none of these is the page asking for something, and stacking them to full width would turn a strip the eye skims into a list it has to read. The test is whether the row is the call to action the section was written for.
114
+ Guidance only — cannot be checked automatically.
115
+ - **Never nest one interactive element inside another** — Nested interactive elements produce ambiguous hit targets and an unreachable inner control for keyboard and screen-reader users. This is why the rule cannot live on any single component — it constrains Button, Link, CardButton and TextInput simultaneously.
116
+ Guidance only — cannot be checked automatically.
117
+ - **A section that paints a ground sets the ink to go with it** — A page sits on color.bg.page, and a section that separates itself from it takes one of three grounds. color.bg.subtle is the light one and needs nothing further — the page's own ink still reads on it. The other two are dark and do not: color.bg.brand-strong takes color.fg.on-brand-strong, and color.bg.brand takes color.fg.on-solid. Both are white, and both are written as the role rather than the white they resolve to. Set one of those backgrounds without its ink and the text inherits whatever the page was already using, which is near-black on a dark band — legible to nobody, and invisible to every check here, because each pair passes contrast on its own and nothing can tell whether a section used both.
118
+
119
+ color.bg.brand carries a second limit that is not about forgetting. Its ink measures 3.39:1, which clears the 3:1 a heading needs and misses the 4.5:1 running copy needs. A coral band may carry a headline and a call to action; a paragraph on one is text the reader has to work through at a ratio the waiver on that role does not cover — the waiver says so itself. A section with copy in it takes color.bg.brand-strong, which is near-black and carries white at full contrast.
120
+
121
+ color.bg.strong is not a section ground. It is the inverted fill for an action that has to outrank what surrounds it, and the components that paint it — Button and ButtonIcon in their inverse form — already set both halves in one rule, which is the pattern a hand-painted section has to copy.
122
+ Guidance only — cannot be checked automatically.
123
+ - **Every gap inside a section names the relationship it expresses, and the same relationship keeps its measure when it turns on its side** — A section's spacing is named by the relationship it expresses, not chosen by size. Every gap in a section has a role token, and reaching for a step off the generic scale because it happens to be the right number is how two sections written by two people stop lining up: the numbers agree today and drift the first time one of them is tuned.
124
+
125
+ The heading group is three steps, not one gap. An eyebrow sits nearer its title than the title sits to its description, and the actions sit further from the description than either — space.heading.eyebrow-title, space.heading.title-description, space.heading.description-actions, in that order. A single `gap` on the group cannot say this; it sets all three the same and the block reads as evenly spaced lines rather than as a mark, a name and a paragraph. The eyebrow has to identify itself for this to work, because a group that opens with a title must not give its description the eyebrow's tighter step.
126
+
127
+ The step between a title and its description follows the size of the title, which is why there are two of it. What a reader sees is not the gap but the gap plus the leading above it, and leading scales with the type: under a display title the leading is most of the space, under a card's title it is half as deep. One value for both makes a card look loose at the size that made a section look right. So space.heading.title-description is the display step and space.heading.title-description-compact is the step at a card's size, and the choice is made by how big the title is rather than by what kind of block it sits in.
128
+
129
+ Every one of those steps is smaller than the gap below the group, and that ordering is what holds the group together as one thing. space.heading.content is the step between a heading group and the content it introduces, and it is the widest measure inside a section: the one gap the eye reads as a change of subject rather than as a continuation.
130
+
131
+ That step is the same whether the content is under the heading or beside it. A split is the same relationship turned on its side — a heading group and the content it introduces — so the space between the two halves is space.heading.content, not a gutter. When the split collapses to one column on a phone the gap becomes vertical again, which is where it started, and nothing has to change for that to be right.
132
+
133
+ space.grid.gutter is between two things of the same kind. Two cards in a row are the same kind; the two halves of a split are not, which is why the gutter is the wrong measure there even when the number would have looked acceptable. The gutter runs on both axes, so a grid reads as an even field rather than as rows with a seam down them.
134
+
135
+ space.inset.card is the card's, not the page's. A row of cards padded differently is a row of different cards, and the fill is the only edge they have. A page composing a row does not get to choose it.
136
+
137
+ Column count is not a breakpoint decision. A row holds as many columns as fit before a column would be narrower than size.grid.column-min, and drops one when they would not. Written as a fixed count that collapses at a breakpoint, a four-up row keeps four columns at every width above the phone and sets its copy two or three words to a line through the whole of the tablet range — and on a narrow desktop, which no breakpoint sits on at all. The threshold is a property of the row rather than of a card: a card fills whatever cell it is given.
138
+
139
+ What this does not cover is the space above and below a section, which is space.section.default and space.section.compact, and the page's own side margin, which is space.inset.page. Those are the section's outside; everything here is its inside.
140
+ Guidance only — cannot be checked automatically.
141
+ - **A section's title is centred over stacked content, starts beside content in columns, and starts on a phone** — Two arrangements, and the title's alignment is decided by which one it is introducing rather than chosen per section.
142
+
143
+ Content stacked under the title — a row of cards, an accordion, a set of steps — takes a centred title. The heading group and the block below it share one axis, so a centred title is symmetric with the thing it names, and the eye drops from the middle of the words to the middle of the set. A title starting at the left over a centred or full-width block reads as belonging to the first column rather than to all of them.
144
+
145
+ Content beside the title — a split, a picture in the other half, anything laid out in columns — takes a title that starts where its content starts. The two share a left edge, and that edge is what says they are one thing read across rather than two stacked. Centring the title there breaks the only alignment the reader has to hold the halves together, and it drifts further from its content the wider the page gets.
146
+
147
+ The heading group is what carries this, not the heading alone. An eyebrow, a title and a description are read as one block and align together; aligning the title without them leaves a centred heading over start-aligned copy, which reads as a mistake rather than as a choice.
148
+
149
+ Centred means all of it, not just the words. A badge, a row of actions, a picture — these are flex children, and a flex child is placed by align-items rather than by text-align, so a block that only sets the text leaves everything that is not text at the start. A hero is the case that shows it: its title and copy centred while its badge and its buttons did not.
150
+
151
+ A capped block centres itself as well as its words. A heading group takes size.heading-group.max, and a cap that is not centred leaves the measure at the start of the column with the words centred inside it — composed, and a long way left of the middle it appears to be in.
152
+
153
+ Both arrangements go to the start on a phone. A centred block reverts below the breakpoint, and the reason is the measure: on a phone the measure is the screen, so a centred title wraps to three or four ragged lines and the eye loses the left edge it was tracking — the same symmetry that reads as composure on a desktop is what makes it hard to scan on a phone. Centring there has to be asked for twice, and a section title is not one of the things that asks: it is centred because there is room for it, and on a phone there is not. Going back to the start means all of it too, and a capped block is the one that hides a leftover: its words go to the start while its measure stays in the middle of the column, which reads as a title still centred over a badge and buttons that are not — the box in the middle and the line inside it at the edge.
154
+
155
+ A hero is a section like any other: its badge, title, copy and actions are one stacked block, so it is centred on a desktop and starts on a phone with everything else.
156
+
157
+ What this is not about is a page whose body is a form or a document. A title over a sign-up form or a guide is not introducing a set of content — it is the first line of the thing itself, and it keeps the alignment the body has, which for running text is the start. The distinction is whether the block below is content the title names or content the title is part of.
158
+ Guidance only — cannot be checked automatically.
159
+ - **Anything a pointer has to hit is at least 24px in both directions** — A control sized by its contents is as big as its contents, and a glyph is chosen for how it reads rather than for how easily it is hit. An icon-only button with no padding is a 16px target — smaller than the pad of a finger, and smaller than the tremor of a hand using a mouse.
160
+
161
+ WCAG 2.2 asks for 24 by 24 CSS pixels at AA. The exceptions are narrower than they look: inline targets inside a sentence are exempt, and so is a small target with 24px of clear space around it — but a clear button sits against the field it empties, and a field is itself a target, so the spacing exemption does not apply to the case that keeps arising.
162
+
163
+ The target and the mark are separate decisions, and conflating them is what produces both faults. Growing the glyph to 24px makes it shout; leaving the button at 16px makes it hard to hit. So the glyph keeps the size that reads correctly beside its text, and the box around it declares a minimum of its own — which is what size.target.min is for.
164
+
165
+ Declared rather than emergent. Padding plus contents does clear 24px in several components today, and it is the weaker statement: it is a size nobody wrote down, so it moves when the type scale or the glyph does, and nothing says it was meant to be a target at all.
166
+ Guidance only — cannot be checked automatically.
167
+ - **A component is written along the reading axis, not along the page's left and right** — Every measure, inset and placement in this package names an axis rather than a side: inline-size and block-size, padding-inline and padding-block, inset-inline-start and inset-block-end, margin-inline: auto, text-align: start. Written that way a component follows whichever direction the page is read in, and a band whose claim ranges to the leading edge with its subject on the trailing one is the same composition in Arabic as in English, mirrored, with nothing restated. Written as left and right it is the same pixels in both, which is a layout nobody designed for the second language.
168
+
169
+ The enum values follow the same rule and for the same reason. A prop that names where a picture sits takes start and end, never left and right — FeatureSplit, FeatureBullets and FeatureScroll all declare mediaPosition that way, and the flex directions they generate flip on their own because row already means the reading order. A value called left would have to be re-read as right by every author working in a mirrored page, which is a translation this system should be doing rather than asking for.
170
+
171
+ The guidance a contract carries is part of the component, so it follows too. Describing a claim as ranging left is describing one of the two languages, and it was written that way here for a while: three of the opening bands said so in their own intent. A reader in a mirrored page meets that sentence and has to decide whether the component is wrong or the sentence is, and neither answer is one documentation should force.
172
+
173
+ Two things genuinely have no logical form and are not covered by this. A CSS gradient takes a physical direction — there is no to inline-end — so a pattern that must mirror has to be stated twice under a direction query, and one that is periodic and centred, like PageHeader's rule grid, does not need to. A glyph that points the way onward is the other: an arrow is a picture of a direction, and mirroring it belongs to the icon set rather than to the component holding it.
174
+ Guidance only — cannot be checked automatically.
175
+
176
+ ## Anti-examples
177
+
178
+ ❌ StickyCards for: Three or four things a reader should compare side by side, at a glance
179
+ A pile shows one card at a time and covers the rest; cards that are compared need to be seen together
180
+ → use `FeatureCards` instead.
181
+
182
+ ❌ StickyCards for: Several things of the same kind, each with a picture worth a screen and room for more than a sentence
183
+ A card in a pile is the height of the tallest one in it, and covered as soon as the next arrives; a long explanation needs a band that stays put while it is read
184
+ → use `FeatureScroll` instead.
185
+
186
+ ❌ StickyCards for: One or two items
187
+ A pile of one is a card, and two cards one over the other is a transition with nothing to stack. Lay them out as a FeatureSplit or a pair of FeatureCards instead
188
+ → nothing in this system does this. Not a gap waiting to be filled — the case belongs outside it.
189
+
190
+ ## Example
191
+
192
+ ```tsx
193
+ import { StickyCards } from '@deriv-com/dds-marketing'
194
+
195
+ <StickyCards title="What we offer" description="A full suite of markets, all in one place." cards={[{"title":"Forex","description":"Trade the world’s major and minor currency pairs with tight spreads.","variant":"accent-strong"},{"title":"Commodities","description":"Gold, oil and more — trade global commodities.","variant":"accent"},{"title":"Derived indices","description":"Markets that run around the clock, every day of the year.","variant":"accent-strong"},{"title":"Stocks and indices","description":"Get exposure to the world’s largest companies and markets.","variant":"surface-light"}]} />
196
+ ```
197
+
198
+ ## In use
199
+
200
+ ### Two layouts
201
+
202
+ Compact piles any number of text-first cards in a narrow column; feature piles about three large cards, each its words beside a picture filling its half.
203
+
204
+ ```tsx
205
+ import { StickyCards } from '@deriv-com/dds-marketing'
206
+
207
+ // Compact, words alone
208
+ <StickyCards title="What we offer" layout="compact" cards={[{"title":"Forex","description":"Trade the world’s major and minor currency pairs with tight spreads.","variant":"accent-strong"},{"title":"Commodities","description":"Gold, oil and more — trade global commodities.","variant":"accent"},{"title":"Derived indices","description":"Markets that run around the clock, every day of the year.","variant":"accent-strong"},{"title":"Stocks and indices","description":"Get exposure to the world’s largest companies and markets.","variant":"surface-light"}]} />
209
+
210
+ // Feature, a picture on every card
211
+ <StickyCards title="Start trading in minutes" layout="feature" cards={[{"title":"Sign up","description":"Create a free account in minutes with just your email.","variant":"surface-light","image":{"src":"/sample-card-image.svg","alt":"","width":800,"height":600}},{"title":"Deposit","description":"Fund your account with the payment method that suits you.","variant":"accent","image":{"src":"/sample-aspect-art.svg","alt":"","width":1200,"height":1200}},{"title":"Trade","description":"Pick a market and place your first trade.","variant":"accent-strong","image":{"src":"/sample-tall-image.svg","alt":"","width":600,"height":900}}]} />
212
+ ```
213
+
214
+ ### A background and a container
215
+
216
+ Compact cards: a photograph behind the card under its wash, and a landscape container beside the words — under them on a phone.
217
+
218
+ ```tsx
219
+ import { StickyCards } from '@deriv-com/dds-marketing'
220
+
221
+ // Background, wash and container
222
+ <StickyCards title="What we offer" layout="compact" cards={[{"title":"Forex","description":"Trade the world’s major currency pairs with tight spreads.","variant":"accent-strong","background":{"src":"/sample-card-image.svg","alt":"","width":1600,"height":900},"image":{"src":"/sample-card-image.svg","alt":"","width":800,"height":600}},{"title":"Commodities","description":"Gold, oil and more, around the clock.","variant":"accent","background":{"src":"/sample-card-image.svg","alt":"","width":1600,"height":900},"image":{"src":"/sample-card-image.svg","alt":"","width":800,"height":600}},{"title":"Stocks","description":"Get exposure to the world’s largest companies.","variant":"surface-light","background":{"src":"/sample-card-image.svg","alt":"","width":1600,"height":900},"image":{"src":"/sample-card-image.svg","alt":"","width":800,"height":600}}]} />
223
+ ```
224
+
225
+ ### Card fills
226
+
227
+ The dark card is the default; coral for a title and a short sentence; the light card stays light in both themes. Alternate them so each edge reads.
228
+
229
+ ```tsx
230
+ import { StickyCards } from '@deriv-com/dds-marketing'
231
+
232
+ // Dark, coral and light
233
+ <StickyCards title="What we offer" cards={[{"title":"Forex","description":"Trade the world’s major and minor currency pairs with tight spreads.","variant":"accent-strong"},{"title":"Commodities","description":"Gold, oil and more — trade global commodities.","variant":"accent"},{"title":"Stocks and indices","description":"Get exposure to the world’s largest companies and markets.","variant":"surface-light"}]} />
234
+ ```
235
+
236
+ ### Grounds
237
+
238
+ The page's three grounds. The cards keep their fills on each; the light and dark cards lift a step on the subtle band.
239
+
240
+ ```tsx
241
+ import { StickyCards } from '@deriv-com/dds-marketing'
242
+
243
+ // Page
244
+ <StickyCards title="What we offer" ground="page" cards={[{"title":"Forex","description":"Trade the world’s major and minor currency pairs with tight spreads.","variant":"accent-strong"},{"title":"Commodities","description":"Gold, oil and more — trade global commodities.","variant":"accent"},{"title":"Derived indices","description":"Markets that run around the clock, every day of the year.","variant":"accent-strong"}]} />
245
+
246
+ // Subtle
247
+ <StickyCards title="What we offer" ground="subtle" cards={[{"title":"Forex","description":"Trade the world’s major and minor currency pairs with tight spreads.","variant":"accent-strong"},{"title":"Commodities","description":"Gold, oil and more — trade global commodities.","variant":"accent"},{"title":"Derived indices","description":"Markets that run around the clock, every day of the year.","variant":"accent-strong"}]} />
248
+
249
+ // Strong
250
+ <StickyCards title="What we offer" ground="strong" cards={[{"title":"Commodities","description":"Gold, oil and more — trade global commodities.","variant":"accent"},{"title":"Derived indices","description":"Markets that run around the clock, every day of the year.","variant":"accent-strong"},{"title":"Stocks and indices","description":"Get exposure to the world’s largest companies and markets.","variant":"surface-light"}]} />
251
+ ```
@@ -104,6 +104,14 @@ Never for: Marking a group. A missing line is not a heading — a table that wan
104
104
  - `upper` — The house setting. Column names in capitals at font.overline — eleven pixels with wide tracking — which reads as a different texture from the values rather than a louder version of them. A header is read once where the data under it is read many times, and capitals are what stop it competing
105
105
  - `title` — Names drawn as they were written, at font.label.sm — twelve pixels, medium, ordinary tracking. Right where the names are long, are proper nouns whose own capitals capitals would flatten, or are in a script that has no case at all, and right wherever a product has decided its tables read as sentences rather than as labels
106
106
 
107
+ ### scope — `col` | `row` (default `col`)
108
+
109
+ - `col` — The default. A name in the header row, announced before every value beneath it
110
+ - `row` — The first cell of a data row when that cell is what the row is about — an instrument, a plan, a country — rather than one more value in it. It sits in the body, in a TableRow, where a TableCell would otherwise go
111
+
112
+ Use when: Which way the cell names: down its column, the usual case, or across its row. A row header is the cell that says what a row is — the market in a list of instruments — and a screen reader announces it before every value in that row, the way it announces a column's name before every value under it.
113
+ Never for: A row header that can be sorted. Sorting orders rows by a column, so the control belongs on the column's name in the header row, never on the name of one row
114
+
107
115
  ### sortable — `boolean` (default `false`)
108
116
 
109
117
  Use when: The reader can order the table by this column. It draws the control and the mark; what pressing it does is the page's, reported through `onSort`
@@ -304,3 +312,14 @@ import { TableHeaderCell } from '@deriv-com/dds-marketing'
304
312
  // title — as written
305
313
  <TableHeaderCell headerCase="title">Name</TableHeaderCell>
306
314
  ```
315
+
316
+ ### Naming a row
317
+
318
+ The first cell of a data row when it is what the row is about — a market, a plan. Announced before every value in its row, the way a column's name is before every value under it.
319
+
320
+ ```tsx
321
+ import { TableHeaderCell } from '@deriv-com/dds-marketing'
322
+
323
+ // scope="row" — the row's name
324
+ <TableHeaderCell scope="row">EUR/USD</TableHeaderCell>
325
+ ```
@@ -0,0 +1,243 @@
1
+ <!-- GENERATED from team-grid.contract.json. Do not edit. -->
2
+
3
+ # TeamGrid
4
+
5
+ A section that sets out the people behind a company: a heading, and a grid of people under it — each a square photograph, their name, what they do and, where they have one, a link to a profile of theirs elsewhere. The first few show at once, and a button under the grid shows the rest and hides them again.
6
+
7
+ One column on a phone, two from the fold and four at full width, so the first four people are one row on a desktop and the button under that row is what makes the list longer.
8
+
9
+ The reference design's Leaders template — AvatarGrid in its source.
10
+
11
+ ## Use when
12
+
13
+ - A leadership team, an advisory board or a regional management list on an about page
14
+ - A responsive grid of person/profile cards — photo, name, role, and an optional external profile link — with an optional line under its heading and a "View all" that shows the rest of a longer list
15
+
16
+ ## Do not use
17
+
18
+ - One person, spotlit, in their own words → `TestimonialSlider`
19
+ - The items are not people — logos, features, figures → `FeatureCards`
20
+ - People who are not who the card says. A name, a face and a role are a claim about a real person — keep each in step with the person, and leave the profile link out unless it is theirs
21
+ - The page's opening band. This sits under the page's heading, never in place of it
22
+
23
+ ## Anatomy
24
+
25
+ TeamGrid renders 14 elements. A token binding names the part it applies to.
26
+
27
+ - `root` — `<section>`, class `dds-teamgrid`. The section. It paints the ground and the ink to go with it — see `ground` — and asks its own width, so the grid folds on the section's measure rather than the window's. A region named by its heading.
28
+ - `inner` — `<div>`, class `dds-teamgrid__inner`. The measure and the gutter, the same column every band holds its content in, and the step from the heading to the grid: space.heading.content.
29
+ - `header` — `<div>`, class `dds-teamgrid__header`. The heading and the line under it. Ranged to the start on a phone and centred from the fold, FeatureCards' header and its reason. It sets no gap: the title's own margin is the step, so a header without a line closes up.
30
+ - `title` — `<h2>`, class `dds-teamgrid__title`. Who these people are. `h2` because a section sits under the page's own heading; each person's name comes below it in the outline.
31
+ - `description` — `<p>`, class `dds-teamgrid__description`. The line under the heading, at the description measure. Rendered only when `description` is set.
32
+ - `people` — `<div>`, class `dds-teamgrid__people`. The grid and the button under it, held together at space.gap.xl — closer than the section's step from the heading, so the button reads as belonging to the list it lengthens.
33
+ - `list` — `<ul>`, class `dds-teamgrid__list`. The people, a list a screen reader counts. One column on a phone, two from the fold and four at full width, at space.grid.gutter across and down. Its id is what the button controls. The people past `collapsedCount` are in it from the first render and hidden — gone from the page, the accessibility tree and the tab order alike — until the button shows them.
34
+ - `item` — `<li>`, class `dds-teamgrid__item`. One person: the photograph over the words, at space.gap.md — closer than the grid's gutter between people, so a name reads as its own photograph's and never as the next one's. No fill and no inset of its own — the reference design sets the people straight on the section's ground, and the photograph's corners are the card's only edge. A person the button shows fades and rises into place a step behind the one before; under reduced motion they are simply there.
35
+ - `media` — `<div>`, class `dds-teamgrid__media`. The photograph's frame, holding an AspectRatio at `square` — the reference design's 1:1 — so every face in the grid is the same shape whatever was uploaded, rounded at radius.surface. Its fill is the grey a picture sits on while it loads, and it follows the ground.
36
+ - `text` — `<div>`, class `dds-teamgrid__text`. The name, the role and the profile link at space.gap.xs: one block of words under the picture.
37
+ - `name` — `<h3>`, class `dds-teamgrid__name`. Who they are, at the smallest heading step. `h3`, under the section's `h2`, so a reader moving by heading moves person to person.
38
+ - `role` — `<p>`, class `dds-teamgrid__role`. What they do, at the body step and quieter than the name: muted on the page and the grey band, the band's own quieter ink on the dark one.
39
+ - `profile` — `<div>`, class `dds-teamgrid__profile`. A standalone Link to a profile of theirs elsewhere — LinkedIn — opening in a new tab with the external mark that says so. Named for the person as well as the site, see `profileName`, and at least size.target.min tall. Brand ink on the page and the grey band, the band's ink on the dark one. Drawn only for a person who gives a `profile`.
40
+ - `actions` — `<div>`, class `dds-teamgrid__actions`. The button that shows the rest of the list and hides it again — Button in its secondary variant at md, `aria-expanded` saying which and `aria-controls` naming the list. Full width on a phone, the rule for a section's actions; its own width and centred from the fold. Drawn only when there are more people than `collapsedCount`.
41
+
42
+ Apply these classes from the generated `teamGridParts` constant rather than writing the strings by hand.
43
+
44
+ ## Composition
45
+
46
+ - Takes no children. Everything it renders comes from props
47
+ - Never contains: `TeamGrid`, `LandingHero`, `ProductHero`, `PageHeader`, `Footer`, `Navbar` — in children or in any prop that takes a node
48
+
49
+ ## Props
50
+
51
+ ### ground — `page` | `subtle` | `strong` (default `page`)
52
+
53
+ - `page` — The default, and the page's own white. Most people sections have nothing to separate themselves from
54
+ - `subtle` — A quiet step off the page — the reference design's slate band — for a section that wants to read as its own. Painted as the band's tint laid over the page's own ground, so it is the same grey whatever is behind the section; the photographs' placeholder is raised to color.bg.surface-card-raised so it does not vanish into the band
55
+ - `strong` — `color.bg.brand-strong`, the dark band, for a section a reader should arrive at. The heading, the names and the roles turn white, the profile links take the band's ink rather than the brand's, and the button takes Button's inverse tone
56
+
57
+ Use when: Which of the page's three grounds this section paints, and with it the ink and the photographs' placeholder — the grey a picture sits on before it arrives, which follows the ground so it never vanishes into the band. A page builds its rhythm from these, and the section above knows nothing about the one below it, so the page is the only thing that can decide.
58
+ Never for: Marking importance. Every section on a page painted a ground is a page with no emphasis left. And never the brand's coral, `color.bg.brand`, which is not offered: that ground belongs to the band that asks a reader to do something.
59
+
60
+ ### title — `string` **Required.**
61
+
62
+ Use when: Always. Who these people are, in the fewest words — "Meet our leadership team". It names the section as a region, so a screen reader can jump to it and past it.
63
+ Never for: A sentence. The line under it is for the sentence.
64
+
65
+ ### description — `string`
66
+
67
+ Use when: A line saying who the set is, when the title alone does not — "The people steering Deriv's products, markets and teams".
68
+ Never for: A paragraph, or an introduction to each person. The cards say who each one is.
69
+
70
+ ### people — `data` `TeamGridMember[]` **Required.**
71
+
72
+ Use when: Always. The people, in the order the page wants them read — by seniority or by relevance, the first `collapsedCount` of them showing at once. Each has a name, a role, a picture with its `alt`, and where there is one a `profile` link. The same framing in every photograph: the grid crops each to a square, and a set of faces at different distances reads as a set of different sources. An empty list draws the heading alone.
73
+ Never for: Anyone who has not agreed to be on the page, or a profile link that is not theirs.
74
+
75
+ ### collapsedCount — `number` (default `4`)
76
+
77
+ Use when: How many people show before the rest are asked for. Four, the default, is one row at full width, so the button sits under a complete row. A list no longer than this has no button at all — there is nothing more to show.
78
+ Never for: Hiding one or two people behind a button. A person more than a row is better shown than asked for: raise this to the length of the list.
79
+
80
+ ### expandLabel — `string` (default `View all`)
81
+
82
+ Use when: The button's words while the rest of the list is hidden. Translate it with the page, and keep it parallel to collapseLabel.
83
+
84
+ ### collapseLabel — `string` (default `View less`)
85
+
86
+ Use when: The button's words once the whole list is showing, for the reason expandLabel exists.
87
+
88
+ ### profileName — `callback`
89
+
90
+ Use when: The accessible name of a person's profile link, given the person. The visible word is the profile's own — "LinkedIn" — and it is the same on every card, so the link is named for its person too. Without this the name is the profile's label and then the person's, "LinkedIn, Alex Morgan": the visible word first, so a reader using speech can say what they see. Pass it to translate that with the page.
91
+ Never for: Visible text. It names the link to assistive technology and is never drawn.
92
+
93
+ ### externalLabel — `string` (default `(opens in a new tab)`)
94
+
95
+ Use when: Said after every profile link's name, because a profile opens in a new tab and a reader should know before they press it — ResourceBanner's words for the same thing. Translate it with the page.
96
+ Never for: Visible text. It is added to the link's name for assistive technology and never drawn.
97
+
98
+ ## Accessibility
99
+
100
+ Guaranteed by the component. Write none of it by hand: the ids these attributes point at are generated per instance, so an attribute set from outside either says the same thing twice or points at nothing.
101
+
102
+ - Announced as: `region`
103
+ - `Tab` — Moves through the profile links of the people showing, in reading order, and then to the button. People the button has not shown are not in the tab order.
104
+ - `Enter` — On the button, shows the rest of the list or hides it again; focus stays on the button. On a profile link, opens it in a new tab.
105
+ - `Space` — On the button, the same as Enter.
106
+ - accessible name from title, so the section is a named region a screen reader can jump to and skip
107
+ - The people are a list, each person's name a heading under the section's, so the set is counted and a reader can move person to person.
108
+ - The button says whether the rest of the list is showing with aria-expanded and names the list with aria-controls. Pressing it leaves focus where it is; the people it shows come straight before it in reading order, and the people it hides leave the tab order with them.
109
+ - Each profile link is named for its person as well as its site, so a list of links heard on its own says whose each one is.
110
+
111
+ ## Rules
112
+
113
+ - **On a phone a section's actions stack and each fills the width, including when there is only one** — On a phone a page's actions stack, and each one fills the width. Not most of it, and not the width its label happens to need — the whole column.
114
+
115
+ Two side by side is what this replaces. On the narrowest screen each gets half a column minus the gap, which is rarely enough for a real label: the words wrap inside the pills, the two buttons come out different heights, and the pair reads as a broken row rather than as a choice. Stacked, each has the whole width and neither has to shorten its words to fit.
116
+
117
+ A single action fills too, and that is the part worth saying, because it is the one that looks unnecessary. A lone button has the room to sit at its natural width and still should not: a full-width control is the phone's convention for the thing the screen is asking you to do, and a pill hugging its label reads as one option among others that are not there. It also makes the target predictable — the whole width, wherever the thumb lands — which a centred pill does not.
118
+
119
+ Filling is the row's job rather than the button's. A button has no width of its own and is not given one; the row it sits in becomes a column and stretches its children, so nothing about the control changes and the same button is full width here and its own size on a desktop.
120
+
121
+ This is the phone's breakpoint, size.breakpoint.md, and not the wider one that decides whether a heading is centred. Those are different questions and they break at different widths: a heading stops being centred when the measure is the screen, while a button stops being full width much sooner than that — stretched across a tablet it is a long way from its own label, and a label centred in a metre of pill is harder to hit than one in a pill the size of the words.
122
+
123
+ What this is not about is every row of controls. A set of filter chips, a pagination strip, a toolbar, a link sitting after a sentence — none of these is the page asking for something, and stacking them to full width would turn a strip the eye skims into a list it has to read. The test is whether the row is the call to action the section was written for.
124
+ Guidance only — cannot be checked automatically.
125
+ - **Never nest one interactive element inside another** — Nested interactive elements produce ambiguous hit targets and an unreachable inner control for keyboard and screen-reader users. This is why the rule cannot live on any single component — it constrains Button, Link, CardButton and TextInput simultaneously.
126
+ Guidance only — cannot be checked automatically.
127
+ - **A section that paints a ground sets the ink to go with it** — A page sits on color.bg.page, and a section that separates itself from it takes one of three grounds. color.bg.subtle is the light one and needs nothing further — the page's own ink still reads on it. The other two are dark and do not: color.bg.brand-strong takes color.fg.on-brand-strong, and color.bg.brand takes color.fg.on-solid. Both are white, and both are written as the role rather than the white they resolve to. Set one of those backgrounds without its ink and the text inherits whatever the page was already using, which is near-black on a dark band — legible to nobody, and invisible to every check here, because each pair passes contrast on its own and nothing can tell whether a section used both.
128
+
129
+ color.bg.brand carries a second limit that is not about forgetting. Its ink measures 3.39:1, which clears the 3:1 a heading needs and misses the 4.5:1 running copy needs. A coral band may carry a headline and a call to action; a paragraph on one is text the reader has to work through at a ratio the waiver on that role does not cover — the waiver says so itself. A section with copy in it takes color.bg.brand-strong, which is near-black and carries white at full contrast.
130
+
131
+ color.bg.strong is not a section ground. It is the inverted fill for an action that has to outrank what surrounds it, and the components that paint it — Button and ButtonIcon in their inverse form — already set both halves in one rule, which is the pattern a hand-painted section has to copy.
132
+ Guidance only — cannot be checked automatically.
133
+ - **Every gap inside a section names the relationship it expresses, and the same relationship keeps its measure when it turns on its side** — A section's spacing is named by the relationship it expresses, not chosen by size. Every gap in a section has a role token, and reaching for a step off the generic scale because it happens to be the right number is how two sections written by two people stop lining up: the numbers agree today and drift the first time one of them is tuned.
134
+
135
+ The heading group is three steps, not one gap. An eyebrow sits nearer its title than the title sits to its description, and the actions sit further from the description than either — space.heading.eyebrow-title, space.heading.title-description, space.heading.description-actions, in that order. A single `gap` on the group cannot say this; it sets all three the same and the block reads as evenly spaced lines rather than as a mark, a name and a paragraph. The eyebrow has to identify itself for this to work, because a group that opens with a title must not give its description the eyebrow's tighter step.
136
+
137
+ The step between a title and its description follows the size of the title, which is why there are two of it. What a reader sees is not the gap but the gap plus the leading above it, and leading scales with the type: under a display title the leading is most of the space, under a card's title it is half as deep. One value for both makes a card look loose at the size that made a section look right. So space.heading.title-description is the display step and space.heading.title-description-compact is the step at a card's size, and the choice is made by how big the title is rather than by what kind of block it sits in.
138
+
139
+ Every one of those steps is smaller than the gap below the group, and that ordering is what holds the group together as one thing. space.heading.content is the step between a heading group and the content it introduces, and it is the widest measure inside a section: the one gap the eye reads as a change of subject rather than as a continuation.
140
+
141
+ That step is the same whether the content is under the heading or beside it. A split is the same relationship turned on its side — a heading group and the content it introduces — so the space between the two halves is space.heading.content, not a gutter. When the split collapses to one column on a phone the gap becomes vertical again, which is where it started, and nothing has to change for that to be right.
142
+
143
+ space.grid.gutter is between two things of the same kind. Two cards in a row are the same kind; the two halves of a split are not, which is why the gutter is the wrong measure there even when the number would have looked acceptable. The gutter runs on both axes, so a grid reads as an even field rather than as rows with a seam down them.
144
+
145
+ space.inset.card is the card's, not the page's. A row of cards padded differently is a row of different cards, and the fill is the only edge they have. A page composing a row does not get to choose it.
146
+
147
+ Column count is not a breakpoint decision. A row holds as many columns as fit before a column would be narrower than size.grid.column-min, and drops one when they would not. Written as a fixed count that collapses at a breakpoint, a four-up row keeps four columns at every width above the phone and sets its copy two or three words to a line through the whole of the tablet range — and on a narrow desktop, which no breakpoint sits on at all. The threshold is a property of the row rather than of a card: a card fills whatever cell it is given.
148
+
149
+ What this does not cover is the space above and below a section, which is space.section.default and space.section.compact, and the page's own side margin, which is space.inset.page. Those are the section's outside; everything here is its inside.
150
+ Guidance only — cannot be checked automatically.
151
+ - **A section's title is centred over stacked content, starts beside content in columns, and starts on a phone** — Two arrangements, and the title's alignment is decided by which one it is introducing rather than chosen per section.
152
+
153
+ Content stacked under the title — a row of cards, an accordion, a set of steps — takes a centred title. The heading group and the block below it share one axis, so a centred title is symmetric with the thing it names, and the eye drops from the middle of the words to the middle of the set. A title starting at the left over a centred or full-width block reads as belonging to the first column rather than to all of them.
154
+
155
+ Content beside the title — a split, a picture in the other half, anything laid out in columns — takes a title that starts where its content starts. The two share a left edge, and that edge is what says they are one thing read across rather than two stacked. Centring the title there breaks the only alignment the reader has to hold the halves together, and it drifts further from its content the wider the page gets.
156
+
157
+ The heading group is what carries this, not the heading alone. An eyebrow, a title and a description are read as one block and align together; aligning the title without them leaves a centred heading over start-aligned copy, which reads as a mistake rather than as a choice.
158
+
159
+ Centred means all of it, not just the words. A badge, a row of actions, a picture — these are flex children, and a flex child is placed by align-items rather than by text-align, so a block that only sets the text leaves everything that is not text at the start. A hero is the case that shows it: its title and copy centred while its badge and its buttons did not.
160
+
161
+ A capped block centres itself as well as its words. A heading group takes size.heading-group.max, and a cap that is not centred leaves the measure at the start of the column with the words centred inside it — composed, and a long way left of the middle it appears to be in.
162
+
163
+ Both arrangements go to the start on a phone. A centred block reverts below the breakpoint, and the reason is the measure: on a phone the measure is the screen, so a centred title wraps to three or four ragged lines and the eye loses the left edge it was tracking — the same symmetry that reads as composure on a desktop is what makes it hard to scan on a phone. Centring there has to be asked for twice, and a section title is not one of the things that asks: it is centred because there is room for it, and on a phone there is not. Going back to the start means all of it too, and a capped block is the one that hides a leftover: its words go to the start while its measure stays in the middle of the column, which reads as a title still centred over a badge and buttons that are not — the box in the middle and the line inside it at the edge.
164
+
165
+ A hero is a section like any other: its badge, title, copy and actions are one stacked block, so it is centred on a desktop and starts on a phone with everything else.
166
+
167
+ What this is not about is a page whose body is a form or a document. A title over a sign-up form or a guide is not introducing a set of content — it is the first line of the thing itself, and it keeps the alignment the body has, which for running text is the start. The distinction is whether the block below is content the title names or content the title is part of.
168
+ Guidance only — cannot be checked automatically.
169
+ - **Anything a pointer has to hit is at least 24px in both directions** — A control sized by its contents is as big as its contents, and a glyph is chosen for how it reads rather than for how easily it is hit. An icon-only button with no padding is a 16px target — smaller than the pad of a finger, and smaller than the tremor of a hand using a mouse.
170
+
171
+ WCAG 2.2 asks for 24 by 24 CSS pixels at AA. The exceptions are narrower than they look: inline targets inside a sentence are exempt, and so is a small target with 24px of clear space around it — but a clear button sits against the field it empties, and a field is itself a target, so the spacing exemption does not apply to the case that keeps arising.
172
+
173
+ The target and the mark are separate decisions, and conflating them is what produces both faults. Growing the glyph to 24px makes it shout; leaving the button at 16px makes it hard to hit. So the glyph keeps the size that reads correctly beside its text, and the box around it declares a minimum of its own — which is what size.target.min is for.
174
+
175
+ Declared rather than emergent. Padding plus contents does clear 24px in several components today, and it is the weaker statement: it is a size nobody wrote down, so it moves when the type scale or the glyph does, and nothing says it was meant to be a target at all.
176
+ Guidance only — cannot be checked automatically.
177
+ - **A component is written along the reading axis, not along the page's left and right** — Every measure, inset and placement in this package names an axis rather than a side: inline-size and block-size, padding-inline and padding-block, inset-inline-start and inset-block-end, margin-inline: auto, text-align: start. Written that way a component follows whichever direction the page is read in, and a band whose claim ranges to the leading edge with its subject on the trailing one is the same composition in Arabic as in English, mirrored, with nothing restated. Written as left and right it is the same pixels in both, which is a layout nobody designed for the second language.
178
+
179
+ The enum values follow the same rule and for the same reason. A prop that names where a picture sits takes start and end, never left and right — FeatureSplit, FeatureBullets and FeatureScroll all declare mediaPosition that way, and the flex directions they generate flip on their own because row already means the reading order. A value called left would have to be re-read as right by every author working in a mirrored page, which is a translation this system should be doing rather than asking for.
180
+
181
+ The guidance a contract carries is part of the component, so it follows too. Describing a claim as ranging left is describing one of the two languages, and it was written that way here for a while: three of the opening bands said so in their own intent. A reader in a mirrored page meets that sentence and has to decide whether the component is wrong or the sentence is, and neither answer is one documentation should force.
182
+
183
+ Two things genuinely have no logical form and are not covered by this. A CSS gradient takes a physical direction — there is no to inline-end — so a pattern that must mirror has to be stated twice under a direction query, and one that is periodic and centred, like PageHeader's rule grid, does not need to. A glyph that points the way onward is the other: an arrow is a picture of a direction, and mirroring it belongs to the icon set rather than to the component holding it.
184
+ Guidance only — cannot be checked automatically.
185
+
186
+ ## Anti-examples
187
+
188
+ ❌ TeamGrid for: One person, spotlit, in their own words
189
+ A grid of one is a portrait with nothing to compare it to; a quote with the name under it says why the person is on the page
190
+ → use `TestimonialSlider` instead.
191
+
192
+ ❌ TeamGrid for: The items are not people — logos, features, figures
193
+ A square photograph over a name and a role is a person's card; anything else in that shape reads as a team page with the wrong team on it
194
+ → use `FeatureCards` instead.
195
+
196
+ ## Example
197
+
198
+ ```tsx
199
+ import { TeamGrid } from '@deriv-com/dds-marketing'
200
+
201
+ <TeamGrid title="Meet our leadership team" description="The people steering Deriv's products, markets and teams across 20 countries." people={[{"name":"Alex Morgan","role":"Chief Executive Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/alex-morgan","label":"LinkedIn"}},{"name":"Sam Rivera","role":"Chief Operating Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/sam-rivera","label":"LinkedIn"}},{"name":"Jordan Lee","role":"Chief Technology Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jordan-lee","label":"LinkedIn"}},{"name":"Taylor Brooks","role":"Chief Financial Officer","image":{"src":"/sample-card-image.svg","alt":""}},{"name":"Casey Quinn","role":"Head of Compliance","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/casey-quinn","label":"LinkedIn"}},{"name":"Morgan Hayes","role":"Head of Product","image":{"src":"/sample-card-image.svg","alt":""}},{"name":"Riley Chen","role":"Head of Marketing","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/riley-chen","label":"LinkedIn"}},{"name":"Jamie Patel","role":"Head of People","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jamie-patel","label":"LinkedIn"}}]} />
202
+ ```
203
+
204
+ ## In use
205
+
206
+ ### A row, and the rest behind a button
207
+
208
+ Four show at once, one row at full width. View all shows the rest; a list of four or fewer has no button at all.
209
+
210
+ ```tsx
211
+ import { TeamGrid } from '@deriv-com/dds-marketing'
212
+
213
+ // Eight people, four showing
214
+ <TeamGrid title="Meet our leadership team" people={[{"name":"Alex Morgan","role":"Chief Executive Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/alex-morgan","label":"LinkedIn"}},{"name":"Sam Rivera","role":"Chief Operating Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/sam-rivera","label":"LinkedIn"}},{"name":"Jordan Lee","role":"Chief Technology Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jordan-lee","label":"LinkedIn"}},{"name":"Taylor Brooks","role":"Chief Financial Officer","image":{"src":"/sample-card-image.svg","alt":""}},{"name":"Casey Quinn","role":"Head of Compliance","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/casey-quinn","label":"LinkedIn"}},{"name":"Morgan Hayes","role":"Head of Product","image":{"src":"/sample-card-image.svg","alt":""}},{"name":"Riley Chen","role":"Head of Marketing","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/riley-chen","label":"LinkedIn"}},{"name":"Jamie Patel","role":"Head of People","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jamie-patel","label":"LinkedIn"}}]} />
215
+
216
+ // Four people, no button
217
+ <TeamGrid title="Meet our leadership team" people={[{"name":"Alex Morgan","role":"Chief Executive Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/alex-morgan","label":"LinkedIn"}},{"name":"Sam Rivera","role":"Chief Operating Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/sam-rivera","label":"LinkedIn"}},{"name":"Jordan Lee","role":"Chief Technology Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jordan-lee","label":"LinkedIn"}},{"name":"Taylor Brooks","role":"Chief Financial Officer","image":{"src":"/sample-card-image.svg","alt":""}}]} />
218
+ ```
219
+
220
+ ### How many show at first
221
+
222
+ collapsedCount sets how many people show before the button. Eight here, so a board of eight is shown whole.
223
+
224
+ ```tsx
225
+ import { TeamGrid } from '@deriv-com/dds-marketing'
226
+
227
+ // collapsedCount 8, the whole board
228
+ <TeamGrid title="Our advisory board" description="Industry voices who guide how we build for traders." people={[{"name":"Alex Morgan","role":"Chief Executive Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/alex-morgan","label":"LinkedIn"}},{"name":"Sam Rivera","role":"Chief Operating Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/sam-rivera","label":"LinkedIn"}},{"name":"Jordan Lee","role":"Chief Technology Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jordan-lee","label":"LinkedIn"}},{"name":"Taylor Brooks","role":"Chief Financial Officer","image":{"src":"/sample-card-image.svg","alt":""}},{"name":"Casey Quinn","role":"Head of Compliance","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/casey-quinn","label":"LinkedIn"}},{"name":"Morgan Hayes","role":"Head of Product","image":{"src":"/sample-card-image.svg","alt":""}},{"name":"Riley Chen","role":"Head of Marketing","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/riley-chen","label":"LinkedIn"}},{"name":"Jamie Patel","role":"Head of People","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jamie-patel","label":"LinkedIn"}}]} collapsedCount={8} />
229
+ ```
230
+
231
+ ### On the grey band and the dark one
232
+
233
+ ground="subtle" tells the section apart from the one above; ground="strong" stops the page, and the links and button turn white.
234
+
235
+ ```tsx
236
+ import { TeamGrid } from '@deriv-com/dds-marketing'
237
+
238
+ // Subtle ground
239
+ <TeamGrid title="Meet our leadership team" description="The people steering Deriv's products, markets and teams across 20 countries." people={[{"name":"Alex Morgan","role":"Chief Executive Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/alex-morgan","label":"LinkedIn"}},{"name":"Sam Rivera","role":"Chief Operating Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/sam-rivera","label":"LinkedIn"}},{"name":"Jordan Lee","role":"Chief Technology Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jordan-lee","label":"LinkedIn"}},{"name":"Taylor Brooks","role":"Chief Financial Officer","image":{"src":"/sample-card-image.svg","alt":""}},{"name":"Casey Quinn","role":"Head of Compliance","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/casey-quinn","label":"LinkedIn"}},{"name":"Morgan Hayes","role":"Head of Product","image":{"src":"/sample-card-image.svg","alt":""}},{"name":"Riley Chen","role":"Head of Marketing","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/riley-chen","label":"LinkedIn"}},{"name":"Jamie Patel","role":"Head of People","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jamie-patel","label":"LinkedIn"}}]} ground="subtle" />
240
+
241
+ // Strong ground
242
+ <TeamGrid title="Meet our leadership team" description="The people steering Deriv's products, markets and teams across 20 countries." people={[{"name":"Alex Morgan","role":"Chief Executive Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/alex-morgan","label":"LinkedIn"}},{"name":"Sam Rivera","role":"Chief Operating Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/sam-rivera","label":"LinkedIn"}},{"name":"Jordan Lee","role":"Chief Technology Officer","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jordan-lee","label":"LinkedIn"}},{"name":"Taylor Brooks","role":"Chief Financial Officer","image":{"src":"/sample-card-image.svg","alt":""}},{"name":"Casey Quinn","role":"Head of Compliance","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/casey-quinn","label":"LinkedIn"}},{"name":"Morgan Hayes","role":"Head of Product","image":{"src":"/sample-card-image.svg","alt":""}},{"name":"Riley Chen","role":"Head of Marketing","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/riley-chen","label":"LinkedIn"}},{"name":"Jamie Patel","role":"Head of People","image":{"src":"/sample-card-image.svg","alt":""},"profile":{"href":"https://example.com/people/jamie-patel","label":"LinkedIn"}}]} ground="strong" />
243
+ ```