plass-ui 1.6.0 → 1.8.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 (373) hide show
  1. package/CHANGELOG.md +494 -0
  2. package/README.md +5 -3
  3. package/dist/components/accordion/PlAccordion.d.ts +19 -1
  4. package/dist/components/accordion/PlAccordion.js +1 -1
  5. package/dist/components/accordion/index.d.ts +1 -1
  6. package/dist/components/alert/PlAlert.js +1 -1
  7. package/dist/components/anchor/PlAnchor.js +1 -1
  8. package/dist/components/animate-appear/PlAnimateAppear.js +1 -1
  9. package/dist/components/animate-counter/PlAnimateCounter.d.ts +5 -2
  10. package/dist/components/animate-counter/PlAnimateCounter.js +1 -1
  11. package/dist/components/animate-float/PlAnimateFloat.js +1 -1
  12. package/dist/components/animate-headline/PlAnimateHeadline.d.ts +10 -1
  13. package/dist/components/animate-headline/PlAnimateHeadline.js +1 -1
  14. package/dist/components/animate-lighting/PlAnimateLighting.js +1 -1
  15. package/dist/components/animate-marquee/PlAnimateMarquee.d.ts +8 -1
  16. package/dist/components/animate-marquee/PlAnimateMarquee.js +1 -1
  17. package/dist/components/animate-scramble/PlAnimateScramble.js +1 -1
  18. package/dist/components/animate-shake/PlAnimateShake.js +1 -1
  19. package/dist/components/animate-split/PlAnimateSplit.js +1 -1
  20. package/dist/components/animate-typing/PlAnimateTyping.js +1 -1
  21. package/dist/components/app-logo/PlAppLogo.d.ts +51 -15
  22. package/dist/components/app-logo/PlAppLogo.js +1 -1
  23. package/dist/components/area-chart/PlAreaChart.d.ts +35 -6
  24. package/dist/components/area-chart/PlAreaChart.js +1 -1
  25. package/dist/components/aspect-ratio/PlAspectRatio.js +1 -1
  26. package/dist/components/avatar/PlAvatar.js +1 -1
  27. package/dist/components/back-top/PlBackTop.d.ts +5 -5
  28. package/dist/components/back-top/PlBackTop.js +1 -1
  29. package/dist/components/badge/PlBadge.js +1 -1
  30. package/dist/components/bar-chart/PlBarChart.d.ts +54 -2
  31. package/dist/components/bar-chart/PlBarChart.js +1 -1
  32. package/dist/components/blockquote/PlBlockquote.d.ts +2 -2
  33. package/dist/components/blockquote/PlBlockquote.js +1 -1
  34. package/dist/components/bottom-navigation/PlBottomNavigation.d.ts +15 -5
  35. package/dist/components/bottom-navigation/PlBottomNavigation.js +1 -1
  36. package/dist/components/breadcrumb/PlBreadcrumb.d.ts +16 -3
  37. package/dist/components/breadcrumb/PlBreadcrumb.js +1 -1
  38. package/dist/components/button/PlButton.d.ts +7 -0
  39. package/dist/components/button/PlButton.js +1 -1
  40. package/dist/components/calendar/PlCalendar.js +1 -1
  41. package/dist/components/card/PlCard.d.ts +9 -5
  42. package/dist/components/card/PlCard.js +1 -1
  43. package/dist/components/carousel/PlCarousel.d.ts +14 -4
  44. package/dist/components/carousel/PlCarousel.js +1 -1
  45. package/dist/components/chat-bubble/PlChatBubble.js +1 -1
  46. package/dist/components/checkbox/PlCheckbox.js +1 -1
  47. package/dist/components/chip/PlChip.d.ts +2 -1
  48. package/dist/components/chip/PlChip.js +1 -1
  49. package/dist/components/code-block/PlCodeBlock.js +1 -1
  50. package/dist/components/collapsible/PlCollapsible.d.ts +11 -0
  51. package/dist/components/collapsible/PlCollapsible.js +1 -1
  52. package/dist/components/color-picker/PlColorPicker.d.ts +3 -1
  53. package/dist/components/color-picker/PlColorPicker.js +1 -1
  54. package/dist/components/combobox/PlCombobox.d.ts +11 -5
  55. package/dist/components/combobox/PlCombobox.js +1 -1
  56. package/dist/components/command-palette/PlCommandPalette.js +1 -1
  57. package/dist/components/confirm/PlConfirmProvider.js +1 -1
  58. package/dist/components/data-table/PlDataTable.d.ts +13 -2
  59. package/dist/components/data-table/PlDataTable.js +1 -1
  60. package/dist/components/date-picker/PlDatePicker.js +1 -1
  61. package/dist/components/date-range-picker/PlDateRangePicker.js +1 -1
  62. package/dist/components/date-time-picker/PlDateTimePicker.js +1 -1
  63. package/dist/components/drawer/PlDrawer.d.ts +27 -2
  64. package/dist/components/drawer/PlDrawer.js +1 -1
  65. package/dist/components/empty/PlEmpty.d.ts +4 -1
  66. package/dist/components/empty/PlEmpty.js +1 -1
  67. package/dist/components/fieldset/PlFieldset.js +1 -1
  68. package/dist/components/file-picker/PlFilePicker.d.ts +32 -1
  69. package/dist/components/file-picker/PlFilePicker.js +1 -1
  70. package/dist/components/file-picker/index.d.ts +1 -1
  71. package/dist/components/floating-action-button/PlFloatingActionButton.d.ts +5 -0
  72. package/dist/components/floating-action-button/PlFloatingActionButton.js +1 -1
  73. package/dist/components/floating-bottom-navigation/PlFloatingBottomNavigation.d.ts +10 -0
  74. package/dist/components/floating-bottom-navigation/PlFloatingBottomNavigation.js +1 -1
  75. package/dist/components/footer/PlFooter.d.ts +3 -1
  76. package/dist/components/footer/PlFooter.js +1 -1
  77. package/dist/components/gallery/PlGallery.js +1 -1
  78. package/dist/components/gauge-chart/PlGaugeChart.d.ts +11 -2
  79. package/dist/components/gauge-chart/PlGaugeChart.js +1 -1
  80. package/dist/components/header/PlHeader.d.ts +4 -2
  81. package/dist/components/header/PlHeader.js +1 -1
  82. package/dist/components/heatmap-chart/PlHeatmapChart.d.ts +1 -1
  83. package/dist/components/heatmap-chart/PlHeatmapChart.js +1 -1
  84. package/dist/components/hover-card/PlHoverCard.js +1 -1
  85. package/dist/components/how-to-steps/PlHowToSteps.js +1 -1
  86. package/dist/components/image/PlImage.js +1 -1
  87. package/dist/components/image/PlImagePreview.d.ts +3 -2
  88. package/dist/components/line-chart/PlLineChart.d.ts +32 -6
  89. package/dist/components/line-chart/PlLineChart.js +1 -1
  90. package/dist/components/list/PlList.d.ts +16 -5
  91. package/dist/components/list/PlList.js +1 -1
  92. package/dist/components/menu/PlMenu.d.ts +19 -7
  93. package/dist/components/menu/PlMenu.js +1 -1
  94. package/dist/components/menubar/PlMenubar.js +1 -1
  95. package/dist/components/meter/PlMeter.js +1 -1
  96. package/dist/components/mockup/PlMockup.d.ts +6 -0
  97. package/dist/components/mockup/PlMockup.js +1 -1
  98. package/dist/components/modal/PlModal.js +1 -1
  99. package/dist/components/navigation-menu/PlNavigationMenu.d.ts +56 -11
  100. package/dist/components/navigation-menu/PlNavigationMenu.js +1 -1
  101. package/dist/components/number-field/PlNumberField.d.ts +7 -5
  102. package/dist/components/number-field/PlNumberField.js +1 -1
  103. package/dist/components/otp-field/PlOtpField.js +1 -1
  104. package/dist/components/overlay/PlOverlay.js +1 -1
  105. package/dist/components/page-layout/PlPageLayout.d.ts +14 -3
  106. package/dist/components/page-layout/PlPageLayout.js +1 -1
  107. package/dist/components/pagination/PlPagination.js +1 -1
  108. package/dist/components/panes/PlPanes.d.ts +4 -1
  109. package/dist/components/panes/PlPanes.js +1 -1
  110. package/dist/components/pie-chart/PlPieChart.d.ts +26 -1
  111. package/dist/components/pie-chart/PlPieChart.js +1 -1
  112. package/dist/components/pill/PlPill.js +1 -1
  113. package/dist/components/popconfirm/PlPopconfirm.d.ts +8 -1
  114. package/dist/components/popconfirm/PlPopconfirm.js +1 -1
  115. package/dist/components/popover/PlPopover.js +1 -1
  116. package/dist/components/progress-box/PlProgressBox.js +1 -1
  117. package/dist/components/progress-circular/PlProgressCircular.d.ts +11 -1
  118. package/dist/components/progress-circular/PlProgressCircular.js +1 -1
  119. package/dist/components/progress-linear/PlProgressLinear.js +1 -1
  120. package/dist/components/radio-group/PlRadioGroup.js +1 -1
  121. package/dist/components/rating/PlRating.js +1 -1
  122. package/dist/components/scatter-chart/PlScatterChart.js +1 -1
  123. package/dist/components/scroll-area/PlScrollArea.js +1 -1
  124. package/dist/components/scroll-zone/PlScrollZone.js +1 -1
  125. package/dist/components/segmented-button/PlSegmentedButton.js +1 -1
  126. package/dist/components/select/PlSelect.d.ts +4 -1
  127. package/dist/components/select/PlSelect.js +1 -1
  128. package/dist/components/sidebar/PlSidebar.d.ts +12 -0
  129. package/dist/components/sidebar/PlSidebar.js +1 -1
  130. package/dist/components/slider/PlSlider.d.ts +2 -1
  131. package/dist/components/slider/PlSlider.js +1 -1
  132. package/dist/components/sparkline/PlSparkline.d.ts +15 -3
  133. package/dist/components/sparkline/PlSparkline.js +1 -1
  134. package/dist/components/stepper/PlStepper.d.ts +11 -0
  135. package/dist/components/stepper/PlStepper.js +1 -1
  136. package/dist/components/switch/PlSwitch.js +1 -1
  137. package/dist/components/table/PlTable.d.ts +11 -1
  138. package/dist/components/table/PlTable.js +1 -1
  139. package/dist/components/tabs/PlTabs.d.ts +18 -1
  140. package/dist/components/tabs/PlTabs.js +1 -1
  141. package/dist/components/text-field/PlTextField.d.ts +6 -8
  142. package/dist/components/text-field/PlTextField.js +1 -1
  143. package/dist/components/text-link/PlTextLink.js +1 -1
  144. package/dist/components/time-picker/PlTimePicker.js +1 -1
  145. package/dist/components/timeline-chart/PlTimelineChart.js +1 -1
  146. package/dist/components/toast/PlToast.js +1 -1
  147. package/dist/components/toggle/PlToggle.js +1 -1
  148. package/dist/components/tooltip/PlTooltip.d.ts +4 -0
  149. package/dist/components/tooltip/PlTooltip.js +1 -1
  150. package/dist/components/tour/PlTour.js +1 -1
  151. package/dist/components/transfer/PlTransfer.d.ts +5 -2
  152. package/dist/components/transfer/PlTransfer.js +1 -1
  153. package/dist/components/tree/PlTree.d.ts +12 -0
  154. package/dist/components/tree/PlTree.js +1 -1
  155. package/dist/components/tree-select/PlTreeSelect.js +1 -1
  156. package/dist/components/typography/PlTypography.d.ts +20 -7
  157. package/dist/components/typography/PlTypography.js +1 -1
  158. package/dist/components/visually-hidden/PlVisuallyHidden.d.ts +12 -3
  159. package/dist/components/visually-hidden/PlVisuallyHidden.js +1 -1
  160. package/dist/components/window-pane/PlWindowPane.d.ts +16 -3
  161. package/dist/components/window-pane/PlWindowPane.js +1 -1
  162. package/dist/css/accordion.css +4 -0
  163. package/dist/css/alert.css +5 -0
  164. package/dist/css/anchor.css +6 -0
  165. package/dist/css/animate-appear.css +3 -0
  166. package/dist/css/animate-blink.css +3 -0
  167. package/dist/css/animate-counter.css +4 -0
  168. package/dist/css/animate-fade.css +3 -0
  169. package/dist/css/animate-float.css +3 -0
  170. package/dist/css/animate-grow.css +3 -0
  171. package/dist/css/animate-headline.css +3 -0
  172. package/dist/css/animate-lighting.css +4 -0
  173. package/dist/css/animate-marquee.css +4 -0
  174. package/dist/css/animate-reveal.css +3 -0
  175. package/dist/css/animate-rotate.css +3 -0
  176. package/dist/css/animate-scramble.css +5 -0
  177. package/dist/css/animate-shake.css +3 -0
  178. package/dist/css/animate-slide.css +3 -0
  179. package/dist/css/animate-split.css +4 -0
  180. package/dist/css/animate-typing.css +4 -0
  181. package/dist/css/animate-zoom.css +3 -0
  182. package/dist/css/app-logo.css +2 -0
  183. package/dist/css/area-chart.css +12 -0
  184. package/dist/css/aspect-ratio.css +2 -0
  185. package/dist/css/avatar.css +2 -0
  186. package/dist/css/back-top.css +11 -0
  187. package/dist/css/badge.css +2 -0
  188. package/dist/css/bar-chart.css +10 -0
  189. package/dist/css/base.css +2 -3
  190. package/dist/css/blockquote.css +2 -0
  191. package/dist/css/bottom-navigation.css +4 -0
  192. package/dist/css/box.css +2 -0
  193. package/dist/css/breadcrumb.css +6 -0
  194. package/dist/css/button-group.css +2 -0
  195. package/dist/css/button.css +7 -0
  196. package/dist/css/calendar.css +17 -0
  197. package/dist/css/card.css +2 -0
  198. package/dist/css/carousel.css +10 -0
  199. package/dist/css/chat-bubble.css +6 -0
  200. package/dist/css/checkbox.css +3 -0
  201. package/dist/css/chip.css +8 -0
  202. package/dist/css/code-block.css +6 -0
  203. package/dist/css/collapsible.css +3 -0
  204. package/dist/css/color-picker.css +17 -0
  205. package/dist/css/combobox.css +10 -0
  206. package/dist/css/command-palette.css +7 -0
  207. package/dist/css/confirm.css +9 -0
  208. package/dist/css/container.css +5 -0
  209. package/dist/css/data-list.css +2 -0
  210. package/dist/css/data-table.css +15 -0
  211. package/dist/css/date-picker.css +16 -0
  212. package/dist/css/date-range-picker.css +16 -0
  213. package/dist/css/date-time-picker.css +16 -0
  214. package/dist/css/divider.css +2 -0
  215. package/dist/css/drawer.css +6 -0
  216. package/dist/css/empty.css +2 -0
  217. package/dist/css/fieldset.css +2 -0
  218. package/dist/css/file-picker.css +8 -0
  219. package/dist/css/flex.css +5 -0
  220. package/dist/css/floating-action-button.css +7 -0
  221. package/dist/css/floating-bottom-navigation.css +5 -0
  222. package/dist/css/footer.css +6 -0
  223. package/dist/css/form.css +2 -0
  224. package/dist/css/gallery.css +15 -0
  225. package/dist/css/gauge-chart.css +11 -0
  226. package/dist/css/grid.css +5 -0
  227. package/dist/css/header.css +6 -0
  228. package/dist/css/heatmap-chart.css +10 -0
  229. package/dist/css/highlight.css +2 -0
  230. package/dist/css/hot-keys.css +3 -0
  231. package/dist/css/hover-card.css +9 -0
  232. package/dist/css/how-to-steps.css +3 -0
  233. package/dist/css/icon-button.css +7 -0
  234. package/dist/css/icon.css +1 -0
  235. package/dist/css/image.css +7 -0
  236. package/dist/css/line-chart.css +12 -0
  237. package/dist/css/list.css +2 -0
  238. package/dist/css/menu.css +5 -0
  239. package/dist/css/menubar.css +5 -0
  240. package/dist/css/meter.css +6 -0
  241. package/dist/css/mockup.css +4 -0
  242. package/dist/css/modal.css +5 -0
  243. package/dist/css/navigation-menu.css +4 -0
  244. package/dist/css/number-field.css +10 -0
  245. package/dist/css/otp-field.css +6 -0
  246. package/dist/css/overlay.css +4 -0
  247. package/dist/css/page-layout.css +7 -0
  248. package/dist/css/pagination.css +8 -0
  249. package/dist/css/panes.css +7 -0
  250. package/dist/css/pie-chart.css +10 -0
  251. package/dist/css/pill.css +4 -0
  252. package/dist/css/popconfirm.css +12 -0
  253. package/dist/css/popover.css +11 -0
  254. package/dist/css/portal.css +1 -0
  255. package/dist/css/progress-box.css +5 -0
  256. package/dist/css/progress-circular.css +5 -0
  257. package/dist/css/progress-linear.css +5 -0
  258. package/dist/css/radio-group.css +3 -0
  259. package/dist/css/rating.css +6 -0
  260. package/dist/css/scatter-chart.css +10 -0
  261. package/dist/css/scroll-area.css +2 -0
  262. package/dist/css/scroll-zone.css +15 -0
  263. package/dist/css/segmented-button.css +5 -0
  264. package/dist/css/select.css +9 -0
  265. package/dist/css/show.css +1 -0
  266. package/dist/css/sidebar.css +16 -0
  267. package/dist/css/skeleton.css +2 -0
  268. package/dist/css/slider.css +3 -0
  269. package/dist/css/sparkline.css +10 -0
  270. package/dist/css/spoiler.css +10 -0
  271. package/dist/css/stack.css +5 -0
  272. package/dist/css/stat.css +2 -0
  273. package/dist/css/stepper.css +8 -0
  274. package/dist/css/switch.css +3 -0
  275. package/dist/css/table.css +3 -0
  276. package/dist/css/tabs.css +7 -0
  277. package/dist/css/text-field.css +8 -0
  278. package/dist/css/text-link.css +6 -0
  279. package/dist/css/time-picker.css +16 -0
  280. package/dist/css/timeline-chart.css +10 -0
  281. package/dist/css/timeline.css +6 -0
  282. package/dist/css/toast.css +5 -0
  283. package/dist/css/toggle.css +5 -0
  284. package/dist/css/toolbar.css +2 -0
  285. package/dist/css/tooltip.css +9 -0
  286. package/dist/css/tour.css +10 -0
  287. package/dist/css/transfer.css +13 -0
  288. package/dist/css/tree-select.css +14 -0
  289. package/dist/css/tree.css +3 -0
  290. package/dist/css/visually-hidden.css +2 -0
  291. package/dist/css/window-pane.css +9 -0
  292. package/dist/internal/animate.d.ts +59 -4
  293. package/dist/internal/animate.js +1 -1
  294. package/dist/internal/bottom-bar.d.ts +29 -0
  295. package/dist/internal/bottom-bar.js +1 -0
  296. package/dist/internal/calendar.js +1 -1
  297. package/dist/internal/chart-frame.d.ts +216 -19
  298. package/dist/internal/chart-frame.js +1 -1
  299. package/dist/internal/chart-line.d.ts +11 -6
  300. package/dist/internal/chart-line.js +1 -1
  301. package/dist/internal/chart.d.ts +149 -1
  302. package/dist/internal/chart.js +1 -1
  303. package/dist/internal/commit-change.d.ts +38 -0
  304. package/dist/internal/commit-change.js +1 -0
  305. package/dist/internal/data-table.d.ts +2 -2
  306. package/dist/internal/data-table.js +1 -1
  307. package/dist/internal/date.d.ts +9 -0
  308. package/dist/internal/date.js +1 -1
  309. package/dist/internal/defaults.d.ts +36 -3
  310. package/dist/internal/defaults.js +1 -1
  311. package/dist/internal/direction.d.ts +8 -2
  312. package/dist/internal/direction.js +1 -1
  313. package/dist/internal/focusable.d.ts +10 -0
  314. package/dist/internal/focusable.js +1 -1
  315. package/dist/internal/form.d.ts +72 -0
  316. package/dist/internal/form.js +1 -1
  317. package/dist/internal/format.d.ts +4 -1
  318. package/dist/internal/format.js +1 -1
  319. package/dist/internal/gallery.d.ts +28 -0
  320. package/dist/internal/gallery.js +1 -1
  321. package/dist/internal/glow.d.ts +39 -3
  322. package/dist/internal/glow.js +1 -1
  323. package/dist/internal/heading.d.ts +11 -0
  324. package/dist/internal/heading.js +1 -0
  325. package/dist/internal/highlight.d.ts +4 -2
  326. package/dist/internal/highlight.js +1 -1
  327. package/dist/internal/icons.d.ts +24 -0
  328. package/dist/internal/icons.js +1 -1
  329. package/dist/internal/labels.d.ts +27 -0
  330. package/dist/internal/labels.js +1 -1
  331. package/dist/internal/layout-box.d.ts +35 -0
  332. package/dist/internal/layout-box.js +1 -0
  333. package/dist/internal/lazy.d.ts +37 -0
  334. package/dist/internal/lazy.js +1 -0
  335. package/dist/internal/loading.d.ts +13 -0
  336. package/dist/internal/loading.js +1 -0
  337. package/dist/internal/media.d.ts +6 -0
  338. package/dist/internal/media.js +1 -1
  339. package/dist/internal/mockup.js +1 -1
  340. package/dist/internal/notch.d.ts +57 -3
  341. package/dist/internal/notch.js +1 -1
  342. package/dist/internal/page-layout.d.ts +24 -13
  343. package/dist/internal/page-layout.js +1 -1
  344. package/dist/internal/picker.d.ts +8 -2
  345. package/dist/internal/picker.js +1 -1
  346. package/dist/internal/progress.d.ts +41 -6
  347. package/dist/internal/progress.js +1 -1
  348. package/dist/internal/search.d.ts +12 -0
  349. package/dist/internal/search.js +1 -1
  350. package/dist/internal/sizer.d.ts +18 -10
  351. package/dist/internal/sizer.js +1 -1
  352. package/dist/internal/steps.d.ts +0 -15
  353. package/dist/internal/steps.js +1 -1
  354. package/dist/internal/styles.d.ts +136 -14
  355. package/dist/internal/styles.js +1 -1
  356. package/dist/internal/table-scroll.d.ts +42 -0
  357. package/dist/internal/table-scroll.js +1 -0
  358. package/dist/internal/table.js +1 -1
  359. package/dist/internal/text.d.ts +8 -5
  360. package/dist/internal/wheel.d.ts +13 -1
  361. package/dist/internal/wheel.js +1 -1
  362. package/dist/internal/window.js +1 -1
  363. package/dist/locales/de.js +1 -1
  364. package/dist/locales/es.js +1 -1
  365. package/dist/locales/fr.js +1 -1
  366. package/dist/locales/ja.js +1 -1
  367. package/dist/locales/ko.js +1 -1
  368. package/dist/locales/zh-Hans.js +1 -1
  369. package/dist/styles.css +1 -1
  370. package/dist/tailwind.css +1163 -81
  371. package/dist/tokens.css +1163 -81
  372. package/dist/types.d.ts +226 -13
  373. package/package.json +18 -17
package/CHANGELOG.md CHANGED
@@ -4,6 +4,500 @@
4
4
 
5
5
  ## vNext (2026--)
6
6
 
7
+ ## 1.8.0 (2026-10-07)
8
+
9
+ ### Breaking changes
10
+
11
+ - **An `aria-label` given beside a visible `label` names the control in the label's place.** On a `PlTextField`, a `PlCheckbox`, a `PlSwitch`, a `PlRadio`, a `PlRadioGroup` and an `inline` `PlColorPicker`, Base UI pointed the control's `aria-labelledby` at the visible label, which outranks an `aria-label`, so the `aria-label` was ignored, where a native control, ARIA and the Flutter build's `semanticLabel` all let it win. It now names the control, and the label's words are not read after it; a `PlRadioGroup` or a `PlSegmentedButton` inside a `PlFieldset` takes it in the legend's place the same way. A caller's `aria-labelledby` still outranks both, and a control with no `aria-label` is named as before. Remove an `aria-label` that was only there beside a `label`, or make it contain the label's words, so a reader who says the visible label to a voice-control tool still reaches the control.
12
+ - **An `aria-label` given beside a visible `label` names a meter or a progress indicator in the label's place.** On a `PlMeter`, a `PlProgressLinear`, a `PlProgressCircular` and a `PlProgressBox`, Base UI pointed the root's `aria-labelledby` at the visible label, which outranks an `aria-label`, so the `aria-label` was ignored, where a native `<meter>` or `<progress>`, ARIA and every field control in this package let it win. It now names the meter or the indicator, and the label's words are not read after it. A caller's `aria-labelledby` still outranks both, and an indicator with no `aria-label` is named as before. Remove an `aria-label` that was only there beside a `label`, or make it contain the label's words, so a reader who sees the label and hears the name can tell both belong to the same indicator.
13
+ - **`PlNavigationMenuItemProps` is a union of a link item and an item that opens a panel.** That is what lets a link item take `render` while an item with children refuses it. A type that extended it with `interface … extends` becomes `type … = PlNavigationMenuItemProps & {…}`, and an `Omit<…>` of it now applies to each half; a call to the component is unchanged.
14
+
15
+ - **A `PlAppLogo` with `src` and no `name` needs `alt`.** Such a logo drew its picture with an empty `alt`, so rendered as the link home it was a link with no name. `PlAppLogoProps` is now a union, and TypeScript reports a `src` with neither: write what the picture says, or `alt=""` for a decorative picture. A call with a `name` is unchanged, artwork passed as `children` is not asked for `alt`, and a JavaScript caller still gets `alt=""`. A type that extended `PlAppLogoProps` with `interface … extends` becomes `type … = PlAppLogoProps & {…}`.
16
+
17
+ ### Added
18
+
19
+ - **`PlVisuallyHidden` takes a `headingLevel`, which renders `<h1>`–`<h6>` in place of the `<span>`.** A heading that structures the page for a screen reader without appearing in the design took `render={<h2 />}`, and `eslint-plugin-jsx-a11y`'s `heading-has-content` reports that empty element on every use. `headingLevel={2}` now renders the `<h2>` itself, clipped like any other content and still revealed by `focusable`. A value outside 1–6 is ignored, and `render` still wins when both are given.
20
+ - **`PlProgressCircular` takes `diameter`, a number of pixels the ring is drawn at instead of `size`'s rung.** The ring's diameters stopped at 32px, so a ring for a loading screen had to override the `<svg>`'s `width` and `height` with CSS, outside the API and broken the moment the markup changed. `diameter={96}` now draws the ring 96 pixels across with the stroke thickened in the ladder's own proportion, while `size` still sets the gap and the text beside it. A value that is not a finite number above zero is ignored, and a ring without it is drawn as before.
21
+ - **`PlFilePicker` takes `onAdd`, which hands over what one drop or dialog pick kept and what it turned away together.** `onReject` and `onFilesChange` were called one after the other for the same drop and neither knew about the other, so an error shown only when a drop produced nothing usable took a flag set in one, cleared in the other and read in a microtask, relying on an order the docs never stated. `onAdd` is called once per batch, after the other two, with a `PlFileBatch` of `kept`, `rejected` and `source` (`'drop'` or `'dialog'`). A drag that carried no file, and a removal, call nothing.
22
+ - **`PlTypography` takes a `headingLevel`, which renders `<h1>`–`<h6>` without changing the type scale `level` picks.** Keeping a heading's size while changing its level took `render={<h2 />}`, and `eslint-plugin-jsx-a11y`'s `heading-has-content` reports that empty element on every use. `headingLevel={2}` now renders the `<h2>` itself. It works on any `level`, so `level="body"` with `headingLevel={2}` is an `<h2>` at body size. A value outside 1–6 is ignored, and `render` still wins when both are given.
23
+ - **`PlFilePicker` takes `preventDocumentDrop`, so a file let go beside the box does not replace the page.** A browser opens a file dropped where nothing takes it, in place of the page, or downloads it, so missing the box by a few pixels threw away whatever the reader had filled in. With `preventDocumentDrop` on, while the picker is mounted and neither disabled nor read-only, a file dragged over the rest of the page shows the no-drop pointer and is not dropped. A drop target that takes the file itself, another picker or a native `<input type="file">` among them, and a drag that carries no file are left alone. It is off by default.
24
+ - **`PlassHeadingLevel` names the levels `1` to `6` a heading can take, and every `headingLevel` prop is typed with it.** `PlAccordion` had its own `PlAccordionHeadingLevel`, so a second component that drew a heading would have needed a second name for the same six numbers. `PlAccordion` and `PlDrawer` now both take `PlassHeadingLevel`, and `PlAccordionHeadingLevel` is still exported as a deprecated alias of it.
25
+
26
+ - **Glass is opaque for a reader who has asked for less transparency.** The sheets, the `glass` controls and the popups drew a translucent fill over a `backdrop-filter` blur, which is the most expensive thing on a screen and exactly what a reader who turned on reduced transparency asked not to see through. With `prefers-reduced-transparency: reduce` the blur is gone and each step of the glass is laid over the theme's own `--plass-surface`, in the light and the dark theme, with the hairline, the shadows and the focus ring as they were. A `glass` `PlOverlay` lays its dim over the surface, and a `PlWindowPane` is opaque whatever its `transparency`. The scrim behind a `PlModal`, a `PlDrawer`, a `PlCommandPalette`, a `PlTour` and a `scrim` `PlOverlay` keeps its dim and loses its blur, which is now the `--plass-scrim-blur` token. Browsers that do not report the preference draw the glass as before.
27
+
28
+ - **`PlBreadcrumbItem`, `PlListItem`, `PlMenuItem`, `PlBottomNavigationItem`, `PlFloatingBottomNavigationItem`, `PlNavigationMenuLink` and a `PlNavigationMenuItem` that is a link take `render`, so a router's `Link` can be the item's link.** These items wrote a fixed `<a href>`, so a site that navigates with Next.js's or React Router's `Link` could only switch to `onClick`, which turned the item into a `<button>` and took the link a crawler follows off the page. `render={<Link href="/docs" />}` now draws the item's link on that element, keeping the item's classes, its `aria-current`, a menu row's role and highlight, and the arrow keys. A `render` alone makes an item a link, as an `href` does, and an `href` on the element wins over the item's own, as on `PlTextLink`; a breadcrumb's `structuredData` reads a step's address off it too. A disabled item and the current step of a trail go nowhere, so they leave it out, and a `PlNavigationMenuItem` that opens a panel is a trigger, so its type refuses `render` beside children.
29
+
30
+ - **`PlDrawer` takes `headingLevel`, so its title sits at the level the page's outline needs.** The title was always an `<h2>`, so a drawer inside a section headed by an `<h2>` put its title beside that heading rather than under it. It takes `1` to `6` and defaults to `2`, in both modes, and an `overlay` drawer's title still names the dialog. Only the element changes, never the type scale, and a level outside the six is read as `2`.
31
+
32
+ - **Every chart takes `initialWidth`, the width it is drawn at until its box has been measured.** A chart is laid out from the width its box is measured at, so the HTML a server sent held an empty box of the right height, and the plot, the axes and a `PlGaugeChart`'s reading arrived only once the script ran. Given `initialWidth`, the server and the render that hydrates its HTML draw the whole chart at that width, a drawing wider than its box is cut at the box's edge, and the measured width takes over as soon as the page has hydrated. A chart without it renders as before.
33
+
34
+ - **`PlStepper` and `PlTree` take `keepMounted`, which keeps content that is not on screen in the document.** A stepper rendered only the panel of the step `active` was on, and a tree built no row of a shut branch, so neither was in the HTML a server sends or a search engine reads. With `keepMounted` a stepper renders every step's panel and a tree builds every branch, and what is not on screen stays hidden, out of the focus order and off the accessibility tree. A kept panel keeps what it holds when the reader steps away, and the tree's arrow keys still walk only the visible rows. It is off by default.
35
+
36
+ - **`PlDrawer` and `PlSidebar` take `keepMounted`, which keeps a closed drawer's content in the document.** A closed `overlay` drawer took its content out of the page, so below `collapseBelow` a sidebar's links were missing at the width a search engine indexes a page at. With `keepMounted` the content stays, hidden, out of the focus order and off the accessibility tree. It is off by default.
37
+
38
+ ### Changed
39
+
40
+ - **A `PlCard` `headerAction` is centred on the first line of the title.** The header row lined the action up with the top of the title, so a `PlIconButton` beside a one-line title sat with its middle about 10px below the title's at `md`, and further below at the larger sizes. The action's middle now lands on the middle of the title's first line, or of the subtitle's when there is no title, and stays there while the title wraps. The header grows to hold whichever of the two is taller, so a taller action moves the title down rather than hanging below it. A header with only an action keeps it at the top.
41
+ - **A `PlFilePicker`'s whole field is its drop area, and the element its native attributes land on hears the drag.** The four drag listeners sat on a `<div>` inside the field, so a drag event dispatched on the element a `data-testid` finds, which is the field, never reached them, and a file let go over the label or the messages was handled by the browser instead. They are now on the field itself, so the label, the box, the file list and the messages are one drop area. `onDragEnter`, `onDragOver`, `onDragLeave` and `onDrop` passed to the picker still run, after the picker has handled the event.
42
+ - **A new `value` makes `PlAnimateCounter` count on from the figure on screen.** It counted from `from` every time, so a figure that moved from 4,812 to 4,830 dropped to 0 and counted all the way up again, although the page said it went on from where the last count landed. It now counts from the figure the last count landed on, or from the frame a count still running had got to, over the same `duration` with the same `easing`. The first count and a replay, such as a second hover or a new `play`, still start at `from`, and reduced motion, `paused` and `trigger` work as before. Until it lands, the box keeps the width of the figure it started from, so a count down to a smaller number narrows once, when it lands, rather than at every digit it loses.
43
+
44
+ - **A disabled `PlNavigationMenuItem` is no longer a Tab stop.** Base UI keeps a disabled trigger in the Tab order, so Tab stopped on an item that opens nothing, where a disabled `PlBottomNavigationItem`, the Flutter menu and most disabled controls are passed over. A disabled trigger or link is now passed over by Tab as well as by the arrow keys, and is still announced as unavailable and drawn dimmed.
45
+
46
+ - **`PlConfirmProvider` and `PlSidebar` fetch their dialog when it is needed rather than with the page.** An app shell of `PlassProvider`, `PlToastProvider`, `PlConfirmProvider`, `PlPageLayout`, `PlSidebar` and `PlHeader` carried the whole dialog stack, 37.2 kB gzip. The confirm dialog is now fetched once the page is idle and mounted by the first question, which still fades in and takes the focus; the sidebar's drawer is fetched once the window is narrower than `collapseBelow`. The same shell is 25.1 kB, and a server render is unchanged.
47
+
48
+ - **`plass-ui/css/<component>.css` registers only the library modules its component reaches.** `plass-ui/css/base.css` scanned every one of the library's internal modules, so a Tailwind project that registered one component still generated the utilities of all of them: `PlButton` on its own came to 14.3 kB gzipped. Each manifest now lists the modules its component reaches, and `base.css` is the tokens alone, which brings `PlButton` to 11.4 kB, `PlButton` with `PlTextField` to 12.1 kB and `PlTypography` to 8.9 kB, against 22.5 kB for `plass-ui/tailwind.css`. Nothing changes in a project's imports, and nothing a component uses is left out.
49
+
50
+ ### Fixed
51
+
52
+ - **The column of span names in a `PlTimelineChart`'s table is headed in the language of the label pack.** It was headed by the English word "label", which no pack could translate, so a Korean page's table was headed "label", "시작", "종료". It now comes from the new `name` label key, "Name", translated in all seven packs. The key is React's alone, as the Flutter chart draws no table.
53
+ - **A `reference` on a `PlTimelineChart` is read as a time.** The chart's description wrote the line's value as a number, so a reference at a date was read as a count of milliseconds, "Freeze 1.8T". It is now written as the chart writes a span's start and end, "Freeze Jan 4, 2026". The Flutter build makes the same change.
54
+ - **A `PlTimelineChart` is described by its rows.** Its description read the one series the chart hands the frame in its rows' place, so a screen reader heard "1 1" after the chart's name. It now names each row and the stretch of time its spans on the plot cover, from the first start to the last end, "Design Jan 1, 2026 – Jan 18, 2026, Build Jan 8, 2026 – Jan 26, 2026", and a row with nothing on the plot by its name alone; a `reference` is still read after them, and the table under the chart still lists every span.
55
+ - **An `aria-label`, `aria-labelledby` or `aria-describedby` on a chart reaches its picture.** On a `PlLineChart`, `PlAreaChart`, `PlBarChart`, `PlScatterChart`, `PlTimelineChart`, `PlPieChart`, `PlHeatmapChart`, `PlGaugeChart` or `PlSparkline` all three landed on the box around the picture, a `<div>` with no role, so they named and described nothing: the picture went on being called by `label` or by "Chart", a gauge or a sparkline with no `label` was no picture at all, and the table under a chart was captioned by `label` alone. They now name the picture, a caller's `aria-labelledby` first, then an `aria-label` in `label`'s place, then `label`, then "Chart", and the table under it is named by the same three in the same order: an `aria-labelledby` points it at the same element, and an `aria-label` or `label` is its caption. A `PlPieChart`'s value column is headed by the same words as that caption, and by nothing when the pie is named by reference alone or not at all, where it was headed by `label` or else by "1". Either of the two makes a gauge a named image and a sparkline a named `role="img"` with its values written out beside it, as `label` does, and a gauge still reads its value after the name. An `aria-describedby` describes the picture, read ahead of the chart's own description, and a sparkline with no name, which is off the accessibility tree, takes none. A chart given none of them is named and described as before.
56
+ - **An endless `PlAnimate*` effect given a finite `repeat` just as its run ended under the setting for less movement is not taken for one that landed.** `animationend` arrives a frame or more after the keyframe has finished, and a count given in between was read as the count of the run that ended, so the effect was marked as landed and stood on its last frame once the setting was turned off, where it should play on from its clock. The run is now measured by when it ended, and one that ended while the effect was still endless is left unmarked.
57
+ - **`PlVisuallyHidden`'s ref is typed as the `HTMLElement` it is handed.** It was typed as a `<span>`, although `headingLevel` and `render` hand it a heading or any other element, so an editor showed `ref.current` as a span that was not one.
58
+ - **An `aria-label` or `aria-labelledby` on a `PlTransfer` or a `PlCalendar` names it.** Both landed on the root, a `<div>` with no role, so they named nothing, and two transfers on one page read their lists as the same "Available" and "Selected" twice. The root now takes `role="group"` when it is given either, so the name lands on the pair or the calendar as a whole; a calendar's grid is still named by its month heading, and with neither the root has no role, as before.
59
+ - **The French, Japanese and Simplified Chinese label packs write their punctuation one way throughout.** Two of the French messages for files a `PlFilePicker` turned away used the typewriter apostrophe where the rest of the pack uses `’`, and the words a `PlTextLink` that opens a new tab says to a screen reader were bracketed in ASCII in the Japanese and Simplified Chinese packs, which use the full-width pair. They now match the rest of each pack.
60
+ - **Muted text clears 4.5:1 on a floating sheet in the dark theme.** `--plass-muted-fg` measured 4.4:1 on the densest step of the glass over the dark page and 4.2:1 over an opaque sheet, which is what a modal or a select's list over a card is and what a reader who has asked for less transparency sees everywhere, so the labels, descriptions and hints there fell below what body text needs. It is a shade lighter in the dark theme, `#a0a9bf` for `#99a2ba`, and clears 4.5:1 on the page and on every step of the glass in both themes. The chart baseline, mixed from it, follows.
61
+ - **An `aria-label` or `aria-labelledby` on a `PlSelect`, `PlCombobox`, `PlNumberField`, `PlOtpField`, `PlSlider`, `PlColorPicker`, `PlDatePicker`, `PlTimePicker`, `PlDateTimePicker`, `PlDateRangePicker`, `PlTreeSelect` or `PlFilePicker` names the control.** Both landed on the field's wrapper, a `<div>` with no role, so a field with no room for a visible `label`, such as a select in a toolbar, had no way to name its trigger, and a slider's `aria-label` named its group but not the thumb a screen reader lands on. They now go to the trigger, the input, every slot of a code as the start of its name, every thumb of a slider, or a file picker's browse button as the start of its name, and an `inline` `PlColorPicker` keeps them on its group. An `aria-label` names the control in a visible `label`'s place, a caller's `aria-labelledby` outranks both, and a picker's trigger still reads its value after the name.
62
+ - **A `PlFilePicker`'s glyph follows `size`, as the Flutter build's does.** It was sized against the font size the page around the picker set, since the box sets none of its own, so it was the same at every `size`, about 23px in a 13px page, and changed with the page; a larger glyph needed a stylesheet rule that outranked the picker's. It is now drawn against the title under it, so it grows and shrinks with `size` and keeps to it whatever the page sets.
63
+ - **A `PlFilePicker` stops showing a file over it when the drag ends without leaving the box.** A drag cancelled with Escape while it was over the box sent the box no `dragleave` in any browser, so the box kept its drag-over edge, wash and halo until the next drag left it or a file was dropped on it. It now goes back to resting as soon as the drag is over: on a drop or a `dragend` anywhere in the page, when the window loses focus, or when the pointer moves again.
64
+ - **A `PlAnimateHeadline` let go of its `index` holds the line that is up for a whole `interval`.** It went on with the wait it had before it was controlled, so a reel given `index` 600ms into an `interval` of 1000 turned 400ms after `index` was taken away, however long it had been controlled. The wait now starts when `index` is taken away, as it does in the Flutter build, and a controlled reel still runs no timer of its own.
65
+ - **A `PlAnimateHeadline` whose lines were all taken away and given back holds the line that comes up for a whole `interval`.** It went on with the wait it had before the lines were taken away, so when the same line came back it turned early. A line that comes back to an empty reel now arrives anew, as it does in the Flutter build.
66
+ - **A `PlAnimateHeadline` that has stopped on its last line, or holds only one, brings the next line up once the line that is up has had its `interval` when a line is added or `loop` is turned on.** It waited a whole `interval` from the change, so a reel stopped on its last line 400ms into an `interval` of 1000 held it for another whole second once a line was added. The wait now goes on counting while there is no line to turn to, and a line already up for its `interval` gives way at once. A change of lines or `loop` while the reel is turning leaves the wait as it was, as before.
67
+ - **A `PlAnimateHeadline` let go by `paused` or back on screen holds its line only for what was left of the wait.** The timer started the wait over each time, so a reel paused 600ms into an `interval` of 1000 held its line for another whole second once let go, the first line waited its whole `delay` again as well, and a reel paused and let go more often than its `interval` never turned. A new `interval` or `delay` is now measured from when the wait began too, and turns the reel at once when the wait is already past it. A reel stopped by its trigger, such as a hover that leaves, still waits a whole `interval` once it starts again.
68
+ - **A `PlAnimateMarquee` held by `paused` keeps the one scrolling copy reduced motion showed when the setting is turned off.** The copies came back and the box clipped them while the strip was still paused, so the reader could no longer scroll to what was past its edge, and the box stopped being a tab stop. It now stays one copy in a box that scrolls, where the reader left it and still a tab stop, until `paused` is let go, and then runs as the moving strip, as before.
69
+ - **A new `delay` given to a `PlAnimateTyping` that is still waiting for its first character is measured from when the wait began.** The typing went on with what was left of the old delay, so a `delay` of 1000 changed to 1600 600ms in typed at 1000, and one changed to 300 waited another 400ms. It now types once the new delay is over, at once if the wait has already gone past it, as the other effects do. A pause still holds the wait, and a new delay once the line has started typing changes nothing.
70
+ - **A `PlAnimateTyping` that types its line once and landed under reduced motion keeps the whole line when the setting is turned off.** Under the setting the whole line is shown, but the typing stood where it was, so giving movement back emptied the line and typed it again. A typing that types once now lands once its `delay` is over, as the other effects do, and keeps the line until it runs again, such as a new `play`, a second hover or a new `text`. Given movement back while it is still waiting, it is empty for the rest of the wait and then types, one never let go still waits for its trigger, and a typing that repeats types again from its first character, as before.
71
+ - **A `PlAnimateLighting` held by `paused` keeps the even glow reduced motion showed when the setting is turned off.** The arc came back at the angle its clock stood at, `0deg` for an endless light, while it was still paused. It now stays an even glow until `paused` is let go, and then turns from where its clock stands, as before: from its start for an endless light.
72
+ - **A finite `PlAnimate*` effect held by `paused` before its `delay` is over keeps the frame reduced motion showed when the setting is turned off.** A paused keyframe stands where its clock stopped, which for a run held before its delay was over is ahead of its start, so giving movement back turned a fade held by `paused`, from the mount or during its wait, from fully drawn to nothing, and a turn from its `to` angle back to its `from`. It now stays on that frame until `paused` is let go, as an endless one does, and then waits out the rest of its delay and runs, as before.
73
+ - **A `PlAnimateMarquee` running across inside one running down keeps its own way.** The vertical strip's rule reached every strip inside it, so a horizontal marquee nested in a vertical one stacked its items in a column and moved down rather than across. It now reaches only the vertical strip's own tracks.
74
+ - **A `PlAnimateCounter` played again with a `delay` shows `from` while it waits.** A replay, such as a second hover, stood on the figure the last count landed on through its wait and then jumped to `from`, where a keyframe shows its first frame while it waits out its delay. It now shows `from` from the moment the replay starts. A new `value` still counts on from the figure on screen, and waits on that figure.
75
+ - **The two copies of an endless vertical `PlAnimateMarquee` stay in step when reduced motion is turned off.** Under the setting a vertical strip was not switched off as a horizontal one is: its first copy ran a pass in no time, which sent `animationend`, and once movement was given back it went on from its own clock while the second copy started afresh, about 100ms behind it, so a gap opened in the strip. A vertical strip is now switched off under the setting as a horizontal one is, and both copies start together when the setting is turned off.
76
+ - **An effect nested inside another `PlAnimate*` effect no longer takes the outer one's settings.** An effect wrote only the slots it had a value for, and the rest came from the effect around it, so a fade inside an endless `PlAnimateRotate` with `easing="linear"` eased linearly, an `alternate` fade with a `repeat` of 2 inside it landed visible under reduced motion rather than faded out, and a fade inside a `timeline="view"` effect followed the scroll. Every effect now reads only its own, a `PlAnimateLighting`, `PlAnimateMarquee` and `PlAnimateHeadline` included.
77
+ - **A finite `PlAnimateLighting` or `PlAnimateMarquee` that landed under reduced motion stays where it landed when the setting is turned off.** Under the setting both were switched off, so nothing landed, and giving movement back played the whole run again from the start. A light or a strip that ends now runs in no time under the setting, as the other effects do, so `animationend` arrives, and once it has landed it stands still when the setting is turned off, the strip where it started and the light where a turn ends, until it runs again. What either draws under the setting is unchanged, and an endless one is still switched off and goes on from its start.
78
+ - **A new `delay` no longer stops a `PlAnimateCounter` or `PlAnimateScramble` that is under way.** The run measured itself against the new delay, so a count with a `delay` of 100 given 1000 400ms in stopped on its frame and, once the new delay had gone by, counted again almost from `from`, and a scramble went back to noise. A run under way now keeps the delay it started with, and a new one applies from the next run. While the run is still waiting, a new delay is measured from when the wait began, as before.
79
+ - **A `PlAnimateCounter` or `PlAnimateScramble` that landed under reduced motion keeps its answer when the setting is turned off.** Under the setting a counter shows its figure and a scramble its line, but the run's clock stood where it was, so giving movement back counted from `from` again or turned the line back into noise: a linear 1000ms count to 1,000 stood at 300 after 300ms. A run now lands once its `delay` is over, as a keyframe does, and stays on its answer until it runs again, such as a new `play`, a `visible` counter seen again or a new `value`. Given movement back while it is still waiting, a run shows `from`, or noise, for the rest of the wait and then runs, and one never let go still waits for its trigger.
80
+ - **An endless `PlAnimate*` effect held by `paused` keeps the frame reduced motion showed when the setting is turned off.** A paused keyframe stands where its clock stopped, which for a run that landed under the setting is the start of a pass and for one paused before it moved is its start, so giving movement back turned an endless fade held by `paused` from fully drawn to an opacity of 0.001 and an endless turn from its `to` angle back to its `from`. It now stays on that frame until `paused` is let go, including one paused with movement before the setting arrived, and then goes on from where its clock stands, as before. A run waiting for its trigger still waits on its first frame.
81
+ - **A new `delay` no longer sends a `PlAnimate*` effect that is under way back to waiting.** The new delay applied to the running keyframe, so a fade with a `delay` of 300 given 1000 at 400ms vanished and started again from nothing once the new delay had passed. A run under way now keeps the delay it started with, and a new one applies from the next run, such as a second hover, a new `play` or a `visible` effect seen again. While the effect is still waiting, a new delay is measured from when the wait began, as before. Each child of a `stagger` and each part of a `PlAnimateSplit` decides for itself, and `PlAnimateMarquee` and `PlAnimateLighting` keep their delay the same way.
82
+ - **A finite `PlAnimate*` effect that landed under reduced motion stays on its last frame when the setting is turned off.** Under reduced motion an effect runs in no time and shows its last frame, but its keyframe went on counting from when the run began, so turning the setting off before the run would have ended put it that far back into the run and played the rest: a 1000ms fade given movement back 250ms after it began dropped to a quarter of its opacity and faded in again. A run that has landed, including one that was running or paused when the setting arrived, now stays where it landed until it runs again. An endless run still goes on from where its passes would have got to, a run still waiting out its `delay` plays when the wait is over, a `timeline="view"` effect follows the scroll again, and one never let go still waits for its trigger.
83
+ - **Under `plass-ui/styles.css`, the label or the glyph of a button of fixed height sits in its middle in WebKit.** These buttons set their height and no padding top and bottom, and WebKit pads a button 2px above and 3px below, so the label of a `PlButton` and of every component drawn with one, a `PlToggle`, a `PlTab`, the trigger of a `PlMenubarMenu`, a `PlNavigationMenuItem` and a `PlSelect`, the days, months, years and times of `PlCalendar`, `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker` and `PlTimePicker`, a pressable `PlChip` and the action of a toast sat half a pixel high, as did the glyph of a `PlNumberField` stepper, a `PlFloatingBottomNavigationItem`, the controls of a `PlWindowPane`, and the × of a `PlChip`, a `PlCombobox` chip, a picker's clear button, `PlAlert`, `PlModal`, `PlDrawer`, `PlPopover`, `PlTour`, a toast and a file in `PlFilePicker`. The × of a file in `PlFilePicker` was also squeezed narrower than it is tall in every browser. Chromium and Firefox pad a button evenly top and bottom, so nothing else moves there; a `p-*` passed in `className` still outranks the new padding, and a project that runs Tailwind's Preflight sees no change.
84
+
85
+ - **Under `plass-ui/styles.css`, the fields inside a component no longer keep the padding a browser gives an `<input>` or a `<textarea>`.** The stylesheet's reset leaves padding alone, and these fields set none of their own, because the shell's padding is the field's. The text of a `PlTextField`, a `PlNumberField` and a `PlCombobox`, and of the search field in `PlTransfer`, `PlDataTable` and `PlTreeSelect`, started 2px further in than the value of a `PlDatePicker` beside it in Chromium and Firefox and 1px in WebKit, and so further in than a `float` label resting over it; a `multiline` `PlTextField` stood 4px taller than its `rows` put it, so a one-row field was taller than a single-line one, and its text started 2px lower and 2px further in; and the query of a `PlCommandPalette` started further in than its row puts it. A project that runs Tailwind's Preflight saw none of this and sees no change.
86
+
87
+ - **Under `plass-ui/styles.css`, the buttons inside a component no longer keep the padding a browser gives a `<button>`.** The stylesheet's reset leaves padding alone, and these buttons set none on one axis or on either. A `PlBreadcrumbItem` with an `onClick`, and a fold that opens, stood 2px taller than a link or a text step in Chromium and Firefox and 5px in WebKit; a `PlPill` with an `onClick` stood taller than one without; the value in the trigger of `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker`, `PlTimePicker`, `PlColorPicker` and `PlTreeSelect` started 4 to 6px further in than the field's padding put it, and a field that is not `fullWidth` was wider by twice that; the chevron and the × of `PlCombobox` were wider than their glyphs; the current `PlCarousel` dot's target was wider than the rest, so the row moved as the slide changed; and in WebKit the chosen `PlColorPicker` swatch stood taller than it was wide. A project that runs Tailwind's Preflight saw none of this and sees no change.
88
+
89
+ - **The interaction light stays under the pointer over what a lit surface holds.** A pointer over the input of a `PlTextField`, `PlNumberField` or `PlCombobox` placed the light by its distance on the screen from the surface's outer edge, so inside a `transform`, a field in a scaled `PlMockup` for example, the light fell short of the pointer by as much as the surface was scaled, and on any page it sat a border's width off the place a pointer over the surface itself gives it. It is now placed in the surface's own pixels from inside its border, as it already was over the surface itself.
90
+
91
+ - **A paused `PlAnimateTyping` holds its line when its `text` changes.** Given a new `text` while `paused` was on, it drew as many characters of the new string as it had typed of the old one, so "Hel" became "Wor", the start of a line it had never typed. The line on screen now stays as it was until `paused` is let go, and the new text is then typed from its first character, as a new run that arrives while it is paused already is. A screen reader is given the new text whole at once, as before.
92
+
93
+ - **A fixed `PlBottomNavigation` or `PlFloatingBottomNavigation` inside a scaled ancestor publishes the room it takes there.** Inside a `transform`, a bar in a scaled `PlMockup` for example, a `fixed` bar is fixed to that box and covers what is laid out in it, but `--plass-bottom-navigation-height` was the height the bar was drawn at, so a page inside that box reserved half the room at half the scale. It is now the height the bar is laid out at, the same number it publishes with nothing scaling it.
94
+
95
+ - **A `PlAnchor` with a `target` inside a scaled ancestor lights the heading the reader has reached.** Inside a `transform`, a shell in a scaled `PlMockup` for example, the list measured how far each heading was below the top of `target` at the size it was drawn at, and took `offset` and the panel's top border off that in the panel's own pixels, so the reading line sat at another depth and a heading was lit before it had reached it. The distance is now measured in the panel's own pixels, which is what `offset` is counted in.
96
+
97
+ - **Charts inside a scaled ancestor read the column or the mark under the pointer.** Inside a `transform`, a chart in a scaled `PlMockup` for example, `PlLineChart`, `PlAreaChart`, `PlBarChart`, `PlScatterChart` and `PlTimelineChart` measured the pointer at the size the chart was drawn at and compared it with a plot laid out in the chart's own pixels, so the panel read another column or another mark than the one under the pointer, the crosshair was drawn through that other column, and <code v-pre>tooltip={{ mode: 'item' }}</code> named another series. The pointer is now read in the chart's own pixels, across and down, in both orientations.
98
+
99
+ - **A `PlAnimateSlide` or `PlAnimateRotate` with `trigger="visible"` and `once={false}` plays to its end inside a mask.** Once such an effect had started, whether it was on screen was judged by where it was drawn rather than where it sits, so a slide a tenth of the way into a box with `overflow: hidden` read as leaving and stopped there until the page scrolled, a turn whose corners swung outside the box started again from its first frame, and a `mode="out"` slide that had left the box went out again each time a scroll moved it. It is now judged by where the element sits, as it already was while the effect waited, so each plays through once, and one that leaves the screen and comes back still plays again.
100
+
101
+ - **A disabled `PlBreadcrumbItem` or `PlListItem` that has only an `onClick` is announced as an unavailable button.** It was drawn as a `<span>` or a `<div>` carrying `aria-disabled` and no role, so a screen reader read it as plain text, where a disabled `PlChip` stays a button. It is now the `<button>` it is when enabled, with `disabled`, so Tab passes over it and its `onClick` is not called, and its look is unchanged. A disabled row says nothing about being `selected`, as a disabled link row does. A step or row with neither `onClick` nor an address stays plain text.
102
+
103
+ - **The time columns of `PlTimePicker` and `PlDateTimePicker` show the chosen row on a scaled page.** When a `transform` reached the popup, on a page scaled as a whole for example, a column measured how far down its chosen row was at the size it was drawn at and scrolled that far in its own pixels, so it opened with the chosen hour or minute out of view, and an arrow, Home or End key chose a row it left out of view. The row is now scrolled into view.
104
+
105
+ - **A `PlScrollZone` inside a scaled ancestor goes to the next item and follows a drag.** Inside a `transform`, a zone in a scaled `PlMockup` for example, the strip measured the distance to the next or the previous item at the size it was drawn at and scrolled that far in its own pixels, so a button stopped part of the way there, and a mouse drag slid the strip at a different speed from the pointer, across, down and under RTL. A button now lands on the item and a drag keeps the strip under the pointer.
106
+
107
+ - **A `PlWindowPane` inside a scaled ancestor moves and resizes in its own pixels.** Inside a `transform`, a window in a scaled `PlMockup` for example, a corner, an edge or the resize key started from the size the window was drawn at, so the window jumped to another size as soon as it was taken hold of; a drag moved the window at a different speed from the pointer; an arrow key on the title bar stopped short of the edge of the view; and a window with no `height` rolled up from, and came back down to, the height it was drawn at. All of them now start from the window's own size, a key moves it 16 of its own pixels, and a drag keeps the title bar or the corner under the pointer.
108
+
109
+ - **A `visible` effect that is not `once` no longer starts again while it stays on screen.** Its observers report every time its share on screen crosses one of their steps, and each report started the effect again, so a half-visible `PlAnimateCounter` counted from `from` again as the page scrolled, and a fade or a slide went back to its first frame. It now starts when it comes on screen, and plays again only after it has left and come back.
110
+
111
+ - **A `PlCarousel` inside a scaled ancestor shows the slide it is sent to.** Inside a `transform`, a carousel in a scaled `PlMockup` for example, the strip measured the distance to a slide at the size it was drawn at and scrolled that far in its own pixels, so it opened on, and went to, a slide short of the one its dots marked, in either direction and under RTL. It now lands on the slide.
112
+
113
+ - **A `PlSidebar` inside a scaled ancestor resizes from the width it is laid out at.** Inside a `transform`, a sidebar in a scaled `PlMockup` for example, a drag or an arrow key started from the width the sidebar was drawn at, so the width jumped as soon as it began and a drag moved the edge at a different speed from the pointer. Both now start from the sidebar's own width, a key moves it 16 of its own pixels, and a drag keeps the edge under the pointer.
114
+
115
+ - **A disabled `PlBreadcrumbItem` or `PlListItem` that has an `href` or a `render` is announced as an unavailable link.** It was drawn as a `<span>` or a `<div>` carrying `aria-disabled` and no role, so a screen reader read it as plain text and never said it was unavailable. It is now an `<a>` with no address, `role="link"` and `aria-disabled="true"`, as a disabled `PlBottomNavigationItem` link is, so it still goes nowhere and Tab still passes over it.
116
+
117
+ - **A new `value` no longer starts a `PlAnimateCounter` its trigger has not started, nor a new line a `PlAnimateScramble`.** A `manual` counter with `play` off, or a `visible` one not yet seen, started counting as soon as its `value` changed, before it was asked to or where nobody could see it. A new `value` before then only changes what the count arrives at: the counter goes on showing `from` and counts to the new value when its trigger comes, and a scramble's new line waits the same way. A counter that has started still counts on from the figure on screen, and `PlAnimateShake` still plays on every new `replay`.
118
+
119
+ - **`PlAnimateCounter` in compact notation writes the figures on its way with no more decimals than the answer.** Compact notation keeps two significant digits where that is the finer rounding, so a count to "5K" showed "1.6" and "1.7K" on the way, and a count to "4.8K" could show "0.048". A frame with more fraction digits than the answer is now rounded to the answer's, so a count to "5K" goes through whole numbers and then "1K", "2K", and a frame that already fits, such as "123" or "1.2K", is written as compact notation writes it. A `roundingPriority` written in `format` is left as it is.
120
+
121
+ - **A `PlAnimateTyping` let go during its `delay` or between two characters waits only what was left of that wait.** Paused during the `delay`, or in the wait before the next pass types its first character, it waited the whole `delay` again once it was let go, and paused between two characters it waited a whole character's time again. Every wait now goes on with the time that was left of it, as the hold already did.
122
+
123
+ - **A disabled `PlBottomNavigationItem` or `PlFloatingBottomNavigationItem` that is a link is announced as an unavailable link.** It is drawn on an `<a>` with no `href`, which has no role, so a screen reader read it as plain text and never said it was unavailable. It now carries `role="link"` with its `aria-disabled`, as a disabled `PlNavigationMenuItem` link does, and Tab still passes over it.
124
+
125
+ - **A `PlPanes` inside a scaled ancestor sizes its panes in its own pixels.** Inside a `transform`, a split in a scaled `PlMockup` for example, the split measured the size it was drawn at rather than the size it is laid out at, so a pane with a pixel `defaultSize` moved as the page hydrated, a pixel `minSize` held it at the wrong width, an arrow key moved the line the wrong distance, and a drag ran slightly ahead of the pointer. Pixel lengths, minimums and an arrow key's 16 pixels are now measured in the split's own pixels, and a drag keeps the handle under the pointer.
126
+
127
+ - **A `PlAnimateTyping` paused during a hold holds for the rest of it when it is let go.** Paused during the hold before `erase` deletes its line, it started deleting one character after it was let go; paused during the hold between two passes without `erase`, it held for the whole hold again. Both now hold for the time that was left of the hold.
128
+
129
+ - **`PlAnimateCounter` given significant digits in `format` writes the figures on its way with no more decimals than the answer.** With `maximumSignificantDigits` alone, a frame was rounded to the significant digits and nothing else, so a count to 4,812 with three of them showed "12.3" on the way to "4,810". A frame now keeps to both the significant digits and the answer's number of fraction digits, whichever is coarser.
130
+
131
+ - **Moving to another step of a `PlStepper` from inside its panel puts the focus in the new step's panel, or on the step itself when it has none.** A Next button in a panel went with the panel, or was hidden with it under `keepMounted`, so the focus fell to the page's body and the next Tab started from the top of the page. The panel that arrives now takes the focus, is announced by its step's name, and the next Tab goes on to what is in it, in both orientations; a step with no panel takes the focus on its own button. The panel is not a Tab stop, and a focus anywhere else, on a step for example, stays where it is.
132
+
133
+ - **A `PlHeader` or `PlFooter` that changes only its `position` inside a `PlPageLayout` has its room reserved again.** The layout measured a bar when it arrived or changed size, so a bar switched from `static` to `fixed` left the page reserving nothing for it and covering the top of the content, or its end for a footer, until something resized it. The layout now measures it on that commit. A collapsed `PlSidebar` also reads the page's direction once rather than on every render, and its drawer turns round with a page that turns over while it is open.
134
+
135
+ - **A `PlSegmentedButton`'s tile and a `PlFloatingBottomNavigation`'s key follow an inline `style` that moves the chosen segment or the current destination.** They measured again only on a commit that changed the value, a prop, the children, a class or a `dir`, so a `style` that moved a segment without resizing the set left the tile where it was until the next change. They now measure on that commit too, and a parent render that hands over the same styles still measures nothing.
136
+
137
+ - **A padded `PlPanes` or `PlMockup` no longer moves as the page hydrates.** A split measured its border box where its panes' `flex-basis` percentages resolve against its content box, so with padding or a border a pane given a pixel `defaultSize` shrank as the split measured itself, and a handle fell behind the pointer and the arrow keys. A mockup measured its content box where the stylesheet scales the device to the box inside its border, so a padded mockup drawn on the server shrank at hydration. Both now measure the box their CSS reads, and a padded mockup's device fills the box inside its border, padding included, as its first paint already did.
138
+
139
+ - **A calendar or date picker no longer works out which day its week starts on at every render.** With no `weekStartsOn` given, `PlCalendar`, `PlDatePicker`, `PlDateRangePicker` and `PlDateTimePicker` built an `Intl.Locale` on every render, and with no `locale` named a formatter as well, about 30 µs each time. The answer is now kept for each locale.
140
+
141
+ - **A calendar keeps its month and weekday names on the right day when the browser's time zone changes while the page is open.** The pickers kept the formatters they write names with, and a formatter keeps the zone it was made in, so after the zone changed a calendar could head September as August or start its week on Saturday until the page was reloaded. The formatters are now made again when the zone changes.
142
+
143
+ - **A `PlSlider` inside a `PlassProvider` writes its value in the provider's `locale`.** It read only its own `locale`, so on a page that named its locale once on the provider a slider wrote `1,234.5` while the number fields and meters beside it wrote `1.234,5`. It now reads the provider's, as `PlNumberField` and `PlMeter` do, and its own `locale` still wins.
144
+
145
+ - **A long `PlDataTable` whose `columns` are written inline no longer draws every row again whenever the component holding it draws.** A row was skipped only while `columns` kept its identity, and an array written inline is new on every render. A row now compares its columns by `key`, `align` and `render`, so a new array that draws alike passes over every row. A `render` written inline is still a new function each time and still draws every row, so a long table keeps its `render` functions, or the whole `columns`, outside the component or in `useMemo`.
146
+
147
+ - **A `PlAnimateMarquee` strip and an indeterminate `PlProgressLinear` segment travel the way the nearest `dir` says, however deeply one region is nested in another.** The stylesheet answered one region inside another, so a right-to-left region inside a left-to-right one inside a right-to-left page ran the English way. A browser with `:dir()` now reads the direction the strip or the segment is in; Chrome 111 to 119, which lack it, still read the `dir` attribute one region deep.
148
+
149
+ - **A disabled `PlNavigationMenuItem` that is a link no longer goes anywhere.** `disabled` reached only an item that opens a panel, so a link item marked disabled kept its `href`, or the router's `Link` it was given through `render`, and a press or Enter still followed it. A disabled link item is now drawn dimmed, as a disabled trigger is, on an `<a>` with no address, announced as an unavailable link. Neither the arrow keys nor Tab stop on it.
150
+
151
+ - **A chart given `height` as a CSS length is drawn at that height.** The prop took any CSS length, but a `PlLineChart`, `PlAreaChart`, `PlBarChart`, `PlScatterChart` or `PlTimelineChart` laid a string out at no height and drew nothing, and a `PlPieChart`, `PlGaugeChart` or `PlHeatmapChart` ignored it and drew at the height its `size` gives. The box now takes the length and the chart is drawn at the height the box is measured at, a scatter's largest bubble included. A server sends the box at that height with nothing drawn in it, with `initialWidth` too, and the chart is drawn once the page has hydrated. A height in pixels and the default are drawn as before.
152
+
153
+ - **Moving to another step of a horizontal `PlStepper` gives that step a panel of its own.** The stepper drew every step's panel in one element, so what had been typed into an uncontrolled field stayed there when the next step put a field of the same kind in the same place. Each step's panel is now built anew when the reader reaches it. A vertical stepper already worked this way, and a `keepMounted` panel still keeps what it holds.
154
+
155
+ - **A `PlSelect` holds itself at the width of its widest option with one hidden element rather than one per option.** To keep the trigger from changing width with its value, the select laid every option out in a hidden element of its own, so ten selects of 250 countries put about 2,500 extra elements in the page, each with a generated box to style and lay out. All the options are now laid out in one hidden element, a line each, and the trigger is exactly as wide as before, including for a label with extra spaces or a line break in it. `PlDatePicker`, `PlTimePicker`, `PlDateTimePicker`, `PlColorPicker` and `PlDateRangePicker` hold their width the same way.
156
+
157
+ - **A preview, a viewer, a confirm dialog or a sidebar drawer whose chunk fails to load no longer breaks the page around it.** `PlImage`'s preview, `PlGallery`'s viewer, `PlConfirmProvider`'s dialog and a collapsed `PlSidebar`'s drawer are fetched on their own, and when one could not be fetched, because the connection dropped or a deploy replaced the chunk under an open page, `React.lazy` threw to the nearest error boundary and took down everything under it. The import is now tried once more, and if that fails too, the preview, viewer or drawer does not open and a question is answered as `Esc` answers it: `confirm` resolves `false` and `alert` resolves. The next press or question asks for the chunk again.
158
+
159
+ - **Answering a `PlConfirmProvider` question puts the focus back on the button that asked it.** The button that should hold the focus took it with `autoFocus` before the dialog noted where the focus had been, so confirming, cancelling or pressing `Esc` left the reader on the page's body, and the next Tab started again from the top of the page. The dialog now moves the focus onto that button itself, and gives it back to whatever held it before the question was asked. A question that follows another in the open dialog, queued or asked as soon as the last one was answered, still takes the focus on its own button.
160
+
161
+ - **A server-rendered `PlPageLayout` keeps the padding and the edges of a `fixed` or full-width `sticky` `PlFooter` clear in the first paint.** The footer's height was known only once the layout measured it after hydration, so the HTML a server sent drew each sidebar a whole footer too tall and let a fixed footer cover the end of the content by its whole height. The stylesheet now reserves the footer's padding for its `size` and `density` and its edges until the layout has measured it. The height of what is inside a footer cannot be known before then, so at hydration the sidebars still get that much shorter, and the end of the content still moves up from under a fixed footer by that much.
162
+
163
+ - **The sidebars under a full-width `sticky` `PlHeader` in a server-rendered `PlPageLayout` start below the header in the first paint, and hydrating no longer resizes them.** The header's height was known only once the layout measured it after hydration, so the HTML a server sent drew each sidebar as tall as the whole window: a short page's footer sat a header's height further down and moved up when the page hydrated, and on a page scrolled before hydration the sidebar was stuck under the header. The stylesheet now reserves the header's floor for its `size` and its edges for a sticky header that spans the full width, as it already did for a `fixed` one, and the measured height takes over when the page hydrates. A header whose content is taller than its `size` allows still moves the sidebars once, when it is measured.
164
+
165
+ - **A `PlAnimateTyping` with a finite `repeat` plays only the passes it was asked for when it is paused and let go.** Each time `paused` let it go, it counted its passes from the first again, so a typing paused during the last of its passes played all the earlier ones again. It now goes on with the pass it was on, and one let go during the hold between two passes holds and then goes on to the next.
166
+
167
+ - **A `PlAnimateTyping` line is in the page's text once at every moment.** The server's HTML ended the line in "||", the caret and the room kept for it, and once the line was typed out it was text twice, in the clipped copy for a screen reader and in the drawn copy, so a search engine that runs the page's script indexed it twice. The characters typed so far and a caret given as a string are now generated content; the clipped copy is the only text and is what a copy gives. How it looks and what a screen reader hears are unchanged, and a caret given as an element is still drawn as it is.
168
+
169
+ - **`PlAnimateCounter` writes the figures on its way with no more decimals than the answer.** Every frame was written with `format`, and the default one writes up to three decimals, so a count to 4,812 showed figures like "1,105.535" on the way, wider than the answer, and the box reserved for the answer widened on every frame but the first and the last. A frame is now rounded to the answer's own number of fraction digits: a count to a whole number shows whole numbers, and a count to 12.5 shows one decimal. The rest of `format` still decides how a frame is written: a currency keeps its two decimals in every frame, and an explicit `maximumFractionDigits` is the most a frame can show, not a number of decimals to fill. A frame that rounds to zero from below is drawn as 0 rather than -0.
170
+
171
+ - **`Esc` closes a `PlCommandPalette`, and every close puts the focus back where it was.** The palette's search list took `Esc` for a popup of its own that it never had and stopped it there, so the key did nothing although the page said it closed the sheet. The field also took the focus before the dialog noted where it had been, so closing the palette by running a command or from the parent left the focus on the page's body. `Esc` now closes the sheet on the first press, whatever is typed in the field, and the focus goes back to whatever held it before the palette opened.
172
+
173
+ - **A parent's render no longer makes a `PlTabs` bar measure its indicator again.** Base UI's indicator measures the list and the chosen tab while it renders, and the bar made a new indicator on every render, so every render of whatever held the bar read the list's box, its scroll width and the tab's computed style for a tab that had not moved. The indicator is now made again only when its look or the bar's direction changes, and it still moves on the same frame as before when the value changes, when a tab is renamed or resized, when the bar is resized, and when the bar or the document turns round.
174
+
175
+ - **A `PlCodeBlock` asks for the highlighter again after the first request for it failed.** If the highlight.js core did not arrive once, for example because the connection dropped while it was in flight, the failure was kept, and every code block on the page stayed plain for as long as the page was open. A failed request is now forgotten: the next block that needs the highlighter asks for it again, and blocks still show their code plain until the colours arrive.
176
+
177
+ - **A `PlCalendar` rendered on a server hydrates in a browser whose clock is on another day.** Today was read in each runtime's own zone, so at 08:00 in Seoul a UTC server marked yesterday and, on the 1st of a month, opened on the month before, and React threw the server's tree away. A server render and its hydration now read the clock in UTC, and the calendar moves its month, its today mark and its tab stop to the reader's day as soon as hydration is done. A calendar rendered only in the browser reads the browser's clock from the first paint, as before.
178
+
179
+ - **A server-rendered `PlMockup` sized by a CSS length is drawn before it hydrates.** At `width='100%'` (the default) or any CSS length, the device was `visibility: hidden` until the browser measured its box, so nothing on its screen was painted until the script ran. The stylesheet now works the scale out from the box with container query units until the measurement replaces it, and only a box measured with no room is hidden.
180
+
181
+ - **A server-rendered `PlPanes` is drawn at its `defaultSize`s before it hydrates.** Every pane took an even share until an effect measured the split, so a split whose first pane asked for a quarter was drawn half and half, then moved. Where the sizes settle the split on their own (percentages, pixel lengths naming every pane, pixel lengths beside a pane that takes the rest), the panes are now sized during render. `rem` and `em` lengths still wait for the measurement.
182
+
183
+ - **A `PlSegmentedButton` or `PlFloatingBottomNavigation` measures its tile only when something it is placed by has changed.** The effect that places the tile listed `children`, a new reference on every render a parent does, so each one read the chosen segment's box back. The first placement also forced a second layout with `void offsetWidth`, and several on one screen recalculated the layout between each other's writes. The tile is now measured on a commit that changed the choice, a prop the segments are sized by, the segments themselves or the direction, and its transition comes back on in the next frame. What is drawn and how it moves are unchanged.
184
+
185
+ - **A `PlSidebar` that cannot be resized no longer reads the document's computed style while it renders.** It turned its default `16rem` width into pixels on every render for a handle it does not draw.
186
+
187
+ - **A server-rendered `PlPageLayout` draws its content below a `fixed` `PlHeader` in the first paint, and hydrating no longer moves it.** The header's height was measured in an effect after hydration, so the HTML a server sent drew the content and the sidebars under the header and pushed them down when the page hydrated, a layout shift of 0.06 to 0.23 on the docs demos. The stylesheet now reserves the header's floor for its `size` and its edges until the layout has measured it, and the layout measures before the browser paints, reading both bars before writing either, so a resize no longer recalculates the page's style once per bar. A header whose content is taller than its `size` allows still moves the content once, when it is measured.
188
+
189
+ - **A list or a paragraph passed to `PlEmpty` as its `description` stays where it was put.** The description was drawn in a `<p>`, which cannot hold a block, so a server-rendered page split it around the list, and React reported a hydration mismatch and rendered the tree again. It is now a `<div>` like the title, and it looks and reads as before.
190
+
191
+ - **A marquee and an indeterminate `PlProgressLinear` inside a left-to-right region of a right-to-left page travel the English way.** The stylesheet turned them round whenever any ancestor said `dir="rtl"`, so an English quote, a code sample or a form set to `dir="ltr"` inside an Arabic page ran its marquee backwards and swept its progress bar from the wrong end. The nearest `dir` now decides, in either order of nesting.
192
+
193
+ - **A `PlSkeleton` highlight runs left to right on a right-to-left page too, as the design language and the Flutter build have it.** The React highlight turned round under RTL because it was placed from the inline start, so the same placeholder swept one way in English and the other in Arabic. A sweep is a light crossing a surface rather than something read along a line, and it now crosses from left to right in every writing direction. The indeterminate `PlProgressLinear` segment still runs toward the reader's end.
194
+
195
+ - **An endless animation rests while it is off screen.** `PlAnimateLighting` redraws on the main thread every frame, and it, the marquee, float and blink, every `repeat="infinite"` keyframe, a looping `PlAnimateHeadline`, an endless `PlAnimateTyping` and the typing caret ran for the whole visit wherever they were on the page. Each now pauses once it leaves the screen and goes on from the frame, line or character it stopped on. Finite effects play as before.
196
+
197
+ - **`PlAnimateCounter` and `PlAnimateScramble` hold the width of their finished line from the first paint.** The box grew with the figure as it counted and with the noise as it settled, so the text beside it moved on every new digit. The finished line is now laid out under the drawn one and not drawn, in the server's HTML too, so a frame narrower than it leaves the text around it where it is. A scramble inside running text is one box now, and moves to the next line whole rather than breaking across two.
198
+
199
+ - **A text effect's line is in the page's text once.** `PlAnimateCounter`, `PlAnimateScramble` and `PlAnimateSplit` put the line they draw beside a clipped copy for a screen reader, and both were text, so the server's HTML and what a search engine indexed held it twice: a counter on 12,345 read "12,3450", and a split or scrambled line said its sentence twice. `PlAnimateHeadline` ran its lines together. The drawn copy is now generated content, the clipped copy is the only text and is what a copy gives, and a headline's lines are separated by a space that is not laid out. Selecting the line no longer highlights the drawn copy, but copying still gives the line once.
200
+
201
+ - **Typing in the search field of a long `PlTreeSelect` or `PlTransfer` no longer folds every label again for each character.** `PlTransfer` also did it again on every tick while a search was active. The matches are unchanged.
202
+
203
+ - **A page with many `PlCodeBlock`s is coloured one short task per block, and each grammar is fetched together with the highlighter.** Blocks waiting on one grammar were coloured back to back in one long task, and the grammar was only requested once the core had arrived. Each block still shows plain text until its colours are ready.
204
+
205
+ - **Ticking a row of a long `PlDataTable` draws that row again, not every row.** With `paging: 'scroll'` every tick redrew every row and its `PlCheckbox`, each row searched the whole list of ticked keys, and every search keystroke folded every cell again. A row now redraws only when something about it changed, the selection is looked up in a set, and each row's search text is folded once until `rows` or `columns` change.
206
+
207
+ - **A masonry `PlGallery` rendered on a server has the right number of lanes before hydration.** Its lane count was worked out in JavaScript from the window, so the server's markup always held two lanes and a desktop page laid the gallery out again once React ran. It is now dealt for every column count `columns` names and the stylesheet draws the one for the window's width, so nothing moves at hydration and a resize no longer re-renders it. A `columns` that is not a whole number, which threw, now rounds as the grid's column count does.
208
+
209
+ - **`preview` on `PlImage` and `PlGallery` no longer downloads the overlay before anyone reaches for it.** The closed preview sat in a `Suspense` boundary on every page that offered one, so its chunk was fetched on load and a server render left a boundary that hydration had to switch to client rendering. The overlay is now rendered from the first open, and its chunk is requested when the pointer, the focus or a touch arrives on the picture or tile.
210
+
211
+ - **A `PlImage` rendered on a server is drawn as soon as its file arrives.** The server's markup held the picture at `opacity: 0` until React had hydrated, with `priority` too, so a hero's Largest Contentful Paint waited for the page's JavaScript and a page without JavaScript never showed it. A picture in a server's markup is now drawn over its placeholder as it decodes, unless it has a `watermark` or `protect`, which keeps it hidden until React has seen it load so the original never shows without its mark or guard; a picture mounted in the browser, and a `src` changed later, still fade in. `PlGallery`'s tiles change with it.
212
+
213
+ - **A horizontal `PlTabs` measures its bar only when something about the bar changed.** It read the list's scroll width after every render, including a parent's render that changed nothing in the bar, and read the computed style on every scroll event. It now measures when the bar is resized, when a tab is added, removed or relabelled, and when the direction turns over; a scroll reads only the scroll position, and the fade still updates on the frame the tabs changed in.
214
+
215
+ - **Scrolling the page over a horizontal `PlTabs` or `PlScrollZone` whose contents fit no longer waits for the main thread.** Both put a wheel listener that can cancel the wheel on the strip as soon as they mounted, so the browser ran it before scrolling the page on every notch, even over a bar that had nothing to scroll. It is now on only while the strip overflows, and an overflowing strip turns a vertical wheel into a sideways scroll as before.
216
+
217
+ - **`hiddenUntilFound` on `PlAccordion` and `PlCollapsible` no longer makes Base UI warn.** Both passed `keepMounted={false}` to Base UI whenever the caller left `keepMounted` out, and Base UI warns in development when an explicit `false` meets `hiddenUntilFound`, which ignores it. `keepMounted` is now passed only when it was given, and closed panels behave as before.
218
+
219
+ - **A server-rendered `PlAvatar` starts loading its picture with the page.** The `<img>` was mounted only once Base UI had preloaded the picture after hydration, so the request waited for the JavaScript. It is now in the server's markup from the start, kept out of sight, out of the layout and away from screen readers until it has loaded, so what is drawn, the fallback's `delay` and what is announced are unchanged. A `loading`, `fetchPriority` or `decoding` passed in `imageProps` now applies to the request that loads the picture, so `loading: 'lazy'` defers an avatar below the fold where before it was fetched at once.
220
+
221
+ - **A heading passed to `PlEmpty` as its `title` stays where it was put.** The title was drawn in a `<p>`, which cannot hold a heading, so a server-rendered page put the heading beside an empty title, and React reported a hydration mismatch and rendered the tree again. The title is now a `<div>`, and it looks and reads as before.
222
+
223
+ - **A `PlSkeleton` and an indeterminate `PlProgressLinear` no longer add to a page's layout shift while they run.** The highlight and the segment travelled on `inset-inline-start`, which laid them out again on every frame, and the browser recorded every one of those frames as a layout shift: a 600×340 skeleton added 0.39 to the page's CLS in five seconds. Both now rest at the start of their box and move on `translate`, over the same path, at the same pace and in the same direction, so nothing on the page is laid out while they run.
224
+
225
+ - **A page rendered on a server hydrates in whatever language the reader's browser uses.** With no `locale` given, every component that writes a number or a date wrote it in the runtime's own locale: `PlAnimateCounter`, the progress components, `PlMeter`, `PlNumberField`, `PlSlider`, the pickers, `PlCalendar` and every chart, and so did a `PlDataTable` sorting text and a `PlAvatar` writing initials. A server's runtime is not the reader's, so an `en-US` server wrote `12,345` where a `de-DE` browser hydrated `12.345`, and React threw the server's tree away and rendered it again. A server render and its hydration now use the locale the component or the nearest `PlassProvider` names, or `en-US` when neither does, and switch to the browser's locale as soon as hydration is done. A page rendered only in the browser writes in the browser's locale from the first paint, as before, and a server-rendered page that gives the provider a `locale` shows the reader's format from the start.
226
+
227
+ - **A quick click on the words of a resting `float` label opens the `PlTreeSelect` under it.** The press gave the trigger the focus, which ended the rest and put the label back in reach of the pointer while it was still lying over the words. A release that came before the label had moved off them landed on the label, the browser gave the click to the frame round both rather than to the trigger, and the picker stayed shut. The label now lets the pointer through until a press that went down on the control is over, and a click on the label once it has risen still reaches the control.
228
+
229
+ ## 1.7.0 (2026-09-28)
230
+
231
+ ### Added
232
+
233
+ - **A field-shaped control takes `labelPlacement: 'float'`, a label that rests inside the control until it is wanted in the notch.** While the control is empty, does not have the focus and has no popup open, the label sits where the value would be written, set as the placeholder is — in the control's own text size, the regular weight and the muted ink — and the placeholder waits behind it. As soon as one of the three stops being true, the label rises into the same notch a `notch` label sits in, at its weight, and it comes back down once all three hold again. It moves by its position, its font size and its weight rather than by a transform, and under reduced motion it arrives at once. The gap in the edge opens as the label starts to rise and closes only once it is back down, so the line never crosses the word. A control that draws something at its start keeps a floating label in the notch, since that is where it would rest: a field with a `startIcon`, a `PlNumberField` with `split` steppers, the date, time and colour pickers with their glyph, and a `PlFilePicker`. It rests in `PlTextField`, `PlNumberField`, `PlCombobox`, `PlSelect` and `PlTreeSelect`, and `PlassProvider` carries it. The Flutter build adds the same placement.
234
+
235
+ - **`PlAccordion` takes `headingLevel`, so its headers sit at the level the page's outline needs.** Every header was an `<h3>`, so an FAQ straight under the page's title skipped a level. It takes `1` to `6` and defaults to `3`; only the element changes, never the type scale.
236
+
237
+ - **`PlAnimateMarquee` takes `label`, which names the strip.** Under `prefers-reduced-motion` the box becomes a tab stop while there is anything to scroll, and that stop had no name unless the caller added `role="group"` and an `aria-label` themselves. `label` makes the box a group with that name, the way `PlScrollZone`'s `label` names its region.
238
+
239
+ - **A `fixed` `PlBottomNavigation` or `PlFloatingBottomNavigation` publishes its height as `--plass-bottom-navigation-height`.** The end of the page was always under the bar, and so was a link reached with Tab, with no way to know how much room to leave. The bar now writes its measured height, the home indicator included, on the root element while it is mounted, and the token is `0px` without one, so `padding-bottom` and `scroll-padding-bottom` on the page reserve exactly the room it covers.
240
+
241
+ - **`PlButton`, `PlIconButton` and `PlFloatingActionButton` take `loadingLabel`, the word a screen reader hears while `loading`.** A loading button was always described by the label set's `loading`, "Loading" in English, which does not say what is being waited for. `loadingLabel` puts the button's own word in its place, such as "Saving", and the name stays what it was.
242
+
243
+ - **An `autoPlay` `PlCarousel` has a button that stops it and starts it again.** There was no way to stop a carousel that advanced on its own, which WCAG 2.2.2 asks of anything that moves for longer than five seconds. The button sits in the frame's top corner, comes first in the focus order, and is named "Stop slide show" or "Start slide show" from the new `carouselStop` and `carouselPlay` keys in all seven label packs, or from `playLabel` and `stopLabel`.
244
+
245
+ - **`PlassChartSeries` takes `id`, which the chart knows a series by from one render to the next.** A series' legend entry was known by its name and how many series before it have that name, so a series renamed between two renders came back switched on and let go of the pointer resting on its entry, and when the first of two series with one name left the data, the second took its state over. A series with an `id` keeps its switched-off state and the pointer on its entry through both. A series without one is known as before, and so is a `PlPieChart`'s slice, which has no `id`. The Flutter build makes the same change.
246
+
247
+ - **A chart's tooltip takes `mode: 'nearest'`, which answers for the mark the pointer is nearest rather than for the column it is over.** It is what a `PlScatterChart` has always done, and on a line or an area it is the answer when two series cross and the reader is pointing at one of them rather than at the month they share. There is no crosshair with it — a crosshair says "these numbers all belong to this column", and there is no column — and the arrow keys walk mark by mark instead of column by column. It measures to the mark, which on a bar is the bar's data end, and it reads a stacked series where the band was actually drawn rather than at its bare value.
248
+
249
+ - **A chart legend can fold past `maxEntries`.** A legend is a key, and a key of twelve names wrapped over four rows is a paragraph the reader has to search — on a card it is also four rows the plot no longer has. Past the number given, the rest go behind a button that says how many they are and opens them, with the same button folding them back. The entries kept are the **first** ones, in the order the series were passed, because that is the order their colours were handed out in and a key that rearranged itself as series were switched off would stop being a key. Nothing is hidden from a screen reader by it: the fold is a real button, reachable by keyboard, and every series is in the table under the chart either way.
250
+
251
+ - **`PlBarChart` takes `sort`, `maxCategories` and `otherLabel`.** A bar chart is the one shape whose categories can be shuffled without losing anything — that is the test for reaching for it over a line chart — so `sort` is free, and it is what turns a wall of bars into a ranking a reader can scan down. `maxCategories` keeps the largest that many and sums the rest into one, which is the answer to a chart of ninety countries: a bar too short to see costs width without saying anything. What is folded is decided by size and never by `sort`, and the fold is always last, because it is not a category but what is left; a fold of nothing but gaps stays a gap rather than becoming a zero. Both are changes to the **data**, so the axis, the tooltip and the table under the chart all agree with the picture about which categories there are — and both happen before a `'full'` stack renormalises, or a category folded away afterwards would have taken its share of every other bar with it. A series hidden from the legend is still counted, because a chart whose columns rearranged themselves when an entry was clicked is one a reader cannot use. The label pack gains `chartOther`, `chartMore` and `chartFewer` in all seven languages.
252
+
253
+ - **A chart takes `reference`, which draws a target, an average or a limit across the plot.** One line or several, each at a value on the **value** axis — so a reference runs across a vertical chart and down a horizontal one, and a caller who turns a bar chart on its side does not have to move it. It is **not data** and is drawn as if it knows that: dashed, in the muted ink rather than a palette slot, and under the marks, because a reference that looks like a series is one a reader will try to read a value off. `color` takes it to a family where the line means something the page already has a colour for, `dashed: false` makes it solid, and `label` sets a short word at the end of it. Each one is also written into the description a screen reader is handed with the chart, because a target is a fact about the picture rather than decoration on it. On `PlLineChart`, `PlAreaChart`, `PlBarChart`, `PlScatterChart` and `PlTimelineChart`.
254
+
255
+ - **A value axis can step by multiplying, with `scale`.** `log` is the one scale in the library that changes what a distance on the plot means: the same length becomes the same **ratio** rather than the same number of units, so the gap from 10 to 100 is the gap from 100 to 1,000 — which is the only way a series that runs from 3 to 3,000,000 is drawn with the small end still legible. There is no zero on it and nothing below it either, and that is stated rather than hidden: the axis floors at the smallest positive power of ten the data needs, or three decades under the top when the data offers none, and a zero or a negative is drawn on that floor. The ticks are powers of ten, with each decade's 2 and 5 as well over a span of two decades or fewer. It is read on the value axis, and on a category axis only where that axis is a second value axis — a `PlScatterChart`'s. A bar is the wrong mark for it, for the same reason a bar's axis cannot be cropped.
256
+
257
+ - **A chart's value labels take `valueLabelColor`, for a chart that needs them in the page's own ink.** They are written in their series' colour by default, which is what says which number belongs to which mark. The palette clears 4:1 against the sheet — the floor the design language sets for a chart _mark_ rather than the 4.5:1 body text wants — so a chart whose labels have to meet the text contrast rule on their own asks for `ink` and gets the page's foreground on every one. On `PlLineChart`, `PlAreaChart` and `PlBarChart`.
258
+
259
+ - **A chart's category axis can turn its labels, with `tickAngle`.** An axis runs out of room across and not down, so a name wider than its slot was cut to it — and past about four characters a cut stops telling two names apart: "Onboar…", "Onboa…" and "Onbo…" are three labels a reader cannot tell apart. `xAxis` now takes an angle in degrees, and a turned label takes one line of text across the axis however long it is, spending the room under the plot instead. `-45` is the one to reach for and `-90` stands the names on end. The band under the plot grows to hold them, up to about two fifths of the chart's height; past that a name is still cut, and the tooltip and the table still have all of it. Only the category axis, and only where it runs along the bottom — a value axis' ticks are numbers already rounded to be short, and a horizontal bar chart's names already have a row each.
260
+
261
+ - **`tickAngle` takes `'auto'`, which turns the labels only once one of them would be cut.** Upright is the best an axis can do when there is room for it, and a diagonal beats a cut at every width, so `auto` is the setting for a chart whose categories are the caller's data rather than the caller's choice. It settles on `0` or `-45` and never on a third angle: one fitted to the longest name would be a different angle on every chart on a dashboard, and would change under the reader as the window is dragged.
262
+
263
+ - **`PlLineChart` and `PlAreaChart` take `nulls`, which has the answer `connectNulls` could not express.** A `null` means _nothing was measured_, which is a different claim from _nothing happened_, and a boolean only had two of the three replies. `gap` breaks the line, which is the default and what the chart has always done; `connect` joins the two sides, which is what `connectNulls` was; and `zero` reads the gap as a nought — on the value axis, in the tooltip and in the table under the chart as well as under the line, because a zero is a value and has to be somewhere on the axis. `connectNulls` still works and is read only where `nulls` says nothing, so nothing has to change today; it is deprecated and goes in 2.0.0.
264
+
265
+ - **`PlPieChart` takes `innerRadius` and `padAngle`.** `shape` picked both — nothing cut out of a `pie`, a little under two thirds out of a `donut` and a `semi`, and a gap of 2px between slices — and neither could be moved. `innerRadius` is a fraction of the radius, so a hole holds its proportion at whatever size the chart is drawn; `padAngle` is in degrees, and widening it is what makes a ring read as separate segments rather than as one band. Left out, both are what they were. A slice narrower than twice the gap still keeps none of it, or a one-degree sliver would invert and draw the whole circle instead of nothing.
266
+
267
+ - **`PlPopconfirm` takes `loadingLabel`, the word a screen reader hears while it waits on `onConfirm`.** Its confirming button was always described by the label set's `loading`, "Loading" in English, which does not say what is being waited for, and the popconfirm had no way to hand it another. `loadingLabel` reaches that button and puts the popconfirm's own word in its place, such as "Revoking", and the name stays what it was.
268
+
269
+ - **`PlTable` takes `label`, which names the table when there is no `caption`.** It is the `<table>`'s `aria-label`, and while a grid wider than its sheet, or taller than its `maxHeight`, scrolls, it also names the box the grid scrolls in, which is a tab stop then and had no name without a caption. The box becomes a group with that name, the way `PlScrollZone`'s `label` names its strip; a `caption` still names both when there is one.
270
+
271
+ - **`PlTabs` takes `align`, which places each label inside its tab.** It moves the words and never the tabs, so it is safe to set on a bar that is already laid out. A horizontal bar sizes every tab to its own label and nothing changes; it takes effect on a `vertical` bar, whose tabs are all as wide as the widest, and on a `fullWidth` one, whose tabs are all an equal share. `start` is what a bar down the side of a settings page usually wants, so the names line up as a list rather than drifting around a centre line. Logical rather than physical, so it turns over under `rtl` and an icon beside the label travels with it.
272
+
273
+ - **The title bar of a `draggable` `PlWindowPane` is a tab stop, and the arrow keys move the window.** A window moved only by dragging, so a keyboard could resize one and never move it. The bar is now a stop ahead of the window's buttons, named from the new `moveWindow` key in all seven label packs or from `moveLabel`, with a ring while the keyboard holds it. Each arrow key moves the window 16px, and 64px with Shift, the way the arrow points in either writing direction, and a key never takes the bar off the screen. Every move goes through `offset`, so a window given `offset` and `onOffsetChange` reports where it should go, which is also how an app adds buttons that move it without a drag.
274
+
275
+ ### Changed
276
+
277
+ - **`plass-ui/styles.css` no longer restyles the page's own images and SVGs.** Its reset made every `<img>`, `<svg>` and `<video>` on the page a block and gave every picture `max-width: 100%` and `height: auto`, so an icon set in a sentence dropped onto a line of its own and a picture with `width` and `height` written on it lost the height it was given. The components now lay out the glyphs and pictures they draw themselves, and a bare `<img>` handed to a `PlCarousel` slide, a `PlAvatar` or a `PlChatBubble`'s `media` still fits its box. A project that runs Tailwind is unaffected: Preflight still applies both rules.
278
+
279
+ - **A field's interaction light is turned down, and steps out of the way while the field is being typed into.** A key is pressed and the reader has moved on; a field is pressed once and then written in for a minute, with the bloom sitting under the words being typed and following a pointer nobody is moving any more. At a key's strength that reads as a stain on the sentence rather than as the surface answering. Both layers are now mixed down to `--plass-glow-field-strength`, a new token at 55% that a theme can move, on `PlTextField`, `PlNumberField`, `PlSelect`, `PlCombobox`, `PlFilePicker` and the six picker triggers. Buttons, toggles, chips, pills and segmented controls are untouched. On the three fields that hold a text input the bloom also fades out on the first keystroke and comes back on the next real pointer move, which is `[data-quiet]` over `.plass-glow` and costs one render per change of it.
280
+
281
+ - **A carousel stops once the focus comes into it, and stays stopped until its button starts it again.** Hover and focus shared one pause, so a pointer passing over the frame started it again while a keyboard reader's focus was still inside. The pointer now pauses it only while it is over the frame. A reader who asked for reduced motion starts stopped rather than never starting, and the live region announces the slide once the carousel has stopped.
282
+
283
+ - **A `PlScatterChart` card and its live region write a bubble's `z` after its y, in brackets.** The size was left to be read off the bubble, and was written only in the table under the chart. It now follows the y through the chart's `format`, or compactly without one, "10, Q1: 22 (5)", and follows a point's own `label` too, since a label stands in for the y alone.
284
+
285
+ - **A `dashed` series is keyed in the legend with a short dashed rule rather than the filled square.** A dashed line reads as a forecast or a target on the plot, and its legend entry was the same square as every other series', so the key did not carry the one distinction the plot did. The entry now draws two dashes at the line's weight and rhythm wherever the line is drawn dashed, on `PlLineChart` and on a `PlAreaChart` that is not stacked; a bar or a stacked band has no line to dash and keeps the square.
286
+
287
+ - **A chart's value labels are written in their series' colour.** `valueLabels` wrote every number in the page's own ink, so a plot with four labelled lines left the reader tracing each number back to the mark it belonged to. Each one now takes the colour of the line or the bar it sits on — or of the point, where a point carries a colour of its own — at a slightly heavier weight to hold up against the sheet. A pie's share is unchanged: it is written _inside_ the slice, where the sheet's own colour is what stays legible on a pale fill and a dark one. The palette clears 4:1 against the sheet, which is the floor the design language sets for a chart mark rather than the 4.5:1 body text wants; every number is also in the tooltip and in the table under the chart.
288
+
289
+ - **A chart legend's switched-off entry fades instead of going grey.** The swatch went part-transparent and the name was recoloured to the muted ink, which read as a second kind of text rather than as the same entry, off. The whole row now fades as one thing — swatch, name and value together, at one opacity — which is what a control that has been switched off looks like everywhere else in the library. The line through the name stays, because it is the half of "off" that survives being read in one colour. The Flutter build makes the same change.
290
+
291
+ - **A `PlCollapsible`'s title and subtitle wrap, as a `PlAccordionItem`'s do.** They were each held to one line and ellipsed, so a heading that ran past the header lost its end with nothing to press for the rest. `truncate` puts both back on one line, for a name out of a database beside a control.
292
+
293
+ - **`PlCommandPalette` draws the first 100 matching commands, with a line under the list saying how many more matched.** Every match was drawn, which made the first open and a short query slow at around two thousand commands. Typing narrows the list, so a command further down is a letter or two away, and the line is written with the label pack's `chartMore` ("150 more").
294
+
295
+ - **A `PlConfirmProvider` sheet draws no × any more, as the Flutter one never has.** A question is answered by its own buttons, which say what each answer does, and while it is `dismissible`, Escape and a press outside still answer no.
296
+
297
+ - **A `PlTextField`'s `startIcon`, `endIcon` and loading spinner stay muted while the field is focused, as a `PlNumberField`'s adornments do.** They turned to the family's accent as the field took the focus, which dyed the content of a glass field that the family otherwise reaches only at its edge, its ring and its caret. The focus still shows on those three.
298
+
299
+ ### Fixed
300
+
301
+ - **A key or a move that changes nothing reports nothing, in both builds, as Base UI's slider reports nothing at an end.** An arrow at the end of a range still reported the value it left unchanged: through `PlPanes` `onResize` and `onResizeEnd`, `PlSidebar` `onResize` and `onResizeEnd`, a `PlWindowPane` corner's `onResize` and a `PlColorPicker`'s `onValueChange` from its square and its opacity rail in both builds, and through a Flutter `PlSlider`'s `onChanged` and `onChangeEnd`. Each now reports only a value that moved, and a drag held against a limit reports nothing either.
302
+
303
+ - **Under reduced motion everything a component eases arrives at once, as the Flutter surfaces do.** Only the pointer light stopped easing under `prefers-reduced-motion`. Every change the house transition carries still eased over `--plass-duration`, a glass key's wash, a field's edge, a shadow, a label's ink and a switch's track, and so did every transition a component writes for itself: a popup, sheet, scrim or toast fading in and out, an accordion, collapsible or tree branch folding, a chevron turning, the `PlTabs` indicator, the `PlSegmentedButton` tile, the `PlFloatingBottomNavigation` key and the `PlSwitch` thumb travelling, a progress bar, meter, ring or gauge filling, and the colours of close buttons, table rows, links, legend entries and field adornments. A `PlImage` picture that arrives at once takes its stand-in away as it arrives, rather than a `--plass-duration` later.
304
+
305
+ - **Clearing a picker from its × hands the focus back to the trigger.** The × leaves the page with the value it cleared, and the focus it held fell to the document, so a keyboard reader who emptied a date started again from the top of the page. It now goes to the trigger of the field they just emptied, on `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker`, `PlTimePicker`, `PlColorPicker` and `PlTreeSelect`.
306
+
307
+ - **The × on a `PlAlert`, a toast, a `PlModal`, a `PlDrawer`, a `PlPopover`, a `PlTour` and a `PlFilePicker` file can be pressed anywhere in a 24px square around it.** It could be pressed only where it is drawn, which at the smaller sizes is under the minimum target size of WCAG 2.5.8. The × is drawn at the same size and nothing around it moves. It is the square the × on a chip and on a picker trigger already had.
308
+
309
+ - **A notched label stays where it is as its field takes and loses the focus, as the Flutter label does.** The edge thickens from 1px to 2px with the focus, and a legend is laid out from the inside of its border, so the label moved a pixel along with it and seemed to shake on a `PlTextField`, `PlSelect`, `PlCombobox`, `PlNumberField` and the six picker triggers. The edge still thickens, inward, and the label no longer moves.
310
+
311
+ - **Under reduced motion, a `PlAnimate*` effect ends on its last frame rather than on the element as it stands.** The effect was switched off, so `<PlAnimateRotate from={0} to={90}>` stood at 0°, a `mode="out"` element never left, and a caller waiting on `animationend` waited for ever. The effect now runs in no time: nothing changes until the moment it would have started, `delay` included, and then it is on its last frame, so the turn stands at 90°, an exit has left and `animationend` fires. An alternating run ends where it would have, an endless one on the last frame of one pass, and a `timeline="view"` effect goes back to the clock. Content waiting for its trigger is still shown as it is.
312
+
313
+ - **A `PlAppLogo` or `PlAspectRatio` rendered as a link draws the house focus ring while the keyboard holds the focus, as the Flutter logo draws it, rather than the browser's own outline.** Neither carried the ring a `render={<a href="…" />}` needs, so a keyboard reader saw the browser's default outline round the one link on the page drawn in another style.
314
+
315
+ - **A `PlAppLogo` plate is drawn in the material a `PlAvatar` is, as in the Flutter build.** It rests at an elevation of 1 on every variant, a `glass` plate takes the neutral `--plass-border` edge and the gloss along its top, and a `ghost` plate takes `--p-soft-press`. A `glass` plate had a white edge that disappeared on a white header and no gloss, a `solid` one had no drop shadow under its tinted one, and a `ghost` one took the paler `--p-soft`.
316
+
317
+ - **A floating `PlBackTop` clears the safe area at its inline end as well as at the bottom.** It added only `env(safe-area-inset-bottom)`, so the camera cutout of a phone held on its side could cover it. It now stands off the side it is against as well, the right or the left under RTL, as `PlFloatingActionButton` does.
318
+
319
+ - **A `ghost` `PlBadge` is lifted by its `elevation`, as a `ghost` `PlAvatar` is.** The badge kept its own copy of the mark material, and that copy left the elevation shadow off `ghost`, so a ghost badge given an `elevation` still lay flat. `solid` and `glass` badges are unchanged.
320
+
321
+ - **A `PlButton`, a `PlIconButton` and a `PlFloatingActionButton` that are `loading` say so after their name.** The button was announced as unavailable with nothing to say why: most screen readers do not read `aria-busy`, and the spinner is hidden from them. It is now described by the label pack's new `loading` key, "Loading" in English, so its name, and an `aria-label` given to it, stays as it was.
322
+
323
+ - **A `PlCalendar` draws its chosen day in its family's gradient, as a picker's calendar does.** The inline calendar set no fill for the chosen cell, so the day it held showed only as a bolder number in the page's ink.
324
+
325
+ - **An `autoPlay` `PlCarousel` holds the slide it is on for a whole `interval` once its browser tab comes back from the background.** It held still while the tab was hidden, but the interval went on running behind it, so the first turn could come a moment after the page was back in sight. Coming back now starts the interval over, as it does when a hidden tab panel shows the carousel again.
326
+
327
+ - **An `autoPlay` `PlCarousel` holds still while a finger is down on its strip, and for a whole `interval` once the finger lifts.** It paused only while a pointer was over the frame, and a browser that takes a touch over to scroll the strip reports that pointer as cancelled and gone while the finger is still dragging, so the next turn pulled the strip from under the finger. Every touch that starts on the strip now holds it until the last one lifts.
328
+
329
+ - **Clicking an arrow or a dot of an `autoPlay` `PlCarousel` stops it in Safari too.** The carousel stopped only because the click brought the focus into it, and Safari does not focus a button it clicks, so there a reader who had steered to a slide saw it turn away at the next interval. A click on an arrow or a dot now stops it by itself, as a press does in the Flutter build, and the button reads "Start slide show" until it is pressed. A carousel the button has just started keeps playing through a click on an arrow or a dot until the focus next comes in or goes out, whether or not the start brought the focus in.
330
+
331
+ - **A `PlCarousel` in a hidden tab or a closed disclosure shows the slide its dots mark when it appears, and an `autoPlay` one waits there while it is hidden.** The strip could not be placed while it had no width, so one mounted there showed the first slide under the dot its `defaultValue` or `value` named, and one hidden after it had been on screen came back on the slide it was hidden on, whatever `value` had moved it to meanwhile. `autoPlay` also went on turning the dots while nobody could see them. The strip is now placed on the current slide every time its width comes back, before that frame is painted and without travelling there, and `autoPlay` holds while the track has no width, then keeps the slide it was hidden on for a whole `interval` once it is shown again, without touching the stop and start button.
332
+
333
+ - **A `PlCarousel` given a `defaultValue` or `value` other than 0 opens on that slide.** The strip was placed only when the slide changed after the first render, so the dots marked the chosen slide while the first one was showing. The strip is now on the chosen slide before the first paint, without travelling there.
334
+
335
+ - **A line or area series the legend lets go of stays cut out under its markers until it is whole again, as in the Flutter build.** The cut went at once while the series' opacity eased back, so its line and band showed through its markers for the length of the ease.
336
+
337
+ - **A named `PlGaugeChart` whose `min` equals `max` is called by its `label` alone.** It draws only its empty state, but its name still gave a reading and the top of the scale, "Quota: 5 / 10", ahead of the empty state's words that describe it. It draws no reading, so its name no longer gives one, as with a `null` value. The Flutter build makes the same change.
338
+
339
+ - **An empty `PlPieChart` `donut` or `semi` no longer draws its `center` over the empty state.** With a total of zero no ring is drawn, and the empty state's words sit where the hole would be, but the caller's `center` was still laid over them in the middle of the box. An empty pie now draws its empty state alone, as the Flutter pie does.
340
+
341
+ - **A chart's empty state and what a `PlPieChart`'s hole holds are read, and so are a named `PlGaugeChart`'s own `center` and its `caption`.** All of them are drawn inside the chart's `role="img"`, whose content a screen reader never reads, so an empty `PlLineChart`, `PlAreaChart`, `PlBarChart`, `PlScatterChart`, `PlTimelineChart`, `PlHeatmapChart` or `PlPieChart` was announced by its name alone, a donut's "2,670 accounts" was never heard, and a gauge's caption and a `center` of its own were lost behind a name that says only the number. They now describe the chart through `aria-describedby`: the empty state's words in place of the summary, the pie's centre ahead of its slices, and a named gauge's own `center`, then its `caption`, or its empty state, leaving out the number its name already says. The Flutter charts already read the first two after their name, and the Flutter build makes the same change to the gauge.
342
+
343
+ - **A line or area chart draws its markers over every band and every line, and its value labels over every marker.** A stacked `PlAreaChart` drew each band's markers and labels before the band above it, so that band's tint covered the upper half of each marker and the labels, and the gap between the two bands ran through the middle of each marker; on a line chart, a line drawn later crossed an earlier series' markers. A series faded for a legend entry still fades its markers and labels with it, and its own line or band does not show through them. The Flutter build makes the same change.
344
+
345
+ - **A `PlScatterChart`, a `PlTimelineChart`, and a `PlLineChart`, `PlAreaChart` or `PlBarChart` whose tooltip is in `nearest` mode go on reading the mark they are on when a series or a row ahead of it leaves the data.** The mark being read was held by its series' place, so when a series before it left, the card and the live region read the mark at the same place in the series that moved into its place, and a mark whose series left handed its reading to the series after it. It is now held by what its series' legend entry is known by, its `id`, its name or else its place, and a timeline's span by its row's name or else its place, and the reading is let go once that series or row has left. The Flutter build makes the same change.
346
+
347
+ - **A `PlPieChart` goes on reading the slice it is on when a slice ahead of it leaves the data.** The slice being read was held by its place, so when a slice before it left, the card and the live region read whichever slice moved into that place, and a slice read as it left the data handed its reading to the slice that moved in. It is now held by its name, as a slice switched off in the legend is, and the reading is let go once no slice drawn has that name. The Flutter build makes the same change.
348
+
349
+ - **Two series of the same name in a chart's legend, or two slices of the same name in a `PlPieChart`'s, are switched off and pointed at one at a time.** Each legend entry was known by its name alone, so switching off either of two series of one name switched off both, pointing at the second faded every series but the first, and React warned of two children with the same key in the legend and in the table under the chart. A series named "1" was known by the same key as an unnamed series in the second place, so the two switched off and on together. A repeated name is now told apart by how many series before it have that name, and a name never meets a place. The Flutter build makes the same change.
350
+
351
+ - **A series switched off in a chart's legend, or a slice switched off in a `PlPieChart`'s, is held by its name when the data changes.** It was held by its place, so when a series ahead of it left the data, the series that moved into that place was switched off instead, and a place past the new end switched a series off again once the data grew back. It is now held by its name, or by its place when it has none, and a series that leaves the data is drawn again if it comes back. `hidden` is still read once, when the chart mounts. The Flutter build makes the same change.
352
+
353
+ - **A `PlPieChart` lets go of the slice it is reading once it is rendered again with that slice drawing no arc.** A slice that left the data went on being read as "0 · 0%", or "0 · NaN%" when the slices left summed to 0, and a slice now worth 0 or a gap went on being read as worth nothing, each with every other slice faded for it. A slice that still draws an arc is still read. The Flutter build makes the same change.
354
+
355
+ - **`Home` and `End` on a line, bar or area chart whose tooltip is in `item` mode read the whole column they move to, as in the Flutter build.** With the pointer still over the plot, either key read only the one value nearest where the pointer last was, where the arrow keys read the whole column, since a key brings no pointer to measure against.
356
+
357
+ - **A `PlHeatmapChart` rendered again without the cell it is reading lets go of it, as in the Flutter build.** A cell the data turned into a gap, or a treemap tile whose value no longer had an area to be, was kept: nothing was read while it was gone, `Escape` was taken with nothing to clear, and once the data brought the cell back it was read again, with its card and drawn whole. A cell that is still there is still read.
358
+
359
+ - **A line, bar or area chart lets go of the column it is reading, and a scatter, a timeline or a chart whose tooltip is in `nearest` mode of the mark it is reading, once it is rendered again without it, as in the Flutter build.** A column past the end was kept, with its crosshair drawn past the plot, and read again once the data grew back, and a mark was held by its place among the marks, so the reading moved onto whichever mark took that place. A column or a mark that is still there is still read.
360
+
361
+ - **A line, bar, area, scatter or pie chart lets go of the legend entry the pointer or the focus is on when it is rendered again without that entry.** The entry's button was removed without reporting a leave, so its series stayed hovered: every remaining entry was drawn faded once the last series went, every series but the one that took the place of a middle one was faded, and the other series faded again once the data brought a series back to that place, wherever the pointer was by then.
362
+
363
+ - **A `PlPieChart` whose tooltip is turned off takes no key, as every other chart.** With `tooltip={false}` or `mode: 'none'`, ← and → still lit a slice and faded the others, with no card and no live region to say which slice it was. The keys now go on to whatever the chart sits in, as they do on a line, bar, area, scatter, timeline or heatmap chart with its tooltip off.
364
+
365
+ - **The first ← on a `PlPieChart` with no slice being read reaches the last slice.** It reached the second from the end, while the first → reached the first slice; the Flutter pie already started a walk backwards on the last.
366
+
367
+ - **Every chart mark fades as a legend entry or the crosshair points at it, and a line's marker and a scatter mark grow under the crosshair, over `--plass-duration`.** The class that eases them was pieced together out of three strings, so the stylesheet never had a rule for it. Every line, area, bar, scatter, pie, heatmap and timeline mark changed in one frame. Under reduced motion they still change at once.
368
+
369
+ - **A `PlPieChart` slice's name is said and written once.** The live region read "Search, Search: 40 · 40%", and the card was headed by the name and wrote it again beside the swatch. The name now heads both with only what the slice is worth after it, "Search, 40 · 40%", as the Flutter card writes it. A custom `tooltip.render` is still handed the slice with its `name`.
370
+
371
+ - **A `PlTimelineChart`'s own `tooltip.render` is handed the row a span sits on, and the span's own name.** Its `category` was the name of whichever row had the same place as the span has along its own row, its `index` was that place, and its item's `name` was the row, so the second span on "Design" was handed "Build", `1` and "Design", and nothing said which span it was. It is now handed the span's row as `category` and that row's place as `index`, as a bar is handed its column, and the span's own `label`, in words, as its item's `name`.
372
+
373
+ - **`Escape` clears a `PlPieChart` readout.** The pie took only the arrow keys, so a slice being read stayed on the card and in the live region until the pointer or the focus left, where every other chart clears it on `Escape`. With a slice being read, the press now clears it and stops there; with none, it goes on to whatever the chart sits in.
374
+
375
+ - **A `PlScatterChart`'s table writes a point's own `label` in place of its y.** The table wrote the bare y beside a point the card called by its label; it now writes the label, as the card and every other chart's table do.
376
+
377
+ - **A `PlTimelineChart` span with no name is read with its row once.** Its card was headed by its row and named the row again beside the swatch, and the live region read "Design, Design: Jan 1, 2026 – Jan 9, 2026". The row now heads the card alone and is read once, "Design, Jan 1, 2026 – Jan 9, 2026"; a span that names itself still has its row beside the swatch. A custom `tooltip.render` is handed no `name` for such a span.
378
+
379
+ - **A `PlScatterChart` point's x is written on its card, in its live region and in its table as the x axis writes it.** It was written as the raw figure, `12345`, where the axis under it wrote `12.3K`. It now goes through the axis' `tickFormat` when it has one and compactly when it has not; the chart's `format` stays the y's, and a date is still written as a date.
380
+
381
+ - **A `PlScatterChart`'s table writes y and z as every chart writes a value.** They were written with at most two decimals and never compacted, so a z the card and the legend never showed was `1,500,000` and a y the card wrote as `48.3K` was `48,300`. They now go through the chart's `format`, or compactly without one.
382
+
383
+ - **`Escape` on a `PlHeatmapChart` is taken only while a cell is being read.** The heatmap answered the key whatever it held and marked it handled, so a heatmap with nothing being read kept the key from a `usePlHotKeys` binding around it, and one with a readout let the same press go on and close the sheet it sat in as well. With a readout, the press now clears it and stops there; with none, it goes on to whatever the chart sits in, as on every other chart.
384
+
385
+ - **A labelled `PlSparkline` reads its numbers the way a chart writes them.** The values written out for a screen reader were the raw figures, `48300` and `1234.567`, where every other chart writes a value it has no `format` for compactly and grouped in the reader's locale. They now go through the same writer, in the provider's `locale`: "1,234.57, 48.3K".
386
+
387
+ - **`Escape` on a chart is taken only while there is a readout to clear.** The plot answered it whatever it held and marked it handled, so a chart with nothing being read kept the key from a `usePlHotKeys` binding around it, and a chart with a readout let the same press go on and close the sheet it sat in as well. With a readout, the press now clears it and stops there; with none, it goes on to whatever the chart sits in, as in the Flutter build.
388
+
389
+ - **A `PlSparkline` value outside a pinned `min` or `max` is cut at the edge of the strip.** It was drawn past the box, so a bar for 2 under a `min` of 5 hung below the strip into whatever sat under it. The marks are now held to the strip's height, so a value outside the range is not seen, and no end dot is drawn on a last value the strip cannot hold.
390
+
391
+ - **An element a chart axis's `tickFormat` returns is written as the words in it.** The type lets a formatter return any node, and the axis passed the result through `String`, so `<b>12</b>` put `[object Object]` on the axis. A tick is SVG text, which holds words and no markup, so an element is now read for its text, on either axis; a string or a number is written as before.
392
+
393
+ - **A read-only `PlCheckbox` that is `indeterminate` is filled with its family's gradient, as a live or disabled half-set box is, and a disabled one drops its edge over the gradient, as a disabled ticked box does.** A read-only half-set box was left as clear glass, with its dash in the page's ink, and a disabled one kept its neutral edge drawn round its gradient.
394
+
395
+ - **A `PlChip` with `onClick` stays a button while it is `disabled`, announced as one that cannot be used, as a disabled `PlButton` is, and what it holds is no longer mounted again as `disabled` changes.** The button was taken away while the chip was disabled, so a screen reader heard a run of text with nothing to say it was a control, and the label, the icons and the count moved out of the button and back, which loaded a `PlAvatar` in `startIcon` again and showed its initials until it had. The label is now a `<button disabled>`, out of the tab order, and a click on it reaches nothing around the chip. The Flutter build makes the same change.
396
+
397
+ - **`PlCodeBlock theme="auto"` follows the nearest element that forces a theme.** On a `.dark` page, or one the system had made dark, a block inside an element forced light with `.light` or `data-theme="light"` was still painted dark, because only a dark ancestor was named. The palette `auto` reads is now declared on every theme root, so the nearest one wins however deep the nesting goes, as it already did for the colour tokens.
398
+
399
+ - **A `PlCollapsible` leaves space between its header and its body.** Under the default header the body began at the open header's tinted edge, so its first line read as part of the title; only a `trigger` of the caller's own had the space. Both now have the space a `PlAccordion` section leaves.
400
+
401
+ - **An open `PlColorPicker` popup inside a disabled `PlFieldset` or `<fieldset>` takes nothing, as with the picker's own `disabled`.** The panel is portalled out of the fieldset, and a popup picker counted only its own `disabled`, so a controlled `open` showed a square, rails, swatches and a value field that still changed the colour while the trigger was disabled. They are now disabled with the fieldset.
402
+
403
+ - **The value field of a disabled `PlColorPicker` is disabled, so Tab passes it and a screen reader announces it as unavailable.** It was only read-only while the picker was `disabled`, so it stayed a tab stop and was read as read-only beside swatches that were disabled, while a disabled `<fieldset>` round the picker disabled it as the browser does. A `readOnly` picker's field is still read-only and takes the focus.
404
+
405
+ - **The chevron of a `PlCombobox` with a `label` is named by `openLabel`, `Open` by default.** Base UI pointed it at the field's label, which outranks an `aria-label`, so a screen reader announced the chevron by the field's name rather than by what it does, and `openLabel` had no effect there.
406
+
407
+ - **The chevron of a disabled `PlCombobox` and the steppers of a disabled `PlNumberField` are drawn at the half opacity of the field they sit in, as a `PlSelect` chevron is, and a disabled combobox shows no clear ×, as a read-only one shows none.** The chevron, the × and the steppers each faded to half a second time inside a field already at half, so they were drawn at a quarter, and the × stood beside a value it could not clear, where the Flutter combobox and the pickers of both builds show none on a disabled field. A stepper that has run into `min` or `max` in a field that is not disabled still fades to half on its own.
408
+
409
+ - **A `PlCombobox` that holds a value keeps what is typed into it as it renders again, under a parent that passes `items` inline and renders on every keystroke as much as with a value the list does not have.** The value was handed to Base UI as a row object made anew each time `items` was, and on every render for a value no option holds, and Base UI took each new object for a new value and wrote its label back over the query, so the field could not be typed into. It also dropped an error a `PlForm`'s `errors` had put on the field, with or without `multiple`, whenever the combobox rendered again. A renamed option's new label is still written into the field, but no longer over a query typed into the open list, as the Flutter combobox does.
410
+
411
+ - **A disabled `PlCombobox` keeps its chevron in the muted ink under the pointer, and so do a `PlNumberField` stepper that has run into `min` or `max` or sits in a disabled field, and the label of a disabled `PlSegmentedButton` segment or of any segment in a disabled set.** The chevron and the steppers turned to the family's accent as the pointer went over them, and a segment's label to the foreground, where a disabled control answers the pointer with nothing else, and the Flutter chevron, steppers and segments stay muted.
412
+
413
+ - **A `PlCombobox` that holds nothing says nothing when `Escape` is pressed with its list closed, or when the text of a single-value field is emptied.** Both called `onValueChange` with `null`, or with an empty array under `multiple`, so a parent heard a change that was not one, where the Flutter combobox calls nothing. A field that holds a value is still emptied by `Escape`, and says so once.
414
+
415
+ - **The clear × on a `PlCombobox` can be pressed anywhere in a 24px square around it.** It could be pressed only where it is drawn, which at the smaller sizes is under the minimum target size of WCAG 2.5.8. The × is drawn at the same size and nothing around it moves; the chevron beside it keeps its own size, and a press on the input opens the list as it always has.
416
+
417
+ - **A `PlCommandPalette` row eases its ink with its wash as the highlight reaches it and leaves it, and washes in the family's `--p-soft-hover`, as a `PlSelect` and a `PlMenu` row do.** The ink changed in one frame while the wash eased, and the wash was the paler `--p-soft`.
418
+
419
+ - **The sort mark of a `PlDataTable` heading turns over as the column changes direction.** Its transition named `transform`, but `rotate-180` sets the separate `rotate` property, so the mark flipped in one frame.
420
+
421
+ - **A `PlDataTable` wider than its sheet, or taller than its `maxHeight`, can be scrolled from the keyboard.** A table with no sortable heading and no tick column had nothing in it to take the focus, which left whatever was past the edge out of reach without a pointer. While the grid scrolls, the box it scrolls in is a tab stop, named by `caption` or, without one, by the new `label`, as a `PlTable`'s is; `label` names the `<table>` itself as well.
422
+
423
+ - **A `PlDataTable` with `manual` paging and no `getRowKey` keys a row by its place in the whole set.** The default key is a row's position in `rows`, and with the pages cut by a server `rows` is only the page on screen, so every page's first row had the key `0` and a row ticked on one page showed ticked on the row in the same place on the next. The position is now counted from the first row of the first page. `getRowKey` is still the one to set: a page the server sends again with a row added moves every key below it.
424
+
425
+ - **A picker inside a disabled native `<fieldset>` looks and reads disabled, as it does inside a disabled `PlFieldset`.** The browser disabled the trigger button, but the field was drawn at rest and said nothing of it: `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker`, `PlTimePicker`, `PlTreeSelect` and a popup `PlColorPicker` read only the `PlFieldset` context. They now count the fieldsets round them as well, as the colour picker's panel already did.
426
+
427
+ - **Every control inside a disabled `PlFieldset` is drawn disabled, exactly as with a `disabled` of its own.** Base UI and the browser disabled the controls in one, but each drew itself from its own `disabled` only, so a text field, a select, a checkbox or a button there kept its resting look, its label's ink and its pointer light, and looked as live as a field outside. The fields, the pickers, `PlCheckbox`, `PlSwitch`, `PlRadioGroup`, `PlSlider`, `PlRating`, `PlSegmentedButton`, `PlToggle`, `PlButton`, `PlCalendar`, `PlPagination` and `PlTransfer` now read the fieldset, a fieldset further out included. A `PlSlider` and a `PlSegmentedButton` in one also stop answering, which they went on doing because neither is a native control nor inside a `Field`; a `PlFilePicker` in one refuses a dropped file; and a `PlFilePicker`, a `PlCalendar`, a `PlDateRangePicker` and a `multiple` `PlTreeSelect` in one no longer hand their values to a `PlForm`, as a disabled field does not. A `PlButton` rendered as a link stays live and is drawn that way, since the browser disables no `<a>`.
428
+
429
+ - **A floating `PlFloatingActionButton` clears the safe area of an edge-to-edge screen.** It stood a fixed `offset` off the corner, so on a phone that draws under its home indicator or navigation bar it sat on top of them. `env(safe-area-inset-*)` on the two edges it is against is now added to `offset`, with the start and the end following the page's direction. A browser reports those insets only to a page whose viewport meta tag has `viewport-fit=cover`; an app that added them to `offset` itself should take them back out.
430
+
431
+ - **A `PlHeader` with no middle puts its actions at the far end of the bar.** With a `brand` and `actions` and no `children`, at the default `align` or at `end`, the actions sat one gap after the brand, and a bar with actions and no brand started with them, where the header page and the Flutter build put them at the end. The actions now take the room an empty middle leaves. A bar with a middle, and a centred one, lay out as before.
432
+
433
+ - **A `fixed` `PlHeader` or `PlFooter` that spans only the content of a `PlPageLayout` no longer covers the sidebars.** A fixed bar spans the window whatever `headerSpan` or `footerSpan` says, but the layout reserved its height only in the column beside the sidebars, so the bar covered the top or the bottom edge of each sidebar and the end of its resize handle. The layout now measures a fixed bar whatever its span, so the sidebars start below a fixed header and end above a fixed footer, whether the page or only the content scrolls. A bar meant to sit between the sidebars is `sticky`.
434
+
435
+ - **A `PlHowToSteps` step's title and the line under it, and a `PlStepper` step's title, ease to their new colours as `active` moves, over `--plass-duration`, and change at once under reduced motion.** Each changed in one frame while the bullet beside it eased.
436
+
437
+ - **A `PlHowToSteps` fills its done and current bullets with its family's gradient, with the number in the ink that goes on it, and eases them as `active` moves, as a `PlStepper` and a `PlTimeline` do.** Its bullets had no gradient, so a done or current step was a bare number in the page's ink, with only a halo round the current one, where the Flutter bullets are filled.
438
+
439
+ - **A `PlMockup` whose `width` or `height` is a number is visible in the server HTML.** The whole device was `visibility: hidden` until the page hydrated and measured it, which left a mockup in a hero empty while the script loaded and kept it out of the LCP candidates. With a size in pixels the scale is known before anything is measured, so the device is drawn at once; a CSS length, the default `'100%'` included, still waits for the measurement.
440
+
441
+ - **A vertical `PlNavigationMenu` opens its panels beside the rail, at the end of the line, level with their item, and its chevrons point there.** `orientation="vertical"` was documented as a nav rail whose panels open beside it, but every panel hung under its item, over the items below it, and each item's chevron pointed down and turned up when its panel opened. A panel now opens at the rail's inline end, which is its left under RTL with `PlassProvider`, with its top level with its item's, as a `PlMenuSubmenu` opens beside its row. Every item spans the rail, so each panel opens against the rail's edge, and the sheet moves down the rail by the items' spacing from one item to the next. An item's chevron points at the inline end and stays there while its panel is open, as a submenu's does. The rail's labels now sit in its middle, as they do in the Flutter build and in a vertical `PlMenubar`.
442
+
443
+ - **A `PlNavigationMenu` sheet eases from one panel to the next, and follows the item it hangs from.** Moving between items, the sheet took the next panel's size in one frame and jumped to its place under the next item, and closing collapsed it to its edges before it could fade. It now eases to the size of the panel coming in and to its place under the new item, whether a trigger or the page changed the panel, and lands on that size however slow the frames are; a panel keeps its own lines while the sheet is between two sizes, and the sheet keeps the size of what it held while it fades out. Opening, and every change under `prefers-reduced-motion`, arrives at once. Once it has arrived, including after a move to a panel of the same size in the same place, it follows the row without easing, so it does not trail a row that scrolls with the page.
444
+
445
+ - **Every `PlNavigationMenu` panel's links are in the server HTML.** A panel was rendered only once it opened, so a crawler that never hovers found none of the links the component exists to hold. Every panel is now kept mounted, and a closed one is hidden: not drawn, not announced and not a focus stop.
446
+
447
+ - **A `PlNavigationMenuLink`'s `startIcon` follows the link's `size`: 1.2em of its title, in a box one of the title's lines high, as in the Flutter build.** It was measured against the page's font, so on a 16px page it was 19.2px in a 24px box whatever the link's `size`, larger than the title at `sm` and `md` and lower than its first line.
448
+
449
+ - **A `PlNumberField` eases a stepper's tint in as it is pressed and its shell's fill and edge in as it takes the focus, and a `PlOtpField` slot eases its own in as it takes the focus, each over the same `--plass-duration` it eases them out over.** A press or a focus changed them in one frame and only the release or the blur eased them, where every other control eases both ways, a `PlTextField` among them, and so does the Flutter build. Under reduced motion they still change at once.
450
+
451
+ - **A `PlNumberField` stepper that has run into `min` or `max` fades to half, as every other disabled control does.** It went to 40% opacity, so on a form the one unavailable stepper was fainter than a disabled button or field beside it, and fainter than the same stepper in the Flutter build. It now fades to 50%.
452
+
453
+ - **A `PlPageLayout` inside another no longer adds a second `<main>`.** The inner layout rendered its own `<main>`, skip link and `id="main"`, so the page had two of each. It now renders its content in a `<div>` and leaves the skip link and the `id` to the outer layout.
454
+
455
+ - **A `PlPill` that starts `expanded` is open on its first frame.** It rendered its panel at a height of 0 and measured it afterwards, so it animated open as it arrived. An open panel is now at its natural height until it has been measured, in the server's HTML as well, and opening and closing afterwards animate as before.
456
+
457
+ - **A progress `value` of `Infinity` draws as full on `PlProgressLinear`, `PlProgressCircular` and `PlProgressBox`.** The shape and the text clamped it to the top of the range, but Base UI read it as indeterminate, so the bar drew neither a fill nor the sweep beside a "100%" and `aria-valuenow` was missing. Base UI is now handed the value clamped into `min`…`max`, so the fill, the value attributes and the text agree. `NaN` is still indeterminate.
458
+
459
+ - **Every segment of a read-only `PlSegmentedButton` keeps its pointer light out, and its label stays in the muted ink under the pointer.** Each segment still carried the interaction light and darkened its label as the pointer went over it, as though it could be chosen, where the light goes out on a read-only control and the Flutter segments of a read-only set show neither.
460
+
461
+ - **The segments of a disabled `PlSegmentedButton` and the chips of a disabled `multiple` `PlCombobox` are drawn at the half opacity of the set or the field they sit in, and a disabled set's segments put their pointer light out.** Each faded to half a second time inside a set or a field already at half, so they were drawn at a quarter and hard to read, and the pointer still lit the segment under it in a disabled set, where the Flutter build puts the light out. A chip in a disabled field keeps the look of a disabled chip, and a segment disabled on its own in a set that is not disabled, like a `PlChip` disabled anywhere else, still fades to half on its own.
462
+
463
+ - **A press on a read-only `PlSelect` does not open it, even when the lock lifts before the press is answered.** Base UI answers a pointer press on the next frame, and `PlSelect` checked `readOnly` at that frame rather than at the press, so a lock lifted in between, such as by a form that unlocks the field on the same press, let the press open the list anyway. The press is now judged by whether the field was locked when it was made.
464
+
465
+ - **A `PlSelect` whose labels are elements no longer draws every one of them to hold its width.** A trigger that is not `fullWidth` laid out every option's label to find the widest, so a list of 250 countries with a flag in each asked for 250 flags before it was opened. Such a label, and a placeholder, is now measured by its text alone, so no picture in it is loaded for the measurement; the trigger can widen by the picture once that option is chosen.
466
+
467
+ - **The resize handle of a `PlSidebar` is drawn over a header or a footer that spans only the content of a `PlPageLayout`, and takes a press there.** The handle straddles the sidebar's edge, but a sidebar that is sticky or glass is a stacking context of its own, so the handle's `z-1` could not rise above a sticky bar's `z-20`, or above a glass bar painted after a start sidebar. The outer half of the handle was hidden under the bar beside it, and a press there went to the bar. While a bar spans the content beside a sidebar, the layout now lifts its sidebars over the column beside them, as the Flutter layout paints them. It does this inside a layer of its own, so a full-width bar, the sticky header of a page the layout scrolls under, and a portal all stay above the sidebars. In that layer, a `fixed` element in the page, such as a floating `PlFloatingActionButton`, goes under a full-width sticky bar where the two meet. A nested layout's content-span bar also no longer draws over the sticky header of the page it scrolls under.
468
+
469
+ - **A `PlTable` wider than its sheet, or taller than its `maxHeight`, can be scrolled from the keyboard.** A table whose cells take no focus left whatever was past the edge out of reach without a pointer. While the grid scrolls, the box it scrolls in is a tab stop, and with a `caption` it is a group named by it. The measurement is a client module of its own, so `PlTable` can still be rendered by a server component.
470
+
471
+ - **A `PlTabs` tab brought into view stops clear of the edge fade.** The chosen tab as the bar was laid out, and the next one along as the arrow keys moved, ended flush against the edge, which is where the 24px fade is strongest. Both now stop the fade's length short of it.
472
+
473
+ - **A mouse press on the padding or on a drawn adornment of a focused `PlTextField` or `PlNumberField` keeps the focus in the field, in both builds.** The two builds and the two fields each did something different: the React text field kept the focus for a press on its padding and lost it for one on an adornment, the React number field lost it for both, and the Flutter fields lost it and took it back. A press anywhere in the field now keeps the focus there, and a button or a link in an adornment still takes its own press.
474
+
475
+ - **A `PlToast` whose colour or variant `update` or `promise` changes, and a `PlAppLogo` plate whose `color` or `variant` changes, ease to their new fill, edge, shadow and ink over `--plass-duration`, and change at once under reduced motion, as a `PlAlert` does.** The toast eased only its opacity and the plate nothing, so both took their new colours in one frame. A `solid` toast's action now takes the toast's own ink, so it eases with the words round it.
476
+
477
+ - **A toast swiped away fades from where the finger left it.** Base UI drops a toast's drag offset the moment the finger lifts, so a toast flicked off jumped back to its place before it faded out. It now holds the offset for its exit. A toast closed any other way fades where it is, as before.
478
+
479
+ - **A `solid` `PlToggle`, a `PlCheckbox`, a `PlRadioGroup` option, a `PlSwitch`, the chosen day or time in a picker, a step's bullet in a `PlStepper` or a `PlTimeline`, and the current page of a `PlPagination` fade their gradient in and out as their state changes, and put it on at once under reduced motion, and a `solid` `PlSegmentedButton` fades its tile in where the first choice of an empty set lands.** The gradient was the control's own `background-image`, which no browser eases to or from nothing, so it arrived in one frame and left in one while the colours round it eased. It is now a layer of its own that fades over `--plass-duration`, as the Flutter controls fade theirs, and looks the same at rest. A `solid` toggle and every `PlPagination` page draw it as an `aria-hidden` `<span>` inside the button, since both of their pseudo-elements are the pointer light, so each page's DOM gains that `<span>`.
480
+
481
+ - **A `PlTooltip` switched off with `disabled` while it is open closes and reports the close through `onOpenChange`, and a controlled `open` shows nothing while it is `disabled`, as the Flutter build does.** `disabled` only stopped the trigger from opening the tooltip. A tooltip opened by hover stayed on the page with the trigger's `aria-describedby` still pointing at it, and `onOpenChange` never heard it close. A controlled `open` also showed a disabled tooltip; it is now answered with `false`. The Flutter build makes the same change to what it reports.
482
+
483
+ - **One tick in a `PlTransfer` renders only the row it changed.** Every row of both lists rendered again for each tick, each one a `PlCheckbox`, so a list of thousands stalled on every press. A row now renders again only when its own label, tick or availability changes. The first render of a long list still draws every row.
484
+
485
+ - **A `PlTransfer` move announcement names a list whose heading is an element by the words the heading draws.** It used the label pack's `transferAvailable` or `transferSelected`, a name the page did not show, while the list's select-all tick read the heading itself. Both now read it the same way.
486
+
487
+ - **A `PlTransfer` select-all tick is named in the order each language puts it.** "Select all" was put in front of the list's heading in every language, so Korean read "전체 선택 사용 가능". The name now comes from the label pack's new `transferSelectAll`, "Select all in Available" in English, which puts the list's name where its own grammar puts it. A `selectAllLabel` given to the component still goes in front of the heading, as before.
488
+
489
+ - **A `PlWindowPane`'s draggable title bar shows the grab hand, closing while the bar is held, and no hand over the caption buttons and `actions`, where nothing drags, in both builds.** The Flutter bar showed the move cursor, and both builds showed their drag cursor in the space round the caption buttons and the actions. The Flutter bar now shows `grab` and `grabbing`, as the React bar's `cursor-grab` and `active:cursor-grabbing`, and both show the default cursor over the buttons' column and the actions.
490
+
491
+ - **A `PlWindowPane` close button pressed on a touch screen draws its × white on the red, as in the Flutter build.** A press turned the button red, but only the pointer's hover turned its × white, and a finger brings no hover with it, so a touch press drew the title bar's own ink on the red.
492
+
493
+ - **A `PlWindowPane` resized as short as it goes stops at its title bar and the frame round it, and `minHeight` does not take it any shorter.** The floor was the bar alone, so a window shrunk by an edge, the resize corner or the arrow keys, or given a `minHeight` below its bar, lost the bottom of its bar to the frame: 2px on most systems, and 4px on `windows8`.
494
+
495
+ - **A rolled-up `PlWindowPane` is as tall as its title bar and the frame round it, however it was rolled up and whatever `size` or `os` it has been given since.** A window that started rolled up through `defaultMinimized` or a controlled `minimized` was only as tall as its bar, so the frame cut the bottom off the bar: 2px on most systems, and 4px on `windows8`. A window rolled up by its button kept the height it had at the press, so one rolled up at `md` and then set to `xl`, or given an `os` with a taller bar, had its bar cut by the difference: 11px on a `macos` window.
496
+
497
+ - **A double click on a `PlWindowPane`'s `actions` belongs to the action and leaves the window's size alone.** The `actions` kept a press from dragging the bar, but not a double click, so an action clicked twice also maximized or restored the window and called `onMaximizedChange`. The caption buttons already kept their double clicks, and a double tap on `actions` already stays theirs in the Flutter build.
498
+
499
+ - **A `PlWindowPane` that is both `maximized` and `minimized` is rolled up to its title bar across the whole of what holds it.** The maximized window stayed `100%` tall whatever `minimized` said, so the minimize button left it filling its container with an empty, inert body and seemed to do nothing. It now keeps the full width and is only as tall as its bar, easing there as any roll-up does, as the Flutter build does. A window rolled up while maximized and restored before it came back down also comes down to its own height rather than growing to the height of the box it filled.
500
+
7
501
  ## 1.6.0 (2026-09-19)
8
502
 
9
503
  ### Breaking changes