Flutter docs
Default skin

Changelog

Every released version, and what breaks when you move between them. Written from the changesets that shipped with each change.

2.5.10

Patch Changes

  • 8a2d42d: Keep full-screen image preview controls readable in both themes with a solid dark canvas and explicit light operation colors.

2.5.9

Patch Changes

  • 9921867: Preserve small directional gaps in ProgressCurve before formatting, and add an independent gap magnitude formatter. Default readings distinguish a nonzero gap from being on plan.

2.5.8

Patch Changes

  • 7704654: Pin the Schedule runtime to SVAR React Gantt 2.8.0 so compact calendar layouts remain visible after container resizing and local builds use the same runtime as installed packages.

2.5.7

Patch Changes

  • 8e0ee0e: Format chart crosshair dates in the same language as the axis and accessible table. Preserve the complete date and value format when the chart palette changes.

2.5.6

Patch Changes

  • ab067b2: Preserve consumer-selected completion precision in schedule readings and detailed progress charts. Allow a separate detail formatter and localized column resize labels.

2.5.5

Patch Changes

  • 0570ac7: Prevent previous and next Pagination arrows from advancing twice on one keyboard activation.

2.5.4

Patch Changes

  • 986537c: Show missing progress as a hyphen while preserving a reported zero percentage.

2.5.3

Patch Changes

  • 0120224: Restore focus to the durable Dropdown trigger when a menu item opens a Modal, including triggers wrapped in Tooltip. Consume Enter activation once so the same keystroke cannot close the new dialog. Preserve upstream refs, notifications, static APIs, and multiple-selection menus that remain open.

    Localize image-preview controls and overflowing tabs through optional LabelsProvider keys while preserving upstream interaction. Preserve InputNumber generic value and ref compatibility, and clarify progress-curve reading copy.

2.5.2

Patch Changes

  • d825d73: Localize the InputNumber stepper buttons through LabelsProvider instead of the English literal Ant writes.

    @rc-component/input-number sets the stepper's accessible name in the element itself as Increase Value / Decrease Value, with no prop and no locale hook to change it — StepHandler takes no rest props — so a consumer could not name them. They now read from Labels.increase / Labels.decrease, like the Select clear button. The role, keyboard model, focus, hold-to-repeat and geometry stay Ant's, and the component adds no element, so consumer layouts and the accessibility tree are unchanged.

    Removable once @rc-component/input-number accepts a label or locale for its steppers.

2.5.1

Patch Changes

  • 24f03bb: Keep form validation text and primary Button states readable in every theme.

  • 24f03bb: Diagram edges can now render when they leave a node through its left or top handle.

  • 24f03bb: Allow schedule detail actions to navigate to native item links while preserving callback and child actions.

  • 24f03bb: Localize the Select clear button through LabelsProvider while preserving explicit labels and Ant Design clear icons.

    Keep the clear glyph compact within a larger hit target and visible during keyboard focus.

2.5.0

Minor Changes

  • 29f1cca: Export a fixed theme bootstrap script for restoring saved light, dark, or system choices before paint. ThemeProvider now keeps working when browser storage or cookie writes are unavailable.

2.4.1

Patch Changes

  • ee6e640: Fix popups opened from an above-drawer Modal rendering underneath it. The dialog now reports its resolved rung through Ant's own zIndex, so date pickers, selects, dropdowns and tooltips derive a rung above the dialog instead of Ant's unpinned arithmetic.

2.4.0

Minor Changes

  • 2edce05: Modal now detects Drawer ancestry automatically and resolves to the documented above-drawer rung. Explicit layer remains only for exceptional overlay call chains.

2.3.0

Minor Changes

  • 902e327: Drawer now resolves the documented drawer stacking rung, and Modal accepts layer="above-drawer" for dialogs opened from a Drawer. The nested dialog and its backdrop resolve above the drawer while the drawer's focus behavior waits for it.

2.2.0

Minor Changes

  • 7dde6d0: Diagram: optional onMaximize and maximizeLabel add an icon-only maximize control among the canvas's own controls, with a tooltip on hover and keyboard focus and the label as its accessible name.

Patch Changes

  • c8f1464: DiagramEditor: clicking a connection now puts it in the inspector. Edges are controlled by the caller and were never marked selected in the runtime's own state, so a selection change never reported one and the inspector could only ever describe nodes.
  • 5925baf: Form help text (Form.Item extra, and other text Ant draws in colorTextDescription) takes the muted foreground, so it reads at WCAG AA on every surface — it measured 4.35:1 on the subtle card surface. The server-rendered seeds now also carry colorTextDisabled, which the runtime theme already sent.
  • c2953b3: Schedule: the day / week / month scale is a segmented control instead of three buttons with the chosen one drawn as a primary, so a page keeps one primary action.
  • febf0f4: A multiple Select now grows with its wrapped tags: the pre-paint fallback no longer holds it to one control height (its floor is Ant's own), so tags on a second line no longer spill over whatever sits below the field.
  • 72d487b: Switch: a 24px track (was Ant's 22px) so it meets the 24px minimum target (WCAG 2.5.8); the handle and width keep Ant's proportions.

2.1.0

Minor Changes

  • 65516ea: Add opt-in native Schedule fullscreen with a full-window fallback, persistent navigation, contained controls and popup surfaces, keyboard exit and focus restoration. Distinguish schedule column headers from content using semantic surface and typography tokens.
  • d2b68cb: Add an opt-in physical-completion Schedule view with aligned readings, a distinct report marker, accessible item details, scale controls, controlled fullscreen navigation, and a loading skeleton. Keep the existing actual-date range mode compatible.
  • ef1c60a: Schedule discloses child rows from the parent's identity cell. Items with a parentId start collapsed, the chevron and item count expose aria-expanded, and expansion survives fullscreen. A child may state no dates (unscheduled: true): it stays in its group, is labelled, and draws no bar. A parent with only some children dated says its coverage is partial.

2.0.0

Major Changes

  • 4d9f9fd: Use Lightweight Charts for CashFlowChart, Sparkline and BarMini. CashFlowChart now supports formatExact for full-precision hover, focus and accessible table values while keeping a compact axis formatter. Donut and TargetBars remain non-time-series exceptions.

    Migration: import Sparkline, BarMini, SparklineProps and BarMiniProps from @ceebee/ui/client instead of @ceebee/ui. They now mount a client-only chart. Remove imports of sparklineGeometry, SparklineGeometry, cashFlowScale, CashFlowScale and heightOf; the chart substrate owns scale and geometry, and those helpers no longer exist. Consumers that abbreviate cash values should pass a currency-aware formatExact in addition to the compact format.

Minor Changes

  • 5ad6434: Improve Schedule's mobile readability and client hydration, add a time-axis marker for today, and show completion in an aligned column rather than default bar badges. Keep unreported progress distinct from a reported zero. Label ProgressCurve gaps in percentage points. Give their visible reading dates locale-aware formatters while preserving machine-readable calendar days. Add responsive schedule controls, clarify Board presentation, and keep immovable cards inspectable through their title.
  • 5ad6434: Add GroupedBarChart for discrete nonnegative totals on a shared zero-based scale, with exact hover and keyboard readings, unknown-value handling, an accessible table, and a matching skeleton. It does not derive a running balance or imply a continuous trend.

Patch Changes

  • 5ad6434: Show an exact-value table inside the optional cash-flow disclosure instead of repeating date labels. Preserve selectable period actions, balance adjustments and the forced-colors table fallback. Add periodLabel for the period column heading.

    Add compact to avoid a second date row in overview charts while retaining keyboard inspection and the exact screen-reader table.

1.21.0

Minor Changes

  • 8e5ae15: Schedule draws dual bars: a planned track against the actuals (ScheduleItem.actual), an overrun/late danger tone, an optional % reading (percentLabels, on by default), and localised actual/not-started/overrun/late strings. Planned-only rows render exactly as before.

1.20.1

Patch Changes

  • 5cd9ee0: Light links step down to the darker brand ramp step. Body-text links also sit on the subtle surface cards paint, and there the old step read 4.48–4.49:1 — under WCAG AA, found by a consumer measuring a board card the seed surfaces all passed. The subtle surface joins the link-contrast spec's surface list (emitted per skin by the token generator, so it cannot drift), and tone-brand is untouched: fills keep their step, only link text moves.

1.20.0

Minor Changes

  • 7efc6e1: Board for long boards (ceebee-ui#43): the collapsed column prop is deprecated and ignored — an empty column renders at full width with its empty content; columns default to 20rem; each column is as tall as its cards up to the viewport less --cb-board-offset (14rem) and then scrolls its cards under a header that stays, so the page does not scroll to reach a column's end; a card title clamped to two lines carries its whole text as a tooltip; badges in meta wrap. Also fixes a hydration mismatch: the drag-and-drop ids now come from React's useId, so a server render and the client agree.
  • 5532d38: DiagramEditor adds nodes from a palette — dragged onto the canvas, or tapped / Enter for the middle of the view — as onAddNode({ kind, position }) requests in whole cells, and renames a node in place through editingId, onRenameSubmit and onRenameCancel. In both Diagram and DiagramEditor, scroll and a two-finger swipe now pan; pinch, Ctrl/⌘ + scroll and the controls zoom.
  • 7d39f33: Select sizes itself to its options: the field is as wide as its longest option (12rem–28rem, never wider than its container), the dropdown may be wider than the field up to 32rem, and a value or option that still does not fit ellipsises with its whole text in a tooltip. A consumer width, popupMatchSelectWidth, labelRender or optionRender still wins.

Patch Changes

  • c5cf17b: DiagramEditor's inspector sits beside the canvas when there is room, as renderInspector has claimed since it shipped (ceebee-ui#24). The editor's body was both the size container and the element its @container rule targeted, and an element cannot query its own size — so the rule never matched and the inspector stayed under the canvas at every width. The editor itself is the container now.
  • c5cf17b: Select's stylesheet actually ships. build-css.mjs concatenates the CSS of named source folders, and Select sat outside every one of them, so its width cap and ellipsis never reached dist/styles.css — a form at 360px scrolled sideways for want of them. Select moves into a bundled folder, and the folder list moves to css-groups.mjs, shared by the build and a spec that fails when any stylesheet sits outside it, so the next one cannot go missing quietly.

1.19.0

Minor Changes

  • 052c91c: CashFlowChart: money in and out per period with the running balance over them. Inflow bars up, outflow bars down, the balance as a line from an opening balance; a period that closes below the line is shaded and the lowest point marked, and one sentence states when it goes under and how far. Periods become buttons when onSelectPeriod is given; a hidden table carries every figure.
  • 05af6dc: Diagram edges take a weight (0–1): the line thickens with the traffic it carries and its arrowhead stays the same size; a weight of 0 draws a path nothing has taken, dashed and quiet. legend lists explicit entries (shape, tone, meaning) so two pills can mean different ends, and legend swatches now render at their size. Diagram takes fitMinZoom for an overview that must show the whole diagram at once.
  • cd88b08: TargetBars takes variant="deviation": each row draws how far it landed from its own target, from a centre line that is the target — right when it beat it, left when it missed, on a symmetric scale. For rows whose targets differ, where a bullet makes the reader subtract. deviationOf and deviationScale are exported beside the existing scale helpers.

1.18.0

Minor Changes

  • 9d8bbc1: Board takes onAddCard(columnId): every column that accepts cards then shows a footer action named for that column ("Add a card to Doing"), so work is added where a person is looking instead of from a toolbar that asks the destination again. A column with accepts: false never offers it. Board.Skeleton takes addCard so the placeholder matches the board's height.
  • ee4ebf6: Dates a person reads are written in the product's language. TimeSeriesChart (and every Composition over it — BalanceCurve, ProgressCurve) takes a locale, defaulting to the document's lang, and uses it for the time axis, the accessible table's day column, and BalanceCurve's reading sentence. The axis is rounded outwards to ticks of 1, 2 or 5 times a power of ten, so a compact formatter is no longer handed a value it must answer with a trailing decimal.

Patch Changes

  • 8b10242: A card's title, when onCardOpen makes it a control, takes the 24px control floor rather than the one pixel under it a line box lands on — the card title is the only way to open a card, and it measured 23px. The drag handle takes the same floor.

1.17.0

Minor Changes

  • d64c631: The Board says when columns are hidden past an edge: a shade on the side(s) that hide one, and ‹ › buttons that scroll by exactly one column, keyboard reachable, with motion or the reader's prefers-reduced-motion choosing smooth or instant. A column given a height ceiling (--cb-board-max-block-size) scrolls its own cards, so the header naming it stays in view and the horizontal scrollbar sits under the columns. BoardColumn.collapsed lets a consumer keep a column — an empty stage of a workflow, say — as a narrow labelled strip the reader opens, which stays a drop target carrying its count. --cb-board-column-inline-size sets the lane width. BoardLabels gains scrollBack, scrollOn, expand and collapse.

1.16.0

Minor Changes

  • 95b4f75: DiagramEditor gains renderInspector: the selected node or edge, described with its incoming and outgoing links, rendered in a consumer panel beside the canvas (below it on a narrow screen) so actions are real buttons and work on touch. Diagram and DiagramEditor open fitted at a readable zoom (never below 0.8×), wrap labels to two lines with the full label as a tooltip, take legendLabels for a legend of the shapes in use, and theme their controls with tokens. describeSelection and shapesInUse are exported.

1.15.0

Minor Changes

  • d5d9871: Every tone reads at WCAG AA both as text and as a fill, in every skin, theme and contrast mode. Light mode darkens the tones so they carry light text; dark mode lightens them so they read on a dark surface, and the text on a tone (--cb-fg-on-brand, --cb-on-warning) turns dark. Tooltip, Tour and Image preview keep light text through the new --cb-fg-on-dark. Filled tags and alerts sit on designed per-mode grounds (--cb-tone-*-bg) instead of the muddy tint Ant generated from a dark seed, and Ant's preset colours (<Tag color="gold">, a green badge) paint with designed per-mode steps that clear AA — Ant's own gold measured 2.76:1. Checked by tone-contrast.spec.ts.

Patch Changes

  • e9bd551: Link-style buttons use the theme's link colour, derived from the brand, instead of the info hue Ant derives links from — one accent per theme — and a spec holds every skin, theme and contrast mode to WCAG AA 4.5:1 on the surfaces a link sits on.

1.14.0

Minor Changes

  • ece09a3: Board cards can be opened without a button on the front. Pass onCardOpen and the card's title becomes the open affordance — click it, or focus it and press Enter or Space — so a card front can be a title and badges rather than a row of action buttons.

    The open lives on the title, not the whole card, for the same reason handle exists: a card that is itself a control has presentational children, so a meta button inside it would leave the accessibility tree. Enter and Space are taken by the open button and stopped from reaching the card's move path, so one press never opens and picks a card up. A card with no onCardOpen is unchanged.

  • 475abf9: Schedule: a new Composition that draws items against a time axis — one row per item, a bar from its planned start to its planned end, the reported progress as a fill, and a late item toned danger.

    It is built on @svar-ui/react-gantt 2.7.3 (MIT), bundled into styles.css the way React Flow is for Diagram, and themed through the substrate's --wx-* properties so a Skin or a theme moves it with everything else.

    Today is named above the axis, and lateness is read from the plan's own dates and progress, because the substrate's vertical markers and critical path are PRO features that the free build ignores silently.

    @svar-ui/react-gantt is a new dependency.

1.13.1

Patch Changes

  • 6eb61d3: Scrollbars take the theme's accent (--cb-tone-brand) instead of a faint foreground grey, so they read in the same colour as the rest of the chrome.

1.13.0

Minor Changes

  • 419a020: The themes declare color-scheme, so the browser's own chrome follows them: a dark theme no longer gets light scrollbars (a white track beside dark surfaces), light native pickers or light autofill. Scrollbars take one colour — the theme's foreground at low strength, no track — through the inherited scrollbar-color, and return to the system's under forced-colors.

1.12.0

Minor Changes

  • 75abef8: TargetBars — actual against target per category, a bullet chart. Every row shares one scale that always contains zero, so a small margin never looks large and a loss draws left of the line; the target is a mark on the same track. Both figures are written as text and the bars are aria-hidden; a null figure reads as unknown rather than zero. Server-safe, plain DOM, with forced-colors and reduced-motion renderings. better="lower" serves costs and delays.

1.11.0

Minor Changes

  • 9947ec8: ThemeProvider takes a locale — one of the locale exports such as idIDLocale — so the strings the component runtime draws itself ("No data", "Select date", pagination) follow the product's language, static dialogs included. The locale objects were already exported; there was no way to apply them.

    ProgressCurve takes dayLabel for its table's day column and formatNumber for the numbers in its reading, so a product that marks decimals with a comma no longer shows 74.4% beside its own 74,4. Defaults are unchanged.

1.10.0

Minor Changes

  • 38b2395: Dated charts: TimeSeriesChart, ProgressCurve and BalanceCurve.

    TimeSeriesChart is the primitive: a canvas plot of any number of dated series, an optional fixed range and an optional baseline, with a format function the product owns. It holds no domain meaning. Because a canvas is not in the accessibility tree, it ships a second rendering rather than a description of the first — a table of every reading, always in the DOM, and shown instead of the canvas under forced-colors: active. Tokens are read from the DOM with getComputedStyle and re-read on every theme, Skin and system colour-scheme change; where they do not resolve, nothing is drawn rather than a colour being invented.

    ProgressCurve is the S-curve: planned against actual on a scale pinned to 0–100%, because autoscale stretches a job at 30% to the full height of the plot and reads as "nearly there". The reading for a given day is stated in words above it.

    BalanceCurve is a running balance read against a line, drawn as a baseline series so the stretch below the threshold is shaded by its own values. The threshold defaults to zero and is a prop: a business that must keep a minimum on hand is in trouble well before it reaches nothing.

    The plot is drawn by TradingView's lightweight-charts 5.2.1 (Apache-2.0), a new dependency imported dynamically inside the mount effect — a server render never evaluates it, and a consumer that shows no chart never downloads it. In a real consumer's production build it lands as its own chunk, 57 KB gzipped. Attribution is in THIRD_PARTY_NOTICES.md, which also gains the @dnd-kit entry Board had been shipping without.

    Also: .cb-visually-hidden now has a rule behind it. It was being written in markup with none, so the text it claimed to hide was simply visible.

1.9.0

Minor Changes

  • 2f314da: Board: a Kanban Composition whose cards move by pointer and by keyboard

    Columns with names, counts and an optional work-in-progress marker; cards carrying whatever a person triages by; and movement as the affordance the pattern is named for. Pointer dragging is @dnd-kit's, with autoscroll, a drop marker and a carried card; the keyboard path is the board's own — Space to pick up, arrows to move, Space to drop, Escape to cancel — because a column that accepts nothing must be stepped over rather than offered, and a card crossing columns should keep its depth.

    Both hands reach one callback with the same move, over pure rules they share, so they cannot disagree about where a card lands. A move applies optimistically and is then validated by the consumer: return { refused } or reject, and the board restores what was there and announces why. An accepted move leaves an Undo, which is an ordinary move back. Every step is announced in a polite live region.

    Cards that carry their own controls set handle, which moves the grab to a handle and leaves the card as plain markup. That is an accessibility requirement rather than a preference: a whole-card grab makes the card a control, and a control has presentational children, so any button inside it leaves the accessibility tree entirely. The keyboard path moves to the handle with it.

    A card whose title is a node takes label for announcements and the handle's name, so a screen reader never reads a uuid. Card titles clamp to two lines, so one long name cannot stretch the row.

    A column whose header is a node rather than a string takes label for its accessible name, so a header carrying status tags is still nameable.

    On a narrow viewport the board renders as lanes — one column behind a switcher — rather than squeezing its columns. Ships Board.Skeleton, a reduced-motion rendering that keeps the drop marker, and a forced-colors rendering.

1.8.0

Minor Changes

  • 79ac716: Give the control ladder a coarse-pointer floor at the 44px touch target.

    The control heights were sm 2rem / md 2.5rem / lg 3rem with no size at the 44px minimum and no coarse-pointer rule, so a touch screen got the same 40px controls as a mouse. A consumer had no supported way to reach 44px without per-field CSS, which its contract disallows.

    --cb-control-floor-fine (2rem, 32px) and --cb-control-floor-coarse (2.75rem, 44px) now name the floor for each pointer, and --cb-control-floor points at the active one. @media (pointer: coarse) switches it to the coarse floor. Each resolved height is max() of a preferred step (--cb-control-height-base-*) and that floor, so sm and md both reach 44px on a touch screen and lg keeps its 48px, while a density scale can retune the ladder but never take a control under the target its pointer can hit.

    Existing tokens keep their names and their fine-pointer values. ThemeBridge now re-reads Ant's geometry when the pointer changes, so a device that gains or loses a mouse mid-session stays in step.

1.7.0

Minor Changes

  • 0376bf6: Diagram and DiagramEditor: nodes joined by arrows on a grid, built on the React Flow runtime (@xyflow/react 12.11.6, MIT) and skinned by Tokens.

    Diagram renders a diagram read-only — pan, zoom and fit controls, nothing focusable or draggable — and describes its viewport by an outline list for assistive technology. DiagramEditor is a separate interaction contract: React Flow's own model for dragging, selecting, deleting and connecting between node handles (by dragging or by clicking one handle then another, which works on touch), with every change turned into a request (onMoveNode in whole cells, onConnect, onRemove, onRename) so the caller owns the data and decides what is valid. It adds keyboard connecting, which the runtime does not offer: C on one node and C on another.

    React Flow's base stylesheet is included in styles.css; consumers import nothing from the substrate. @xyflow/react is a new dependency. Not included: automatic layout, grouping, undo and a minimap.

1.6.0

Minor Changes

  • 9c31790: PanZoomCanvas now offers fullscreen from its own toolbar, beside zoom and reset.

    A canvas is usually laid out inside a page column narrower than the diagram it holds. The documented answer used to be composing the canvas inside Drawer or Modal, which produced a separate trigger, a sheet to dismiss, and a canvas sized to the sheet rather than the screen.

    The control uses the browser's Fullscreen API, so the browser promotes the element above every stacking context, keeps focus inside it, and closes it on Escape. The component adds no dismissal contract, focus trap, or z-index of its own. Where the API is unavailable the control is not rendered, rather than offered and inert.

    New optional props: fullscreenLabel and exitFullscreenLabel.

1.5.4

Patch Changes

  • f4b74ff: Map colorTextDisabled so a disabled control's label stays readable.

    The runtime derived its disabled foreground from its own algorithm rather than from any token the theme bridge sent, landing on a 25%-alpha grey: 1.19:1 against a light stage and 2.08:1 against a dark one. WCAG exempts inactive controls from 1.4.3, and that exemption is the wrong thing to lean on — the label on a disabled control is exactly what tells a person why it is disabled and what would enable it, so it is the one piece of text on that control that has to survive.

    The muted foreground rather than the subtle one, for the reason colorTextPlaceholder already records: subtle clears AA against the plain surface and nothing else, while a disabled control sits on a translucent wash over whatever surface a product put it on. Disabled still reads as disabled through its border and its ground, neither of which depends on colour.

1.5.3

Patch Changes

  • fa07526: Keep vendored Tag and CheckableTag geometry aligned across plain, icon-leading, linked, filled, and solid content.

1.5.2

Patch Changes

  • dacf4d6: Preserve Modal title and description semantics, restore trigger focus across controlled unmounts, and expose stable CeeBee modal layer classes.

1.5.1

Patch Changes

  • 6beab7d: Hide Segmented's native radio input in the token-based pre-paint fallback so a first request without a persisted theme remains visually identical until Ant's runtime stylesheet arrives.

1.5.0

Minor Changes

  • f6f29e2: Add generated, mode-aware Ant theme seeds and a server-readable resolved-mode cookie contract for SSR style extraction. ThemeProvider now accepts initialMode, skin, and contrast, while the server-safe entry exports seed and cookie helpers. Moodboard is included in the shared web/Flutter Skin registry. Framework adapters also receive CeeBee-owned cache, extraction, and provider seams, preventing a package-manager symlink or non-deduped dependency tree from giving the adapter and Ant separate CSS-in-JS instances that silently extract no rules.

Patch Changes

  • 1d7ad0b: Checkbox now paints from Tokens before Ant's stylesheet lands, instead of not painting at all.

    A server-rendered Checkbox had no border and no background, so a reader saw nothing where a checkbox belongs until hydration, then the box appeared. It is now sized, bordered, and filled from Tokens at the first paint, and its label carries the same spacing Ant gives it, so the row no longer widens by 16px on hydration.

    The size is derived rather than guessed: Ant's controlInteractiveSize is controlHeight / 2, confirmed against theme.getDesignToken(). Its 6px corner comes from borderRadiusSM, which no Ceebee Token carries, so no radius is stated — the box is square for one paint rather than wrong by two pixels in the other direction.

    This also fixes a rule that had never matched anything: the box is .ant-checkbox itself in this runtime, not a .ant-checkbox-inner child.

  • 467e9b7: Restore generated Flutter parity for the link foreground and canvas block-size tokens already exposed by the CSS token source.

1.4.2

Patch Changes

  • cf321e7: The Ant pre-paint layer now matches Ant's own size and spacing, so controls no longer resize at hydration.

    1.4.1 stated colour and radius but left geometry to Ant, which meant an Input was 30px tall before hydration and 48px after. Sizes are now Ant's own derived values, read out of rendered controls and expressed in Tokens rather than guessed: Ant lands on a spacing Token less its own border width, so space-4 - border-width is the button's 15px and space-3 - border-width is the input's 11px. Measured result for Button, icon-only Button, and a large Input: identical geometry before and after hydration.

    Two fixes found while measuring. .ant-select-selector never matched anything — this runtime puts the box on .ant-select itself — so the rule was dead. And allowClear renders its clear control on an empty field and hides it from JavaScript-built CSS, so a stray clear icon was painted inside every such input until hydration; the hidden state is now stated in the layer.

    Where a variant's geometry has not been measured this way, the layer still states no size for it. A wrong size flickers as badly as no size and pretends to be right.

    Consumers: a blanket button, input { font: inherit } reset defeats this layer's type scale. An element selector outranks the :where() these rules are wrapped in, so a control Ant renders at 14px is laid out at 16px until hydration and then resizes. Inherit font-family alone and let the control own its size.

1.4.1

Patch Changes

  • 5419afd: The Ant pre-paint layer no longer states constraint properties, which an Ant rule cannot override.

    :where() guarantees Ant wins for the same property, not for a different one. min-height and min-width are constraints Ant does not set — it sets height and width — so 1.4.0's values survived every Ant rule and permanently changed control geometry. In a consumer's header that widened the icon-only buttons until the brand overlapped them at 360px, caught by that product's own layout test before it reached production.

    Buttons now state height, which Ant does set. The icon-only rule keeps padding and drops its width floor. Inputs, selects, and segmented controls state only colour, border, radius, and type — never size — because their sizing was not measured the way Button's was, and a size this layer got wrong would outlive every override. A test now rejects any min-* or max-* property in the layer.

1.4.0

Minor Changes

  • 42ff03f: Ant's primitives now paint from Ceebee Tokens before Ant's own JavaScript-generated CSS arrives.

    A server-rendered document carries Ant's class names and none of its rules, so buttons, inputs, selects, and segmented controls painted as native browser controls until hydration — a square grey button on a Ceebee surface, in both colour modes. styles.css now carries a pre-paint layer for those classes, authored from Tokens and wrapped entirely in :where() so it has no specificity and every real Ant rule overrides it the moment it lands. It reads Ant's own server-rendered variant and size classes, so a text button does not borrow a surface for the length of the first paint.

    Consumers get this by importing @ceebee/ui/styles.css as before; nothing to configure. Note that server-extracting Ant's rules with @ant-design/nextjs-registry is still not recommended — Ant's seed comes from live CSS Tokens, which a server cannot read, so extracted rules carry Ant's default light palette. See docs/adr/0022-ceebee-paints-ant-primitives-before-ant-does.md.

    CommandPalette's search input no longer picks up a consumer's global :focus-visible outline. The stylesheet already declared outline: none on it, but a bare :focus-visible rule ties on specificity and wins on import order, drawing a hard box around a deliberately borderless field. The rule is now stated against :focus-visible so it holds, and the search row takes the brand colour on focus so the indicator is not simply removed.

1.3.3

Patch Changes

  • 08910a8: Give each sticker an angle that belongs to it. Deriving the tilt from the array index meant dismissing one sticker re-tilted every sticker after it, so the collection appeared to rearrange itself in response to a dismissal it had nothing to do with.

1.3.2

Patch Changes

  • f03bba4: Take placeholder text to the muted foreground. The subtle foreground only cleared WCAG AA against the plain surface, so a control on any tinted or paper surface fell short.

1.3.1

Patch Changes

  • 90592cd: Read Skin tokens on the first client render instead of in an effect, so a themed page no longer flashes Ant's own default palette before the Skin arrives.

1.3.0

Minor Changes

  • 6cde75f: Add --cb-fg-link for brand-coloured text on a surface, and send Ant a placeholder colour. Ant derived its own 25%-alpha placeholder grey, which failed WCAG AA text contrast in both modes.

1.2.0

Minor Changes

  • c40e26f: Add PanZoomCanvas with pointer, pinch, wheel, keyboard, reduced-motion, and skeleton contracts.

1.1.1

Patch Changes

  • Ship the cross-platform token bridge on top of the Moodboard release line.

1.1.0

Minor Changes

  • e99f451: Add the Moodboard Skin, the taped-paper Surface variant, and the controlled StickerGroup composition with settle-in, peel-away, reduced-motion, and matching Skeleton states.

1.0.2

Patch Changes

  • Add cross-platform token sources and strengthen accessibility token cascading for the Flutter package bridge.

1.0.1

Patch Changes

  • faeec46: Fix @ceebee/ui/client failing to load outside a bundler.

    The four locale re-exports used extensionless subpaths. The package they come from ships no exports map, so those resolve only under a bundler's resolution — under plain Node ESM they throw ERR_MODULE_NOT_FOUND and take the whole entry with them. That reaches anything importing the client entry outside a bundler: a Vitest suite in the node environment, a script, or a Next app that lists @ceebee/ui in serverExternalPackages.

    They now point at the CommonJS files, which Node ESM loads through interop and a bundler resolves just the same.

    Also dropped embla-carousel-react from peerDependencies. Nothing has imported it since the carousel source port was removed, so it was asking consumers to install an engine the package does not use.

1.0.0

Major Changes

  • 55b36f3: 1.0.0 — one component per job, one entry per boundary.

    The library used to ship its own implementation of components it also documented, and a separate @ceebee/ui/antd entry alongside it. That meant two answers to most questions. This release settles them: there is now exactly one of each component, reached through one of two entries.

    Entries

    @ceebee/ui/antd is gone. Everything it exported now comes from @ceebee/ui/client.

    diff
    - import { Button, Table } from '@ceebee/ui/antd';+ import { Button, Table } from '@ceebee/ui/client';

    The split that remains is the one worth keeping: @ceebee/ui is server-safe and ships no client runtime; @ceebee/ui/client carries "use client". AntdThemeBridge is renamed ThemeBridge (AntdThemeBridgeProps → ThemeBridgeProps); ThemeProvider wraps it for you, so most consumers never name it.

    Removed: duplicate implementations

    Every component that had a second implementation now has one. Import each from @ceebee/ui/client — same name, same API, themed by the active Skin:

    Affix, Alert, Anchor, Avatar, Badge, BorderBeam, Breadcrumb, Button, Calendar, Card, Carousel, Collapse, Descriptions, Divider, Drawer, Dropdown, Empty, FloatButton, Image, Listy, Masonry, Menu, Modal, Notification, Pagination, Popconfirm, Popover, QRCode, Result, Skeleton, Space, Spin, Splitter, Statistic, Steps, Table, Tabs, Tag, Timeline, Tooltip, Tree, Watermark — together with every *Skeleton variant and the sort, autoplay and table helpers that served them.

    Renamed on the way:

    RemovedUse
    ProgressBar, ProgressRingProgress (type="circle" for the ring)
    StaticAnchorAnchor
    StaticStepsSteps
    LinkButtonButton with href
    AvatarGroupAvatar.Group
    DialogClose, DrawerCloseModal and Drawer render their own close control
    DateRangePickerDatePicker.RangePicker
    TreeSearchSelectTreeSelect with showSearch
    Coachmark, TourTour

    ringGeometry and its RingGeometry type go with ProgressRing; they existed only to lay that ring out. Note thickness was a pixel width while strokeWidth is a percentage of the ring's diameter, and tone/hue become a strokeColor reading the matching Ceebee token:

    tsx
    // before<ProgressRing value={12} max={20} size={56} thickness={7} hue="blue" label="Review queue" />
    // after<Progress type="circle" percent={60} size={56} strokeWidth={12}  strokeColor="var(--cb-decor-blue)" aria-label="Review queue" />

    Coachmark and Tour take tourReducer, initialTourState, resolveTarget, hasEnded, the TourState / TourAction / TourStatus / StepTarget types, and the SeenStore type with them — the Seen Store existed to remember whether a tour had run.

    Removed: the legacy form layer

    Input, Textarea, Select, Checkbox, Radio/RadioGroup, Switch, Rate, Slider, Upload, AutoComplete, Cascader, ColorPicker, DatePicker, TimePicker, InputNumber, Mentions, TreeSelect, and Transfer are each replaced by the same-named export from @ceebee/ui/client, along with their skeletons, prop types, and the date, time, number and upload helpers that only served them.

    Field goes with them. It was the shared label, hint and error wiring behind all of the above; Form.Item is that contract:

    tsx
    import { Form, Input } from "@ceebee/ui/client";
    <Form layout="vertical">  <Form.Item    label="Email"    extra="Work address"    validateStatus="error"    help="Already taken"  >    <Input />  </Form.Item></Form>;

    What stays

    The layout and foundation primitives (Surface, Flex, Grid, Container, Text, Heading, PageContainer), the app shell (Sidebar, TopBar, NavigationMenu), CommandPalette, Checklist, the widgets (Donut, Sparkline, BarMini, Leaderboard), the motion helpers, and the Token, Skin and Labels system.

    Donut stays alongside Progress: it draws one arc per slice, which Progress has no equivalent for. Popover and Tooltip stay — DatePicker, TimePicker, Cascader, Mentions, ColorPicker, Popconfirm, Slider, Steps, Menu and the app shell are all built on them.

    Two components had reached into something removed and now draw for themselves: Leaderboard renders its own initials bubble rather than Avatar, and PageContainer.Skeleton its own placeholders rather than Skeleton — both are server-safe, and the runtime's versions are not.

    The Labels contract loses back, done, skip, and progress, which only the removed Tour and Coachmark spoke. next stays — the carousel and the image preview still use it.

    Dependencies

    Dropped from dependencies: uqr and the thirteen @rc-component/* engines, none of which the package imports since the source ports that used them were removed. antd and dayjs remain. Attribution is unchanged: THIRD_PARTY_NOTICES.md retains every upstream copyright notice, and docs/component-sources.json still records each component's lineage.

    Slider reads its filled track from Ceebee's ramp

    The runtime derives a Slider's filled track, handle, and active dot from one palette step off the primary seed. That step lands close to white for a seed as light as Ceebee's brand, so a filled track was indistinguishable from its rail and the control read as disabled. ThemeBridge now sets trackBg, trackHoverBg, handleColor, and dotActiveBorderColor from --cb-brand-300 and --cb-brand-400, which follow the active Skin and colour scheme like every other bridged token.

0.6.0

Minor Changes

  • a090d13: Add the server-safe Card family with Meta, responsive Grid, loading, and matching Skeleton anatomy.
  • a090d13: Add Anchor, FloatButton, and Splitter with native navigation, action, and resizing contracts.
  • a090d13: Add Collapse with single and multiple disclosure modes, add composable modal confirmation and matching Modal Skeleton coverage, and extend Image with grouped fullscreen preview, zoom, localized controls, controlled state, and matching preview-grid Skeletons.
  • a090d13: Add a server-safe PageContainer frame with matching skeleton for ProLayout-style page composition.
  • a090d13: Add anchored Popconfirm, persistent Notification, inline Calendar, persistent navigation Menu, and single/range Slider compositions with matching loading skeletons. Isolate DatePicker's internal calendar CSS namespace from Calendar.
  • a090d13: Add TreeSelect, Cascader, semantic ColorPicker, and server-safe Listy components with matching skeletons.
  • a090d13: Add Masonry, Affix, Watermark, and BorderBeam coverage with documented motion and loading contracts.
  • a090d13: Add server-safe Space, Descriptions, and Result compositions with matching Skeletons.
  • a090d13: Add hierarchical Tree, inline Mentions, two-list Transfer, and server-safe QRCode coverage.

Patch Changes

  • a090d13: Guard every :hover rule behind @media (hover: hover).

    A touchscreen has no hover, so a browser applies :hover on tap and leaves it applied until something else is touched. Every pressable surface in this package — buttons, tags, tabs, table rows, calendar days, select options — therefore stayed lit after a finger passed over it, and a scroll left a trail of controls looking pressed. It was reported as "everything I scroll past gets clicked", which is exactly what it looks like.

    44 rules across 27 files. Where a selector list mixed :hover with :focus-visible or [data-active], only the hover half moved: keyboard focus and keyboard selection are not hover, and both must keep working on a device that cannot hover.

0.5.2

Patch Changes

  • d241414: An open AutoComplete list no longer rides over the page's sticky chrome.

    The popup follows its anchor, and a page that scrolls takes the anchor with it. Measured at 390px against a sticky masthead, stepping the scroll:

    tsx
    scrollY  anchorTop  overlapsHeader  anchorHidden    120         23           false         false    180        -37            TRUE         false    240        -97            TRUE          true

    The anchor slides under the header while the popup, at --cb-z-dropdown, goes on being painted above it. Base UI marks the anchor hidden only once it has left the viewport entirely, so there is a band of scroll where nothing knows anything is wrong.

    No z-index settles it — a dropdown opened from sticky chrome has to be above it, and one whose anchor has scrolled under it must not be — so the fix is behavioural. AutoComplete is modal now, which is what Base UI already makes a Select by default: the page is held still while the list is open and the situation cannot arise. On a touch device Base UI deliberately leaves the page scrollable (matching a native picker), so there the list closes on a page scroll instead. Scrolling the list itself does not close it.

    No API change. If you were relying on being able to scroll the page with an AutoComplete open, that is what changes.

0.5.1

Patch Changes

  • 11b8591: Two fixes to overlays measured on a phone-sized viewport.

    A scrolling dialog's scrollbar no longer lands on its fields. Modal's body became an internally scrolling box in 0.5.0 so long content stays in the viewport; a scroller draws its bar at its own inline edge, and the body ran to the dialog's content edge. Measured at 390×780: the first input ran 41→349 inside a 349-wide scroller — no gap at all. macOS and Android float an overlay bar over the field; Windows reserves width and squeezes it.

    .cb-modal__body and .cb-drawer__body now set scrollbar-gutter: stable and bleed into the dialog's own padding to put it back as their own, so the bar is drawn over padding rather than over content on either kind of platform. It also stops a focused field's ring being clipped at the scroller's edge.

    A Select or AutoComplete popup is as wide as its options need. Both were pinned to --anchor-width. Dropping from a field that is half a filter row — 175px on a phone — options that are place names wrapped to two and three lines each: measured heights of 56, 56, 56, 76, 56 against 36 apiece once the popup sizes to its content.

    The anchor's width is the floor now rather than the value, so a popup is still never narrower than the field it belongs to, and the ceiling is --available-width, the room the positioner measured. max-height follows the same shape, so a long list on a short screen is bounded by the screen.

    No API changes; both are CSS, and both apply to every consumer on the next install.

0.5.0

Minor Changes

  • c37a843: Breaking: fifteen components are renamed to the names Ant Design uses.

    The old names are gone, not deprecated. A consumer upgrading to this release has to rename its imports; nothing warns at runtime, the build simply stops finding them.

    WasNow
    StackFlex
    StatCardStatistic
    BreadcrumbsBreadcrumb
    DialogModal
    ComboboxAutoComplete
    EmptyStateEmpty
    DataTableTable
    SpinnerSpin
    TextInputInput
    DateInputDatePicker
    TimeInputTimePicker
    NumberInputInputNumber
    FileDropUpload
    DropdownMenuDropdown
    StepperSteps

    The cb- class namespaces moved with them, so a consumer that styles against .cb-dialog or .cb-stack has to follow. DialogClose keeps its name because Base UI's own part is called that.

    Known callers at the time of writing: a small consumer uses Stack in 26 places; a dashboard consumer uses Stack in 53, StatCard in 20 and Breadcrumbs in 2. Neither picks this up on its own — a caret range on a 0.x version does not cross a minor — so both stay on what they have until somebody bumps them deliberately.

    Also in this release:

    • Tag — Badge's interactive sibling. Pressing it and removing it are two gestures, so they are two props (onClick/pressed, and onClose).
    • Rate — a row of stars that is a radiogroup, readable as well as settable. Pressing the current score clears it.
    • Divider — a bare &lt;hr>, or a role="separator" when it carries a label.
  • c37a843: Add an independent Drawer primitive and keep overlay interaction contracts separate. Establish a semantic stacking ladder for every portalled or fixed layer, with anchored overlays layered by their Positioner. Keep long Dialog content within the viewport with an internally scrolling body. Fix horizontal Popover and Coachmark arrows, and give Tooltip matching placement and optional-arrow controls. Let Combobox fetch its options per query with loadItems, debounced, guarded against out-of-order replies, and with browser-side matching switched off so a server's answer survives intact. Give RadioGroup a segmented variant for the two-to-four-option band where a Select costs a press for nothing, and align Combobox's option and id props with Select's so swapping between them is one word. Add LinkButton for the anchor that wears a button's look, sharing the button's classes rather than a second copy of them, with a render seam so a router's own Link stays itself.

0.4.1

Patch Changes

  • Keep tall grouped dropdown menus inside the viewport with contained internal scrolling.

0.4.0

Minor Changes

  • Add accessible labelled sections to DropdownMenu for app menus that combine distinct action kinds.

0.3.3

Patch Changes

  • Preserve the standard backdrop-filter declaration when consumer CSS is compiled by Lightning CSS.

0.3.2

Patch Changes

  • Increase regular-glass backdrop diffusion while preserving enough translucency for the frosted effect to remain visible.

0.3.1

Patch Changes

  • Increase regular glass density across the default, Astra, and Clarity skins so navigation, toolbars, and text-bearing overlays retain separation and legibility over content. Clear glass remains the high-translucency option for compact controls over media.

0.3.0

Minor Changes

  • 8b926fb: Add regular and clear glass materials to Surface, plus the content-first Clarity skin and its adaptive accessibility fallbacks.

0.2.1

Patch Changes

  • Mark Lucide and Embla as required runtime peers so a clean package installation can resolve both public entry points without consumers manually repairing the dependency graph.

0.2.0

Minor Changes

  • 28fbb10: Carousel on Embla, with autoplay that stops for the pointer, for focus, for a background tab, and for reduced motion — plus Carousel.Skeleton sharing the real track geometry. Coachmark (anchored bubble with a spotlight that tracks its target through scroll and resize) and Tour (step sequencing, with memory injected through a SeenStore the app owns). Popover and Tooltip on Base UI, and the Reveal / Stagger motion primitives.

  • cc59ad4: Badge, Alert, EmptyState, Toast, Tabs, Avatar with AvatarGroup, Donut, Sparkline, and BarMini.

    Alert distinguishes what interrupts from what waits: danger and warning are assertive alerts, the quieter tones are polite status messages. Toast ships its own viewport with the provider, and an error toast has no timeout. Avatar is server-safe — initials render underneath the image, so a broken photo URL degrades without JavaScript, and the fallback colour is derived from the name so a person keeps their colour everywhere.

    The widget arithmetic is pure and tested: donut arcs drop values that cannot be drawn instead of drawing them backwards, and a sparkline puts a flat series on the middle line rather than collapsing it onto the floor.

  • cbc4350: Completes the form controls: Select (a themed listbox on Base UI, not a native &lt;select>), Checkbox with an indeterminate state, RadioGroup with per-option descriptions, Switch with a settings-row layout, and NumberInput whose arithmetic — parsing a comma decimal, clamping on blur rather than on keystroke, stepping without floating-point crumbs, and treating an empty field as null — is a pure module with its own tests.

    Every control takes its label, description wiring, and error state from Field, and every callback now receives the value alone: Base UI's second argument no longer leaks through Checkbox and Switch.

  • cb485db: First slice: structure and skin tokens with CSS-first theming, the Astra skin, Surface with plain/tinted/glass/gradient variants, Stack/Grid/Container, Text/Heading, Skeleton with shape atoms, ProgressRing, StatCard with its own Skeleton, Button, Field with TextInput and Textarea, Dialog on Base UI, and the ThemeProvider and MotionProvider seams. Server-safe primitives ship from the package root; everything interactive from @ceebee/ui/client.

  • e4f477a: Closes the gaps the audit listed.

    LabelsProvider puts every string the library says out loud in one place — carousel and pagination controls, date and time triggers, file drop copy, stepper arrows, the shell's collapse button, and the Tour's buttons. Override only what you translate; the rest fall back to English. A Tour's own labels prop still wins, because one tour may speak differently from the product around it.

    Sidebar items take items for one level of children: expanded they are a disclosure with an indent rail, and collapsed the rail's hover flyout carries the label and the whole submenu, so a collapsed sidebar is readable rather than a column of guesses.

    Also: Leaderboard.Skeleton and Timeline.Skeleton, a truncate flag per table column, ToastProvider position (six logical placements, with a top stack entering from above), and prefers-contrast: more support in the tokens. Accessibility is now checked by axe in the test suite, across form controls, data display, navigation and feedback.

  • a5151f5: Combobox, DateInput with its own calendar, FileDrop, CommandPalette, Spinner, ProgressBar, Timeline, and the onboarding Checklist.

    The date parser reads ISO and day-first input, expands a two-digit year, and refuses 31 February rather than rolling it into March; the calendar grid is always six weeks so the popover never changes height. FileDrop reports every rejection with a reason instead of dropping files silently, and keeps a real file input underneath the drop zone. The palette ranks an exact label above a prefix, a word start, a substring, and finally a keyword — so "logout" finds "Sign out" — and ties keep the caller's order.

    ScrollReveal is dropped from the plan: Reveal and Stagger already take onView, which is the same feature with one fewer component to maintain.

  • fa6151d: Leaderboard and a blur-up Image, which completes the planned inventory.

    Leaderboard is an ordered list with an optional medal tint on the top three and a marker for the viewer's own row. Image reserves its space through aspectRatio and fades in over a blurred placeholder — and it checks complete on the ref, because a cached image can finish loading before React attaches onLoad, which is the bug that leaves the frame permanently blank.

  • f0805b4: DataTable with sortable headers and its own Skeleton, Pagination, DropdownMenu, Breadcrumbs, Stepper, and the Sidebar and TopBar shell.

    Sorting cycles through three states, so there is a way back to the order the server gave, and the state lives on the header as aria-sort rather than only in the arrow. The page list keeps the first and last page reachable and spells out a single skipped page instead of hiding it behind an ellipsis of the same width. A collapsed Sidebar keeps every label in its accessible name, and the last breadcrumb is text with aria-current rather than a link to the page you are already on.

  • 8ece16c: Adds TimeInput, and fixes a run of defects found by reading the running site.

    The Coachmark bubble was painted under its own spotlight: the z-index sat on a position: static popup, where it does nothing, while the spotlight's own layer stacked above it. It now lives on the positioner Base UI portals.

    Combobox handed Base UI a bare string where it expects whole items, so the field displayed the id ("id" instead of "Indonesia") and selection returned an object the caller could not use. The mapping now happens inside the component, and its options have an even rhythm whether or not they carry a description line.

    Disabled controls were only dimmed, which reads as nothing at a glance. A disabled checkbox, radio or switch now loses its fill and takes a dashed edge, and disabled menu, select and tab items are struck through.

    Also: clicking a DateInput field opens its calendar rather than only the trailing button, the FileDrop zone is a single label so the picker opens exactly once from anywhere in it, and a Toast with an action lays its button out on its own row instead of squeezing it beside the text.

    Dark mode was hard to read: panel edges measured 1.37:1 against their surface where WCAG asks 3:1 of a control boundary, and the two page greys were 1.09:1 apart. The ramp is re-spread — control edges now measure 3.45:1 in dark and 3.0:1 in light.

Patch Changes

  • 99b9f52: Refine the glass Surface into a layered, skin-tunable material with composed elevation and opaque, high-contrast, reduced-transparency, and forced-colour fallbacks.

  • acbcb1e: Fixes a click hijack introduced by the hit-target work: .cb-switch::before carried the 24px pointer target, but the switch establishes no containing block, so the pseudo-element grew to the width of a distant ancestor and swallowed clicks across the page — on the Button docs page, every click toggled the loading switch. The switch never needed it (it is already 40×24), and the rule now applies only to checkbox and radio, which are positioned.

    A test locks the invariant: any absolutely positioned, percentage-sized pseudo-element must sit on a host that is itself positioned. It is checked in the CSS because jsdom has no layout to measure.

  • fa9a858: The Toast action is a soft button rather than bare text. Unstyled at rest, its padding read as uneven space beside the title, and only a hover explained where the control began.

  • cf618fa: Tidy-up pass over the whole surface.

    The Switch was built from hand-tuned numbers and its thumb sat off-centre; its geometry is now derived from the track's own dimensions, so the insets are equal on all four sides. NumberInput's value was centred between two steppers while every other control's text starts at the same left edge — the steppers moved to the trailing edge and the value lines up with the rest of a form.

    Colour and focus rings are fully tokenised: the dialog scrim, the coachmark spotlight, and the warning button's foreground were literal oklch() values, and five different outline-offset numbers are now two tokens (--cb-focus-offset and --cb-focus-offset-inset).

    The neutral text ramp failed WCAG AA in light mode — hint and description text measured 2.89:1 against its surface. Muted and subtle are re-spread and now measure 7.16:1 and 4.70:1 in light, 9.25:1 and 5.41:1 in dark. List items and &lt;kbd> elements the library renders gained margin resets, so a host app's typography can no longer inflate them, and the Donut now honours reduced motion like the ProgressRing already did.

  • c4e2aac: UI/UX audit pass.

    Icon-only Buttons are square: with no children they drop the text padding, the gap and the empty label span, so a row-action button is a 40px square with its glyph centred instead of a wide control with an off-centre icon. DataTable is denser — header and cells at the smaller spacing step, a fixed 3rem row height, the table's own gutter on the first and last cell, and no text padding around a cell that holds only a control.

    Accessibility: checkbox, radio and switch now present a 24×24 pointer target (WCAG 2.5.8) while the visual box stays 18px, and a table's sort control fills its header cell rather than being a 14px-tall target. The coachmark and popover tail is a clipped rotated square carrying the popup's own border, which joins the edge exactly — the hand-drawn SVG never did. Dialog placement uses logical properties, so the corner variant follows the writing direction.