@apostel/bedrock 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +216 -0
  3. package/dist/accessible-icon/parts.d.ts +16 -0
  4. package/dist/accessible-icon/parts.d.ts.map +1 -0
  5. package/dist/accessible-icon/parts.js +24 -0
  6. package/dist/accessible-icon/parts.js.map +1 -0
  7. package/dist/accordion/controlled-root.d.ts +19 -0
  8. package/dist/accordion/controlled-root.d.ts.map +1 -0
  9. package/dist/accordion/controlled-root.js +53 -0
  10. package/dist/accordion/controlled-root.js.map +1 -0
  11. package/dist/accordion/parts.d.ts +35 -0
  12. package/dist/accordion/parts.d.ts.map +1 -0
  13. package/dist/accordion/parts.js +52 -0
  14. package/dist/accordion/parts.js.map +1 -0
  15. package/dist/accordion/root.d.ts +24 -0
  16. package/dist/accordion/root.d.ts.map +1 -0
  17. package/dist/accordion/root.js +45 -0
  18. package/dist/accordion/root.js.map +1 -0
  19. package/dist/accordion/shared.d.ts +23 -0
  20. package/dist/accordion/shared.d.ts.map +1 -0
  21. package/dist/accordion/shared.js +16 -0
  22. package/dist/accordion/shared.js.map +1 -0
  23. package/dist/alert-dialog/parts.d.ts +16 -0
  24. package/dist/alert-dialog/parts.d.ts.map +1 -0
  25. package/dist/alert-dialog/parts.js +20 -0
  26. package/dist/alert-dialog/parts.js.map +1 -0
  27. package/dist/anchor.d.ts +27 -0
  28. package/dist/anchor.d.ts.map +1 -0
  29. package/dist/anchor.js +70 -0
  30. package/dist/anchor.js.map +1 -0
  31. package/dist/aspect-ratio/parts.d.ts +16 -0
  32. package/dist/aspect-ratio/parts.d.ts.map +1 -0
  33. package/dist/aspect-ratio/parts.js +15 -0
  34. package/dist/aspect-ratio/parts.js.map +1 -0
  35. package/dist/avatar/parts.d.ts +23 -0
  36. package/dist/avatar/parts.d.ts.map +1 -0
  37. package/dist/avatar/parts.js +51 -0
  38. package/dist/avatar/parts.js.map +1 -0
  39. package/dist/bedrock.css +64 -0
  40. package/dist/capabilities.d.ts +25 -0
  41. package/dist/capabilities.d.ts.map +1 -0
  42. package/dist/capabilities.js +40 -0
  43. package/dist/capabilities.js.map +1 -0
  44. package/dist/checkbox/controlled-root.d.ts +18 -0
  45. package/dist/checkbox/controlled-root.d.ts.map +1 -0
  46. package/dist/checkbox/controlled-root.js +28 -0
  47. package/dist/checkbox/controlled-root.js.map +1 -0
  48. package/dist/checkbox/parts.d.ts +29 -0
  49. package/dist/checkbox/parts.d.ts.map +1 -0
  50. package/dist/checkbox/parts.js +42 -0
  51. package/dist/checkbox/parts.js.map +1 -0
  52. package/dist/client-render.d.ts +10 -0
  53. package/dist/client-render.d.ts.map +1 -0
  54. package/dist/client-render.js +14 -0
  55. package/dist/client-render.js.map +1 -0
  56. package/dist/collapsible/controlled-root.d.ts +17 -0
  57. package/dist/collapsible/controlled-root.d.ts.map +1 -0
  58. package/dist/collapsible/controlled-root.js +21 -0
  59. package/dist/collapsible/controlled-root.js.map +1 -0
  60. package/dist/collapsible/parts.d.ts +14 -0
  61. package/dist/collapsible/parts.d.ts.map +1 -0
  62. package/dist/collapsible/parts.js +24 -0
  63. package/dist/collapsible/parts.js.map +1 -0
  64. package/dist/collapsible/root.d.ts +16 -0
  65. package/dist/collapsible/root.d.ts.map +1 -0
  66. package/dist/collapsible/root.js +28 -0
  67. package/dist/collapsible/root.js.map +1 -0
  68. package/dist/collapsible/shared.d.ts +11 -0
  69. package/dist/collapsible/shared.d.ts.map +1 -0
  70. package/dist/collapsible/shared.js +26 -0
  71. package/dist/collapsible/shared.js.map +1 -0
  72. package/dist/compose-refs.d.ts +29 -0
  73. package/dist/compose-refs.d.ts.map +1 -0
  74. package/dist/compose-refs.js +67 -0
  75. package/dist/compose-refs.js.map +1 -0
  76. package/dist/context-menu/root.d.ts +17 -0
  77. package/dist/context-menu/root.d.ts.map +1 -0
  78. package/dist/context-menu/root.js +50 -0
  79. package/dist/context-menu/root.js.map +1 -0
  80. package/dist/controlled.d.ts +120 -0
  81. package/dist/controlled.d.ts.map +1 -0
  82. package/dist/controlled.js +91 -0
  83. package/dist/controlled.js.map +1 -0
  84. package/dist/create-controlled-root.d.ts +29 -0
  85. package/dist/create-controlled-root.d.ts.map +1 -0
  86. package/dist/create-controlled-root.js +92 -0
  87. package/dist/create-controlled-root.js.map +1 -0
  88. package/dist/dialog/controlled-root.d.ts +15 -0
  89. package/dist/dialog/controlled-root.d.ts.map +1 -0
  90. package/dist/dialog/controlled-root.js +21 -0
  91. package/dist/dialog/controlled-root.js.map +1 -0
  92. package/dist/dialog/parts.d.ts +18 -0
  93. package/dist/dialog/parts.d.ts.map +1 -0
  94. package/dist/dialog/parts.js +80 -0
  95. package/dist/dialog/parts.js.map +1 -0
  96. package/dist/dialog/root.d.ts +22 -0
  97. package/dist/dialog/root.d.ts.map +1 -0
  98. package/dist/dialog/root.js +39 -0
  99. package/dist/dialog/root.js.map +1 -0
  100. package/dist/dialog/shared.d.ts +32 -0
  101. package/dist/dialog/shared.d.ts.map +1 -0
  102. package/dist/dialog/shared.js +53 -0
  103. package/dist/dialog/shared.js.map +1 -0
  104. package/dist/dropdown-menu/controlled-root.d.ts +7 -0
  105. package/dist/dropdown-menu/controlled-root.d.ts.map +1 -0
  106. package/dist/dropdown-menu/controlled-root.js +13 -0
  107. package/dist/dropdown-menu/controlled-root.js.map +1 -0
  108. package/dist/dropdown-menu/root.d.ts +13 -0
  109. package/dist/dropdown-menu/root.d.ts.map +1 -0
  110. package/dist/dropdown-menu/root.js +22 -0
  111. package/dist/dropdown-menu/root.js.map +1 -0
  112. package/dist/hover-card/controlled-root.d.ts +9 -0
  113. package/dist/hover-card/controlled-root.d.ts.map +1 -0
  114. package/dist/hover-card/controlled-root.js +15 -0
  115. package/dist/hover-card/controlled-root.js.map +1 -0
  116. package/dist/hover-card/root.d.ts +13 -0
  117. package/dist/hover-card/root.d.ts.map +1 -0
  118. package/dist/hover-card/root.js +19 -0
  119. package/dist/hover-card/root.js.map +1 -0
  120. package/dist/index.d.ts +274 -0
  121. package/dist/index.d.ts.map +1 -0
  122. package/dist/index.js +211 -0
  123. package/dist/index.js.map +1 -0
  124. package/dist/interest.d.ts +30 -0
  125. package/dist/interest.d.ts.map +1 -0
  126. package/dist/interest.js +94 -0
  127. package/dist/interest.js.map +1 -0
  128. package/dist/label/parts.d.ts +14 -0
  129. package/dist/label/parts.d.ts.map +1 -0
  130. package/dist/label/parts.js +21 -0
  131. package/dist/label/parts.js.map +1 -0
  132. package/dist/menu/parts.d.ts +64 -0
  133. package/dist/menu/parts.d.ts.map +1 -0
  134. package/dist/menu/parts.js +131 -0
  135. package/dist/menu/parts.js.map +1 -0
  136. package/dist/menu/shared.d.ts +7 -0
  137. package/dist/menu/shared.d.ts.map +1 -0
  138. package/dist/menu/shared.js +9 -0
  139. package/dist/menu/shared.js.map +1 -0
  140. package/dist/menubar/parts.d.ts +20 -0
  141. package/dist/menubar/parts.d.ts.map +1 -0
  142. package/dist/menubar/parts.js +34 -0
  143. package/dist/menubar/parts.js.map +1 -0
  144. package/dist/navigation-menu/parts.d.ts +28 -0
  145. package/dist/navigation-menu/parts.d.ts.map +1 -0
  146. package/dist/navigation-menu/parts.js +46 -0
  147. package/dist/navigation-menu/parts.js.map +1 -0
  148. package/dist/open-state.d.ts +23 -0
  149. package/dist/open-state.d.ts.map +1 -0
  150. package/dist/open-state.js +106 -0
  151. package/dist/open-state.js.map +1 -0
  152. package/dist/popover/controlled-root.d.ts +14 -0
  153. package/dist/popover/controlled-root.d.ts.map +1 -0
  154. package/dist/popover/controlled-root.js +17 -0
  155. package/dist/popover/controlled-root.js.map +1 -0
  156. package/dist/popover/parts.d.ts +27 -0
  157. package/dist/popover/parts.d.ts.map +1 -0
  158. package/dist/popover/parts.js +39 -0
  159. package/dist/popover/parts.js.map +1 -0
  160. package/dist/popover/root.d.ts +24 -0
  161. package/dist/popover/root.d.ts.map +1 -0
  162. package/dist/popover/root.js +24 -0
  163. package/dist/popover/root.js.map +1 -0
  164. package/dist/popover/shared.d.ts +22 -0
  165. package/dist/popover/shared.d.ts.map +1 -0
  166. package/dist/popover/shared.js +25 -0
  167. package/dist/popover/shared.js.map +1 -0
  168. package/dist/progress/parts.d.ts +25 -0
  169. package/dist/progress/parts.d.ts.map +1 -0
  170. package/dist/progress/parts.js +32 -0
  171. package/dist/progress/parts.js.map +1 -0
  172. package/dist/radio-group/controlled-root.d.ts +19 -0
  173. package/dist/radio-group/controlled-root.d.ts.map +1 -0
  174. package/dist/radio-group/controlled-root.js +29 -0
  175. package/dist/radio-group/controlled-root.js.map +1 -0
  176. package/dist/radio-group/parts.d.ts +36 -0
  177. package/dist/radio-group/parts.d.ts.map +1 -0
  178. package/dist/radio-group/parts.js +62 -0
  179. package/dist/radio-group/parts.js.map +1 -0
  180. package/dist/roving.d.ts +26 -0
  181. package/dist/roving.d.ts.map +1 -0
  182. package/dist/roving.js +124 -0
  183. package/dist/roving.js.map +1 -0
  184. package/dist/scroll-area/parts.d.ts +29 -0
  185. package/dist/scroll-area/parts.d.ts.map +1 -0
  186. package/dist/scroll-area/parts.js +44 -0
  187. package/dist/scroll-area/parts.js.map +1 -0
  188. package/dist/select/parts.d.ts +57 -0
  189. package/dist/select/parts.d.ts.map +1 -0
  190. package/dist/select/parts.js +76 -0
  191. package/dist/select/parts.js.map +1 -0
  192. package/dist/separator/parts.d.ts +14 -0
  193. package/dist/separator/parts.d.ts.map +1 -0
  194. package/dist/separator/parts.js +15 -0
  195. package/dist/separator/parts.js.map +1 -0
  196. package/dist/slider/parts.d.ts +32 -0
  197. package/dist/slider/parts.d.ts.map +1 -0
  198. package/dist/slider/parts.js +52 -0
  199. package/dist/slider/parts.js.map +1 -0
  200. package/dist/slot.d.ts +3 -0
  201. package/dist/slot.d.ts.map +1 -0
  202. package/dist/slot.js +49 -0
  203. package/dist/slot.js.map +1 -0
  204. package/dist/switch/parts.d.ts +21 -0
  205. package/dist/switch/parts.d.ts.map +1 -0
  206. package/dist/switch/parts.js +31 -0
  207. package/dist/switch/parts.js.map +1 -0
  208. package/dist/tabs/controlled-root.d.ts +11 -0
  209. package/dist/tabs/controlled-root.d.ts.map +1 -0
  210. package/dist/tabs/controlled-root.js +15 -0
  211. package/dist/tabs/controlled-root.js.map +1 -0
  212. package/dist/tabs/parts.d.ts +20 -0
  213. package/dist/tabs/parts.d.ts.map +1 -0
  214. package/dist/tabs/parts.js +42 -0
  215. package/dist/tabs/parts.js.map +1 -0
  216. package/dist/tabs/root.d.ts +15 -0
  217. package/dist/tabs/root.d.ts.map +1 -0
  218. package/dist/tabs/root.js +26 -0
  219. package/dist/tabs/root.js.map +1 -0
  220. package/dist/tabs/shared.d.ts +12 -0
  221. package/dist/tabs/shared.d.ts.map +1 -0
  222. package/dist/tabs/shared.js +11 -0
  223. package/dist/tabs/shared.js.map +1 -0
  224. package/dist/toast/parts.d.ts +40 -0
  225. package/dist/toast/parts.d.ts.map +1 -0
  226. package/dist/toast/parts.js +102 -0
  227. package/dist/toast/parts.js.map +1 -0
  228. package/dist/toggle/controlled-root.d.ts +9 -0
  229. package/dist/toggle/controlled-root.d.ts.map +1 -0
  230. package/dist/toggle/controlled-root.js +14 -0
  231. package/dist/toggle/controlled-root.js.map +1 -0
  232. package/dist/toggle/parts.d.ts +20 -0
  233. package/dist/toggle/parts.d.ts.map +1 -0
  234. package/dist/toggle/parts.js +29 -0
  235. package/dist/toggle/parts.js.map +1 -0
  236. package/dist/toggle-group/parts.d.ts +28 -0
  237. package/dist/toggle-group/parts.d.ts.map +1 -0
  238. package/dist/toggle-group/parts.js +57 -0
  239. package/dist/toggle-group/parts.js.map +1 -0
  240. package/dist/toolbar/parts.d.ts +22 -0
  241. package/dist/toolbar/parts.d.ts.map +1 -0
  242. package/dist/toolbar/parts.js +27 -0
  243. package/dist/toolbar/parts.js.map +1 -0
  244. package/dist/tooltip/controlled-root.d.ts +9 -0
  245. package/dist/tooltip/controlled-root.d.ts.map +1 -0
  246. package/dist/tooltip/controlled-root.js +15 -0
  247. package/dist/tooltip/controlled-root.js.map +1 -0
  248. package/dist/tooltip/parts.d.ts +23 -0
  249. package/dist/tooltip/parts.d.ts.map +1 -0
  250. package/dist/tooltip/parts.js +28 -0
  251. package/dist/tooltip/parts.js.map +1 -0
  252. package/dist/tooltip/root.d.ts +13 -0
  253. package/dist/tooltip/root.d.ts.map +1 -0
  254. package/dist/tooltip/root.js +17 -0
  255. package/dist/tooltip/root.js.map +1 -0
  256. package/dist/tooltip/shared.d.ts +13 -0
  257. package/dist/tooltip/shared.d.ts.map +1 -0
  258. package/dist/tooltip/shared.js +9 -0
  259. package/dist/tooltip/shared.js.map +1 -0
  260. package/dist/tooltip/use-controlled-interest-root.d.ts +19 -0
  261. package/dist/tooltip/use-controlled-interest-root.d.ts.map +1 -0
  262. package/dist/tooltip/use-controlled-interest-root.js +31 -0
  263. package/dist/tooltip/use-controlled-interest-root.js.map +1 -0
  264. package/dist/tooltip/use-interest-root.d.ts +16 -0
  265. package/dist/tooltip/use-interest-root.d.ts.map +1 -0
  266. package/dist/tooltip/use-interest-root.js +39 -0
  267. package/dist/tooltip/use-interest-root.js.map +1 -0
  268. package/dist/types.d.ts +55 -0
  269. package/dist/types.d.ts.map +1 -0
  270. package/dist/types.js +2 -0
  271. package/dist/types.js.map +1 -0
  272. package/dist/validate-element.d.ts +12 -0
  273. package/dist/validate-element.d.ts.map +1 -0
  274. package/dist/validate-element.js +21 -0
  275. package/dist/validate-element.js.map +1 -0
  276. package/dist/validate-trigger.d.ts +14 -0
  277. package/dist/validate-trigger.d.ts.map +1 -0
  278. package/dist/validate-trigger.js +59 -0
  279. package/dist/validate-trigger.js.map +1 -0
  280. package/dist/visually-hidden/parts.d.ts +12 -0
  281. package/dist/visually-hidden/parts.d.ts.map +1 -0
  282. package/dist/visually-hidden/parts.js +25 -0
  283. package/dist/visually-hidden/parts.js.map +1 -0
  284. package/package.json +81 -0
  285. package/skills/migrate-to-bedrock/SKILL.md +188 -0
@@ -0,0 +1,188 @@
1
+ ---
2
+ name: migrate-to-bedrock
3
+ description: Migrate a React codebase from Radix UI primitives to @apostel/bedrock. Use when the user asks to migrate off Radix, replace @radix-ui/react-* imports, adopt bedrock, or move a shadcn/ui project onto native platform primitives. Also use when asked why a bedrock trigger throws, why a dialog no longer closes on backdrop click, or why data-state selectors stopped matching.
4
+ ---
5
+
6
+ # Migrating from Radix to bedrock
7
+
8
+ bedrock is Radix-**shaped**: same compound components, same part names, same
9
+ `asChild`. It is built on the top layer, invoker commands, anchor positioning
10
+ and native form controls instead of on JavaScript that reimplements them.
11
+
12
+ That means most of a migration is mechanical, and the parts that are not are
13
+ concentrated in four places. Do the mechanical work first; the four judgement
14
+ calls are what the user needs to actually decide.
15
+
16
+ **Before touching anything, read `docs/gaps.md` in the bedrock repo or at
17
+ <https://bedrock.sams.land/gaps.html> and tell the user what applies to them.**
18
+ Three of the items there stop migrations dead, and finding that out after a
19
+ half-finished refactor is worse than finding out first.
20
+
21
+ ## Step 0 — establish whether this is viable
22
+
23
+ Check these before writing any code. Report what you find; do not decide alone.
24
+
25
+ | check | how | if yes |
26
+ | --- | --- | --- |
27
+ | jsdom component tests that click a trigger | `rg -l 'render\(' --glob '*.test.*'` and look for dialog/menu interaction | **This is the big one.** jsdom has no invoker commands, so those clicks do nothing. Every such test needs rewriting against a real browser. Say so before anything else. |
28
+ | triggers that are not `<button>` | `rg -B2 'Trigger asChild' -A3` | Each one is a build error at mount, not a warning. Router `<Link>` inside a `Dialog.Trigger` is the most common. |
29
+ | `data-state` in CSS, Tailwind config, or shared utilities | `rg 'data-state' --glob '!node_modules'` | Repo-wide selector change. `:open` cannot select an ancestor. |
30
+ | overlays with content in them | `rg -A5 'Dialog.Overlay\|AlertDialog.Overlay'` | `::backdrop` is a pseudo-element. A spinner or close button inside the overlay is a redesign. |
31
+
32
+ If the project has hundreds of jsdom component tests and no Playwright setup,
33
+ that cost dwarfs everything else. Say that plainly rather than starting.
34
+
35
+ ## Step 1 — mechanical changes
36
+
37
+ Safe to apply across the repo.
38
+
39
+ ```diff
40
+ -import * as Dialog from '@radix-ui/react-dialog'
41
+ +import { Dialog } from '@apostel/bedrock'
42
+ ```
43
+
44
+ Then, inside the tree:
45
+
46
+ | Radix | bedrock | why |
47
+ | --- | --- | --- |
48
+ | `<X.Portal>` | delete the wrapper, keep its children | The top layer means there is nothing to portal past. |
49
+ | `<X.Overlay className="…" />` | delete it; move the classes to `backdrop:` on Content | The overlay is `::backdrop`. |
50
+ | `[data-state="open"]` | `:open` | Native state, no JS mirror. |
51
+ | `[data-state="closed"]` | `:not(:open)` | |
52
+ | `[data-state="checked"]` | `:checked` | |
53
+ | `[data-state="on"]` | `[aria-pressed="true"]` | Toggle and ToggleGroup. |
54
+ | `forceMount` | delete it | Content unmounts when closed and there is no opt-out. Check what depended on it. |
55
+ | `data-[state=open]:animate-in` | `open:` + `starting:` + `transition-discrete` | Tailwind 4 has all three built in. |
56
+
57
+ For animation, the shape is always the same:
58
+
59
+ ```tsx
60
+ className="
61
+ scale-95 opacity-0 transition-all transition-discrete duration-150
62
+ open:scale-100 open:opacity-100
63
+ starting:open:scale-95 starting:open:opacity-0
64
+ "
65
+ ```
66
+
67
+ `transition-discrete` is what keeps the element displayed while it fades out;
68
+ without it the exit is skipped. On a top-layer element also transition
69
+ `overlay`, or the exit plays *underneath* whatever was above it.
70
+
71
+ ## Step 2 — the four judgement calls
72
+
73
+ Do not resolve these silently. Each one changes behaviour a user will notice.
74
+
75
+ ### Triggers must be buttons
76
+
77
+ Radix attaches a click handler to whatever you give it. bedrock does not, and
78
+ throws at mount in development.
79
+
80
+ ```diff
81
+ -<Dialog.Trigger asChild><Link href="/upgrade">Upgrade</Link></Dialog.Trigger>
82
+ +<Dialog.Trigger asChild><button type="button" onClick={…}>Upgrade</button></Dialog.Trigger>
83
+ ```
84
+
85
+ The second most common failure is subtler: a `<button>` inside a `<form>`
86
+ defaults to `type="submit"`, and the browser ignores `commandfor` on a submit
87
+ button. Add `type="button"`.
88
+
89
+ If the element genuinely cannot change — a third-party component that renders a
90
+ `div` — `useDialogTrigger()` hands over the props and the accessibility
91
+ responsibility. Use it as a last resort and say so in a comment.
92
+
93
+ ### Light dismiss is gone
94
+
95
+ A modal `<dialog>` does not close on a backdrop click. Users report this as "the
96
+ dialog is stuck". Restore it per dialog if the product wants it:
97
+
98
+ ```tsx
99
+ <Dialog.Content {...{ closedby: 'any' }} />
100
+ ```
101
+
102
+ Ask before applying it globally — for a destructive confirmation, *not*
103
+ dismissing on an outside click is usually the better behaviour.
104
+
105
+ ### `onOpenChange` on the default root cannot refuse
106
+
107
+ It is a `toggle` listener: it reports, it cannot decline.
108
+
109
+ Grep for handlers that conditionally avoid calling `setOpen` — an
110
+ unsaved-changes guard, a "are you sure" interstitial. Those roots, and only
111
+ those, move to the controlled import:
112
+
113
+ ```diff
114
+ -import { Dialog } from '@apostel/bedrock'
115
+ +import { Dialog } from '@apostel/bedrock/controlled'
116
+ ```
117
+
118
+ Everything else keeps the smaller bundle. A handler that just resets a form or
119
+ fires analytics is served by the default root.
120
+
121
+ ### Closed content is unmounted
122
+
123
+ This is usually a *win* — a form inside a dialog resets itself, and any
124
+ `onOpenChange` handler that existed only to reset state can be deleted. Look for
125
+ those and remove them; that is the cleanup the migration pays for.
126
+
127
+ It bites in two places: content that was measured while closed, and an
128
+ animation library driving presence. Both need the state hoisting above the
129
+ dialog.
130
+
131
+ ## Step 3 — per-primitive notes
132
+
133
+ Only what actually differs. Everything not listed is a straight swap.
134
+
135
+ - **Accordion** — `type="single"` is `<details name>`, so an open item can
136
+ always be closed; Radix's `collapsible={false}` has no equivalent. `Header`
137
+ renders the `<summary>` and `Trigger` sits inside it.
138
+ - **Checkbox, Switch, RadioGroup, Slider, Progress** — real elements, so
139
+ `Indicator`, `Thumb`, `Track` and `Range` render nothing. Their styles move to
140
+ `::before`, `::-webkit-slider-thumb`, `::-webkit-progress-value`. Watch for
141
+ icon *components* inside those parts: an `<input>` has no children, so a
142
+ `<CheckIcon />` inside a checkbox has nowhere to go and becomes CSS.
143
+ - **Slider** — one thumb. A two-thumb range has no native equivalent; flag it.
144
+ - **Select** — a real `<select>`. `Select.Value` is `<selectedcontent>`, so
145
+ there is no `placeholder` render prop. On phones this gets the OS picker,
146
+ which is usually desirable and occasionally a design regression.
147
+ - **Tabs** — the unselected panel is unmounted rather than hidden.
148
+ - **Tooltip / HoverCard** — no `Provider`; the delay is a prop on the root. The
149
+ trigger may be an `<a>`.
150
+ - **Menus** — submenus need no configuration, and Escape closes only the topmost
151
+ layer. If the code has layer-ordering workarounds, delete them.
152
+ - **NavigationMenu** — `Viewport` renders nothing.
153
+
154
+ ## Step 4 — verify
155
+
156
+ 1. `npx tsc --noEmit` — the prop-level differences surface here.
157
+ 2. Run the app and open every migrated overlay **in a browser**. The dev throw
158
+ for a bad trigger only fires when the component actually mounts, so a trigger
159
+ behind a rare branch will not appear until it renders.
160
+ 3. Check the console for bedrock warnings: they name the pseudo-element to
161
+ style instead of the part that now renders nothing.
162
+ 4. If the project has jsdom tests for these components, they are now testing
163
+ nothing. Either port them to Playwright or delete them — leaving them green
164
+ and meaningless is the worst outcome.
165
+
166
+ ## shadcn/ui projects
167
+
168
+ There is a registry, so most components are one command rather than a rewrite:
169
+
170
+ ```bash
171
+ npx shadcn@latest add https://bedrock.sams.land/r/dialog.json
172
+ ```
173
+
174
+ 24 items cover everything that was Radix-backed except `menubar`,
175
+ `navigation-menu`, `toast`, `sheet` and `drawer`. Commit before running it — the
176
+ CLI overwrites `components/ui/*.tsx`, and the diff is the thing worth reading.
177
+
178
+ Note that `dialog` from the registry is the **uncontrolled** build and has no
179
+ `open` prop; it throws in development telling you to install
180
+ `dialog-controlled` instead. Install whichever the app actually uses.
181
+
182
+ ## What to tell the user at the end
183
+
184
+ - which files changed, and which of the four judgement calls you applied
185
+ - any trigger you could not fix without changing an element they own
186
+ - whether their tests still test anything
187
+ - the measured bundle difference, if they care: it is in the bedrock README per
188
+ primitive, and it is not uniform — menus save far less than dialogs do