@ui-sdk/web 0.0.0-stage → 0.1.0-beta.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 (467) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +10 -2
  3. package/browser/web/accordion.js +1 -0
  4. package/browser/web/alert-dialog.js +1 -0
  5. package/browser/web/autocomplete.js +1 -0
  6. package/browser/web/avatar.js +1 -0
  7. package/browser/web/button.js +1 -0
  8. package/browser/web/calendar.js +1 -0
  9. package/browser/web/carousel.js +1 -0
  10. package/browser/web/checkbox.js +1 -0
  11. package/browser/web/chunks/0eRGibL6.js +1 -0
  12. package/browser/web/chunks/2bIEUO6_.js +1 -0
  13. package/browser/web/chunks/5A9w2ULz.js +1 -0
  14. package/browser/web/chunks/5RSyydjV.js +1 -0
  15. package/browser/web/chunks/9pWTy2wl.js +1 -0
  16. package/browser/web/chunks/B0f2j6zz.js +1 -0
  17. package/browser/web/chunks/B1rux6br.js +1 -0
  18. package/browser/web/chunks/B3-APaNf.js +1 -0
  19. package/browser/web/chunks/B7EgSgjQ.js +1 -0
  20. package/browser/web/chunks/B8QcOzJU.js +1 -0
  21. package/browser/web/chunks/BFueEkwm.js +3 -0
  22. package/browser/web/chunks/BJj3oS29.js +1 -0
  23. package/browser/web/chunks/BNSvTCpr.js +1 -0
  24. package/browser/web/chunks/BPD0kAZB.js +1 -0
  25. package/browser/web/chunks/BQeduZqT.js +1 -0
  26. package/browser/web/chunks/B_CVFyiz.js +1 -0
  27. package/browser/web/chunks/B_ns1YHO.js +1 -0
  28. package/browser/web/chunks/BcwkVTsR.js +1 -0
  29. package/browser/web/chunks/BeaQ6TYM.js +1 -0
  30. package/browser/web/chunks/BgBB013u.js +1 -0
  31. package/browser/web/chunks/BgsKhHDm.js +1 -0
  32. package/browser/web/chunks/BjMcbuhi.js +9 -0
  33. package/browser/web/chunks/Bjiy_F2N.js +1 -0
  34. package/browser/web/chunks/BqScCLwv.js +1 -0
  35. package/browser/web/chunks/BqX3Utqx.js +1 -0
  36. package/browser/web/chunks/BtVkQf3z.js +1 -0
  37. package/browser/web/chunks/By30h-7B.js +1 -0
  38. package/browser/web/chunks/C1zqYpy2.js +1 -0
  39. package/browser/web/chunks/C436juFf.js +1 -0
  40. package/browser/web/chunks/C4_bULs1.js +1 -0
  41. package/browser/web/chunks/C4omqJEz.js +1 -0
  42. package/browser/web/chunks/C7tt0T3n.js +1 -0
  43. package/browser/web/chunks/C8w7s5dg.js +1 -0
  44. package/browser/web/chunks/CElY4erO.js +1 -0
  45. package/browser/web/chunks/CFODo-m2.js +1 -0
  46. package/browser/web/chunks/CL4wjam9.js +1 -0
  47. package/browser/web/chunks/CMg-8UVf.js +1 -0
  48. package/browser/web/chunks/COWKfakj.js +1 -0
  49. package/browser/web/chunks/CQBU0qZ4.js +1 -0
  50. package/browser/web/chunks/CV6uABgc.js +1 -0
  51. package/browser/web/chunks/C_SWFpWQ.js +1 -0
  52. package/browser/web/chunks/C_Uanbrl.js +1 -0
  53. package/browser/web/chunks/Ce_CSanC.js +1 -0
  54. package/browser/web/chunks/Cg-ZmRhS.js +1 -0
  55. package/browser/web/chunks/Cgf2wPI_.js +1 -0
  56. package/browser/web/chunks/Cuep_3Vf.js +1 -0
  57. package/browser/web/chunks/D02PaHn6.js +1 -0
  58. package/browser/web/chunks/D5LiaI5R.js +1 -0
  59. package/browser/web/chunks/D9UgVYvF.js +1 -0
  60. package/browser/web/chunks/DC08tnkE.js +2 -0
  61. package/browser/web/chunks/DG8YGSvu.js +1 -0
  62. package/browser/web/chunks/DIQd0FYD.js +1 -0
  63. package/browser/web/chunks/DMLqacE8.js +1 -0
  64. package/browser/web/chunks/DSUI3yI-.js +1 -0
  65. package/browser/web/chunks/DT3Hb76X.js +1 -0
  66. package/browser/web/chunks/DY75g3-W.js +1 -0
  67. package/browser/web/chunks/DZpNQKZ9.js +1 -0
  68. package/browser/web/chunks/Db-W1wrC.js +1 -0
  69. package/browser/web/chunks/Dc1xK3Ii.js +1 -0
  70. package/browser/web/chunks/DdF6awFs.js +1 -0
  71. package/browser/web/chunks/DdaCLml8.js +1 -0
  72. package/browser/web/chunks/Dh8MLjQO.js +1 -0
  73. package/browser/web/chunks/DiuslJRy.js +1 -0
  74. package/browser/web/chunks/DjRcw3Y0.js +1 -0
  75. package/browser/web/chunks/Djdv-16J.js +1 -0
  76. package/browser/web/chunks/DnFXe_lF.js +1 -0
  77. package/browser/web/chunks/DneGv1oC.js +1 -0
  78. package/browser/web/chunks/DsVxP-p-.js +1 -0
  79. package/browser/web/chunks/DwDilCo2.js +1 -0
  80. package/browser/web/chunks/DwV48AXv.js +1 -0
  81. package/browser/web/chunks/F37aF4-L.js +1 -0
  82. package/browser/web/chunks/LyB6XIbN.js +1 -0
  83. package/browser/web/chunks/NKnbYGjC.js +1 -0
  84. package/browser/web/chunks/Vk-rVi16.js +1 -0
  85. package/browser/web/chunks/XsM0J1Ex.js +1 -0
  86. package/browser/web/chunks/asSN9iEp.js +1 -0
  87. package/browser/web/chunks/c-AjHfdF.js +1 -0
  88. package/browser/web/chunks/dR-12TPY.js +1 -0
  89. package/browser/web/chunks/hR4xM0EC.js +1 -0
  90. package/browser/web/chunks/hqOaBxAg.js +1 -0
  91. package/browser/web/chunks/kMKqqeG4.js +1 -0
  92. package/browser/web/chunks/kXboBcfi.js +1 -0
  93. package/browser/web/chunks/l4aQIg22.js +1 -0
  94. package/browser/web/chunks/tJC0JSU4.js +1 -0
  95. package/browser/web/chunks/wHdS_QYM.js +1 -0
  96. package/browser/web/chunks/yv8dTvNI.js +1 -0
  97. package/browser/web/chunks/yw9M3nfi.js +1 -0
  98. package/browser/web/collapsible.js +1 -0
  99. package/browser/web/color-picker.js +1 -0
  100. package/browser/web/combobox.js +2 -0
  101. package/browser/web/command.js +1 -0
  102. package/browser/web/core.js +5 -0
  103. package/browser/web/date-input.js +1 -0
  104. package/browser/web/date-picker.js +1 -0
  105. package/browser/web/dialog.js +1 -0
  106. package/browser/web/drawer.js +1 -0
  107. package/browser/web/field.js +1 -0
  108. package/browser/web/file-input.js +1 -0
  109. package/browser/web/hover-card.js +1 -0
  110. package/browser/web/image-cropper.js +1 -0
  111. package/browser/web/input.js +1 -0
  112. package/browser/web/label.js +1 -0
  113. package/browser/web/listbox.js +1 -0
  114. package/browser/web/menu.js +1 -0
  115. package/browser/web/menubar.js +1 -0
  116. package/browser/web/meter.js +1 -0
  117. package/browser/web/navigation-menu.js +1 -0
  118. package/browser/web/number-input.js +1 -0
  119. package/browser/web/otp-input.js +2 -0
  120. package/browser/web/overlays.js +1 -0
  121. package/browser/web/pagination.js +1 -0
  122. package/browser/web/password-input.js +1 -0
  123. package/browser/web/popover.js +1 -0
  124. package/browser/web/preferences.js +1 -0
  125. package/browser/web/progress.js +1 -0
  126. package/browser/web/radio.js +1 -0
  127. package/browser/web/rating.js +1 -0
  128. package/browser/web/resizable.js +1 -0
  129. package/browser/web/scroll-area.js +1 -0
  130. package/browser/web/segmented-control.js +1 -0
  131. package/browser/web/select.js +1 -0
  132. package/browser/web/separator.js +1 -0
  133. package/browser/web/slider.js +1 -0
  134. package/browser/web/switch.js +1 -0
  135. package/browser/web/tabs.js +1 -0
  136. package/browser/web/textarea.js +1 -0
  137. package/browser/web/time-input.js +1 -0
  138. package/browser/web/timeline.js +1 -0
  139. package/browser/web/toast.js +1 -0
  140. package/browser/web/toggle.js +1 -0
  141. package/browser/web/token-input.js +8 -0
  142. package/browser/web/toolbar.js +1 -0
  143. package/browser/web/tooltip.js +1 -0
  144. package/browser/web/tree.js +1 -0
  145. package/browser/web/trigger-menu.js +4 -0
  146. package/browser/web/waterfall.js +1 -0
  147. package/browser/web.css +3378 -0
  148. package/browser/web.js +3 -0
  149. package/custom-elements.json +32315 -0
  150. package/dist/accordion.d.ts +2 -0
  151. package/dist/accordion.js +57 -0
  152. package/dist/action.d.ts +16 -0
  153. package/dist/action.js +39 -0
  154. package/dist/agent.d.ts +2 -0
  155. package/dist/agent.js +7 -0
  156. package/dist/autocomplete.d.ts +2 -0
  157. package/dist/autocomplete.js +120 -0
  158. package/dist/autoload.d.ts +18 -0
  159. package/dist/autoload.js +63 -0
  160. package/dist/avatar.d.ts +2 -0
  161. package/dist/avatar.js +27 -0
  162. package/dist/browser-core.d.ts +2 -0
  163. package/dist/browser-core.js +8 -0
  164. package/dist/button.d.ts +2 -0
  165. package/dist/button.js +35 -0
  166. package/dist/calendar.d.ts +23 -0
  167. package/dist/calendar.js +429 -0
  168. package/dist/carousel.d.ts +2 -0
  169. package/dist/carousel.js +54 -0
  170. package/dist/checkbox.d.ts +4 -0
  171. package/dist/checkbox.js +75 -0
  172. package/dist/collapsible.d.ts +2 -0
  173. package/dist/collapsible.js +28 -0
  174. package/dist/color-picker.d.ts +2 -0
  175. package/dist/color-picker.js +64 -0
  176. package/dist/combobox.d.ts +2 -0
  177. package/dist/combobox.js +334 -0
  178. package/dist/command.d.ts +2 -0
  179. package/dist/command.js +122 -0
  180. package/dist/date-input.d.ts +3 -0
  181. package/dist/date-input.js +87 -0
  182. package/dist/date-picker.d.ts +2 -0
  183. package/dist/date-picker.js +174 -0
  184. package/dist/default-text.d.ts +3 -0
  185. package/dist/default-text.js +24 -0
  186. package/dist/define-element.d.ts +404 -0
  187. package/dist/define-element.js +1489 -0
  188. package/dist/dialog.d.ts +3 -0
  189. package/dist/dialog.js +94 -0
  190. package/dist/drawer.d.ts +2 -0
  191. package/dist/drawer.js +96 -0
  192. package/dist/entries/accordion.d.ts +3 -0
  193. package/dist/entries/accordion.js +7 -0
  194. package/dist/entries/alert-dialog.d.ts +3 -0
  195. package/dist/entries/alert-dialog.js +13 -0
  196. package/dist/entries/autocomplete.d.ts +3 -0
  197. package/dist/entries/autocomplete.js +7 -0
  198. package/dist/entries/avatar.d.ts +2 -0
  199. package/dist/entries/avatar.js +3 -0
  200. package/dist/entries/button.d.ts +2 -0
  201. package/dist/entries/button.js +3 -0
  202. package/dist/entries/calendar.d.ts +3 -0
  203. package/dist/entries/calendar.js +7 -0
  204. package/dist/entries/carousel.d.ts +3 -0
  205. package/dist/entries/carousel.js +7 -0
  206. package/dist/entries/checkbox.d.ts +3 -0
  207. package/dist/entries/checkbox.js +15 -0
  208. package/dist/entries/collapsible.d.ts +3 -0
  209. package/dist/entries/collapsible.js +7 -0
  210. package/dist/entries/color-picker.d.ts +3 -0
  211. package/dist/entries/color-picker.js +7 -0
  212. package/dist/entries/combobox.d.ts +3 -0
  213. package/dist/entries/combobox.js +7 -0
  214. package/dist/entries/command.d.ts +3 -0
  215. package/dist/entries/command.js +7 -0
  216. package/dist/entries/date-input.d.ts +3 -0
  217. package/dist/entries/date-input.js +7 -0
  218. package/dist/entries/date-picker.d.ts +3 -0
  219. package/dist/entries/date-picker.js +7 -0
  220. package/dist/entries/dialog.d.ts +3 -0
  221. package/dist/entries/dialog.js +7 -0
  222. package/dist/entries/drawer.d.ts +3 -0
  223. package/dist/entries/drawer.js +8 -0
  224. package/dist/entries/field.d.ts +2 -0
  225. package/dist/entries/field.js +3 -0
  226. package/dist/entries/file-input.d.ts +3 -0
  227. package/dist/entries/file-input.js +13 -0
  228. package/dist/entries/hover-card.d.ts +3 -0
  229. package/dist/entries/hover-card.js +7 -0
  230. package/dist/entries/image-cropper.d.ts +3 -0
  231. package/dist/entries/image-cropper.js +7 -0
  232. package/dist/entries/input.d.ts +3 -0
  233. package/dist/entries/input.js +8 -0
  234. package/dist/entries/label.d.ts +2 -0
  235. package/dist/entries/label.js +3 -0
  236. package/dist/entries/listbox.d.ts +3 -0
  237. package/dist/entries/listbox.js +7 -0
  238. package/dist/entries/menu.d.ts +3 -0
  239. package/dist/entries/menu.js +7 -0
  240. package/dist/entries/menubar.d.ts +3 -0
  241. package/dist/entries/menubar.js +7 -0
  242. package/dist/entries/meter.d.ts +2 -0
  243. package/dist/entries/meter.js +3 -0
  244. package/dist/entries/navigation-menu.d.ts +3 -0
  245. package/dist/entries/navigation-menu.js +7 -0
  246. package/dist/entries/number-input.d.ts +3 -0
  247. package/dist/entries/number-input.js +7 -0
  248. package/dist/entries/otp-input.d.ts +3 -0
  249. package/dist/entries/otp-input.js +7 -0
  250. package/dist/entries/overlays.d.ts +2 -0
  251. package/dist/entries/overlays.js +3 -0
  252. package/dist/entries/pagination.d.ts +3 -0
  253. package/dist/entries/pagination.js +11 -0
  254. package/dist/entries/password-input.d.ts +3 -0
  255. package/dist/entries/password-input.js +7 -0
  256. package/dist/entries/popover.d.ts +3 -0
  257. package/dist/entries/popover.js +7 -0
  258. package/dist/entries/preferences.d.ts +3 -0
  259. package/dist/entries/preferences.js +13 -0
  260. package/dist/entries/progress.d.ts +2 -0
  261. package/dist/entries/progress.js +3 -0
  262. package/dist/entries/radio.d.ts +3 -0
  263. package/dist/entries/radio.js +7 -0
  264. package/dist/entries/rating.d.ts +3 -0
  265. package/dist/entries/rating.js +8 -0
  266. package/dist/entries/resizable.d.ts +3 -0
  267. package/dist/entries/resizable.js +11 -0
  268. package/dist/entries/scroll-area.d.ts +3 -0
  269. package/dist/entries/scroll-area.js +7 -0
  270. package/dist/entries/segmented-control.d.ts +3 -0
  271. package/dist/entries/segmented-control.js +7 -0
  272. package/dist/entries/select.d.ts +3 -0
  273. package/dist/entries/select.js +7 -0
  274. package/dist/entries/separator.d.ts +2 -0
  275. package/dist/entries/separator.js +3 -0
  276. package/dist/entries/slider.d.ts +3 -0
  277. package/dist/entries/slider.js +7 -0
  278. package/dist/entries/switch.d.ts +3 -0
  279. package/dist/entries/switch.js +7 -0
  280. package/dist/entries/tabs.d.ts +3 -0
  281. package/dist/entries/tabs.js +7 -0
  282. package/dist/entries/textarea.d.ts +3 -0
  283. package/dist/entries/textarea.js +7 -0
  284. package/dist/entries/time-input.d.ts +3 -0
  285. package/dist/entries/time-input.js +7 -0
  286. package/dist/entries/timeline.d.ts +3 -0
  287. package/dist/entries/timeline.js +7 -0
  288. package/dist/entries/toast.d.ts +2 -0
  289. package/dist/entries/toast.js +3 -0
  290. package/dist/entries/toggle.d.ts +3 -0
  291. package/dist/entries/toggle.js +12 -0
  292. package/dist/entries/token-input.d.ts +3 -0
  293. package/dist/entries/token-input.js +17 -0
  294. package/dist/entries/toolbar.d.ts +3 -0
  295. package/dist/entries/toolbar.js +7 -0
  296. package/dist/entries/tooltip.d.ts +3 -0
  297. package/dist/entries/tooltip.js +7 -0
  298. package/dist/entries/tree.d.ts +3 -0
  299. package/dist/entries/tree.js +17 -0
  300. package/dist/entries/trigger-menu.d.ts +3 -0
  301. package/dist/entries/trigger-menu.js +11 -0
  302. package/dist/entries/waterfall.d.ts +3 -0
  303. package/dist/entries/waterfall.js +11 -0
  304. package/dist/events.d.ts +1658 -0
  305. package/dist/events.js +0 -0
  306. package/dist/field.d.ts +2 -0
  307. package/dist/field.js +101 -0
  308. package/dist/file-input.d.ts +2 -0
  309. package/dist/file-input.js +140 -0
  310. package/dist/forward-attributes.d.ts +2 -0
  311. package/dist/forward-attributes.js +22 -0
  312. package/dist/history.d.ts +2 -0
  313. package/dist/history.js +11 -0
  314. package/dist/hover-card.d.ts +2 -0
  315. package/dist/hover-card.js +31 -0
  316. package/dist/image-cropper.d.ts +2 -0
  317. package/dist/image-cropper.js +66 -0
  318. package/dist/index.d.ts +66 -0
  319. package/dist/index.js +269 -0
  320. package/dist/input.d.ts +3 -0
  321. package/dist/input.js +69 -0
  322. package/dist/label.d.ts +2 -0
  323. package/dist/label.js +11 -0
  324. package/dist/list.d.ts +40 -0
  325. package/dist/list.js +192 -0
  326. package/dist/listbox.d.ts +2 -0
  327. package/dist/listbox.js +84 -0
  328. package/dist/menu.d.ts +4 -0
  329. package/dist/menu.js +295 -0
  330. package/dist/menubar.d.ts +4 -0
  331. package/dist/menubar.js +30 -0
  332. package/dist/meter.d.ts +2 -0
  333. package/dist/meter.js +22 -0
  334. package/dist/navigation-menu.d.ts +2 -0
  335. package/dist/navigation-menu.js +114 -0
  336. package/dist/number-input.d.ts +2 -0
  337. package/dist/number-input.js +73 -0
  338. package/dist/observe.d.ts +2 -0
  339. package/dist/observe.js +7 -0
  340. package/dist/otp-input.d.ts +2 -0
  341. package/dist/otp-input.js +72 -0
  342. package/dist/overlays.d.ts +13 -0
  343. package/dist/overlays.js +156 -0
  344. package/dist/pagination.d.ts +2 -0
  345. package/dist/pagination.js +127 -0
  346. package/dist/password-input.d.ts +2 -0
  347. package/dist/password-input.js +57 -0
  348. package/dist/popover.d.ts +2 -0
  349. package/dist/popover.js +64 -0
  350. package/dist/popup.d.ts +40 -0
  351. package/dist/popup.js +112 -0
  352. package/dist/preferences.d.ts +2 -0
  353. package/dist/preferences.js +56 -0
  354. package/dist/progress.d.ts +2 -0
  355. package/dist/progress.js +22 -0
  356. package/dist/radio.d.ts +2 -0
  357. package/dist/radio.js +33 -0
  358. package/dist/rating.d.ts +2 -0
  359. package/dist/rating.js +64 -0
  360. package/dist/resizable.d.ts +2 -0
  361. package/dist/resizable.js +42 -0
  362. package/dist/scroll-area.d.ts +2 -0
  363. package/dist/scroll-area.js +45 -0
  364. package/dist/segmented-control.d.ts +2 -0
  365. package/dist/segmented-control.js +28 -0
  366. package/dist/select.d.ts +2 -0
  367. package/dist/select.js +239 -0
  368. package/dist/separator.d.ts +2 -0
  369. package/dist/separator.js +11 -0
  370. package/dist/slider.d.ts +2 -0
  371. package/dist/slider.js +77 -0
  372. package/dist/tabs.d.ts +2 -0
  373. package/dist/tabs.js +37 -0
  374. package/dist/timeline.d.ts +2 -0
  375. package/dist/timeline.js +27 -0
  376. package/dist/toast.d.ts +20 -0
  377. package/dist/toast.js +177 -0
  378. package/dist/toggle.d.ts +3 -0
  379. package/dist/toggle.js +64 -0
  380. package/dist/token-input.d.ts +2 -0
  381. package/dist/token-input.js +103 -0
  382. package/dist/toolbar.d.ts +2 -0
  383. package/dist/toolbar.js +36 -0
  384. package/dist/tooltip.d.ts +2 -0
  385. package/dist/tooltip.js +35 -0
  386. package/dist/tree.d.ts +2 -0
  387. package/dist/tree.js +147 -0
  388. package/dist/trigger-menu.d.ts +2 -0
  389. package/dist/trigger-menu.js +133 -0
  390. package/dist/waterfall.d.ts +2 -0
  391. package/dist/waterfall.js +140 -0
  392. package/docs/README.md +89 -0
  393. package/docs/agents.md +1523 -0
  394. package/docs/components/accordion.md +1401 -0
  395. package/docs/components/alert-dialog.md +788 -0
  396. package/docs/components/autocomplete.md +1692 -0
  397. package/docs/components/avatar.md +261 -0
  398. package/docs/components/button.md +441 -0
  399. package/docs/components/calendar.md +1536 -0
  400. package/docs/components/carousel.md +1055 -0
  401. package/docs/components/checkbox.md +1045 -0
  402. package/docs/components/collapsible.md +535 -0
  403. package/docs/components/color-picker.md +651 -0
  404. package/docs/components/combobox.md +3274 -0
  405. package/docs/components/command.md +1431 -0
  406. package/docs/components/date-input.md +563 -0
  407. package/docs/components/date-picker.md +1136 -0
  408. package/docs/components/dialog.md +1360 -0
  409. package/docs/components/drawer.md +1201 -0
  410. package/docs/components/field.md +519 -0
  411. package/docs/components/file-input.md +617 -0
  412. package/docs/components/hover-card.md +486 -0
  413. package/docs/components/image-cropper.md +489 -0
  414. package/docs/components/input.md +327 -0
  415. package/docs/components/label.md +208 -0
  416. package/docs/components/listbox.md +1015 -0
  417. package/docs/components/menu.md +1212 -0
  418. package/docs/components/menubar.md +460 -0
  419. package/docs/components/meter.md +298 -0
  420. package/docs/components/navigation-menu.md +1573 -0
  421. package/docs/components/number-input.md +1087 -0
  422. package/docs/components/otp-input.md +558 -0
  423. package/docs/components/pagination.md +639 -0
  424. package/docs/components/password-input.md +649 -0
  425. package/docs/components/popover.md +1433 -0
  426. package/docs/components/preferences.md +243 -0
  427. package/docs/components/progress.md +406 -0
  428. package/docs/components/radio.md +855 -0
  429. package/docs/components/rating.md +431 -0
  430. package/docs/components/resizable.md +866 -0
  431. package/docs/components/scroll-area.md +898 -0
  432. package/docs/components/segmented-control.md +421 -0
  433. package/docs/components/select.md +1957 -0
  434. package/docs/components/separator.md +238 -0
  435. package/docs/components/slider.md +1018 -0
  436. package/docs/components/switch.md +583 -0
  437. package/docs/components/tabs.md +948 -0
  438. package/docs/components/textarea.md +331 -0
  439. package/docs/components/time-input.md +235 -0
  440. package/docs/components/timeline.md +622 -0
  441. package/docs/components/toast.md +1223 -0
  442. package/docs/components/toggle.md +522 -0
  443. package/docs/components/token-input.md +854 -0
  444. package/docs/components/toolbar.md +815 -0
  445. package/docs/components/tooltip.md +860 -0
  446. package/docs/components/tree.md +1243 -0
  447. package/docs/components/trigger-menu.md +1110 -0
  448. package/docs/components/waterfall.md +709 -0
  449. package/docs/controllers.md +66 -0
  450. package/docs/core.md +8027 -0
  451. package/docs/custom-parts.md +91 -0
  452. package/docs/first-component.md +88 -0
  453. package/docs/forms.md +125 -0
  454. package/docs/installation.md +141 -0
  455. package/docs/introduction.md +32 -0
  456. package/docs/lists-and-menus.md +166 -0
  457. package/docs/llms.txt +1566 -0
  458. package/docs/parts-and-composition.md +57 -0
  459. package/docs/popups.md +92 -0
  460. package/docs/rendering.md +71 -0
  461. package/docs/server-rendering.md +118 -0
  462. package/docs/state-and-events.md +134 -0
  463. package/docs/styling-and-animation.md +104 -0
  464. package/docs/utilities/async-options.md +332 -0
  465. package/docs/utilities/shortcuts.md +184 -0
  466. package/html-custom-data.json +14792 -0
  467. package/package.json +302 -3
@@ -0,0 +1,1536 @@
1
+ # Calendar
2
+
3
+ Choose one date, several dates, or a range from a month grid.
4
+
5
+ Calendar shows dates directly on the page. Use it for a booking flow, a schedule, or any place where the dates should stay visible. For a text field that opens a calendar, use [Date Picker](./date-picker.md).
6
+
7
+ ```ts title="basic.ts"
8
+ import "@ui-sdk/web/calendar"
9
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
10
+
11
+ function mount(root: HTMLElement) {
12
+ root.innerHTML = `
13
+ <ui-calendar default-value="2026-10-16">
14
+ <div data-slot="row">
15
+ <ui-calendar-previous>
16
+ <button data-variant="ghost">
17
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
18
+ </button>
19
+ </ui-calendar-previous>
20
+ <ui-calendar-heading></ui-calendar-heading>
21
+ <ui-calendar-next>
22
+ <button data-variant="ghost">
23
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
24
+ </button>
25
+ </ui-calendar-next>
26
+ </div>
27
+ <ui-calendar-grid>
28
+ <ui-calendar-weekdays>
29
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
30
+ </ui-calendar-weekdays>
31
+ <ui-calendar-weeks>
32
+ <template><ui-calendar-day></ui-calendar-day></template>
33
+ </ui-calendar-weeks>
34
+ </ui-calendar-grid>
35
+ </ui-calendar>
36
+ `
37
+ }
38
+
39
+ export { mount }
40
+ ```
41
+
42
+ Choose a date, then use Previous or Next to change the month. Arrow keys move through the days.
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ npm install @ui-sdk/web @ui-sdk/core
48
+ ```
49
+
50
+ Import the component to register its elements. The import is safe on the server.
51
+
52
+ ```ts
53
+ import "@ui-sdk/web/calendar"
54
+ ```
55
+
56
+ ## Composition
57
+
58
+ Use the following composition to build a `ui-calendar`:
59
+
60
+ ```text
61
+ ui-calendar
62
+ ├── ui-calendar-previous
63
+ │ └── button
64
+ ├── ui-calendar-heading
65
+ ├── ui-calendar-next
66
+ │ └── button
67
+ └── ui-calendar-grid
68
+ ├── ui-calendar-weekdays
69
+ │ └── template
70
+ │ └── ui-calendar-weekday
71
+ └── ui-calendar-weeks
72
+ └── template
73
+ └── ui-calendar-day
74
+ ```
75
+
76
+ The heading names the displayed month. Previous and Next change it, and a grid holds its weekday headers and dates. Templates inside the weekdays and weeks are copied for each header and date.
77
+
78
+ An empty `ui-calendar` renders the default structure, and an empty `ui-calendar-grid` renders its headers and weeks. Write the parts out to add your classes and content. Each grid is named by its month and has the `grid` role.
79
+
80
+ ## Usage
81
+
82
+ Import the parts and put them together:
83
+
84
+ ```html
85
+ <script type="module">
86
+ import "@ui-sdk/web/calendar"
87
+ </script>
88
+
89
+ <ui-calendar default-value="2026-10-16">
90
+ <ui-calendar-previous><button>‹</button></ui-calendar-previous>
91
+ <ui-calendar-heading></ui-calendar-heading>
92
+ <ui-calendar-next><button>›</button></ui-calendar-next>
93
+ <ui-calendar-grid>
94
+ <ui-calendar-weekdays>
95
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
96
+ </ui-calendar-weekdays>
97
+ <ui-calendar-weeks>
98
+ <template><ui-calendar-day></ui-calendar-day></template>
99
+ </ui-calendar-weeks>
100
+ </ui-calendar-grid>
101
+ </ui-calendar>
102
+ ```
103
+
104
+ ## Values
105
+
106
+ Dates are ISO strings such as `"2026-10-16"`, with no time or time zone attached. No date library is needed. The [date helpers](../core.md#dates) handle arithmetic; `Intl` formats names and labels.
107
+
108
+ | Mode | Value |
109
+ | -------------------- | ------------------------------- |
110
+ | `"single"` (default) | A date string or `null`. |
111
+ | `"multiple"` | A sorted array of date strings. |
112
+ | `"range"` | `[start, end]` or `null`. |
113
+
114
+ A value with the wrong shape for its mode is treated as no selection.
115
+
116
+ ## Range
117
+
118
+ `mode="range"` chooses a start and then an end. Moving the pointer or focus previews the range; the value changes once both ends are chosen. A date before the start ends it too, and becomes the start. Escape cancels an unfinished range.
119
+
120
+ ```ts title="range.ts"
121
+ import "@ui-sdk/web/calendar"
122
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
123
+
124
+ function mount(root: HTMLElement) {
125
+ root.innerHTML = `
126
+ <ui-calendar mode="range" months="2" month-format="short" default-value="2026-10-12,2026-10-16">
127
+ <div data-slot="row">
128
+ <ui-calendar-previous>
129
+ <button data-variant="ghost">
130
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
131
+ </button>
132
+ </ui-calendar-previous>
133
+ <ui-calendar-heading></ui-calendar-heading>
134
+ <ui-calendar-next>
135
+ <button data-variant="ghost">
136
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
137
+ </button>
138
+ </ui-calendar-next>
139
+ </div>
140
+ <div data-slot="row">
141
+ <ui-calendar-grid offset="0">
142
+ <ui-calendar-weekdays>
143
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
144
+ </ui-calendar-weekdays>
145
+ <ui-calendar-weeks>
146
+ <template><ui-calendar-day></ui-calendar-day></template>
147
+ </ui-calendar-weeks>
148
+ </ui-calendar-grid>
149
+ <ui-calendar-grid offset="1">
150
+ <ui-calendar-weekdays>
151
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
152
+ </ui-calendar-weekdays>
153
+ <ui-calendar-weeks>
154
+ <template><ui-calendar-day></ui-calendar-day></template>
155
+ </ui-calendar-weeks>
156
+ </ui-calendar-grid>
157
+ </div>
158
+ </ui-calendar>
159
+ `
160
+ }
161
+
162
+ export { mount }
163
+ ```
164
+
165
+ Choose a start date, then move to an end date to preview the range. Choose the end to finish, or press Escape to cancel the unfinished range.
166
+
167
+ ## Multiple
168
+
169
+ `mode="multiple"` toggles each date independently. Its value is a sorted array, even when dates were chosen in another order.
170
+
171
+ ```ts title="multiple.ts"
172
+ import "@ui-sdk/web/calendar"
173
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
174
+
175
+ function mount(root: HTMLElement) {
176
+ root.innerHTML = `
177
+ <div>
178
+ <ui-calendar mode="multiple" default-value="2026-10-07,2026-10-16,2026-10-26">
179
+ <div data-slot="row">
180
+ <ui-calendar-previous>
181
+ <button data-variant="ghost">
182
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
183
+ </button>
184
+ </ui-calendar-previous>
185
+ <ui-calendar-heading></ui-calendar-heading>
186
+ <ui-calendar-next>
187
+ <button data-variant="ghost">
188
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
189
+ </button>
190
+ </ui-calendar-next>
191
+ </div>
192
+ <ui-calendar-grid>
193
+ <ui-calendar-weekdays>
194
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
195
+ </ui-calendar-weekdays>
196
+ <ui-calendar-weeks>
197
+ <template><ui-calendar-day></ui-calendar-day></template>
198
+ </ui-calendar-weeks>
199
+ </ui-calendar-grid>
200
+ </ui-calendar>
201
+ </div>
202
+ `
203
+ }
204
+
205
+ export { mount }
206
+ ```
207
+
208
+ Choose another date to add it, or choose a selected date to remove it.
209
+
210
+ ## Unavailable Dates
211
+
212
+ The `unavailable` property accepts a date, `{ from, to }`, `{ weekday: [0, 6] }` for weekends, `{ before }`, `{ after }`, a predicate, or an array of these matchers. Weekday zero is Sunday. The attribute takes comma-separated dates.
213
+
214
+ Unavailable dates cannot be selected, but stay focusable so keyboard and screen reader users can discover them. They expose `aria-disabled` and `data-unavailable`.
215
+
216
+ ```ts title="unavailable.ts"
217
+ import "@ui-sdk/web/calendar"
218
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
219
+
220
+ const unavailable = [
221
+ "2026-10-08",
222
+ { from: "2026-10-20", to: "2026-10-23" },
223
+ { weekday: [0, 6] },
224
+ ]
225
+
226
+ function mount(root: HTMLElement) {
227
+ root.innerHTML = `
228
+ <ui-calendar>
229
+ <div data-slot="row">
230
+ <ui-calendar-previous>
231
+ <button data-variant="ghost">
232
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
233
+ </button>
234
+ </ui-calendar-previous>
235
+ <ui-calendar-heading></ui-calendar-heading>
236
+ <ui-calendar-next>
237
+ <button data-variant="ghost">
238
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
239
+ </button>
240
+ </ui-calendar-next>
241
+ </div>
242
+ <ui-calendar-grid>
243
+ <ui-calendar-weekdays>
244
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
245
+ </ui-calendar-weekdays>
246
+ <ui-calendar-weeks>
247
+ <template><ui-calendar-day></ui-calendar-day></template>
248
+ </ui-calendar-weeks>
249
+ </ui-calendar-grid>
250
+ </ui-calendar>
251
+ `
252
+ root.querySelector("ui-calendar")!.unavailable = unavailable
253
+ }
254
+
255
+ export { mount }
256
+ ```
257
+
258
+ Try an unavailable date, then choose an available one. Unavailable days remain focusable so their state can be heard.
259
+
260
+ ## Deselect
261
+
262
+ `deselectable` lets a second press on the selected date clear it, in single mode. Without it, a second press keeps the date selected.
263
+
264
+ ## Min and Max
265
+
266
+ `min` and `max` set the earliest and latest selectable dates. Dates outside those bounds cannot receive focus. Arrow keys stop at the bounds, and Previous and Next stop at their months.
267
+
268
+ ```ts title="min-max.ts"
269
+ import "@ui-sdk/web/calendar"
270
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
271
+
272
+ function mount(root: HTMLElement) {
273
+ root.innerHTML = `
274
+ <ui-calendar default-value="2026-10-16" min="2026-10-05" max="2026-11-20">
275
+ <div data-slot="row">
276
+ <ui-calendar-previous>
277
+ <button data-variant="ghost">
278
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
279
+ </button>
280
+ </ui-calendar-previous>
281
+ <ui-calendar-heading></ui-calendar-heading>
282
+ <ui-calendar-next>
283
+ <button data-variant="ghost">
284
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
285
+ </button>
286
+ </ui-calendar-next>
287
+ </div>
288
+ <ui-calendar-grid>
289
+ <ui-calendar-weekdays>
290
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
291
+ </ui-calendar-weekdays>
292
+ <ui-calendar-weeks>
293
+ <template><ui-calendar-day></ui-calendar-day></template>
294
+ </ui-calendar-weeks>
295
+ </ui-calendar-grid>
296
+ </ui-calendar>
297
+ `
298
+ }
299
+
300
+ export { mount }
301
+ ```
302
+
303
+ Move toward either end of the allowed dates. Keyboard navigation and month buttons stop at the limits.
304
+
305
+ ## Disabled and Read Only
306
+
307
+ `disabled` prevents focus, selection, and month navigation. `read-only` allows navigation so dates can be inspected, but keeps the selected value unchanged.
308
+
309
+ ```ts title="disabled.ts"
310
+ import "@ui-sdk/web/calendar"
311
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
312
+
313
+ function mount(root: HTMLElement) {
314
+ root.innerHTML = `
315
+ <ui-calendar disabled default-value="2026-10-16">
316
+ <div data-slot="row">
317
+ <ui-calendar-previous>
318
+ <button data-variant="ghost">
319
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
320
+ </button>
321
+ </ui-calendar-previous>
322
+ <ui-calendar-heading></ui-calendar-heading>
323
+ <ui-calendar-next>
324
+ <button data-variant="ghost">
325
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
326
+ </button>
327
+ </ui-calendar-next>
328
+ </div>
329
+ <ui-calendar-grid>
330
+ <ui-calendar-weekdays>
331
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
332
+ </ui-calendar-weekdays>
333
+ <ui-calendar-weeks>
334
+ <template><ui-calendar-day></ui-calendar-day></template>
335
+ </ui-calendar-weeks>
336
+ </ui-calendar-grid>
337
+ </ui-calendar>
338
+ `
339
+ }
340
+
341
+ export { mount }
342
+ ```
343
+
344
+ Try choosing a date or changing months. A disabled calendar has no available controls.
345
+
346
+ ```ts title="read-only.ts"
347
+ import "@ui-sdk/web/calendar"
348
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
349
+
350
+ function mount(root: HTMLElement) {
351
+ root.innerHTML = `
352
+ <ui-calendar read-only default-value="2026-10-16">
353
+ <div data-slot="row">
354
+ <ui-calendar-previous>
355
+ <button data-variant="ghost">
356
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
357
+ </button>
358
+ </ui-calendar-previous>
359
+ <ui-calendar-heading></ui-calendar-heading>
360
+ <ui-calendar-next>
361
+ <button data-variant="ghost">
362
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
363
+ </button>
364
+ </ui-calendar-next>
365
+ </div>
366
+ <ui-calendar-grid>
367
+ <ui-calendar-weekdays>
368
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
369
+ </ui-calendar-weekdays>
370
+ <ui-calendar-weeks>
371
+ <template><ui-calendar-day></ui-calendar-day></template>
372
+ </ui-calendar-weeks>
373
+ </ui-calendar-grid>
374
+ </ui-calendar>
375
+ `
376
+ }
377
+
378
+ export { mount }
379
+ ```
380
+
381
+ Move through dates and months. The selected value stays unchanged.
382
+
383
+ ## Two Months
384
+
385
+ Set `months` to `2` and render grids with offsets `0` and `1`. Arrow keys move between the grids without turning the page. Dates that another visible month already shows leave an empty cell, so each date appears once.
386
+
387
+ `page-by="page"` makes Previous, Next, and navigation beyond the visible grids move by the full number of months.
388
+
389
+ ```ts title="two-months.ts"
390
+ import "@ui-sdk/web/calendar"
391
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
392
+
393
+ function mount(root: HTMLElement) {
394
+ root.innerHTML = `
395
+ <ui-calendar months="2" page-by="page" month-format="short" default-value="2026-10-16">
396
+ <div data-slot="row">
397
+ <ui-calendar-previous>
398
+ <button data-variant="ghost">
399
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
400
+ </button>
401
+ </ui-calendar-previous>
402
+ <ui-calendar-heading></ui-calendar-heading>
403
+ <ui-calendar-next>
404
+ <button data-variant="ghost">
405
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
406
+ </button>
407
+ </ui-calendar-next>
408
+ </div>
409
+ <div data-slot="row">
410
+ <ui-calendar-grid offset="0">
411
+ <ui-calendar-weekdays>
412
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
413
+ </ui-calendar-weekdays>
414
+ <ui-calendar-weeks>
415
+ <template><ui-calendar-day></ui-calendar-day></template>
416
+ </ui-calendar-weeks>
417
+ </ui-calendar-grid>
418
+ <ui-calendar-grid offset="1">
419
+ <ui-calendar-weekdays>
420
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
421
+ </ui-calendar-weekdays>
422
+ <ui-calendar-weeks>
423
+ <template><ui-calendar-day></ui-calendar-day></template>
424
+ </ui-calendar-weeks>
425
+ </ui-calendar-grid>
426
+ </div>
427
+ </ui-calendar>
428
+ `
429
+ }
430
+
431
+ export { mount }
432
+ ```
433
+
434
+ Move between October and November with arrow keys. Next advances both visible months to December and January.
435
+
436
+ ## Fixed Weeks
437
+
438
+ `fixed-weeks` renders six week rows in every month. Set `show-outside-days` to `false` to leave dates from neighboring months blank.
439
+
440
+ ```ts title="fixed-weeks.ts"
441
+ import "@ui-sdk/web/calendar"
442
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
443
+
444
+ function mount(root: HTMLElement) {
445
+ root.innerHTML = `
446
+ <ui-calendar fixed-weeks show-outside-days="false" default-month="2026-02">
447
+ <div data-slot="row">
448
+ <ui-calendar-previous>
449
+ <button data-variant="ghost">
450
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
451
+ </button>
452
+ </ui-calendar-previous>
453
+ <ui-calendar-heading></ui-calendar-heading>
454
+ <ui-calendar-next>
455
+ <button data-variant="ghost">
456
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
457
+ </button>
458
+ </ui-calendar-next>
459
+ </div>
460
+ <ui-calendar-grid>
461
+ <ui-calendar-weekdays>
462
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
463
+ </ui-calendar-weekdays>
464
+ <ui-calendar-weeks>
465
+ <template><ui-calendar-day></ui-calendar-day></template>
466
+ </ui-calendar-weeks>
467
+ </ui-calendar-grid>
468
+ </ui-calendar>
469
+ `
470
+ }
471
+
472
+ export { mount }
473
+ ```
474
+
475
+ Move from February to March. Both grids keep six week rows, and dates outside each month are hidden.
476
+
477
+ ## Month and Year Selects
478
+
479
+ `ui-calendar-month-select` and `ui-calendar-year-select` are native selects for jumping directly to a date, such as a birthday. The year options run from `min` to `max`, or ten years either side of the current year shown when bounds are missing. Months outside the bounds are disabled.
480
+
481
+ They update the first displayed month with the `select-change` reason. Their state exposes the options if you want to render a different select.
482
+
483
+ ```ts title="month-year-select.ts"
484
+ import "@ui-sdk/web/calendar"
485
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
486
+
487
+ function mount(root: HTMLElement) {
488
+ root.innerHTML = `
489
+ <ui-calendar default-month="1990-06" min="1920-01-01" max="2026-10-16">
490
+ <div data-slot="row">
491
+ <ui-calendar-previous>
492
+ <button data-variant="ghost">
493
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
494
+ </button>
495
+ </ui-calendar-previous>
496
+ <ui-calendar-heading></ui-calendar-heading>
497
+ <div data-slot="row">
498
+ <ui-calendar-month-select>
499
+ <select></select>
500
+ </ui-calendar-month-select>
501
+ <ui-calendar-year-select>
502
+ <select></select>
503
+ </ui-calendar-year-select>
504
+ </div>
505
+ <ui-calendar-next>
506
+ <button data-variant="ghost">
507
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
508
+ </button>
509
+ </ui-calendar-next>
510
+ </div>
511
+ <ui-calendar-grid>
512
+ <ui-calendar-weekdays>
513
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
514
+ </ui-calendar-weekdays>
515
+ <ui-calendar-weeks>
516
+ <template><ui-calendar-day></ui-calendar-day></template>
517
+ </ui-calendar-weeks>
518
+ </ui-calendar-grid>
519
+ </ui-calendar>
520
+ `
521
+ }
522
+
523
+ export { mount }
524
+ ```
525
+
526
+ Choose a year and month to jump directly to a birthday, then choose the day.
527
+
528
+ ## Custom Month and Year Selects
529
+
530
+ The month and year selects are native `select` elements. To use your own select, set the calendar's `month` property from your select's value, and update your select on `monthchange`, as with the [Select](./select.md) component here. Keep a `ui-calendar-heading` in the calendar, visually hidden if you like, so the grid stays named.
531
+
532
+ ```ts title="custom-select.ts"
533
+ import "@ui-sdk/web/calendar"
534
+ import "@ui-sdk/web/select"
535
+ import { Check, ChevronDown, createElement } from "lucide"
536
+
537
+ const months = [
538
+ "January",
539
+ "February",
540
+ "March",
541
+ "April",
542
+ "May",
543
+ "June",
544
+ "July",
545
+ "August",
546
+ "September",
547
+ "October",
548
+ "November",
549
+ "December",
550
+ ].map((label, index) => ({ value: String(index + 1).padStart(2, "0"), label }))
551
+ const years = ["2024", "2025", "2026", "2027", "2028"].map((year) => ({
552
+ value: year,
553
+ label: year,
554
+ }))
555
+
556
+ const items = (options: { value: string; label: string }[]) =>
557
+ options
558
+ .map(
559
+ (option) => `
560
+ <ui-select-item value="${option.value}">
561
+ ${option.label}
562
+ <ui-select-item-indicator>${createElement(Check, { "aria-hidden": "true" }).outerHTML}</ui-select-item-indicator>
563
+ </ui-select-item>
564
+ `
565
+ )
566
+ .join("")
567
+
568
+ function mount(root: HTMLElement) {
569
+ root.innerHTML = `
570
+ <ui-calendar default-month="2026-10" default-value="2026-10-16" min="2024-01-01" max="2028-12-31">
571
+ <div data-slot="row">
572
+ <ui-calendar-heading></ui-calendar-heading>
573
+ <ui-select default-value="10">
574
+ <ui-select-trigger>
575
+ <button aria-label="Month">
576
+ <ui-select-value></ui-select-value>
577
+ ${createElement(ChevronDown, { "aria-hidden": "true" }).outerHTML}
578
+ </button>
579
+ </ui-select-trigger>
580
+ <ui-select-content hidden>${items(months)}</ui-select-content>
581
+ </ui-select>
582
+ <ui-select default-value="2026">
583
+ <ui-select-trigger>
584
+ <button aria-label="Year">
585
+ <ui-select-value></ui-select-value>
586
+ ${createElement(ChevronDown, { "aria-hidden": "true" }).outerHTML}
587
+ </button>
588
+ </ui-select-trigger>
589
+ <ui-select-content hidden>${items(years)}</ui-select-content>
590
+ </ui-select>
591
+ </div>
592
+ <ui-calendar-grid>
593
+ <ui-calendar-weekdays>
594
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
595
+ </ui-calendar-weekdays>
596
+ <ui-calendar-weeks>
597
+ <template><ui-calendar-day></ui-calendar-day></template>
598
+ </ui-calendar-weeks>
599
+ </ui-calendar-grid>
600
+ </ui-calendar>
601
+ `
602
+ const calendar = root.querySelector("ui-calendar")!
603
+ const [monthSelect, yearSelect] = root.querySelectorAll("ui-select")
604
+ calendar.addEventListener("monthchange", (event) => {
605
+ const [year, monthOfYear] = event.detail.value.split("-")
606
+ yearSelect.value = year
607
+ monthSelect.value = monthOfYear
608
+ })
609
+ monthSelect.addEventListener("valuechange", (event) => {
610
+ if (event.detail.value) {
611
+ calendar.month = `${calendar.month.split("-")[0]}-${event.detail.value}`
612
+ }
613
+ })
614
+ yearSelect.addEventListener("valuechange", (event) => {
615
+ if (event.detail.value) {
616
+ calendar.month = `${event.detail.value}-${calendar.month.split("-")[1]}`
617
+ }
618
+ })
619
+ }
620
+
621
+ export { mount }
622
+ ```
623
+
624
+ Choose a month or a year from the selects. The calendar follows, and the arrow keys still move between days.
625
+
626
+ ## Language
627
+
628
+ `locale` controls month names, weekday headers, and full date labels. If you omit it, Calendar reads the surrounding page’s `lang` after it mounts, with the browser’s language as a fallback. The initial server and client render use English, so they always match. The first weekday follows the locale, and so changes after mount too, unless `week-starts-on` sets it. `weekday-format` accepts `"narrow"`, `"short"`, or `"long"`.
629
+
630
+ Previous and Next are named "Previous month" and "Next month". Write `aria-label` on their buttons to name them in another language. Dates remain Gregorian. Localized year systems, such as Buddhist or Japanese era years, can still appear in the labels.
631
+
632
+ ```ts title="locale.ts"
633
+ import "@ui-sdk/web/calendar"
634
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
635
+
636
+ function mount(root: HTMLElement) {
637
+ root.innerHTML = `
638
+ <ui-calendar locale="de-DE" week-starts-on="1" weekday-format="narrow" default-value="2026-10-16">
639
+ <div data-slot="row">
640
+ <ui-calendar-previous>
641
+ <button data-variant="ghost">
642
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
643
+ </button>
644
+ </ui-calendar-previous>
645
+ <ui-calendar-heading></ui-calendar-heading>
646
+ <ui-calendar-next>
647
+ <button data-variant="ghost">
648
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
649
+ </button>
650
+ </ui-calendar-next>
651
+ </div>
652
+ <ui-calendar-grid>
653
+ <ui-calendar-weekdays>
654
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
655
+ </ui-calendar-weekdays>
656
+ <ui-calendar-weeks>
657
+ <template><ui-calendar-day></ui-calendar-day></template>
658
+ </ui-calendar-weeks>
659
+ </ui-calendar-grid>
660
+ </ui-calendar>
661
+ `
662
+ }
663
+
664
+ export { mount }
665
+ ```
666
+
667
+ Move through the calendar. Month names, weekday headers, and full date labels are in German, with Monday first.
668
+
669
+ ## Name Formats
670
+
671
+ Set `weekday-format` for the weekday headers and `month-format` for the heading and the month select. Both take `"narrow"`, `"short"` or `"long"`, and follow the calendar's locale. Screen readers still hear full names: the grid and each day keep their long labels.
672
+
673
+ ```ts title="formats.ts"
674
+ import "@ui-sdk/web/calendar"
675
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
676
+
677
+ function mount(root: HTMLElement) {
678
+ root.innerHTML = `
679
+ <ui-calendar months="2" page-by="page" default-value="2026-10-16" weekday-format="narrow" month-format="short">
680
+ <div data-slot="row">
681
+ <ui-calendar-previous>
682
+ <button data-variant="ghost">
683
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
684
+ </button>
685
+ </ui-calendar-previous>
686
+ <ui-calendar-heading></ui-calendar-heading>
687
+ <ui-calendar-next>
688
+ <button data-variant="ghost">
689
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
690
+ </button>
691
+ </ui-calendar-next>
692
+ </div>
693
+ <div data-slot="row">
694
+ <ui-calendar-grid offset="0">
695
+ <ui-calendar-weekdays>
696
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
697
+ </ui-calendar-weekdays>
698
+ <ui-calendar-weeks>
699
+ <template><ui-calendar-day></ui-calendar-day></template>
700
+ </ui-calendar-weeks>
701
+ </ui-calendar-grid>
702
+ <ui-calendar-grid offset="1">
703
+ <ui-calendar-weekdays>
704
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
705
+ </ui-calendar-weekdays>
706
+ <ui-calendar-weeks>
707
+ <template><ui-calendar-day></ui-calendar-day></template>
708
+ </ui-calendar-weeks>
709
+ </ui-calendar-grid>
710
+ </div>
711
+ </ui-calendar>
712
+ `
713
+ }
714
+
715
+ export { mount }
716
+ ```
717
+
718
+ The weekday headers are narrow and the heading uses short month names: Oct – Nov 2026.
719
+
720
+ ## Custom Days
721
+
722
+ Put your own content in the day template and update it from each day’s date and state attributes. This example adds a price below each day. Weekday headers can have custom content too.
723
+
724
+ ```ts title="custom-day.ts"
725
+ import { weekday } from "@ui-sdk/core/date"
726
+ import "@ui-sdk/web/calendar"
727
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
728
+
729
+ const priceOf = (date: string) => 80 + ((Number(date.slice(8)) * 7) % 5) * 10
730
+
731
+ function mount(root: HTMLElement) {
732
+ root.innerHTML = `
733
+ <ui-calendar today="2026-10-16" default-value="2026-10-16">
734
+ <div data-slot="row">
735
+ <ui-calendar-previous>
736
+ <button data-variant="ghost">
737
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
738
+ </button>
739
+ </ui-calendar-previous>
740
+ <ui-calendar-heading></ui-calendar-heading>
741
+ <ui-calendar-next>
742
+ <button data-variant="ghost">
743
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
744
+ </button>
745
+ </ui-calendar-next>
746
+ </div>
747
+ <ui-calendar-grid>
748
+ <ui-calendar-weekdays>
749
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
750
+ </ui-calendar-weekdays>
751
+ <ui-calendar-weeks>
752
+ <template>
753
+ <ui-calendar-day>
754
+ <span data-slot="stack">
755
+ <span data-label></span>
756
+ <span data-slot="hint" data-price></span>
757
+ </span>
758
+ </ui-calendar-day>
759
+ </template>
760
+ </ui-calendar-weeks>
761
+ </ui-calendar-grid>
762
+ </ui-calendar>
763
+ `
764
+ const calendar = root.querySelector("ui-calendar")!
765
+ const decorate = () => {
766
+ for (const day of calendar.querySelectorAll("ui-calendar-day")) {
767
+ const label = day.querySelector("[data-label]")!
768
+ if (label.textContent) {
769
+ continue
770
+ }
771
+ const date = day.getAttribute("value")!
772
+ label.textContent = String(Number(date.slice(8)))
773
+ day.querySelector("[data-price]")!.textContent = `$${priceOf(date)}`
774
+ }
775
+ const firstWeek = calendar.querySelector("ui-calendar-week")
776
+ calendar
777
+ .querySelectorAll("ui-calendar-weekday")
778
+ .forEach((header, index) => {
779
+ const name = header.getAttribute("aria-label")
780
+ const date = firstWeek?.children[index]?.getAttribute("value")
781
+ if (!name || !date || header.firstElementChild) {
782
+ return
783
+ }
784
+ const abbr = document.createElement("abbr")
785
+ abbr.title = name
786
+ abbr.toggleAttribute(
787
+ "data-weekend",
788
+ weekday(date) === 0 || weekday(date) === 6
789
+ )
790
+ abbr.append(...header.childNodes)
791
+ header.append(abbr)
792
+ })
793
+ }
794
+ const observer = new MutationObserver(decorate)
795
+ observer.observe(calendar, {
796
+ childList: true,
797
+ subtree: true,
798
+ attributeFilter: ["aria-label"],
799
+ })
800
+ decorate()
801
+ return () => observer.disconnect()
802
+ }
803
+
804
+ export { mount }
805
+ ```
806
+
807
+ Choose a date to see its price selected with it. Dates outside the current month have no price.
808
+
809
+ ## Range Limits
810
+
811
+ `min-days` and `max-days` count both ends. A three-day range includes its start, the day between, and its end. A press that would break a limit keeps the start, so another end can be chosen. Until then, the dates that can't end the range are disabled.
812
+
813
+ A range cannot include an unavailable date unless `allow-unavailable` is set. That allows unavailable dates between the ends; they still cannot be chosen as an endpoint.
814
+
815
+ ```ts title="range-limits.ts"
816
+ import "@ui-sdk/web/calendar"
817
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
818
+
819
+ function mount(root: HTMLElement) {
820
+ root.innerHTML = `
821
+ <ui-calendar mode="range" min-days="3" max-days="8" unavailable="2026-10-17,2026-10-18" allow-unavailable>
822
+ <div data-slot="row">
823
+ <ui-calendar-previous>
824
+ <button data-variant="ghost">
825
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
826
+ </button>
827
+ </ui-calendar-previous>
828
+ <ui-calendar-heading></ui-calendar-heading>
829
+ <ui-calendar-next>
830
+ <button data-variant="ghost">
831
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
832
+ </button>
833
+ </ui-calendar-next>
834
+ </div>
835
+ <ui-calendar-grid>
836
+ <ui-calendar-weekdays>
837
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
838
+ </ui-calendar-weekdays>
839
+ <ui-calendar-weeks>
840
+ <template><ui-calendar-day></ui-calendar-day></template>
841
+ </ui-calendar-weeks>
842
+ </ui-calendar-grid>
843
+ </ui-calendar>
844
+ `
845
+ }
846
+
847
+ export { mount }
848
+ ```
849
+
850
+ In October 2026, choose October 15 then October 20. This example accepts 3–8 days and allows the range to span the unavailable weekend.
851
+
852
+ ## Range in Progress
853
+
854
+ The root's state has `anchor`, the start while an end is being picked, and `focused`, the date with the Tab stop. `anchorchange` and `focusedchange` events report them before they change, to show the range being picked outside the calendar, and canceling either keeps things as they were. A day that takes focus because the months turned reports nothing.
855
+
856
+ ```ts title="range-progress.ts"
857
+ import "@ui-sdk/web/calendar"
858
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
859
+ import { log } from "./log"
860
+
861
+ function mount(root: HTMLElement) {
862
+ root.innerHTML = `
863
+ <div>
864
+ <ui-calendar mode="range">
865
+ <div data-slot="row">
866
+ <ui-calendar-previous>
867
+ <button data-variant="ghost">
868
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
869
+ </button>
870
+ </ui-calendar-previous>
871
+ <ui-calendar-heading></ui-calendar-heading>
872
+ <ui-calendar-next>
873
+ <button data-variant="ghost">
874
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
875
+ </button>
876
+ </ui-calendar-next>
877
+ </div>
878
+ <ui-calendar-grid>
879
+ <ui-calendar-weekdays>
880
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
881
+ </ui-calendar-weekdays>
882
+ <ui-calendar-weeks>
883
+ <template><ui-calendar-day></ui-calendar-day></template>
884
+ </ui-calendar-weeks>
885
+ </ui-calendar-grid>
886
+ </ui-calendar>
887
+ </div>
888
+ `
889
+ const calendar = root.querySelector("ui-calendar")!
890
+ calendar.addEventListener("anchorchange", (event) => {
891
+ log(
892
+ root,
893
+ event.detail.value ? `Started on ${event.detail.value}` : "Range finished"
894
+ )
895
+ })
896
+ calendar.addEventListener("focusedchange", (event) =>
897
+ log(root, `Focus on ${event.detail.value}`)
898
+ )
899
+ }
900
+
901
+ export { mount }
902
+ ```
903
+
904
+ Choose a start date, then move with the arrow keys. The console logs the start and each focused date until Enter picks the end.
905
+
906
+ ## Controlled
907
+
908
+ The selection and displayed month are separate. `value` uses the shape for the chosen mode; `month` names the first displayed month, such as `"2026-10"`.
909
+
910
+ Read and write the element’s `value` and `month` properties. Listen for `valuechange` and `monthchange`, and call `event.preventDefault()` to reject a change. Use `default-value` and `default-month` for initial state. [State and Events](../state-and-events.md) covers the binding pattern, and the [reason tables](#reasons) list Calendar’s reasons.
911
+
912
+ ```ts title="controlled.ts"
913
+ import "@ui-sdk/web/calendar"
914
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
915
+ import { log } from "./log"
916
+
917
+ function mount(root: HTMLElement) {
918
+ root.innerHTML = `
919
+ <div>
920
+ <ui-calendar default-value="2026-10-16" default-month="2026-10">
921
+ <div data-slot="row">
922
+ <ui-calendar-previous>
923
+ <button data-variant="ghost">
924
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
925
+ </button>
926
+ </ui-calendar-previous>
927
+ <ui-calendar-heading></ui-calendar-heading>
928
+ <ui-calendar-next>
929
+ <button data-variant="ghost">
930
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
931
+ </button>
932
+ </ui-calendar-next>
933
+ </div>
934
+ <ui-calendar-grid>
935
+ <ui-calendar-weekdays>
936
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
937
+ </ui-calendar-weekdays>
938
+ <ui-calendar-weeks>
939
+ <template><ui-calendar-day></ui-calendar-day></template>
940
+ </ui-calendar-weeks>
941
+ </ui-calendar-grid>
942
+ </ui-calendar>
943
+ </div>
944
+ `
945
+ const calendar = root.querySelector("ui-calendar")!
946
+ calendar.addEventListener("valuechange", (event) => {
947
+ log(root, `Selected ${String(event.detail.value)} (${event.detail.reason})`)
948
+ })
949
+ calendar.addEventListener("monthchange", (event) => {
950
+ log(root, `Showing ${event.detail.value} (${event.detail.reason})`)
951
+ })
952
+ }
953
+
954
+ export { mount }
955
+ ```
956
+
957
+ Choose a date or change the month. The console logs each change and its reason.
958
+
959
+ ## Today
960
+
961
+ Today gets `aria-current="date"` and `data-today`. When there is no selection or starting month, the calendar opens on today’s month. It reads today on the client; set `today` to pin a date explicitly.
962
+
963
+ `time-zone` determines which date is today. It does not convert the selected date into another time zone.
964
+
965
+ ```ts title="time-zone.ts"
966
+ import "@ui-sdk/web/calendar"
967
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
968
+
969
+ function mount(root: HTMLElement) {
970
+ root.innerHTML = `
971
+ <div>
972
+ <ui-calendar time-zone="Pacific/Kiritimati">
973
+ <div data-slot="row">
974
+ <ui-calendar-previous>
975
+ <button data-variant="ghost">
976
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
977
+ </button>
978
+ </ui-calendar-previous>
979
+ <ui-calendar-heading></ui-calendar-heading>
980
+ <ui-calendar-next>
981
+ <button data-variant="ghost">
982
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
983
+ </button>
984
+ </ui-calendar-next>
985
+ </div>
986
+ <ui-calendar-grid>
987
+ <ui-calendar-weekdays>
988
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
989
+ </ui-calendar-weekdays>
990
+ <ui-calendar-weeks>
991
+ <template><ui-calendar-day></ui-calendar-day></template>
992
+ </ui-calendar-weeks>
993
+ </ui-calendar-grid>
994
+ </ui-calendar>
995
+ <p data-slot="hint">
996
+ Today is marked as it is in Kiritimati, the first place to see each new day.
997
+ </p>
998
+ </div>
999
+ `
1000
+ }
1001
+
1002
+ export { mount }
1003
+ ```
1004
+
1005
+ Find the marked date for today in Pacific/Kiritimati, which may differ from today in your time zone.
1006
+
1007
+ ## Driving It from Anywhere
1008
+
1009
+ A `createCalendarController()` instance, set as the element's `controller` property, can set the value, change months, and focus a date. The element's `api` has the same methods. Focusing a date in another month shows that month first. [Controllers](../controllers.md) covers setup and lifecycle.
1010
+
1011
+ ```ts title="controller.ts"
1012
+ import { todayDate } from "@ui-sdk/core/date"
1013
+ import { createCalendarController } from "@ui-sdk/web/calendar"
1014
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
1015
+
1016
+ function mount(root: HTMLElement) {
1017
+ root.innerHTML = `
1018
+ <div data-slot="stack">
1019
+ <div data-slot="controls">
1020
+ <button type="button" data-action="christmas">Go to Christmas</button>
1021
+ <button type="button" data-action="today">Today</button>
1022
+ </div>
1023
+ <ui-calendar default-value="2026-10-16" default-month="2026-10">
1024
+ <div data-slot="row">
1025
+ <ui-calendar-previous>
1026
+ <button data-variant="ghost">
1027
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
1028
+ </button>
1029
+ </ui-calendar-previous>
1030
+ <ui-calendar-heading></ui-calendar-heading>
1031
+ <ui-calendar-next>
1032
+ <button data-variant="ghost">
1033
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
1034
+ </button>
1035
+ </ui-calendar-next>
1036
+ </div>
1037
+ <ui-calendar-grid>
1038
+ <ui-calendar-weekdays>
1039
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
1040
+ </ui-calendar-weekdays>
1041
+ <ui-calendar-weeks>
1042
+ <template><ui-calendar-day></ui-calendar-day></template>
1043
+ </ui-calendar-weeks>
1044
+ </ui-calendar-grid>
1045
+ </ui-calendar>
1046
+ </div>
1047
+ `
1048
+ const calendar = createCalendarController()
1049
+ root.querySelector("ui-calendar")!.controller = calendar
1050
+ const select = (date: string) => {
1051
+ calendar.setValue(date)
1052
+ calendar.focus(date)
1053
+ }
1054
+ root
1055
+ .querySelector('[data-action="christmas"]')!
1056
+ .addEventListener("click", () => select("2026-12-25"))
1057
+ root
1058
+ .querySelector('[data-action="today"]')!
1059
+ .addEventListener("click", () => select(todayDate()))
1060
+ }
1061
+
1062
+ export { mount }
1063
+ ```
1064
+
1065
+ Press Go to Christmas to select December 25 and move focus there, or Today to jump back to today and select it.
1066
+
1067
+ ## Presets
1068
+
1069
+ Drive a range calendar from buttons for the ranges people pick most. Each preset sets the value through the [controller](#driving-it-from-anywhere) and moves focus to its first day, using the [date helpers](../core.md#dates) for the arithmetic.
1070
+
1071
+ ```ts title="presets.ts"
1072
+ import { addDays, endOfMonth, startOfMonth } from "@ui-sdk/core/date"
1073
+ import { createCalendarController } from "@ui-sdk/web/calendar"
1074
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
1075
+
1076
+ const today = "2026-10-16"
1077
+ const presets = [
1078
+ { label: "Last 7 Days", range: [addDays(today, -6), today] },
1079
+ { label: "This Month", range: [startOfMonth(today), endOfMonth(today)] },
1080
+ { label: "Next 30 Days", range: [today, addDays(today, 29)] },
1081
+ ] as const
1082
+
1083
+ function mount(root: HTMLElement) {
1084
+ root.innerHTML = `
1085
+ <div data-slot="stack">
1086
+ <div data-slot="controls">
1087
+ ${presets
1088
+ .map(
1089
+ (preset, index) =>
1090
+ `<button type="button" data-size="sm" data-preset="${index}">${preset.label}</button>`
1091
+ )
1092
+ .join("")}
1093
+ </div>
1094
+ <ui-calendar mode="range" today="${today}" default-month="2026-10">
1095
+ <div data-slot="row">
1096
+ <ui-calendar-previous>
1097
+ <button data-variant="ghost">
1098
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
1099
+ </button>
1100
+ </ui-calendar-previous>
1101
+ <ui-calendar-heading></ui-calendar-heading>
1102
+ <ui-calendar-next>
1103
+ <button data-variant="ghost">
1104
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
1105
+ </button>
1106
+ </ui-calendar-next>
1107
+ </div>
1108
+ <ui-calendar-grid>
1109
+ <ui-calendar-weekdays>
1110
+ <template><ui-calendar-weekday></ui-calendar-weekday></template>
1111
+ </ui-calendar-weekdays>
1112
+ <ui-calendar-weeks>
1113
+ <template><ui-calendar-day></ui-calendar-day></template>
1114
+ </ui-calendar-weeks>
1115
+ </ui-calendar-grid>
1116
+ </ui-calendar>
1117
+ </div>
1118
+ `
1119
+ const calendar = createCalendarController()
1120
+ root.querySelector("ui-calendar")!.controller = calendar
1121
+ for (const button of root.querySelectorAll<HTMLElement>("[data-preset]")) {
1122
+ const { range } = presets[Number(button.dataset.preset)]!
1123
+ button.addEventListener("click", () => {
1124
+ calendar.setValue([...range])
1125
+ calendar.focus(range[0])
1126
+ })
1127
+ }
1128
+ }
1129
+
1130
+ export { mount }
1131
+ ```
1132
+
1133
+ Choose Last 7 Days, This Month or Next 30 Days. The range is selected and the calendar shows its start.
1134
+
1135
+ ## Custom Rendering
1136
+
1137
+ Write the header row inside `ui-calendar-weekdays` yourself to add columns of your own, and add cells to the week rows the calendar renders. Custom elements can't build a `<table>`, so the parts carry the table's roles; give your own cells the `columnheader` or `rowheader` role. [Parts and Composition](../parts-and-composition.md) covers changing a part’s element without losing its behavior.
1138
+
1139
+ ```ts title="custom-grid.ts"
1140
+ import { addMonths } from "@ui-sdk/core/date"
1141
+ import "@ui-sdk/web/calendar"
1142
+ import { ChevronLeft, ChevronRight, createElement } from "lucide"
1143
+
1144
+ function isoWeek(date: string) {
1145
+ const day = new Date(`${date}T00:00:00Z`)
1146
+ day.setUTCDate(day.getUTCDate() + 3 - ((day.getUTCDay() + 6) % 7))
1147
+ const firstThursday = new Date(Date.UTC(day.getUTCFullYear(), 0, 4))
1148
+ const days = (day.getTime() - firstThursday.getTime()) / 86400000
1149
+ return 1 + Math.round((days - 3 + ((firstThursday.getUTCDay() + 6) % 7)) / 7)
1150
+ }
1151
+
1152
+ function mount(root: HTMLElement) {
1153
+ root.innerHTML = `
1154
+ <ui-calendar default-value="2026-10-16" week-starts-on="1">
1155
+ <div data-slot="row">
1156
+ <ui-calendar-previous>
1157
+ <button data-variant="ghost">
1158
+ ${createElement(ChevronLeft, { "aria-hidden": "true" }).outerHTML}
1159
+ </button>
1160
+ </ui-calendar-previous>
1161
+ <ui-calendar-heading></ui-calendar-heading>
1162
+ <ui-calendar-next>
1163
+ <button data-variant="ghost">
1164
+ ${createElement(ChevronRight, { "aria-hidden": "true" }).outerHTML}
1165
+ </button>
1166
+ </ui-calendar-next>
1167
+ </div>
1168
+ <ui-calendar-grid>
1169
+ <ui-calendar-weekdays>
1170
+ <div>
1171
+ <span role="columnheader"><abbr title="Week">Wk</abbr></span>
1172
+ ${Array.from({ length: 7 }, (_, index) => `<ui-calendar-weekday index="${index}"></ui-calendar-weekday>`).join("")}
1173
+ </div>
1174
+ </ui-calendar-weekdays>
1175
+ <ui-calendar-weeks>
1176
+ <template><ui-calendar-day></ui-calendar-day></template>
1177
+ </ui-calendar-weeks>
1178
+ </ui-calendar-grid>
1179
+ </ui-calendar>
1180
+ `
1181
+ const calendar = root.querySelector("ui-calendar")!
1182
+ const previous = root.querySelector("ui-calendar-previous button")!
1183
+ const weeks = root.querySelector("ui-calendar-weeks")!
1184
+ const showPrevious = (month: string) => {
1185
+ previous.setAttribute(
1186
+ "title",
1187
+ `Show ${addMonths(`${month}-01`, -1).slice(0, 7)}`
1188
+ )
1189
+ }
1190
+ const numberWeeks = () => {
1191
+ for (const week of weeks.querySelectorAll("ui-calendar-week")) {
1192
+ const date = week
1193
+ .querySelector("ui-calendar-day[value]")
1194
+ ?.getAttribute("value")
1195
+ if (!date) {
1196
+ continue
1197
+ }
1198
+ let header = week.querySelector<HTMLElement>(
1199
+ ':scope > [role="rowheader"]'
1200
+ )
1201
+ if (!header) {
1202
+ header = document.createElement("span")
1203
+ header.setAttribute("role", "rowheader")
1204
+ header.dataset.slot = "hint"
1205
+ }
1206
+ if (week.firstElementChild !== header) {
1207
+ week.prepend(header)
1208
+ }
1209
+ const number = String(isoWeek(date))
1210
+ if (header.textContent !== number) {
1211
+ header.textContent = number
1212
+ }
1213
+ }
1214
+ }
1215
+ const observer = new MutationObserver(numberWeeks)
1216
+ observer.observe(weeks, { childList: true, subtree: true })
1217
+ numberWeeks()
1218
+ showPrevious(calendar.month)
1219
+ calendar.addEventListener("monthchange", (event) =>
1220
+ showPrevious(event.detail.value)
1221
+ )
1222
+ return () => observer.disconnect()
1223
+ }
1224
+
1225
+ export { mount }
1226
+ ```
1227
+
1228
+ The table is your own markup, built from the grid's state. Navigate and select as usual.
1229
+
1230
+ ## Styling
1231
+
1232
+ Use `data-selected`, `data-today`, `data-outside`, `data-unavailable`, and `data-disabled` to style dates. A range also exposes its start, middle, end, and unfinished preview. Navigation buttons also get `data-disabled` when they reach the bounds.
1233
+
1234
+ Each part has a `data-slot`, such as `calendar-day`, and its state attributes are listed under [Data Attributes](#data-attributes).
1235
+
1236
+ ## Data Attributes
1237
+
1238
+ ### `<ui-calendar>`
1239
+ `[data-slot="calendar"]`
1240
+
1241
+ - `data-mode` (`single`, `multiple`, `range`): How the calendar selects.
1242
+ - `data-readonly`: Present when dates can be focused but not picked.
1243
+ - `data-disabled`: Present on the root, grids and selects when the calendar is disabled, on a day that can't be picked, and on Previous or Next at `min` or `max`.
1244
+
1245
+ ### `<ui-calendar-previous>`
1246
+ `[data-slot="calendar-previous"]`
1247
+
1248
+ - `data-disabled`: Present on the root, grids and selects when the calendar is disabled, on a day that can't be picked, and on Previous or Next at `min` or `max`.
1249
+
1250
+ ### `<ui-calendar-next>`
1251
+ `[data-slot="calendar-next"]`
1252
+
1253
+ - `data-disabled`: Present on the root, grids and selects when the calendar is disabled, on a day that can't be picked, and on Previous or Next at `min` or `max`.
1254
+
1255
+ ### `<ui-calendar-month-select>`
1256
+ `[data-slot="calendar-month-select"]`
1257
+
1258
+ - `data-disabled`: Present on the root, grids and selects when the calendar is disabled, on a day that can't be picked, and on Previous or Next at `min` or `max`.
1259
+
1260
+ ### `<ui-calendar-year-select>`
1261
+ `[data-slot="calendar-year-select"]`
1262
+
1263
+ - `data-disabled`: Present on the root, grids and selects when the calendar is disabled, on a day that can't be picked, and on Previous or Next at `min` or `max`.
1264
+
1265
+ ### `<ui-calendar-grid>`
1266
+ `[data-slot="calendar-grid"]`
1267
+
1268
+ - `data-disabled`: Present on the root, grids and selects when the calendar is disabled, on a day that can't be picked, and on Previous or Next at `min` or `max`.
1269
+
1270
+ ### `<ui-calendar-day>`
1271
+ `[data-slot="calendar-day"]`
1272
+
1273
+ - `data-value` (`2026-10-01`, `2026-10-14`, `2026-10-16`, `2026-10-18`, `2026-10-24`, `2026-09-30`): A day's ISO date.
1274
+ - `data-selected`: Present on selected days. In a range, every day from start to end.
1275
+ - `data-today`: Present on today, beside `aria-current="date"`.
1276
+ - `data-disabled`: Present on the root, grids and selects when the calendar is disabled, on a day that can't be picked, and on Previous or Next at `min` or `max`.
1277
+ - `data-unavailable`: Present on a day `unavailable` names. It stays focusable.
1278
+ - `data-outside`: Present on a day of the month before or after its grid's.
1279
+ - `data-range-start`: Present on the day a range starts, including one being picked.
1280
+ - `data-range-middle`: Present on the days inside a range, between its ends.
1281
+ - `data-range-end`: Present on the day a range ends, including one being picked.
1282
+ - `data-preview`: Present on the days of a range being picked, from its start to the day under the pointer or focus.
1283
+
1284
+ ## API Reference
1285
+
1286
+ Props are attributes in kebab case, like `default-value`, `week-starts-on` and `show-outside-days`, and
1287
+ numbers are numbers. `value`, `default-value` and `unavailable` take comma-separated dates, and the
1288
+ `unavailable` property takes any matcher, like `{ weekday: [0, 6] }`. `controller` is a property. Callbacks are DOM events.
1289
+
1290
+ ### Calendar
1291
+
1292
+ - `mode?` (`CalendarMode`): How many dates it selects: one, any number, or a range from a start to an end.
1293
+ - `value?` (`CalendarValue`): The selected value: an ISO date for `"single"`, a list for `"multiple"`, `[start, end]` for `"range"`, or `null`. Pass it to control the calendar.
1294
+ - `defaultValue?` (`CalendarValue`): The value it starts with, when uncontrolled.
1295
+ - `valuechange event`: Runs when a date is pressed (`"item-press"`), with Enter or Space too, or a controller sets the value (`"imperative"`). A range changes once both ends are picked. `details.cancel()` keeps the value.
1296
+ - `month?` (`string`): The first month shown, like `"2026-10"`. Pass it to control the calendar.
1297
+ - `defaultMonth?` (`string`): The first month it shows, when uncontrolled. Defaults to the selected date's month, else today's.
1298
+ - `monthchange event`: Runs when the months shown want to change: Previous or Next, a key moving focus to another month, a press on a day of another month, or a controller.
1299
+ - `min?` (`string`): The earliest date that can be picked. Keys stop there, and Previous stops at its month.
1300
+ - `max?` (`string`): The latest date that can be picked.
1301
+ - `unavailable?` (`DateMatchers`): Dates that can't be picked, like booked days, as data: ISO dates, `{ from, to }`, `{ weekday: [0, 6] }` for weekends, `{ before }`, `{ after }`, or a function. They stay focusable, so keyboard users hear them.
1302
+ - `disabled?` (`boolean`): Whether nothing can be picked or focused.
1303
+ - `readOnly?` (`boolean`): Whether dates can be focused but not picked.
1304
+ - `deselectable?` (`boolean`): Whether pressing the selected date again clears it, in `"single"` mode.
1305
+ - `focusedchange event`: Runs before keys, a press or `focus()` move focus to another day. Canceling keeps focus where it was.
1306
+ - `anchorchange event`: Runs before a press starts a range, or ends it or Escape drops it, with `null`. Canceling keeps the start as it was.
1307
+ - `months?` (`number`): How many months show side by side, each in its own grid.
1308
+ - `pageBy?` (`"month" | "page"`): Whether Previous, Next and keys turn one month, or all the months shown.
1309
+ - `locale?` (`string`): The language of the names and labels. Defaults to the page's `lang`, then the browser's.
1310
+ - `weekStartsOn?` (`number`): The first day of the week, 0 for Sunday. Defaults to the locale's.
1311
+ - `weekdayFormat?` (`"long" | "narrow" | "short"`): How weekday headers are written.
1312
+ - `monthFormat?` (`"long" | "narrow" | "short"`): How month names are written, in the heading and the month select.
1313
+ - `fixedWeeks?` (`boolean`): Whether every month shows six weeks, so the calendar doesn't change height.
1314
+ - `showOutsideDays?` (`boolean`): Whether the days of the months before and after show at the edges.
1315
+ - `today?` (`string`): Today's date, for `aria-current` and the starting month. Defaults to today in `timeZone`, read on the client.
1316
+ - `timeZone?` (`string`): The time zone that says what day today is, like `"America/New_York"`. Defaults to the browser's.
1317
+ - `minDays?` (`number`): The fewest days a range can span, counting both ends.
1318
+ - `maxDays?` (`number`): The most days a range can span, counting both ends.
1319
+ - `allowUnavailable?` (`boolean`): Whether a range may hold unavailable dates, like a stay over a booked night.
1320
+ - `controller?` (`object`): Drives it from anywhere. Create one with `createCalendarController()`.
1321
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
1322
+
1323
+ ### CalendarPrevious
1324
+
1325
+ Shows the previous month. `<ui-calendar-previous>` wraps a `button`, which is disabled at `min`, and makes one if you write none.
1326
+
1327
+ ### CalendarHeading
1328
+
1329
+ Names the months shown, like "October 2026". `<ui-calendar-heading>` is a level 2 heading the grid is labelled by, and screen readers hear it change.
1330
+
1331
+ ### CalendarNext
1332
+
1333
+ Shows the next month. `<ui-calendar-next>` wraps a `button`, which is disabled at `max`, and makes one if you write none.
1334
+
1335
+ ### CalendarMonthSelect
1336
+
1337
+ `<ui-calendar-month-select>` wraps a native `select` of the months, in the locale, that changes the first month shown. It fills a `select` that has no options.
1338
+
1339
+ ### CalendarYearSelect
1340
+
1341
+ `<ui-calendar-year-select>` wraps a native `select` of the years from `min` to `max`, or 10 either side of the year shown. It fills a `select` that has no options.
1342
+
1343
+ ### CalendarGrid
1344
+
1345
+ - `offset?` (`number`): Which of the months shown it is, from 0.
1346
+
1347
+ ### CalendarWeekdays
1348
+
1349
+ `<ui-calendar-weekdays>` renders the header row. A `<template>` inside it holds the `<ui-calendar-weekday>` each column copies.
1350
+
1351
+ ### CalendarWeekday
1352
+
1353
+ - `index` (`number`): The column, from 0.
1354
+
1355
+ ### CalendarWeeks
1356
+
1357
+ `<ui-calendar-weeks>` renders the week rows. A `<template>` inside it holds the `<ui-calendar-day>` each date copies.
1358
+
1359
+ ### CalendarDay
1360
+
1361
+ - `value` (`string`): Its ISO date.
1362
+ - `offset?` (`number`): The offset of the grid it's in.
1363
+
1364
+ ### State
1365
+
1366
+ The same state shows as data attributes on each element.
1367
+
1368
+ `Calendar`:
1369
+
1370
+ - `value` (`CalendarValue`): The selected value.
1371
+ - `month` (`string`): The first month shown.
1372
+ - `mode` (`CalendarMode`): How it selects.
1373
+ - `disabled` (`boolean`): Whether nothing can be picked or focused.
1374
+ - `readOnly` (`boolean`): Whether dates can be focused but not picked.
1375
+ - `focused` (`string | null`): The date with the tab stop, `null` until days render in the browser.
1376
+ - `anchor` (`string | null`): A range's start while its end is being picked, else `null`.
1377
+
1378
+ `CalendarHeading`:
1379
+
1380
+ - `month` (`string`): The first month shown, to format the heading yourself.
1381
+ - `label` (`string`): The months shown in `monthFormat`, like "October 2026", or "October – November 2026".
1382
+
1383
+ `CalendarMonthSelect` and `CalendarYearSelect`:
1384
+
1385
+ - `value` (`number`): The first month shown's month, 1 to 12, or its year.
1386
+ - `options` (`CalendarSelectOption[]`): Every month, or the years from `min` to `max` (10 either side of the year shown without them).
1387
+ - `disabled` (`boolean`): Whether the calendar's `disabled` option is on. Inside a Date Picker, a disabled `<fieldset>` around the picker counts too.
1388
+
1389
+ `CalendarPrevious` and `CalendarNext`:
1390
+
1391
+ - `month` (`string`): The first month it shows.
1392
+ - `disabled` (`boolean`): Whether it can't go further, past `min` or `max`.
1393
+
1394
+ `CalendarGrid`:
1395
+
1396
+ - `month` (`string`): The month it shows: the first month shown, plus its `offset`.
1397
+ - `label` (`string`): The month's name and year in `monthFormat`, like "October 2026".
1398
+ - `weekdays` (`number[]`): The weekday of each column, 0 for Sunday, in the order they show.
1399
+ - `weeks` (`CalendarGridCell[][]`): The weeks, each seven cells. Render a day for each cell that isn't hidden.
1400
+
1401
+ `CalendarWeekday`:
1402
+
1403
+ - `weekday` (`number`): The weekday, 0 for Sunday.
1404
+ - `label` (`string`): Its name, in `weekdayFormat`.
1405
+ - `name` (`string`): Its full name, which screen readers hear.
1406
+
1407
+ `CalendarDay`:
1408
+
1409
+ - `date` (`string`): Its ISO date.
1410
+ - `label` (`string`): The day of the month in the locale's digits, like "16".
1411
+ - `selected` (`boolean`): Whether it's selected. In a range, every date from start to end is.
1412
+ - `today` (`boolean`): Whether it's the `today` option's date, or else today in `timeZone`, read once the calendar runs in the browser.
1413
+ - `outside` (`boolean`): Whether it belongs to the month before or after its grid's.
1414
+ - `disabled` (`boolean`): Whether it can't be picked: outside `min` and `max`, unavailable, unable to end the range being picked, or the calendar is disabled.
1415
+ - `unavailable` (`boolean`): Whether `unavailable` names it.
1416
+ - `rangeStart` (`boolean`): Whether a range starts here, including one being picked.
1417
+ - `rangeEnd` (`boolean`): Whether a range ends here, including one being picked.
1418
+ - `inRange` (`boolean`): Whether it's inside a range, ends included, including one being picked.
1419
+
1420
+ ### Reasons
1421
+
1422
+ `event.detail.reason` says why the value changed:
1423
+
1424
+ - `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
1425
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
1426
+
1427
+ And why the month changed:
1428
+
1429
+ - `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
1430
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
1431
+ - `previous-press` (`MouseEvent | KeyboardEvent`): A pagination's or a carousel's Previous part was pressed.
1432
+ - `next-press` (`MouseEvent | KeyboardEvent`): A pagination's or a carousel's Next part was pressed.
1433
+ - `list-navigation` (`KeyboardEvent`): Arrow, Home or End keys moved the selection, typing picked an item, or Ctrl or ⌘ with A selected every item of a listbox.
1434
+ - `select-change`
1435
+
1436
+ Why focus moves to another day:
1437
+
1438
+ - `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
1439
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
1440
+ - `list-navigation` (`KeyboardEvent`): Arrow, Home or End keys moved the selection, typing picked an item, or Ctrl or ⌘ with A selected every item of a listbox.
1441
+ - `focus-in` (`FocusEvent`): Keyboard focus moved into the component, which stops a rotating carousel, as the carousel pattern asks, or onto a canvas item, which brought it into view, or onto a calendar's day another way, like a screen reader's.
1442
+
1443
+ And why a range's start changed:
1444
+
1445
+ - `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
1446
+ - `escape-key` (`KeyboardEvent`): Escape, or a submenu's close key (ArrowLeft in left-to-right layouts).
1447
+
1448
+ ### Controller
1449
+
1450
+ Create one with `createCalendarController()` and pass it as `controller`. Every write method takes an optional `{ reason, event }` and returns `false` when the change is canceled, there's nowhere to go, or the calendar is disabled or read-only.
1451
+
1452
+ - `getValue()` (`() => CalendarValue`): The selected value: an ISO date, a list of them, or a `[start, end]` range.
1453
+ - `setValue(value, options?)` (`(value: CalendarValue, options?: CalendarChangeOptions<CalendarValueChangeReason> | undefined) => boolean`): Selects a value. Returns false when canceled, unchanged, disabled, read-only, or holding a date people can't pick, like one past `max`.
1454
+ - `getMonth()` (`() => string`): The first month shown, like `"2026-10"`.
1455
+ - `setMonth(month, options?)` (`(month: string, options?: CalendarChangeOptions<CalendarMonthChangeReason> | undefined) => boolean`): Shows a month first. Returns false when canceled or unchanged.
1456
+ - `previous(options?)` (`(options?: CalendarChangeOptions<CalendarMonthChangeReason> | undefined) => boolean`): Shows the months before, as the Previous part does.
1457
+ - `next(options?)` (`(options?: CalendarChangeOptions<CalendarMonthChangeReason> | undefined) => boolean`): Shows the months after, as the Next part does.
1458
+ - `focus(date?)` (`(date?: string | undefined) => void`): Focuses a date, turning the months to show it, or the date with the tab stop.
1459
+
1460
+ ### Events
1461
+
1462
+ Events fire on their own element and don't bubble, with the new value in `event.detail.value` and the `reason` and `event` beside it. Calling `preventDefault()` cancels the change. Type the element as `UiCalendarElement` for typed listeners.
1463
+
1464
+ | Member | On | Description |
1465
+ | --------------------- | ------------- | ----------------------------------------------------------------------------------- |
1466
+ | `value` property | `ui-calendar` | The selected value. Setting it selects it |
1467
+ | `month` property | `ui-calendar` | The first month shown. Setting it turns there |
1468
+ | `api` property | `ui-calendar` | The [controller](#controller) |
1469
+ | `controller` property | `ui-calendar` | A controller from `createCalendarController()` |
1470
+ | `valuechange` event | `ui-calendar` | `detail: { value, reason, event, source }` |
1471
+ | `monthchange` event | `ui-calendar` | `detail: { value: month, reason, event, source }` |
1472
+ | `focusedchange` event | `ui-calendar` | `detail: { value: date, reason, event, source }`, before focus moves to another day |
1473
+ | `anchorchange` event | `ui-calendar` | `detail: { value: start, reason, event, source }`, `null` once the range ends |
1474
+
1475
+ ## Accessibility
1476
+
1477
+ It follows the grid of the [WAI-ARIA date picker dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/): each month is a `grid` named by its month, weekday headers are `columnheader`s named by their full names, and each day is a focusable `gridcell` named by its full date, like "Friday, October 16, 2026", with `aria-selected`, `aria-disabled` and `aria-current="date"`. A grid that selects several dates or a range is `aria-multiselectable`. Every line below is an automated test.
1478
+
1479
+ ### Keyboard
1480
+
1481
+ The grid has one Tab stop: the selected date when visible, otherwise today, otherwise the first available date. Moving into another month shows it and then focuses the date.
1482
+
1483
+ | Key | Action |
1484
+ | ------------------------------- | ----------------------------------------------- |
1485
+ | Left / Right Arrow | Move one day, following the page’s direction. |
1486
+ | Up / Down Arrow | Move one week. |
1487
+ | Home / End | Move to the first or last day of the week. |
1488
+ | Page Up / Page Down | Move one month, keeping the day where possible. |
1489
+ | Shift+Page Up / Shift+Page Down | Move one year. |
1490
+ | Enter / Space | Select the focused date. |
1491
+ | Escape | Cancel an unfinished range. |
1492
+
1493
+ ### Tested Behavior
1494
+
1495
+ - The calendar shows a month as a grid of days under weekday headers.
1496
+ - The selected date holds the tab stop, else today.
1497
+ - Tab leaves the grid after one stop.
1498
+ - Arrow keys move focus by day and week.
1499
+ - Moving past the month's edge shows the next month and focuses the day there.
1500
+ - Page Up and Page Down move by month, and with Shift by year.
1501
+ - The day of the month stays the same, or the month's last, when moving by month.
1502
+ - Home and End go to the first and last day of the week.
1503
+ - The week starts on the locale's first day.
1504
+ - Pressing a day selects it.
1505
+ - Enter on a focused day selects it.
1506
+ - Space on a focused day selects it.
1507
+ - Previous and Next show the month before and after, and the heading announces it.
1508
+ - Min and max stop keys there, and Previous and Next at their months.
1509
+ - Unavailable dates can be focused but not selected.
1510
+ - In multiple mode, presses toggle dates.
1511
+ - In range mode, two presses pick a range, previewed in between.
1512
+ - In range mode, keys preview the end and Escape drops the start.
1513
+ - A range keeps to its fewest and most days, and keeps its start.
1514
+ - Two months show side by side, and focus moves between them without turning.
1515
+ - Pressing a day of the next month selects it and shows its month.
1516
+ - Pressing another month's day before min keeps the month shown.
1517
+ - Pressing another month's day before min leaves focus on the day that had it.
1518
+ - A controlled calendar shows what the app passes.
1519
+ - A controller turns to a date and focuses it.
1520
+ - A disabled calendar has no tab stop and selects nothing.
1521
+ - A read-only calendar moves focus but selects nothing.
1522
+
1523
+ **Month and year selects**
1524
+
1525
+ - They show the first month shown and turn the calendar, with the select-change reason.
1526
+ - Years run from min to max, and months past them are disabled.
1527
+ - They follow the page's language.
1528
+
1529
+ Runs in React, Vue and web components.
1530
+
1531
+ Beyond the baseline:
1532
+
1533
+ - Keys pressed without waiting still land on the right day.
1534
+ - A day of next month takes focus even when its month renders after the key.
1535
+
1536
+ Runs in React, Vue and web components.