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
LabelsProviderinstead of the English literal Ant writes.@rc-component/input-numbersets the stepper's accessible name in the element itself asIncrease Value/Decrease Value, with no prop and no locale hook to change it —StepHandlertakes no rest props — so a consumer could not name them. They now read fromLabels.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-numberaccepts 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
LabelsProviderwhile 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
layerremains 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
onMaximizeandmaximizeLabeladd 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 incolorTextDescription) 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 carrycolorTextDisabled, 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
parentIdstart collapsed, the chevron and item count exposearia-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
formatExactfor 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,SparklinePropsandBarMiniPropsfrom@ceebee/ui/clientinstead of@ceebee/ui. They now mount a client-only chart. Remove imports ofsparklineGeometry,SparklineGeometry,cashFlowScale,CashFlowScaleandheightOf; the chart substrate owns scale and geometry, and those helpers no longer exist. Consumers that abbreviate cash values should pass a currency-awareformatExactin addition to the compactformat.
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
periodLabelfor the period column heading.Add
compactto 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-brandis untouched: fills keep their step, only link text moves.
1.20.0
Minor Changes
- 7efc6e1:
Boardfor long boards (ceebee-ui#43): thecollapsedcolumn prop is deprecated and ignored — an empty column renders at full width with itsemptycontent; 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 inmetawrap. Also fixes a hydration mismatch: the drag-and-drop ids now come from React'suseId, so a server render and the client agree. - 5532d38:
DiagramEditoradds nodes from apalette— dragged onto the canvas, or tapped / Enter for the middle of the view — asonAddNode({ kind, position })requests in whole cells, and renames a node in place througheditingId,onRenameSubmitandonRenameCancel. In bothDiagramandDiagramEditor, scroll and a two-finger swipe now pan; pinch, Ctrl/⌘ + scroll and the controls zoom. - 7d39f33:
Selectsizes 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,labelRenderoroptionRenderstill wins.
Patch Changes
- c5cf17b:
DiagramEditor's inspector sits beside the canvas when there is room, asrenderInspectorhas claimed since it shipped (ceebee-ui#24). The editor's body was both the size container and the element its@containerrule 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.mjsconcatenates the CSS of named source folders, and Select sat outside every one of them, so its width cap and ellipsis never reacheddist/styles.css— a form at 360px scrolled sideways for want of them. Select moves into a bundled folder, and the folder list moves tocss-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 whenonSelectPeriodis 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.legendlists explicit entries (shape, tone, meaning) so two pills can mean different ends, and legend swatches now render at their size.DiagramtakesfitMinZoomfor an overview that must show the whole diagram at once. - cd88b08:
TargetBarstakesvariant="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.deviationOfanddeviationScaleare exported beside the existing scale helpers.
1.18.0
Minor Changes
- 9d8bbc1:
BoardtakesonAddCard(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 withaccepts: falsenever offers it.Board.SkeletontakesaddCardso 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 alocale, defaulting to the document'slang, and uses it for the time axis, the accessible table's day column, andBalanceCurve'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
onCardOpenmakes 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, withmotionor the reader'sprefers-reduced-motionchoosing 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.collapsedlets 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-sizesets the lane width.BoardLabelsgainsscrollBack,scrollOn,expandandcollapse.
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, takelegendLabelsfor a legend of the shapes in use, and theme their controls with tokens.describeSelectionandshapesInUseare 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 bytone-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
onCardOpenand 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
handleexists: a card that is itself a control has presentational children, so ametabutton 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 noonCardOpenis 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-gantt2.7.3 (MIT), bundled intostyles.cssthe way React Flow is forDiagram, 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-ganttis 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 inheritedscrollbar-color, and return to the system's underforced-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 arearia-hidden; anullfigure 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:
ThemeProvidertakes alocale— one of the locale exports such asidIDLocale— 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.ProgressCurvetakesdayLabelfor its table's day column andformatNumberfor the numbers in its reading, so a product that marks decimals with a comma no longer shows74.4%beside its own74,4. Defaults are unchanged.
1.10.0
Minor Changes
-
38b2395: Dated charts:
TimeSeriesChart,ProgressCurveandBalanceCurve.TimeSeriesChartis the primitive: a canvas plot of any number of dated series, an optional fixed range and an optional baseline, with aformatfunction 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 underforced-colors: active. Tokens are read from the DOM withgetComputedStyleand 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.ProgressCurveis 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.BalanceCurveis 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-charts5.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 inTHIRD_PARTY_NOTICES.md, which also gains the@dnd-kitentryBoardhad been shipping without.Also:
.cb-visually-hiddennow 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
labelfor 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
labelfor 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 3remwith 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-floorpoints at the active one.@media (pointer: coarse)switches it to the coarse floor. Each resolved height ismax()of a preferred step (--cb-control-height-base-*) and that floor, sosmandmdboth reach 44px on a touch screen andlgkeeps 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.
ThemeBridgenow 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:
DiagramandDiagramEditor: nodes joined by arrows on a grid, built on the React Flow runtime (@xyflow/react12.11.6, MIT) and skinned by Tokens.Diagramrenders a diagram read-only — pan, zoom and fit controls, nothing focusable or draggable — and describes its viewport by an outline list for assistive technology.DiagramEditoris 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 (onMoveNodein 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:Con one node andCon another.React Flow's base stylesheet is included in
styles.css; consumers import nothing from the substrate.@xyflow/reactis a new dependency. Not included: automatic layout, grouping, undo and a minimap.
1.6.0
Minor Changes
-
9c31790:
PanZoomCanvasnow 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
DrawerorModal, 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, orz-indexof its own. Where the API is unavailable the control is not rendered, rather than offered and inert.New optional props:
fullscreenLabelandexitFullscreenLabel.
1.5.4
Patch Changes
-
f4b74ff: Map
colorTextDisabledso 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
colorTextPlaceholderalready 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, andcontrast, 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
Checkboxhad 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
controlInteractiveSizeiscontrolHeight / 2, confirmed againsttheme.getDesignToken(). Its 6px corner comes fromborderRadiusSM, 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-checkboxitself in this runtime, not a.ant-checkbox-innerchild. -
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
Inputwas 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, sospace-4 - border-widthis the button's 15px andspace-3 - border-widthis the input's 11px. Measured result forButton, icon-onlyButton, and a largeInput: identical geometry before and after hydration.Two fixes found while measuring.
.ant-select-selectornever matched anything — this runtime puts the box on.ant-selectitself — so the rule was dead. AndallowClearrenders 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. Inheritfont-familyalone 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-heightandmin-widthare constraints Ant does not set — it setsheightandwidth— 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 anymin-*ormax-*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.cssnow 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.cssas before; nothing to configure. Note that server-extracting Ant's rules with@ant-design/nextjs-registryis 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. Seedocs/adr/0022-ceebee-paints-ant-primitives-before-ant-does.md.CommandPalette's search input no longer picks up a consumer's global:focus-visibleoutline. The stylesheet already declaredoutline: noneon it, but a bare:focus-visiblerule ties on specificity and wins on import order, drawing a hard box around a deliberately borderless field. The rule is now stated against:focus-visibleso 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-linkfor 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/clientfailing to load outside a bundler.The four locale re-exports used extensionless subpaths. The package they come from ships no
exportsmap, so those resolve only under a bundler's resolution — under plain Node ESM they throwERR_MODULE_NOT_FOUNDand 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/uiinserverExternalPackages.They now point at the CommonJS files, which Node ESM loads through interop and a bundler resolves just the same.
Also dropped
embla-carousel-reactfrompeerDependencies. 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/antdentry 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/antdis gone. Everything it exported now comes from@ceebee/ui/client.- import { Button, Table } from '@ceebee/ui/antd';+ import { Button, Table } from '@ceebee/ui/client';The split that remains is the one worth keeping:
@ceebee/uiis server-safe and ships no client runtime;@ceebee/ui/clientcarries"use client".AntdThemeBridgeis renamedThemeBridge(AntdThemeBridgeProps→ThemeBridgeProps);ThemeProviderwraps 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*Skeletonvariant and the sort, autoplay and table helpers that served them.Renamed on the way:
Removed Use ProgressBar,ProgressRingProgress(type="circle"for the ring)StaticAnchorAnchorStaticStepsStepsLinkButtonButtonwithhrefAvatarGroupAvatar.GroupDialogClose,DrawerCloseModalandDrawerrender their own close controlDateRangePickerDatePicker.RangePickerTreeSearchSelectTreeSelectwithshowSearchCoachmark,TourTourringGeometryand itsRingGeometrytype go withProgressRing; they existed only to lay that ring out. Notethicknesswas a pixel width whilestrokeWidthis a percentage of the ring's diameter, andtone/huebecome astrokeColorreading the matching Ceebee token:// 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" />CoachmarkandTourtaketourReducer,initialTourState,resolveTarget,hasEnded, theTourState/TourAction/TourStatus/StepTargettypes, and theSeenStoretype 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, andTransferare each replaced by the same-named export from@ceebee/ui/client, along with their skeletons, prop types, and thedate,time,numberanduploadhelpers that only served them.Fieldgoes with them. It was the shared label, hint and error wiring behind all of the above;Form.Itemis that contract: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.Donutstays alongsideProgress: it draws one arc per slice, whichProgresshas no equivalent for.PopoverandTooltipstay — 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:
Leaderboardrenders its own initials bubble rather thanAvatar, andPageContainer.Skeletonits own placeholders rather thanSkeleton— both are server-safe, and the runtime's versions are not.The
Labelscontract losesback,done,skip, andprogress, which only the removed Tour and Coachmark spoke.nextstays — the carousel and the image preview still use it.Dependencies
Dropped from
dependencies:uqrand the thirteen@rc-component/*engines, none of which the package imports since the source ports that used them were removed.antdanddayjsremain. Attribution is unchanged:THIRD_PARTY_NOTICES.mdretains every upstream copyright notice, anddocs/component-sources.jsonstill 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.
ThemeBridgenow setstrackBg,trackHoverBg,handleColor, anddotActiveBorderColorfrom--cb-brand-300and--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, persistentNotification, inlineCalendar, persistent navigationMenu, and single/rangeSlidercompositions 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
:hoverrule behind@media (hover: hover).A touchscreen has no hover, so a browser applies
:hoveron 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
:hoverwith:focus-visibleor[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
AutoCompletelist 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:
scrollY anchorTop overlapsHeader anchorHidden 120 23 false false 180 -37 TRUE false 240 -97 TRUE trueThe 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.
AutoCompleteis modal now, which is what Base UI already makes aSelectby 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
AutoCompleteopen, 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__bodyand.cb-drawer__bodynow setscrollbar-gutter: stableand 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
SelectorAutoCompletepopup 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-heightfollows 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.
Was Now StackFlexStatCardStatisticBreadcrumbsBreadcrumbDialogModalComboboxAutoCompleteEmptyStateEmptyDataTableTableSpinnerSpinTextInputInputDateInputDatePickerTimeInputTimePickerNumberInputInputNumberFileDropUploadDropdownMenuDropdownStepperStepsThe
cb-class namespaces moved with them, so a consumer that styles against.cb-dialogor.cb-stackhas to follow.DialogClosekeeps its name because Base UI's own part is called that.Known callers at the time of writing: a small consumer uses
Stackin 26 places; a dashboard consumer usesStackin 53,StatCardin 20 andBreadcrumbsin 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, andonClose).Rate— a row of stars that is a radiogroup, readable as well as settable. Pressing the current score clears it.Divider— a bare<hr>, or arole="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.Skeletonsharing 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 aSeenStorethe app owns). Popover and Tooltip on Base UI, and theReveal/Staggermotion 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
<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 asnull— 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 throughCheckboxandSwitch. -
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.
LabelsProviderputs 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 ownlabelsprop still wins, because one tour may speak differently from the product around it.Sidebar items take
itemsfor 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.SkeletonandTimeline.Skeleton, atruncateflag per table column,ToastProvider position(six logical placements, with a top stack entering from above), andprefers-contrast: moresupport 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.
ScrollRevealis dropped from the plan:RevealandStaggeralready takeonView, 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
aspectRatioand fades in over a blurred placeholder — and it checkscompleteon the ref, because a cached image can finish loading before React attachesonLoad, 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-sortrather 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 witharia-currentrather 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: staticpopup, 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::beforecarried 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 differentoutline-offsetnumbers are now two tokens (--cb-focus-offsetand--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
<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.