Skip to content
Redpanda UI
Esc
↑↓navigate↵open⌘Jpreview
On this page

Strict toolchain migration

Migrate removed compatibility APIs and stricter form contracts.

Review the required consumer import and prop changes before updating. Reinstall the affected copied source first, then update application calls and run your checks before deploying. Installing a registry component does not migrate application calls.

Reinstall all registry components already installed in your project with --overwrite, including components that depend on a changed item. The CLI updates dependencies of the requested item, not its existing reverse dependents. For example, updating SanitizedHtml alone leaves an older copied CodeComparison importing its removed helper export. Reinstall CodeComparison as well. Review local changes before overwriting copied source.

Keep the copied source notices

Every registry item now depends on source-notices, which installs the registry, shadcn, and Buf license texts and their provenance notice under src/components/redpanda-ui/licenses. Keep these files with redistributed source; they are data assets, not application imports or runtime dependencies.

The checked shadcn CLI can remove leading comments from copied TypeScript and CSS, including copyright headers and explanatory JSDoc. Registry JSON retains those comments; the common notice files preserve the license trail independently of that transformation. Do not assume that installed source is byte-identical to its registry payload, or delete the notice directory when cleaning unused components.

Removed compatibility APIs

Removed Replacement
code-block-dynamic, DynamicCodeBlock, SyncCodeBlock, its CodeBlock and Pre, TERMINAL_THEMES Code Block, SimpleCodeBlock, useHighlightedCode, and variant="terminal"
sonner registry component and demos Toast, its Toaster, and toast manager
asyncHandler from lib/async-handler Remove the retired wrapper and copied file. Let promise-aware APIs await asynchronous actions; handle failures explicitly at application-owned void event boundaries.
Heading, Text, textVariants, headingVariants Semantic HTML with text-heading-*, text-body, text-label, or text-caption utilities
RedpandaIcon and RedpandaLogo from icons Redpanda Logo, with variant="mark" for the icon
Stepper initialStep defaultStep
Drawer activeSnapPoint, setActiveSnapPoint, snapToSequentialPoint snapPoint, onSnapPointChange, snapToSequentialPoints
InputOTP noScriptCSSFallback, pushPasswordManagerStrategy Remove these unused props. Base UI uses real character inputs.
FocusScopeContentProps, onOpenAutoFocus, onCloseAutoFocus Configure the underlying Base UI popup’s initialFocus or finalFocus. The old hooks did nothing.
Chart legend content’s verticalAlign and align Recharts position, also accepted by ChartLegendContent
SimpleCodeBlock size="md" and size="lg" size="default"; use size="sm" for compact blocks
FlattenProtoOneofs<T> The generated message type, with its original discriminated oneof union
defineStepper(...).Steps ReturnType<typeof useStepper> for the instance type, or the real steps array for descriptors. The removed property was an empty placeholder.
ShadcnAutoFormFieldComponents and the internal AutoFormFieldComponentRegistry map Pass only your overrides through AutoForm’s formComponents prop. Built-in renderers are merged automatically. Use fieldRegistry for field matching.
useMemoizedArray from lib/input-utils Use the actual array and stable domain IDs. React Compiler owns derived-value optimization; array identity is not an event or subscription signal.

Delete retired component files from consumer projects as well as their imports. The registry CLI’s --overwrite option updates existing items; it does not delete components removed from the registry.

Also remove these retired internal files from your copied registry directory after reviewing and preserving any local modifications. A whole-project typecheck still checks obsolete files even when your application no longer imports them; in particular, the old barrels re-export removed symbols.

text
components/auto-form/fields/index.tscomponents/auto-form/fields/choicebox.tsxcomponents/auto-form/proto/index.tscomponents/auto-form/proto/schema.tscomponents/auto-form/proto/validation.tscomponents/auto-form/slot.tsxlib/use-proto-form/proto-paths.tslib/async-handler.tsx

Paths above are relative to your registry installation root, commonly src/components/redpanda-ui. Migrate imports to the current owning modules; do not recreate the barrels or add typecheck exclusions for these retired copies.

RedpandaProvider now mounts the Base UI toast provider and viewport. Replace all sonner imports and remove the dependency. Do not mount a second global Toaster beside it.

tsx
import { toast } from '@/components/redpanda-ui/components/toast/manager';
toast.add({ title: 'Settings saved', type: 'success', timeout: 5000 });

Use toast.close(id) instead of toast.dismiss(id). Replace Sonner’s duration with timeout and action with actionProps. See the toast migration table for promise and action calls.

Group and Choicebox retirement

The already deprecated Group and Choicebox entries are removed too, including their demos and legacy-only tests. See the Group migration and Choicebox migration.

Group, useGroup, GroupContextValue, GroupPosition, and groupItemClasses are no longer public APIs. Use ButtonGroup for attached actions, InputGroup for input composition, and FieldGroup/FieldSet for related controls. Button/Input/Select no longer read a hidden Group context. Choicebox* exports and AutoForm’s choicebox built-in are removed; migrate field metadata to radio or register a deliberate custom renderer. No compatibility alias remains. An explicitly requested unregistered renderer now reports a configuration error with its requested name instead of silently substituting the schema’s default control. Register custom controls through the existing formComponents/fieldRegistry contract.

Client pagination uses native actions

SimplePagination now renders named native buttons, with real disabled endpoints and type="button", rather than placeholder links that cancel navigation. Without onPageChange its controls are disabled. Use PaginationLink, PaginationPrevious, and PaginationNext with actual destinations for navigation; these remain anchors. Tests and CSS targeting the former SimplePagination anchors must migrate. Page/ellipsis identity follows the page or named gap, not its array position. Previous/next controls adopt the standard Button spacing recipe instead of local padding overrides.

Chart colors use theme tokens directly

This is an additional breaking styling change, not deprecated-API cleanup: ChartStyle and ChartConfig.theme are removed. ChartContainer no longer injects a runtime stylesheet or generates --color-${series} aliases. The strict no-runtime-stylesheet rule has no Chart exception.

Keep ChartConfig.color as optional series metadata and pass it directly to Recharts. Replace every old series-name reference, including gradient stops, strokes, and per-datum fills:

tsx
const chartConfig = {  desktop: { label: 'Desktop', color: 'var(--color-chart-1)' },};
<Bar dataKey="desktop" fill={chartConfig.desktop.color} />;

The previous fill="var(--color-desktop)" no longer receives an automatic declaration. Remove standalone ChartStyle calls and move former per-series light/dark mappings into named tokens in the application’s checked theme stylesheet, preferably using the existing chart palette. Theme CSS now owns first-paint and live theme resolution; no observer, effect, new wrapper, or raised browser floor is required. Caller styles and native props still forward. An explicit id now also reaches the native container; previously it was consumed only to name the generated chart selector. data-chart retains its chart- prefix.

Tooltip indicators keep explicit color, datum fill, and payload color precedence, then use matching config color as a fallback. Legend payload color takes precedence over the same metadata fallback. Series labels, icons, and open string keys remain. See the Chart migration.

Tooltip formatter now follows the Recharts callback contract: its fifth argument is the original series payload array, not the current data row. Read the current row from the third argument’s payload property instead. The fourth argument is the original series index, including when hidden series are omitted from display.

Form contracts

KeyValueField rows have stable identity

KeyValueField.value and onChange now use KeyValueRow[], not plain KeyValuePair[]. Every row has a stable id and a pair. Allocate IDs once when creating or loading rows with createKeyValueRows; retain them through edits, clones and reordering. Do not regenerate IDs during render or from an array index. Project back to plain submission or validation data with keyValuePairs.

For React Hook Form arrays, retain its field-array identity rather than creating a second competing identity owner. See the checked examples and detailed KeyValueField migration before overwriting copied components and updating callers.

SchemaProvider<T> now describes the validated output. Its validateSchema function accepts Record<string, unknown> and returns SchemaValidation<T>. Validate or normalize raw values before returning success. Do not claim that incoming values already have type T.

AutoForm’s unused internal proto/schema and proto/validation forwarding modules are removed. Import protobuf APIs from their owning lib/protobuf-provider entrypoint rather than deep-linking through AutoForm. The supported AutoForm defaultRegistry export remains.

Passing a generated protobuf descriptor to AutoForm infers its generated output. A dynamic descriptor cannot promise an unrelated application type. Use an explicitly typed provider adapter when transforming messages into another result.

useProtoForm preserves generated oneof unions and branch-specific paths. Its submit callback receives the validator’s output type. createMessage checks field values before returning a typed message instead of asserting raw data into that type.

Strict protobuf values

useProtoForm.createMessage() requires native protobuf field values; a numeric string is not an int32 value. Register ordinary React Hook Form number inputs with valueAsNumber: true. The separate ProtoProvider/formValuesToMessage form-conversion boundary intentionally supports numeric text. Clearing a numeric input can produce NaN, which is invalid rather than a successful conversion to zero. Integer fields must fit their actual scalar range; float/double infinity support remains governed by the protobuf type and schema constraints.

64-bit form conversion supports integer text, bigint and safe integer numbers. Preserve integer text rather than passing large values through lossy JavaScript numbers; fractional values are not silently truncated.

Initialize message-valued defaults with the generated protobuf APIs. For example, use create(TimestampSchema, { seconds: 0n, nanos: 0 }), not a plain { seconds, nanos } object masquerading as a typed Timestamp. Strict construction continues to throw for incompatible values; do not catch and discard those errors.

Validation reports conversion failures at their real field, repeated-item, map-row or oneof-value paths while retaining independent constraint errors. One bad number must not replace unrelated email or username feedback. Message-level and affected collection constraints are reported as incomplete until conversion errors are fixed; a diagnostic projection is never successful submission data.

Replace setOneofValue(path, case, value, options?) with setOneofValue(path, { case, value }, options?). Paths must identify a generated oneof, and the selection must retain its case/value correlation. Replacing the whole selection removes the previous branch’s values.

ts
form.setOneofValue('preferredContact', { case: 'preferredPhone', value: '+15551234567' });

getNestedErrors(path) returns unknown, not a caller-selected generic type. Narrow the dynamic result before reading it:

ts
const error = form.getNestedErrors('shippingAddress.lineOne');if (typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string') {  showError(error.message);}

InputGroup now renders a native fieldset. Change root refs from HTMLDivElement to HTMLFieldSetElement. Its addons remain presentational and do not add keyboard stops.

JSONField keeps explicit empty and falsy values. Examples are placeholders, not submitted data. Mismatched values show an error and can be corrected in JSON mode. Created options no longer mutate caller-owned arrays.

Repository checks

Use the pinned Bun version from package.json. Run bun run type:check for both TSRS and the TypeScript 7.1 dev-canary tsc. Editor diagnostics and package-only checks do not replace the root quality gate.

Registry builds use the locked development-only shadcn CLI through Bun. They no longer download an isolated older CLI to work around incompatible Zod versions. Consumer registry JSON retains the same schema and source targets.

The source gate rejects authored deprecated declarations as well as deprecated API usage. Remove retired exports and publish a breaking migration rather than keeping unused compatibility declarations or exempting their tests.

Repair violations at source. Suppression directives, compiler opt-outs, ignored-looking authored paths, and weakened project settings are checked by the root policy scripts. Keep dependency and generated-output directories separate from authored source.

The rule gate compares both compilers’ actual program inputs with authored TypeScript, JavaScript, JSX, and Astro files. A workspace’s own project must include its source; importing it from another workspace is not a substitute. Imports cannot move compiled code into excluded build, dependency-free output, or context directories to avoid the source checks. Alternate compiler configs require explicit review rather than silently changing a file’s compiler settings.

Authored source, configuration, and documentation must be real workspace files, not symbolic links. Scanners share a filesystem inventory and reject source links, directory links, and broken links rather than silently skipping them. The pinned TSRS editor executable shim is not a source file; its exact dependency target is checked.

Do not force-add files under ignored dependency, output, or context roots. Tracked files in those roots are rejected. Compiler inputs are also checked by their real filesystem identity: a dependency-looking link cannot launder application source from an ignored output directory or another checkout. Copy external development source into a checked workspace or publish and install the dependency normally.

Vue and Svelte component files and runtime imports require renderer coverage review. They cannot silently bypass the current React/Astro compiler and rule policy.

The native quality config rejects per-file overrides, inherited configs, disabled plugins, and unreviewed source exclusions even when its root rule severities remain unchanged.

MDX authoring

Documentation MDX is now a data-only presentation format. Move executable modules, callbacks, handlers and interactive native HTML into checked TypeScript components, then register those components through packages/docs/components.ts. Named data imports must resolve to checked local source, not generated output or dependencies.

Literal metadata, static classes, inert JSX comments and fenced code examples remain supported. Write displayed union types as strings, for example type: "'default' | 'outline'"; the JavaScript expression 'default' | 'outline' computes zero rather than a type description.

HAST type dependencies

Remove the obsolete hast runtime package from copied highlighter installations. The registry now installs @types/hast as a development dependency for the type-only AST imports. Keep hast-util-to-jsx-runtime: it remains the actual runtime renderer. Reinstall use-shiki with --overwrite to receive the corrected dependency metadata.

Typed mocks and client components

Declare mock signatures explicitly, including when supplying an implementation:

ts
const save = vi.fn<(name: string) => Promise<void>>(() => Promise.resolve());const actual = await vi.importActual<typeof import('./client')>('./client');

Call mock factories directly rather than extracting vi.fn into an untyped factory alias. The bypass gate also checks factories nested inside assertions, which the pinned native Vitest detector does not reliably visit. Use TypeScript for these mock factories.

Use dynamic-import module arguments in vi.mock so factories are checked against the actual exports. For overloaded or generic APIs, use a real-module spy (for example, vi.mock(import('./client'), { spy: true })) rather than erasing overloads through a handwritten function mock. Keep fresh result factories when each call needs a new Response or mutable object; a shared mockResolvedValue is not equivalent.

The exported test-utils/vitest-motion-setup now uses Motion’s supported MotionGlobalConfig.skipAnimations setting instead of replacing its components and hooks. Tests retain real refs, contexts, motion.create, and MotionValues while animations complete immediately. Keep actual animation and reduced-motion assertions in browser tests.

A component in a use client module cannot be asynchronous. This applies to named, arrow, and default component exports, including registry source copied into another framework. The native gate runs the Doctor detector directly instead of relying on Next application inference. Asynchronous non-component utilities and server components remain allowed.

Separate recipe imports

Component entry points no longer export class recipes. Import recipes from the component’s variants module to separate non-component exports and improve Fast Refresh compatibility. This does not guarantee a boundary: component entries that also export hooks can still invalidate during an edit, depending on the bundler and the importing module. This applies to Button, Badge, Table, Avatar, Code Block, Code Tabs, and the other components that expose a class recipe. Component and type imports keep their existing paths.

tsx
import { Button } from '@/components/redpanda-ui/components/button';import { buttonVariants } from '@/components/redpanda-ui/components/button/variants';

The same path applies inside the workspace: @redpanda/registry/components/button/variants. Reinstall each affected component with --overwrite to obtain its recipe module before updating imports.

Pure helper exports also moved out of component entry points:

Helper New module beneath the registry component directory
escapeHtmlText, replaceWithSanitizedHtml sanitized-html/html
formPathToLabel form-error-summary/error-data
initTheme, themeScript theme-provider/theme-script
toast, createToastManager toast/manager

Search validators now come from lib/use-data-table-search-params/validator, not the hook entry point. useDataTableSearchParams and its provider keep their entry point.

Replace renderMultiSelectOptions(options) with <MultiSelectOptions options={options} />. The component preserves recursive groups, separators, item identity, and selection behavior. Demo-only column arrays, artwork data, and the secret adapter context are no longer public exports. Button and Badge showcase inventories moved to their demos’ data modules.

AutoForm’s field matching metadata now lives in auto-form/fields/definitions. Its existing root registry exports remain available. Internal scalar helpers live in fields/field-values, test-id hooks in fields/use-field-test-ids, and option labels render as <OptionLabel option={option} /> rather than calling a JSX-returning helper. The secret adapter context is private; use SecretSelectorAdapterProvider.

Primitive styling

Use Label, FormLabel, and FieldLabel’s size="sm" or size="lg" for their shared type scale, and variant="strong" for semibold nested-field text. Do not apply typography utilities to labels or use a native label as an unrelated section heading.

EditableText now requires a stable label for the input’s accessible name. Keep the label independent of the editable value, for example label="Topic name". Validation messages describe both the display action and editing input. Enter commits and Escape cancels without losing keyboard focus; ordinary blur still moves focus to the next control. The specialized inline editor owns its action appearance rather than repainting the generic Button.

Use Card padding="none" for edge-to-edge media or rows. Use the header, content, and footer padding props for their insets. A directly nested padded header or footer supplies its matching top or bottom inset inside an edge-to-edge card.

Use NavigationMenuLink variant="trigger" for top-level links beside menu triggers instead of applying the trigger’s class recipe at the call site.

Use PopoverContent padding="none" instead of p-0, or padding="sm" instead of p-2. Its default 16-pixel inset and calendar interaction are unchanged.

Use Command variant="standalone" for the rounded command panels instead of adding rounded-lg border shadow-md. The default elevated and embedded treatments remain unchanged.

Sidebar menus now size against Base UI’s --anchor-width. Replace legacy w-[--radix-dropdown-menu-trigger-width] classes in copied demos with w-(--anchor-width).

SidebarMenuButton owns its open-popup background and text colors, including when animated hover highlighting is enabled. Remove duplicated data-[popup-open] color overrides from menu triggers. Leaving the pointer over the open menu no longer clears the trigger’s surface.

Use TableCell numeric instead of a tabular-nums override. Use its existing align, weight, and truncate props for those treatments.

Use TableHead variant="compact" for compact uppercase column headings. Use TableCell’s density, font, size, and tone props instead of duplicating padding, monospace, font-size, and text-color utilities. The default table and cell appearance is unchanged. See cell appearance for the available values.

Use Avatar shape="square" instead of applying rounded-lg separately to the root and fallback. AvatarImage and AvatarFallback now inherit the root radius. Loaded images in square avatars now retain the square treatment instead of reverting to a circle. The default round avatar is unchanged.

Input now preserves native caller styles instead of overwriting them when measuring adornments. Measured prefix and suffix padding uses typed CSS custom properties. Check copied editable-text inputs that previously relied on their explicit width being ignored.

Brand icons and the treadmill illustration now use fixed artwork variables supplied by the theme. Reinstall @redpanda/theme with --overwrite when updating these components. Keep the new style/artwork-colors.css file beside theme.css; its import is already included. The vector paths and default artwork colors remain unchanged in light and dark themes. These fixed pigments are not interface color or action-state utilities.

Auth0’s fixed hosted-login colors now have named theme tokens, including hover and focus shadow colors. Update the theme when reinstalling Auth0 too. The provider, focus, and background colors retain their existing values; they do not switch to the application’s dark palette.

AutoForm context and failure reporting

The internal compatibility aliases AutoFormRenderContext, AutoFormRuntimeContext, useAutoFormRenderContext, useAutoFormRuntimeContext, InternalAutoFormRenderContextValue, and InternalAutoFormRuntimeContextValue are removed. Use the single useAutoForm() context API instead of depending on the former split.

Field-change callback failures now appear in a separate alert. They are not field validation failures and do not mark a valid input as invalid. Handle expected failures inside your callback; do not rely on a browser-global unhandled-error reporter.

Boolean fields reject non-boolean values instead of treating strings such as "false" as enabled. Array and map renderers reject scalar collection values. Invalid schema/value combinations in JSONField show a recoverable error rather than silently coercing data. Correct the input or use its JSON editor before submitting.

Payload-editor changes cancel ownership of an earlier parse result, including when the newer text is malformed, the editor is reset, or the form unmounts. Custom asynchronous parsers may still finish their own work; their obsolete result is not applied to the form.

useTheme() now requires a matching ThemeProvider, normally supplied by RedpandaProvider. Calling it without a provider throws instead of returning an inert setter.

Calendar composition

Use Calendar variant="outlined" for a framed calendar, variant="elevated" for a frame with a small shadow, and variant="embedded" inside an already padded surface. The default remains the padded, unframed calendar. Replace root border, shadow, background, and padding overrides with these variants.

CalendarDayButton uses DayPicker’s day props directly. Configure locale on Calendar, not separately on a day-button override. Its data-day attribute now uses stable yyyy-MM-dd values, matching DayPicker’s own date metadata, instead of locale-formatted text. Changing testId no longer remounts custom day components or discards their state.

Form and documentation IDs

Demo controls use instance-scoped IDs and matching label and description references. Use role- or label-based browser locators instead of fixed demo IDs. Auth0Input also generates its default DOM ID; pass id explicitly when a host integration requires a fixed value. Its submitted field name remains username.

Component docs retain the stable #recent-changes heading fragment in their single page footer. When composing several ComponentChangelog regions, pass a distinct headingId for each region that needs a stable fragment. Omitting it keeps instance-scoped IDs for accessible labeling; those generated IDs are not stable permalink targets.

Inset field labels

Replace <Label className="px-1"> with <Label inset> for labels aligned with field controls. Default label padding and native htmlFor activation are unchanged. The inset prop is consumed by Label and is not forwarded to the DOM.

Replace <DropdownMenuItem className="gap-2 p-2"> with <DropdownMenuItem padding="comfortable">. Standard padding remains the default; items already own their icon/text gap.

Use <DropdownMenuLabel variant="muted"> for small, subtle section headings and variant="identity" for unpadded identity blocks. These replace the former text-body-sm text-subtle and p-0 font-normal overrides without changing their spacing, typography, or theme colors. Default section labels remain unchanged.

Option labels

Replace className="font-normal" on Label, FieldLabel, and FormLabel with variant="option". Default labels retain medium weight. FormLabel owns its validation color through Label’s invalid appearance instead of restyling it.

Badge and filter appearance

Badge and badgeVariants accept only tone, variant="solid" | "subtle" | "outline", size, and disabled for appearance. The exported BadgeVariant and BadgeVariantKey compatibility types are removed. JavaScript callers using retired spellings now receive an explicit error too.

Replace variant="success-inverted" with tone="success" variant="subtle", and variant="primary-outline" with tone="primary" variant="outline". Replace the flat disabled styles with the disabled prop. Preserve emphasis with disabled variant="subtle" for disabled-inverted or disabled variant="outline" for disabled-outline. The simple, ghost, and link aliases are removed; choose a semantic tone/emphasis and use render for a real anchor. Bare variant="outline" now uses the default tone’s outline treatment rather than the former generic border/foreground recipe.

Call badgeVariants({ tone: 'success', variant: 'subtle' }) instead of passing a flat style key. Its former CVA class/className options are also removed; merge caller-owned layout separately.

DataTableFilter uses the same two axes. Its default is still tone="primary" with variant="subtle". Replace variant="default-outline" with tone="default" variant="outline". DataTableFilterVariant now means only the three emphasis values, not the internal Badge style-key union. The variants demo no longer presents fake-disabled filter controls.

Readable code defaults

Code Block, Code Editor, Code Tabs, and useShiki now use github-light-high-contrast for their default light syntax palette. The dark default remains github-dark-default. Custom themes remain supported; check their ink against the real background rather than assuming a theme name guarantees readable contrast.

Code Comparison focus annotations now outline focused lines instead of blurring and fading the surrounding code. The default highlight color is var(--color-accent), not a solid destructive surface. No hover gesture is needed to read the full example.

Input appearance ownership

Replace Input color, shadow, typography, and padding overrides with the finite appearance, tone, textStyle, and size props. tone="success" now owns verified-value focus feedback, while aria-invalid remains authoritative. Use textStyle="monospace" for machine values and InputStart / InputEnd for measured adornment space.

Composite fields use appearance="embedded" so their containing control owns the focus ring. Inline editable text uses appearance="inline" and inherits its surrounding heading or body typography instead of falling back to the normal input text size. Compact list search uses the existing sm spacing scale.

Auth0Input is the hosted provider’s branded native field, not a restyled general Input. Its props follow native input props; general Input adornment, step-control, and appearance props do not apply.

Native color-swatch controls use ColorInput rather than transparent text Input overrides. presentation="swatch" retains the painted proxy at rest and reveals the native picker on keyboard focus.

Native form composition and choice labels

Wrap popup triggers in FormControl, rather than rendering FormControl inside a trigger’s render prop. The outer control supplies the ID before the trigger creates its internal relationship. This keeps FormLabel activation, accessible naming, and validation descriptions bound to the real trigger.

tsx
<FormControl>  <PopoverTrigger render={<Button variant="outline">Choose a date</Button>} /></FormControl>

Label and FormLabel now own their whole-choice surfaces. Replace card, pill, attached, tile, and compact-card appearance overrides with the corresponding surface prop. Selection and disabled paint follow the real control, not a duplicated React state. Checkbox cards now share the radio-card spacing, rounding, and selected treatment; enabled provider tiles no longer look disabled. Ordinary optional labels use the shared label gap, and labels in addons inherit their container’s text tone.

Banner now forwards its native div ref and caller style contract. A supplied height prop takes precedence over style.height; otherwise native height is retained, with 3rem as the default. Other caller style properties are no longer overwritten by the component’s height calculation. Authored repository styles still use the theme and CSS custom-property policy; forwarding consumer props is not a styling exemption.

Textarea recipes and native validation

Replace appearance overrides on Textarea with its finite recipes. Use textStyle="monospace" for scalar code values, textStyle="code" for JSON, or appearance="document" textStyle="code-compact" for recessed CSS/code documents. Keep className for layout/dimensions. Existing size and resize props remain supported, and TextareaProps is exported.

Use InputGroupTextarea for composite inputs: its group owns border/focus, while the textarea keeps native multiline editing and content autosizing without a resize handle. Decorative addons now focus textarea controls as well as inputs.

Remove redundant destructive-border classes; set aria-invalid or use Field validation context. Name JSON editors and associate visible errors with aria-describedby. The AutoForm payload JSON editor now exposes these semantics while retaining invalid drafts for correction.

The form-control matrix no longer paints fake Hover/Focus columns. It shows real rest, filled, invalid, and disabled states in an inert specimen grid; its link opens the live interaction gallery for actual keyboard/pointer states.

Finite lazy-loader maps

Runtime imports must use literal specifiers. Unbounded dynamic imports and bundler glob/context calls can pull test-only implementations into production, so the source gate rejects them. Reusable test support belongs outside runner-discovered test entries. Type-only ambient imports and inert JSON fixtures remain supported.

After adding a playground route, run bun run --filter=playground update:components. After adding or removing a registry demo, run bun run --filter=docs update:demos. Both loader maps remain checked source. bun run lint:loaders rejects missing or stale maps without modifying them; the ordinary root lint and quality commands require it.

Auth0 action ownership (breaking)

Auth0Button and Auth0SubmitButton now own their two fixed hosted-login appearances, rather than repainting a general Button. They render through Base UI’s native button primitive; no generic headless action or new global Button variant is exported.

Remove these inherited general-Button props from Auth0 actions:

  • variant, size, weight, shape
  • className, style
  • as, to, href, target, rel, icon

Put layout spacing on a containing element. Do not replace an appearance override with paint on a wrapper, child, or custom render. Hosted palette values remain the centrally declared static-auth0-* theme tokens. Use the general Button for a general application action rather than passing application Button recipes into a hosted Auth0 action.

Native attributes and events, ref, render, nativeButton, focusableWhenDisabled, disabled, isLoading, and testId remain supported. render still infers non-native keyboard behavior for a known non-button intrinsic; custom component renders can state nativeButton={false} explicitly. Loading sets busy/disabled state and retains a centered spinner without replacing the label’s natural geometry. A focusable disabled action remains keyboard-focusable but cannot activate.

Provider connection IDs remain open strings. Known IDs select the existing icon; unknown IDs keep an empty icon slot. data-provider, primary/secondary action markers, data-slot="button", test IDs, and native form metadata remain intact. Submit defaults remain type="submit", name="action", value="default"; native caller overrides still work.

Intentional visual corrections

The provider’s declared hosted border was previously overridden by the general outline Button’s important neutral border: measured rest color was rgb(89, 114, 166). It now uses the existing hosted token, rgb(201, 202, 206), including hover. Pressing a provider action no longer adds the general outline Button’s neutral active background/border over the hosted treatment; the existing visible keyboard focus treatment remains authoritative. Provider icons now honor their declared 20-pixel size instead of the general Button’s 16-pixel descendant SVG rule.

The measured 52-pixel action height, 3-pixel corners, text/padding, resting shadow, active scaling, and composed visible keyboard focus remain preserved. Auth0’s light hosted surface remains fixed under both application themes; no claim is made that the corrected border/icon pixels match the old bug.

Reinstall the affected Auth0 component and its installed reverse dependents through the registry CLI. Registry regeneration must carry the direct Base UI dependency and Spinner registry dependency instead of retaining a general Button dependency solely for these actions.

MotionHighlight: explicit item and foreground composition

This is a breaking composition cleanup. MotionHighlight no longer discovers or wraps children. Wrap each selectable foreground in an explicit MotionHighlightItem. Remove controlledItems and itemsClassName; put the latter’s wrapper classes on the appropriate item.

tsx
<MotionHighlight defaultValue="topics">  <MotionHighlightItem value="topics" render={<Button onClick={openTopics}>Topics</Button>} /></MotionHighlight>

The old <MotionHighlightItem><Button>Topics</Button></MotionHighlightItem> implicitly cloned its child. Move that foreground element to render. Plain children now populate the default foreground div; they are not inspected, cloned, or treated as a render target. A render callback receives the supported Base UI props and { active, disabled, value } state.

Remove asChild. Items consistently retain their decorative wrapper while enabled. render replaces the foreground, not that wrapper. An item ref still references its wrapper div; put the native button/link ref on the element passed to render. Disabling the entire decoration with enabled={false} removes the wrapper, clears its ref, and preserves the native foreground/ref/actions.

Provide value explicitly for controlled/default selection. Identity is no longer inferred from the foreground’s id or data-value. value takes precedence over the item’s own id; without either, a React-generated identity is used. Put a semantic role on the item when requesting automatic aria-selected; it is forwarded to the foreground and only selection-supporting roles receive that attribute. Ordinary buttons and links do not receive invalid aria-selected.

Native/render handlers run once, before item handlers and internal activation. event.preventDefault() cancels decorative activation; Base UI’s supported preventBaseUIHandler() composition also remains available. Native Enter/Space activation remains native rather than being synthesized by the highlighter. Hover handlers likewise run once and can cancel before activation.

disabled is decorative: omitted item values inherit the root; explicit disabled={false} overrides that inherited setting. It prevents highlight activation, not the underlying action. Set disabled on the native Button separately when the action itself must be unavailable.

Actual Sidebar and Files consumers retain their existing wrapper, peer/data-slot, foreground and native-control composition. This slice does not change the parent indicator’s layout animation or introduce LazyMotion.

MiniCalendarDay: owned date-action appearance

MiniCalendarDay now owns its finite month/day layout, selected/today paint, focus feedback, disabled state, and loading treatment directly on the installed Base UI Button primitive. MiniCalendarNavigation remains the ordinary registry Button; its existing appearance API was not changed.

Breaking API changes

  • Date actions no longer inherit generic Button appearance props (variant, size, weight, shape, icon), polymorphic/navigation aliases (as, to, href, target, rel), className, style, or replacement children. Use a containing element for layout and a normal Link for navigation. No unstyled/style escape was added.
  • Date actions own aria-pressed; select through MiniCalendar’s value, defaultValue, and onValueChange instead of writing that attribute.
  • A day onClick now composes with selection instead of replacing it. The caller runs first; event.preventDefault() or event.preventBaseUIHandler() cancels selection. Existing handlers that implemented selection themselves should instead observe onValueChange, or explicitly cancel if replacing selection is intentional.
tsx
<MiniCalendar onValueChange={setDate} value={date}>  <MiniCalendarDays>    {(day) => <MiniCalendarDay date={day} disabled={!canSelectDate(day)} key={day.toISOString()} />}  </MiniCalendarDays></MiniCalendar>

Native attributes/ref, native type override, Base UI render/nativeButton/focusableWhenDisabled, disabled, isLoading, and testId remain supported. A day defaults to type="button", so ordinary selection does not submit an enclosing form. Loading preserves the accessible date name and sets busy/disabled semantics. A known intrinsic non-button render automatically receives Base UI’s non-native keyboard behavior; custom components can provide nativeButton={false}.

Default icon-only navigation now has Previous dates / Next dates labels. Custom navigation render elements retain their own names; the existing custom-layout demo now explicitly labels both icon-only actions. The existing seven-day demo now demonstrates disabled weekends.

LoadingAnimation: explicit SVG progress

LoadingAnimation no longer inspects or clones SVG children to inject progress. Pass progress explicitly to the SVG component. The container’s progress still controls Lottie playback; it does not overwrite independently controlled child props.

tsx
<LoadingAnimation type="svg">  <YourSVGComponent progress={progress} /></LoadingAnimation>

Existing WaitingRedpanda and RunningRedpanda compositions already pass their progress explicitly. Their playback behavior remains unchanged.

CheckboxView: decoration, not an inaccessible checkbox

CheckboxView no longer exposes a non-operable role="checkbox". It is hidden from the accessibility tree. Its containing control must supply the accessible name, keyboard behavior, and selected/pressed state. Use Checkbox for an actual checkbox, not a click handler on the decoration.

The decorative API now contains only checked, variant, className, ref, and testId. Remove inherited div event, ARIA, role, and tab-index props. Query the owning control by role or the decoration by its explicit test ID; the old [role="checkbox"] selector no longer identifies decorative ink. Its visual checked/indeterminate treatment and div ref remain supported.

ItemGroup: native list structure

ItemGroup renders ul rather than a div with a list role. Its native props and ref now use HTMLUListElement. Wrap each item in an explicit li, and keep an inter-item separator inside the preceding li. Do not put role="listitem" on a linked Item: the enclosing li owns collection semantics and the anchor keeps its link semantics. Standalone Item composition and its render prop are unchanged.

tsx
<ItemGroup>  <li>    <Item render={<a href="/topics/orders" />}>Orders</Item>  </li>  <li>    <Item>Audit</Item>  </li></ItemGroup>

Documentation environment

The optional LOGO_DEV_TOKEN must be a publishable pk_ key, never a secret sk_ key. Logo badges embed this value in statically published image URLs. Empty/unset values remain optional. The centralized typed environment module uses Astro’s import.meta.env server boundary; components no longer read process.env. See Logo.dev’s key distinction and Astro environment variables.

ButtonGroup: native action grouping

ButtonGroup now renders a native fieldset, with implicit group semantics. Change div refs and native props to HTMLFieldSetElement. Its orientation, connected-button treatment, nested groups, and ordinary Tab navigation remain; this is not a toolbar with arrow-key focus management. Supply aria-label or aria-labelledby when the group needs an accessible name. The native disabled prop now disables its descendant form controls through browser semantics.

Open demo exports

Open-state demos now have named exports, like the other registry demos. Replace import DialogOpenDemo from '.../dialog-open' with import { DialogOpenDemo } from '.../dialog-open'. The same change applies to AlertDialog, Combobox, Command, Credenza, Drawer, DropdownMenu, HoverCard, Menubar, MultiSelect, Popover, Select, Sheet, and Tooltip open-state demos. There is no default-export compatibility alias.

Canonical demo entrypoints

Documentation loader shards now select the exact named PascalCaseFolderDemo export at compile time. Both loadDemo('theme-background') and loadDemo('theme-background-demo') select ThemeBackgroundDemo, never the first helper function in a module. Default-export and arbitrary first-function fallback behavior is removed; fix a demo’s named export rather than depending on module order.

InputGroupTextareaExample is now InputGroupTextareaDemo. ButtonGroupDropdownDemo is now ButtonGroupDropdownMenuDemo. There are no aliases. Regenerate checked demo loader maps with bun run --filter=docs update:demos.

Calendar native date-action ownership

CalendarDayButton now owns its finite date-action appearance on a true native button, using the installed DayPicker protocol rather than repainting the general registry Button. The single/range endpoint/range middle/today/focus/disabled states, native events, native attributes, custom day content and stable day.isoDate metadata remain supported. DayPicker, not a second keyboard manager, owns keyboard movement and selection.

Breaking public paint-map changes

Removed from Calendar: classNames, styles, modifiersClassNames, modifiersStyles. Legacy map props also fail explicitly at runtime, including when forwarded through an object spread, instead of overriding the owned recipes or being silently discarded. Remove all four keys from wrapper props and forwarded objects: a present key with an undefined value is still a retired prop and fails the same guard. Supported optional props keep their declared undefined behavior; this is not a blanket ban on undefined values.

Use variant for the existing finite surface treatment. The new booked prop accepts a DayPicker Matcher | Matcher[] and owns the real struck-through booking treatment, including the existing full-opacity booked-cell presentation. It marks appearance only; pass disabled for unavailable dates. Existing custom modifiers still reach DayPicker, and other selection/availability semantics are unchanged.

tsx
const bookedDates = [new Date(2026, 9, 26), new Date(2026, 9, 27)];
<Calendar  booked={bookedDates}  disabled={bookedDates}  mode="single"  onSelect={setDate}  selected={date}  variant="elevated"/>;

Distinctions, not a claim that every style surface disappeared:

  • Calendar root className and singular style remain the separate existing layout/cell-sizing contract. This slice did not migrate those APIs or their existing cell-size callers. They are not replacements for the retired day-paint maps.
  • Incoming CalendarDayButton.className and .style remain DayPicker native protocol props, needed by custom day wrappers forwarding the framework’s props; no general Button variant/size/render/navigation inheritance is introduced. components.DayButton still supports real custom native day content.
  • buttonVariant still styles only month navigation via the existing Button recipe. The month-navigation styling and existing root cell-size overrides were not hidden or redesigned.

Forward all received native props and the original day/modifiers from a custom components.DayButton to CalendarDayButton. Do not replace DayPicker’s supplied disabled with modifiers.disabled: it deliberately leaves the focused disabled day natively focusable with aria-disabled, while suppressing selection. Date actions default to type="button" and retain an explicit native type override.

Forwarded refs now compose with the internal focus ref rather than replacing it. The public CalendarDayButtonProps includes the native button ref; React object refs and callback cleanup are retained. CalendarProps also preserves DayPicker’s discriminated selection modes while omitting the retired maps.

The aggregate @redpanda/registry/demo/theme exports ThemeDemo, not individual section demos. Import an individual demo directly from its existing dedicated entrypoint instead:

tsx
// Beforeimport { ThemeBackgroundDemo } from '@redpanda/registry/demo/theme';// Afterimport { ThemeBackgroundDemo } from '@redpanda/registry/demo/theme-background';
Export Dedicated entrypoint suffix
ThemeBackgroundDemo theme-background
ThemeBordersDemo theme-borders
ThemeChartDemo theme-chart
ThemeElevationDemo theme-elevation
ThemeFamilyMapDemo theme-family-map
ThemeSemanticPairsDemo theme-semantic-pairs
ThemeShapeDemo theme-shape
ThemeSidebarDemo theme-sidebar
ThemeStaticDemo theme-static
ThemeTextDemo theme-text
ThemeToneBordersDemo theme-tone-borders
ThemeTypographyDemo theme-typography

Use @redpanda/registry/demo/<suffix> for each mapping. Dedicated export names and rendered sections are unchanged.

Documentation preview controls

Preview fullscreen/restart controls now have explicit accessible action names and fullscreen state. The unused tweak-pane branch is removed. Controls use the owned Button recipes and dynamic viewport sizing instead of restyling Motion buttons. The external editor affordance is a real link: its complete registry URL is encoded as one query parameter and retains normal browser link behavior. Its inverse Button recipe and always-visible text intentionally replace the bespoke responsive paint.

ListView: move row interaction to an explicit action

ListView is now a presentational grid, not a button-like container. Remove interactive, all root event handlers (including onClick), role, tabIndex, and contentEditable. Whole-row hover/pointer behavior is removed. Put the action in ListViewEnd: a native Button for an action, an application Link for navigation, or a Switch/menu trigger. Do not wrap the flow-content row in a button.

tsx
<ListView>  <ListViewStart title="Cluster A" description="Production workload" />  <ListViewEnd>    <Button onClick={openDetails} type="button">      Open cluster    </Button>  </ListViewEnd></ListView>

The root’s finite prop surface is children, className, id, ref, style, title, hidden, inert, dir, lang, size, variant, and testId. Generic DOM passthrough is removed; legacy event/role props in spread objects are not forwarded at runtime. The ref still targets the layout HTMLDivElement; existing slot and group APIs remain unchanged. Label icon-only actions and switches with an item-specific accessible name. The demo’s four former no-op row actions now have actual independent selection Buttons with aria-pressed and visible state.

MultiSelect: separate the opener and selected-value actions

Replace the former MultiSelectTrigger > MultiSelectValue nesting:

tsx
<MultiSelect>  <label htmlFor="framework-choice">Frameworks</label>  <MultiSelectControl className="w-96">    <MultiSelectValue maxDisplay={3} maxItemLength={20} />    <MultiSelectTrigger id="framework-choice" placeholder="Select frameworks" />  </MultiSelectControl>  <MultiSelectContent>{/* Existing search/list/items */}</MultiSelectContent></MultiSelect>
  • Import new MultiSelectControl. Move the outer width/layout classes there.
  • Move placeholder from Value to Trigger. Value no longer accepts it.
  • Trigger no longer accepts children. It is a genuine native opener, not a DIV role button. Its native event target/ref is HTMLButtonElement; native button attributes go there. type="button" is owned, not configurable. No public render/nativeButton/asChild escape added.
  • Keep selected chips outside the opener; the shell itself is noninteractive. Native removal buttons are independent siblings, not pressable descendants.
  • Wrap only Trigger in FormControl, not the shell; pass field ID, name, ref, onBlur, invalid/described-by metadata to Trigger. Associated labels/aria-labelledby now name the real button without being overridden by the generic fallback label.
  • SimpleMultiSelect composes this anatomy internally. Its options/value/width API remains; aria-label/aria-labelledby are additionally supported. onOpenChange can access Base UI details, while existing boolean-only handlers remain assignable.

JSONField array editing identity

No public prop/export changed. Internal insert/remove transactions now preserve retained row controls and local state after the parent accepts the emitted JSON. This includes React Hook Form, which clones accepted values. Ignoring or rejecting an update no longer changes row identity.

Unmatched external document replacements reset form-local state safely, including reordered/replaced arrays. The implementation never guesses row identity from values. An equal-value external replacement during a pending edit is indistinguishable from an equivalent cloned acknowledgment; use a standard React key for an unconditional reset:

tsx
<JSONField key={documentRevision} onChange={setValue} schema={schema} value={value} />

Only JSON domain values reach onChange/submission; UI IDs remain private. Existing schema-owned created choices survive normal mode changes.

Card section recipes and calendar examples

Use <CardHeader separator> or <CardFooter separator> instead of authored border/padding overrides. The recipe adds the section border and 16px inset for size="sm", 24px for other sizes including omitted default md. Existing unseparated defaults and legacy border class spacing remain unchanged; this API addition alone is nonbreaking.

The calendar examples intentionally standardize compact Cards to the existing sm recipe (8px section gap, one horizontal inset), footer gaps to finite values, time-preset footer insets to24px, and preset actions to natural wrapping. Inline calendar cells are40px instead42px to fit mobile without overflow. These are reviewed example appearance changes, not exact geometry preservation.

Use NavigationMenuLink variant="featured" | "icon" | "rich" for promotional destinations, compact icon rows, and icon/title/description rows instead of overriding owned paint/spacing. Existing default/trigger variants and native link/ref/render contracts remain.

Use NavigationMenuPanelButton direction="forward" | "back" for inline submenu actions, not destination links. It is a native non-submitting Button; pass onClick/disabled/ref normally. The demo restores Back/originating-action focus across panel changes.

Examples intentionally standardize featured padding to24px and Home columns to equal widths, and collapse wide mobile grids into readable single columns. Popup dimensions/position snap to Base UI’s measured values instead of animating layout properties; entrance/content compositor motion remains. No claim of pixel-identical example output or measured performance gain.

Upload and loading controls

Dropzone owns its upload-action recipe instead of repainting Button. Use appearance="avatar" instead of className="size-32 rounded-full p-0". Its className now controls the outer layout, while the forwarded ref and test ID still identify the native upload action. The native file input is a hidden sibling, not a second exposed control nested in a button. Keep custom action content phrasing-only and noninteractive; put independent actions outside Dropzone.

Every rejected file and validation reason is displayed in the built-in, associated error message. onError receives an AggregateError containing all rejection reasons and filenames; remove duplicate validation alerts from callers. A later accepted selection clears that error. Native picker/validator errors and synchronous onDrop failures remain visible even without an onError callback. Upload action identity no longer resets when the accepted file list changes.

Button loading states retain their accessible action name while the spinner is visible. Icon-only loading examples provide explicit names. Native disabled/busy behavior and geometry are unchanged.

The documentation package-manager selector adopts the owned ToggleGroup recipe, with a named group, exactly one pressed choice, and arrow/Space keyboard selection. This intentionally replaces the custom miniature paint and spacing; generated commands keep their selected manager and overwrite behavior.

AutoForm: replace marker children with explicit slots

AutoFormSlot and its before/after marker-child API are removed, without a compatibility alias. Pass slots: readonly AutoFormSlotDescriptor[] instead:

tsx
<AutoForm  schema={schema}  slots={[    { id: 'intro', placement: 'start', content: <p>Account details</p> },    { id: 'name-help', placement: 'before', field: 'username', content: <p>Choose a username.</p> },  ]}/>

Every descriptor has a caller-owned unique/nonblank stable ID and exactly one placement: start, or before/after with a root schema field key. Do not infer IDs from labels/content/index. Unknown root anchors and invalid IDs fail visibly with no usable form below the error. Correct the slots through a new array to retry with the same schema; ordinary valid descriptor-array changes do not reset form state.

Start content appears once per currently rendered mode/step panel, not once per configured step. Anchored content follows its field across Simple/Advanced and steps, hiding when the field is not part of the current projection; JSON has no form slots. Slot state remains stable on insertion/reordering within a mounted placement. Ordinary children are no longer traversed or assigned replacement keys: existing non-stepper children still precede fields, and stepper children still follow controls. Replace prior ambiguous markers with both before+after by separate explicitly keyed descriptors if both placements are genuinely wanted.

Stat: explicitly render linked labels

labelLink is removed without an alias. Replace empty link elements with renderLabelLink, applying the supplied StatLabelLinkProps (className, children) to the actual native or framework link:

tsx
<Stat  label="Topics"  value="6"  renderLabelLink={(props) => <a {...props} href="/topics" />}/>
<Stat  label="Topics"  value="6"  renderLabelLink={(props) => <Link {...props} to="/clusters/$clusterId/topics" params={{ clusterId }} />}/>

The caller owns href/to, refs, events, accessible-name overrides, and any explicit class merging. Supplied children contain the label and decorative arrow; supplied className retains the caption/hover/focus recipe. Stat no longer clones or inspects link elements. Plain and linked Stat remain statically server-renderable; no new client directive/hooks/composition dependency.

Explicit identity in animated AvatarGroup, BadgeGroup and DotGroup

  • Animated components/avatar-group replaces JSX children with readonly {id, content: ReactElement} items. The static AvatarGroup from components/avatar is unchanged.
  • BadgeGroup replaces JSX children with readonly items containing required id, content and plain-text textValue. Its overflow renderer receives the actual typed item records, including inferred domain fields, not ReactNode children. Hidden textValue entries remain available to assistive technology before a supplementary visual tooltip opens.
  • DotGroup replaces arbitrary JSX children/implicit injected props with explicit {id,kind:'status',variant?,pulsing?,label?} or {id,kind:'count',count,max?,variant?,label?} descriptors. Status-only groups support all sizes; count/mixed groups require sm/md/lg. Compose arbitrary custom elements separately; there is no compatibility clone path.
  • IDs must be nonblank, unique and independent of editable display content or list positions. They survive reorder/edit/delete; changing an ID intentionally remounts its item. Empty/duplicate IDs fail loudly. Badge/Dot maxVisible must be a nonnegative integer.

Avatar motion now owns local synchronous minimal Motion features, preserving supported full-Motion content through a non-strict boundary. No provider setup is required. Measured cold-consumer reduction is not guaranteed for apps already loading full Motion.

Tags and removable badge examples

TagsValue with onRemove now requires an item-specific removeLabel, for example removeLabel="Remove React". Omit both props for passive tags. The name is consumed by the native action, not forwarded to the tag span. Removing a selected tag restores focus to its enclosing picker without opening the popup.

Tags removal actions now use the standard destructive-ghost icon Button, and the removable Badge example uses large Badges with standard ghost icon actions. These are intentional target-size, typography, wrapping, and theme-tone changes rather than pixel-identical miniature controls. Its reset action uses the normal link Button. Repeated keyboard removal/reset retains focus on the next available action.

CommandDialog owns its dialog recipe

CommandDialog.commandProps.variant is removed. Remove nested variant overrides; the wrapper now always selects the actual dialog recipe, including its input, group, and option geometry. Use a standalone Command for another surface variant. commandProps.size, filtering, labels, metadata, and layout-only className remain supported.

Triggerless keyboard palettes can pass finalFocus, with the same public contract as DialogContent, to identify their closing focus destination. Capture the focused control before opening, not again while closing. The Command dialog demo and documentation demonstrate an invoking-control ref, so Escape or the shortcut returns focus to the original control rather than the document body.

Files and CodeTabs

  • CodeTabs owns compact code-viewer spacing. Remove inherited generic Tabs size; supported visual variant is standard (default). Native root/ref/keyboard props remain. Controlled value is authoritative: onValueChange requests a change but cannot override rejected parent value. onCopy receives selected original source string, not a clipboard event.
  • Folder no longer accepts generic AccordionItem render replacement. Keep Files/Folder/File composition; customize children/sideComponent. Native root/folder/file refs remain. File leaves remain passive display divs; folders are Tab/Enter/Space disclosures, not ARIA trees.
  • Replace Git-state recolors/indicators with status=“modified” or status=“untracked”. Owner provides text tone/M/U or dot and permanent readable status. sideComponent overrides visual indicator, not accessible status.
  • Folder layout and CodeTabs underline now position immediately instead of interpolating layout dimensions. Folder icon/moving row-hover motion remains; keyboard focus has its own 3px ring. Copy uses standard ghost hover/pressed tones.
  • Files owns native anatomy. Update selectors targeting generic Accordion implementation/private file-button wrapper to actual file, folder-trigger, folder-content slots. install-tabs root/header/content slots remain.

Compact annotation examples

JSONField now surfaces an invalid JSON draft after the existing 300-millisecond pause instead of silently swallowing its parse failure. The draft remains editable; correcting it clears the error. Parent update failures are visible too, and switching to the Form view still requires valid JSON. This is intentional feedback, not a successful coercion or a reason to discard the user’s pending text.

JSONField’s schema-type annotations use the default outline Badge spacing rather than miniature padding overrides. Avatar notification examples use CountDot for counts, with intrinsic width for multiple digits and accessible notification labels; the overflow count remains visually capped at 99+. Avatar status dots use their existing stacked halo recipe. Refresh the affected examples with their primitive dependencies; notification count typography and halo geometry intentionally standardize to CountDot rather than the old repainted Badge.

Table pinned boundary columns

Use matching TableHead/TableCell pinned=“start” or pinned=“end” instead of caller sticky/edge/border/background classes. One boundary column per logical edge only; there is no multi-column offset API. The component owns an opaque card surface, correct hover/expanded/selected row washes, separators and stacking. That card surface remains opaque with Table variant=“simple”; horizontal pinning does not enable a vertically sticky header. Existing native refs/attributes/content remain supported. No public API removed.

The three examples now use standard Badge/Button/Table recipes, human column labels, named search/page-size controls, and a wrapping mobile toolbar. Intentional standardization, not pixel parity. Refresh installed Table and the affected demos together so copied variants.ts and consumer calls agree.

The nested-menu demo now shows two supported compositions: a controlled dialog under DropdownMenuContent keepMounted, and the recommended controlled sibling dialog. The former active “Broken” branch was removed: its current closeOnClick={false} behavior did open the dialog, contradicting the explanation. The unmounting risk is now factual, inert guidance rather than a deliberately incorrect interactive example.

Use a real DropdownMenuItem.onClick to set dialog state; wrapping it in a non-native AlertDialogTrigger changed its exposed role to button and interfered with menu semantics. A kept-mounted descendant whose invoking menu item becomes hidden supplies the actual menu-trigger ref as the popup’s finalFocus. The sibling example does not need that override. The existing topic example intentionally retains its menu while its confirmation is open.

All fixed account/topic/cluster confirmation copy and Cancel/action labels remain unchanged, now owned by distributable lib/demo-delete-confirmation. Dialog roots, triggers and open-state teaching remain in the demos. No component API is removed by this extraction.

  • Sidebar now owns its mobile navigation Dialog, rail, contextual separator and navigation highlight recipes rather than repainting Sheet/Button/Separator. Keep existing Sidebar composition, state callbacks, rail actions and menu APIs.
  • Mobile Sidebar native ref and attributes now reach the actual popup (previously the ref was dropped). It still uses the configured portal container and slide/overlay behavior. Escape/outside dismissal restores opener focus.
  • An accessible Close sidebar action is paint-clipped at rest and visibly revealed on keyboard focus. Its absolute box never changes; it cannot intercept the pointer while clipped. This adds a real modal escape for assistive technology without inserting an idle header row.
  • SidebarSeparator no longer accepts generic Separator variant: its contextual sidebar-border style always overrode that color prop. Orientation, decorative/semantic roles, ref/native attributes remain.
  • Generic Sheet implementation selectors are no longer Sidebar anatomy. Use sidebar, sidebar-overlay and existing menu slots. collapsible=“none” still stays inline on mobile; the right inspector example intentionally follows that contract.

Interaction-state examples use owned recipes

Topic information in the registry interaction-state demo is now a semantic data table, not rows that look pressable but have no action. The Playground’s service actions remain real native buttons with exclusive pressed state and visible Selected text; they now use the existing ghost/lg recipe and list-owned dividers instead of repainting Button. Standard40px targets/radius/padding/typography intentionally replace the former custom row treatment. No component API is removed.

Theme specimen copy actions

Theme token captions and swatches now use standard link/ghost Button recipes. The color sample is inert content inside the action, not a replacement Button background. Copying still writes the complete token class string. Captions wrap instead of truncating; link color, focus treatment, target size, and prose width intentionally follow the shared recipes. No public theme-token API is removed.

ListCard: explicit single-action composition

  • Replace root onClick={selectModel} with action={{ label: 'Select model', onPress: selectModel }}. onPress has no DOM-event argument. It is not a compatibility alias for the removed click API.
  • Action roots are Base UI nonnative buttons backed by an actual div, preserving flow-content markup and HTMLDivElement refs. action owns role, tab order, accessible name, disabled state and optional controlled pressed state; these cannot be overridden with root props in the action branch.
  • Action-card descendants must be noninteractive. This is an authoring contract: arbitrary React children cannot be statically validated, and ListCard does not traverse or clone them. Place links, switches, menus, buttons and interactive overflow inside a passive <ListCard> instead. The old test blessing a nested button and card activation was removed, not preserved as compatibility behavior.
  • action.disabled blocks pointer/keyboard activation and removes the root from the tab sequence. Native div keyboard handlers remain available: prevent Enter in onKeyDown, Space in onKeyUp; native click-capture cancellation is honored.
  • Use action.pressed only for toggle selection; the parent remains its sole state owner. The example model grid now selects a model and displays the result instead of running a no-op callback. Model enablement survives list/grid switches.
  • The action focus treatment intentionally changes from a 2px outline to the standard 3px ring. Optional pressed selection has an accent wash. Default passive appearance, Card size/variant defaults and footer controls remain unchanged.
  • Shared Card root recipe is a real card/variants.ts source dependency, not a new Card styling switch. Reinstall/regenerate Card and ListCard together so copied source includes this helper and rewritten flattened imports. There is no new value reexport from Card’s component entrypoint.

InputGroup appearance and native actions

  • Use InputGroup shape="pill" and InputGroupButton shape="pill" instead of local --radius or rounded-class overrides. The button’s inherited pill contract now works even with icon-xs/xs sizing; defaults are unchanged.
  • Replace className="border-b" on block-start addons and className="border-t" on block-end addons with separator. This finite prop owns the border and adjacent padding. It is deliberately unavailable on inline addons.
  • Attach popover/tooltip triggers to the native InputGroupButton, not to an addon containing a button. Name icon actions; expose aria-pressed for actual favorite state.
  • Use semantic <code> for filenames inside InputGroupText. The sample intentionally adopts normal-weight standard code typography (previously medium), retaining Geist Mono 14px/21px; it is not a proven redundant-weight removal.
  • The script sample now edits actual data, copies the current draft with visible errors, resets the sample, and reports real line count. The no-op Run control and fabricated cursor-position status were removed; it does not execute code.
  • Dropdown/popover samples adopt standard owner radii, typography and description colors rather than bespoke paint. These are intentional visual changes, not exact pixel-parity claims.

Addon click-to-focus now ignores physically portaled targets: selecting a dropdown item restores focus to its invoking button instead of incorrectly focusing a sibling input. Decorative input/textarea and SVG button focus remain supported.

Table viewport and row focus ownership

Table adds stickyHeader and onViewportScroll?: React.UIEventHandler<HTMLElement>. For a sticky header, give the parent an explicit height and let Table stickyHeader own the sole scrolling surface. Remove caller-owned overflow-auto, border/rounding, sticky-header positioning, header paint/shadow, and move viewport scroll handling from the old outer wrapper to onViewportScroll. Do not move native table ref, onScroll, className, or other table attributes: those still target the actual <table>.

The new sticky viewport is a named native section with keyboard scrolling. Its name shares the table’s aria-label/aria-labelledby, or defaults to “Scrollable table.” Default unbounded tables are unchanged. TableRow owns its real inset three-pixel keyboard focus ring; activatable rows still need their actual activation, eligibility, and keyboard protocol. The ring paints on a pointer-inert row pseudo-element because WebKit does not paint collapsed-table row box shadows. Native cell actions and pinned-cell positioning remain independent. Expanded DataTable cells use existing density="none" rather than padding overrides. Faceted selected-value badges now use standard Badge medium weight instead of forcing normal weight.

Demo status/category names are explicit human labels rather than CSS capitalization; warn is now displayed as “Warning.” Sticky and virtual demos use the standard Table card surface/separator rather than a caller-painted background and duplicated outer border. These are intentional design/correctness changes, not pixel-parity claims. No existing public props are removed.

Form error summaries and native navigation examples

FormErrorSummary now subscribes to its React Hook Form provider’s actual error state. It displays every distinct criteria message, with an explicit fallback for messageless error leaves. Error actions focus inputs owned by the same form, including external form-associated controls. Ambiguous standalone targets report a visible error instead of focusing an arbitrary same-name field. Existing props remain accepted, but backend debug details are now hidden by default. Enable the new showDebug prop only from trusted development configuration, using the same value during server rendering and hydration. Keep private diagnostics hidden in production.

The anchor-tab examples now use real per-instance panel fragments. Native Enter, click, Back and Forward update their selection. These instance IDs are not a persistent routing contract: use application-owned router state for permanent shareable routes. The playground no longer sets an obsolete document-wide base URL that redirected fragment links away from their current page. Navigation-menu operations links lead to actual Redpanda documentation instead of missing anchors.

Native dropdown menu navigation

Use the new DropdownMenuLinkItem for navigation instead of nesting an anchor inside DropdownMenuItem and transferring padding to the child. It delegates to the installed Base UI Menu.LinkItem: the actual menu item is the native anchor, preserving href, browser modifiers, refs, native fragment navigation, keyboard behavior and supported render composition. Action items remain DropdownMenuItem.

DropdownMenuLinkItem shares the existing inset/padding recipe; use aria-current="page" for persistent current-page appearance rather than consumer colors. Its default closeOnClick is false, matching Base UI; opt into closeOnClick when the menu should close on local navigation. There is no invented disabled-link API.

DropdownMenuItem.render now works instead of being silently overwritten by a hardcoded DIV. Existing supplied hosts, refs, handlers and state-aware class callbacks now participate in the documented Base UI composition contract. For native buttons retain Base UI’s nativeButton flag; links should use the actual LinkItem part.

The project-link example now points to real factual summary sections with stable project-navigation destination IDs. Native fragments stay on the current page and focus the destination; modified-click opens the same real target in a new tab without a custom navigation/focus machine. When rendering multiple copies of DropdownMenuLinkTriggerDemo, pass a distinct, stable id to each. Do not use useId() for bookmarkable URL fragments: its generated identity can differ in a fresh render tree. These example IDs are not a new application routing API.

The person-menu sample uses existing padding="comfortable" directly on the row instead of a zero-padding action containing a separately padded Item. Its 8px row gap, 4px content gap and standard menu radius are intentional existing-recipe adoption, not exact pixel-parity. The meaningful avatar grayscale treatment is unchanged and still flagged for the next finite owner slice.

Toggle Group composition

No public props were removed. Attached corner geometry now follows the actual DOM buttons instead of inspecting and rewrapping React children. Fragments and shared choice components can render direct sibling items without making every item look like an isolated segment. Detached, horizontal, vertical and outline recipes retain their existing geometry. A single attached choice rounds all four corners. Avoid DOM wrappers between the group and its buttons.

Multi-select command appearance

MultiSelect keeps its public selection, option-metadata, search, maximum-count and focus contracts. Its embedded Command and CommandList now use their standard spacing rather than a second 4px horizontal inset and extra list padding. Disabled options use CommandItem’s owned foreground and 50% opacity instead of applying an additional muted-text repaint. These are intentional appearance changes; anchored popup widths and native keyboard/removal behavior stay unchanged. Option caching no longer relies on manual memoization for effect stability, including in consumers without React Compiler.

AutoForm imperative methods

onFormInit is removed rather than exempting its mount-effect callback. Use formRef for the live React Hook Form methods; it preserves the validated output type and becomes null on unmount. Read methods from event handlers instead of copying the form instance into parent state. Continue using onSubmit and onFieldChange for their actual events. A native form DOM ref belongs in formProps.ref, not formRef.

tsx
import { useRef } from 'react';import type { UseFormReturn } from 'react-hook-form';import { AutoForm, type AutoFormProps } from '@/components/redpanda-ui/components/auto-form';import { Button } from '@/components/redpanda-ui/components/button';
type Values = { username: string };
function Editor({ schema }: { schema: AutoFormProps<Values>['schema'] }) {  const formRef = useRef<UseFormReturn<Record<string, unknown>, unknown, Values>>(null);  return (    <>      <AutoForm<Values> formRef={formRef} schema={schema} />      <Button onClick={() => formRef.current?.reset()} type="button">        Reset fields      </Button>    </>  );}

Scroll progress refs and motion

ScrollProgress’s ref now returns only its actual native scroll container, or null in document scrolling mode and after unmount. It no longer fabricates a detached DIV when no container exists. The scroll-hint demo uses a static decorative arrow instead of a perpetual 1.25-second bounce; actual horizontal and vertical reading-progress updates still track native scrolling.

Tabs styling ownership and live examples

No Tabs props were added or removed. Unopted default segmented pills keep their recipe.

  • Vertical example uses the existing size="xl" (16px list-to-panel gap) and standard segmented-pill list instead of repainting it transparent with zero padding. Its fill and 4px inset are an intentional appearance standardization, not parity.
  • ScrollableTabsList still composes the same DragScrollArea and TabsList. Underline-only 1px baseline space now belongs to a native content wrapper inside the unchanged scroll viewport. Consumers depending on incidental direct-child DOM selectors should target TabsList semantics/anatomy instead. Public list refs/props still reach the actual tablist.
  • Underline trigger focus is now a 3px inset ring of the same color, fixing the scrollport’s clipped top/bottom focus paint. Default segmented trigger focus stays exterior. Scroll viewport dimensions, masks, 4px opaque baseline band, drag cancellation and keyboard scrolling remain unchanged.
  • Combined Tabs/ToggleGroup example moves its 12px horizontal /16px bottom content inset onto its actual native content container. This is not a new panel padding prop or a global default change.
  • Appearance matrix is now two live, labeled specimens with actual panels and neutral Overview/Activity/Unavailable labels. Forced hover paint, pointer blocking, removed tab stops and the fake fourth-state cell are gone. Hover is demonstrated by real pointer interaction. Matrix height/copy intentionally change.
  • Disabled Base UI tabs remain focusable for discoverability but cannot activate on Enter/Space. This supported behavior is retained, not changed to match an incorrect test assumption.

Owned Avatar identity and Spinner context appearance

Use AvatarFallback tone="primary" | "success" | "brand" for the corresponding owned fill/foreground pairs; default preserves the neutral fallback. These are identity tones, not an online/offline announcement. Keep presence in a separately named status indicator.

Use AvatarImage monochrome for photo-only desaturation. The static AvatarGroup exported from avatar separately accepts monochrome for its direct avatar subtrees, including fallbacks. Do not conflate those effects.

Use Avatar frame="group" for the shared 3px background separator around overlapping avatars and animated AvatarGroup overlap="tight" for 12px overlap. Defaults remain no frame and standard 8px overlap; dimensions, stable item IDs, native refs, motion, inverted stacking and tooltip composition remain unchanged. The static and animated AvatarGroup APIs are separate exports from different modules.

Use Spinner tone="primary" | "brand" | "informative" | "success" | "warning" | "destructive" for context ink instead of consumer text-color classes. Its default tone inherits current color, preserving Button/Badge ink. The role remains a pending status; tone does not claim task completion or failure. Native SVG props, refs and caller-provided names remain supported. Docs no longer suggest pasting a second incomplete spinner implementation.

Spinner examples adopt existing Item/ItemMedia corner recipes rather than inheriting a local radius override. The Badge radius override was proven ineffective and removed without changing its measured appearance. The download sample is now explicitly an illustrative, non-live snapshot: 129 MB / 1000 MB matches 12.9%, and the no-op Cancel was removed. No download, charge or cancellation workflow was fabricated.

Shared sidebar application chrome

The six Cloud and self-hosted Console examples now use SidebarDemoOrganizationSwitcher for their complete organization menu. Provide stable, nonempty organization IDs, the selected ID as value, and onValueChange. Keep that selected-ID state in the application/demo owner, outside the mobile sidebar popup: closing a mobile sidebar unmounts its contents. Reordering organization data retains selection by ID and displays current name/plan data. Empty collections, duplicate IDs, or an unavailable selected ID fail visibly instead of rendering a blank sidebar.

The shared SidebarDemoPageHeader owns the existing Admin/Cloud/Console breadcrumb and collapse toggle. Page bodies, navigation, account menu and trial/billing behavior stay in their existing owners. No component API was removed by this extraction.

Self-hosted organization-logo rendering now honors its supplied menu size. The settled menu image changes from distorted22×32px to square16×16px; the32×32px header image and its existing rounding remain unchanged. This is a deliberate geometry fix, not byte-identical visual parity.

When copying these examples manually, copy the two checked lib/sidebar-demo-* owners and their ordinary dependencies too. Registry generation handles the dependency closure; no new runtime dependency is required.

Stepper: explicit slots and native process actions

Breaking changes:

  • Replace Stepper.Step marker children with required title and optional description / panel ReactNode props. Rendered content is not discovered by component name or identity. Fragments, text and multiple nodes are preserved. Existing Stepper.Title, Description, Panel and Controls still support Base UI render and native refs.
  • title is the visible step-label slot, not the native HTML tooltip attribute. Put status chips into description; keep inline active-panel conditions explicit. Panels no longer silently disappear in horizontal mode.
  • Horizontal/vertical indicators are native named buttons in a navigation landmark and ordered list, not incomplete tabs. Update role/name locators from numeric tab to button named by the visible title, and selected-state checks to aria-current="step". Remove custom role, tabIndex, aria-selected and aria-controls props; Stepper owns its semantics. Panels are ordinary content, not fake tabpanels. Circle layout is a named, noninteractive progress indicator.
  • Tab/Shift+Tab visit enabled native actions. Left/Right focus actions horizontally, Up/Down vertically; Home/End focus the first/last eligible action. Convenience keys are scoped to the Provider and never activate. Enter/Space invokes the consumer’s onClick, preserving validation. preventDefault() cancels convenience handling. Native disabled and Stepperize linear-policy-ineligible actions are skipped. There is no cross-instance DOM lookup.
  • Step indicator variant keeps the finite shared Button tone recipes but rejects unstyled and null. The owner preserves the separate current-step halo and native keyboard-focus ring. className belongs to the list-item layout, not the private indicator’s paint.
  • The variants demo now keeps the current flow step while switching layout, rather than remounting/resetting the flow. Per-step status chips illustrate navigation position, not live provisioning outcomes. Tracking scrolls a panel on active-step changes; it does not move focus and does not recenter on typing.
tsx
<Stepper.Step  of={step.id}  title={<Stepper.Title>{step.title}</Stepper.Title>}  description={<Stepper.Description>{step.description}</Stepper.Description>}  panel={methods.is(step.id) ? <Stepper.Panel>Step content</Stepper.Panel> : null}  onClick={() => {    methods.goTo(step.id).catch(reportVisibleNavigationError);  }}/>

Use an external Stepper.Panel for shared horizontal content when appropriate. Keep the existing typed Stepperize Provider, controlled-step options, async guards and shared/local useStepper behavior.

Example and documentation density

Examples use the existing control recipes rather than repainting their colors, padding or type locally. The changelog expansion control now uses the standard small ghost Button, with a 32px target; inline code uses the 12px token. The Create Layout provider field adopts standard form/radio spacing and a native support Link. Selection-toolbars, cluster-name callouts and theme-palette pane headers provide at least 8px around bounded text; the CRUD example uses a divider instead of a nested full card. On narrow screens, its named search input wraps with the actions rather than collapsing until its hint disappears. These are intentional appearance and accessibility fixes, not unchanged screenshot claims.

An externally owned DataTable controller

Additive API; no existing DataTable props or exports removed. The convenience DataTable keeps its controller defaults, page clamping, responsive visibility and toolbar callback.

tsx
import { DataTableView, useDataTable } from '@/components/redpanda-ui/components/data-table';
const table = useDataTable({ data, columns });// Existing filter, facet and URL hooks keep this same controller.return <DataTableView table={table} />;

showPagination defaults false and only controls footer visibility. It never changes manualPagination, slices server data, clamps external state, inserts selection columns, or takes ownership of model options. Set those on useDataTable. Existing DataTable.pagination behavior remains unchanged; it is not the new footer-only setting.

The view requires the actual bound AppTable-capable controller and consumes no selected .state shape. Full, narrow and null-selected controllers remain supported without asserting projected state back to full state.

Reinstall @redpanda/data-table through the CLI to receive the complete renderer and row helper closure. Existing consumers only need to migrate if replacing repeated standard table rendering. No dependency/version change introduced.

The URL-filter demo’s status hint now uses standard Badge typography rather than a local monospace override. It describes active URL state, not executable code. This is an intentional appearance standardization.

SidebarMenuButton is the native action owner. New SidebarMenuLink owns main navigation and SidebarMenuSubLink owns nested navigation. SidebarMenuSubButton and its props export were removed, without a deprecated alias: that misleading name previously defaulted to an anchor (or, incorrectly, a tooltip button). Replace it with SidebarMenuSubLink and move the destination/children onto the owner:

tsx
<SidebarMenuSubLink href="/projects">Projects</SidebarMenuSubLink>

Use SidebarMenuLink href="/projects" instead of rendering an anchor through a disabled SidebarMenuButton. Native link paths require a destination. Enabled controls still accept render, including an actual TanStack Router Link; native role, href, ref, Enter/modifier/context behavior and caller cancellation are retained.

Disabled controls cannot use render. This is enforced by the type union and a descriptive runtime error for JavaScript callers. Custom renderers can restore navigation attributes or run their own router callbacks before merged handlers; pretending those paths are disabled would be unsafe. For conditional availability, branch between a disabled native owner without render and the enabled router-rendered owner.

Native actions remain type=button by default and do not submit enclosing forms. Disabled native buttons use native disabled behavior. Disabled links retain ARIA link semantics, remove href, reject activation and skip Tab. Associate a persistent disabled reason with aria-describedby and a unique useId-based text node; do not rely on visual Tooltip alone.

Disabled navigation now dims exactly once. Knowledge Base changes from inherited effective25% opacity to the owner50% recipe; Reassign Partitions stays effectively50%. The standard More row uses normal menu foreground instead of a custom70% tint. These are intentional standardizations, not pixel-identical parity.

Fixed-banner Sidebar example

The fixed-banner demo now actually exercises the existing bannerHeight app-shell contract. Its desktop sidebar is collapsible and starts below the fixed40px banner; the native SidebarInset owns the content offset. Mobile starts with navigation closed, exposes a real Toggle Sidebar action, and opens an unoffset modal navigation dialog above the banner. Escape restores focus to the opener.

This intentionally replaces the demo’s always-inline navigation. It does not change the public collapsible="none" contract: inline sidebars do not become mobile dialogs or consume desktop fixed-container offsets. Keep inline layouts in normal document flow; do not rely on bannerHeight to pad them.

The three navigation labels, badges and trial/upgrade text are unchanged. This repairs an existing overlap where the Workspace group label was painted behind the absolute banner. No new runtime dependency or shared shell was added.

Profile examples and radio appearance

Additive RadioGroup API: variant="attached" owns joined vertical choices (zero gap, overlapping borders, rounding and shadow). Pair with existing Label surface="attached". Standard appearance and horizontal layout remain unchanged; TypeScript rejects attached+horizontal. Arbitrary supported radio values, native form naming, refs, keyboard behavior and event cancellation remain supported.

Use existing FormItem horizontal/card-horizontal recipes and standard RadioGroup spacing rather than local gap/padding paint. This intentionally standardizes the affected examples: radio gaps12px→10px, option-row gaps12px→8px and switch-card padding12px→16px. No utility-shaped spacing props or generic styling escape added.

The attached example now fits its real container:420px minimum replaced by full available width362px desktop/342px mobile. This fixes actual narrow-viewport clipping; it is not pixel parity.

Both profile examples now share concrete email, notification preferences, DOB and email-preference components in the existing profile-form-example library. They retain their shared exact input/output schema and typed RHF control. Manual Username/Bio composition versus SimpleFormField remains intentional. The email-preference heading uses sentence case.

Standalone notification form now forwards controlled value/name/blur, first actual radio ref and real group error IDs. Invalid submission focuses/describes the control; native FormData contains the selected value.

No public prop removed. CLI consumers reinstall changed components/demos through the CLI to receive complete helper dependencies. RadioGroup now includes its finite variants helper and the existing class-variance-authority dependency; profile examples share the expanded substantive profile library item.

ScrollArea fade example

The gradient-fade example uses native Tailwind mask utilities rather than duplicate inline mask declarations. The fade now occupies10% of each vertical edge (25.6px at the demonstrated256px height) instead of a fixed24px. Native wheel scrolling and the280×256 mask owner remain unchanged; Chromium and WebKit were exercised. Reinstall the example through the CLI; no component API changed.

Self-contained Motion features

CopyButton and ScrollProgress retain their existing HTMLMotionProps contracts, including drag and layout. Their local synchronous LazyMotion owners include domMax; MotionHighlight also includes domMax for shared background layout. Consumers do not need to add a provider. Existing MotionConfig inheritance and full Motion child/render elements remain supported; no strict context is imposed. Files uses its included MotionHighlight feature owner for its private folder icon.

Unpublished LookoutMark and PlaygroundMark application chrome now accept only actual supported className and (Lookout) burstSignal inputs, not arbitrary SVG Motion props. They are not registry-distributed public APIs. Their existing hover, fan/burst geometry and appearance stay unchanged. Playground shuffle failures are reported while mounted, but canceled/unmounted lifetimes do not report errors.

No claimed 30kB or frame-rate improvement. Paired standalone production probes show domMax provider overhead (small byte regressions) and closed domAnimation owner savings; full application output must be reported separately.

Empty surfaces

Reinstall the Empty registry item before adopting its new additive root variants:

tsx
// Dashed outline: replace local border/border-dashed paint.<Empty variant="outline">...</Empty>
// Token-backed recessed gradient: keep caller-owned layout separate.<Empty className="h-full" variant="muted">...</Empty>

The default surface, all compound exports, native root div attributes/ref, testId, and consumer className forwarding remain supported. EmptyMedia still has its separate default/icon variants. The root’s finite variants do not include null or unstyled.

The default and secondary-action demos now use the standard link recipe’s action ink instead of local subtle text paint. This is an intentional appearance change, not pixel parity. Their underlying illustrative Create/Import/Upload/Refresh actions have not gained backend behavior.

Native button-styled navigation

Use the new ButtonLink export from the existing Button registry item for native destinations. It requires href, shares standard Button appearance, and forwards native anchor attributes/ref and caller cancellation. It does not accept unstyled/null variants, render/as, disabled or isLoading. Keep actual router Links and apply the public buttonVariants helper. Button remains an action owner: rendering an anchor through it deliberately gives button keyboard semantics; changing only role does not undo Space activation.

The four Empty documentation destinations now use native ButtonLink. They keep their destinations and appearance while announcing links, activating with Enter and leaving Space to native scrolling. No remote documentation availability or backend action success is implied. Reinstall @redpanda/button and changed Empty demos through the CLI.

Existing SVG artwork

Existing path coordinates with four or more decimal places were rounded to two digits. IDs, view boxes, native attributes and theme paint remain unchanged. Paired real-browser raster checks cover all38 exported icons at16/32/256px in both themes, the docs editor mark, and automatic/controlled treadmill states; small antialiasing differences are intentional, not pixel-parity claims. Controlled treadmill progress now uses the native SVG transform attribute rather than CSS transform; observed matrices and frame geometry are unchanged. No new artwork or external asset fetch was introduced.

Reinstall Breadcrumb to get component-owned icon/text alignment and 6px spacing. Compose icons and text normally; remove repeated inline-flex/items-center/gap-1.5 classes from BreadcrumbLink and BreadcrumbPage. BreadcrumbItem already owns the same spacing for adjacent controls such as CopyButton. Native props/refs, className forwarding, custom render elements/children and event cancellation remain supported; no new public prop is required.

The responsive example now uses the existing wrapping list layout and readable full labels rather than80px caps and ellipses. This intentionally changes mobile presentation to two rows. Its desktop collapsed destinations use the existing DropdownMenuLinkItem with closeOnClick, so Enter follows the native href and dismisses the menu instead of merely clicking a wrapper around an anchor. The mobile Drawer still uses its native links and explicit Close action.

The Card example uses standard size=sm instead of local padding (vertical8→16px, horizontal32→24px), and its icon-only Home destination has an accessible name. These are intentional standardization/accessibility changes, not pixel parity. CopyButton behavior and callbacks are unchanged.

MotionHighlight parent geometry and root ref

Parent mode now owns native attachment, measurement, resize/scroll observers and optional frame following. Independently attached, replaced or removed items no longer leave stale geometry. Duplicate active values retain the first connected wrapper in DOM order; removing it selects the surviving match. Explicit item forceUpdateBounds={false} still overrides the root, while inherited/explicit true follows transforms.

MotionHighlight’s already advertised ref now points to its real parent container. It is null in children mode, while disabled through enabled={false}, and after unmount. Callback-ref cleanup is preserved. No native-prop passthrough or new polymorphic root API was added.

Breaking context API change: the exported useMotionHighlight() hook no longer exposes setBounds or clearBounds. Use explicit MotionHighlightItem composition and native item/root refs; consumers must not publish child-measured rectangles into parent state. The context now registers native item attachments for its single parent geometry owner.

Native activation, cancellation, hover, decorative disabled inheritance, item foreground/ref composition and children-mode shared layout remain supported. Changing mode still changes the actual wrapper structure and can remount children; this is not a state-preserving layout switch.

Runtime layout data remains data

Auth0’s existing imageURL/minHeight, OTP’s closed textAlign option, and drag-scroll edge gradients now use their component-owned classes and typed CSS custom properties rather than authored inline appearance. Their public values and consumer style precedence are unchanged. Drag-scroll keeps the underline protection and native horizontal scrolling; the supported modern browser path uses standard mask compositing.

Grouped controls use existing recipes

Reinstall ButtonGroup: its separator now respects the existing Separator default, subtle and strong variants instead of repainting every choice. Orientation, dimensions and decorative semantics remain unchanged.

Use the existing Button icon size, InputGroup pill shapes and warning/ghost variants rather than padding overrides, ambient radius variables or state paint classes. Grouped menu/popover examples use standard corners and typography; currency metadata uses standard Select typography. The voice example preserves the draft and only toggles input availability. It does not record or send audio. InputGroupText uses associated static guidance rather than a fabricated character countdown, and accepts unrestricted multiline drafts.

Standard table rendering and demo actions

The basic, full-featured, cluster and list-layout examples use the existing DataTableView with their externally owned controllers. Columns, filtering, sorting, visibility, selection, custom toolbars and external pagination remain under caller control. Empty cells now span visible columns, including after hiding a column and filtering to no matches. Column labels are literal accessible text rather than CSS capitalization.

The cluster and list-layout examples share the demo-only DemoClusterRowActions({clusterId}) support owner. Copy cluster ID performs a real clipboard write and reports synchronous or asynchronous failure visibly. View details, Edit cluster and Delete cluster are disabled because these examples do not implement those operations. Production applications must supply their real actions, not import that demonstration contract. Include the helper when copying these demos manually; registry installation resolves its dependency closure.

Native action recipes and modal previews

Dialog close actions and CodeEditor copy actions use their existing ghost recipes instead of local color/effect overrides. The label information action uses the standard24px icon button, with standard action ink and native popover/focus behavior. The destructive confirmation uses the existing destructive action variant.

AlertDialogOpenDemo now opens from its real Delete cluster trigger instead of making its entire document inert on mount. Alert dialogs remain modal even when their portal is visually contained. Cancel or confirm closes the surface; other gallery examples and navigation are usable before opening it. The component’s modal semantics are not relaxed, and the example does not perform a backend deletion.

Progress status variants

Reinstall Progress and replace descendant color overrides with variant="success" or variant="warning". Its default primary fill and native value/range/indeterminate semantics are unchanged. The status is explicit, not inferred from the percentage. Keep a visible label or status icon alongside color, and provide an accessible name. The backup/quota examples now do so.

Checkbox rows and InputGroup demo recipes

No public API change. Demo row actions now use the standard ghost/md Button recipe: 36px rather than33px rows, 8px rather than12px internal gap, standard radius and button ink. The bounded50-row list retains its native row buttons, pressed state, select-all/mixed behavior and scrolling.

InputGroup Help uses existing shape="pill"; redundant Spinner text paint is removed. URL text uses standard8px rather than forced4px left padding. These are intentional design-system standardizations, not promises of pixel-identical rendering. Existing input values and action behavior are unchanged.

Information-only controls use Popover

Tooltip is intentionally a supplementary visual label for an independently named action; do not invent Tooltip ARIA. Dedicated information icons use actual native Button/InputGroupButton with PopoverTrigger, optional openOnHover, PopoverTitle and PopoverDescription. Keyboard and touch activation must work without hover.

Six InputGroup/Label information controls now follow this anatomy, retaining historical demo paths/exports. Essential Bootstrap formatting and password requirements stay visibly associated with inputs while popups are closed. Password now enforces its stated native eight-character minimum. The examples do not submit/verify credentials. Larger standard popovers and persistent labels/instructions are intentional appearance corrections, not pixel parity.

Date and disclosure demo recipe corrections

No component API change. Disclosure actions use standard32square icon-sm Buttons. Date triggers and caption selects use existing standard paint/padding; preset controls use real external spacing.

The mobile date drawer uses embedded Calendar with spacing-scale11 cells:44square date targets,308px calendar width, fitting320px viewports. This intentionally replaces the old42.66px narrow /52px wide viewport clamp and redundant calendar padding. Selection, native month/year controls, keyboard protocol and focus restoration remain.

Today/Tomorrow now resolve local calendar days when activated, including after midnight and across daylight-saving changes, instead of a fixed2022 sample date. Time presets only describe the selected date/time; misleading booking success and the inactive Review meeting action are removed. Nothing books/submits a meeting.

The Admin, Cloud, Cluster, Serverless and Console examples now share finite application catalogs through SidebarDemoNavigation. The Cloud examples share SidebarDemoCloudFrame, whose optional billing slot renders the existing distinct trial feature under its SidebarProvider. These are demo support modules, not new generic product navigation or layout APIs. Existing demo exports, including both separately tested SidebarOptInForm features, remain available.

  • Leaf destinations are native links, not empty disclosure buttons. Their existing # destinations remain illustrative document-top links; no backend routes, selected-route state or account operations are invented. Real AI groups retain native disclosure buttons and now expose the previously discarded Cluster/Serverless Knowledge Base item, with its unavailable reason.
  • Admin and Console integration branding is static information, not an inert focusable button. The existing Redpanda Admin / Integration text is intentionally preserved in both. The decorative logo no longer repeats an unrelated accessible label. Static branding does not independently animate width/height/padding.
  • Sidebar feedback now disables tooltip behavior whenever the effective TooltipContent is hidden. Expanded available links no longer open an invisible tooltip that consumes the mobile dialog’s first Escape. Collapsed title hints, applicable unavailable-item hints, native disabled navigation guards, caller hidden overrides and upstream visual-only Tooltip semantics remain intact. No new tooltip ARIA or open-state machine is introduced.

When copying an older application example, use a native SidebarMenuLink for a destination, or pair a CollapsibleTrigger with actual content for a group. Do not retain the old empty Collapsible wrappers. The newly visible Cluster/Serverless child intentionally moves the following rows downward; the static identity is intentionally removed from tab order. Cloud organization logos retain their actual pre-existing32px rendering; this change does not silently implement the previously ignored logo className.

Reinstall Trace for the additive TraceLink export. It owns the standard source/file icons, line/title anatomy and accessible new-tab hint. Source links open a new tab; variant="file" uses the current tab. Native anchor attributes/ref and caller cancellation remain available. Existing custom anchor/TraceLine composition stays supported. The linked-source and file-step examples now share this owner without changing their destinations or labels.

The standalone and dialog Command examples share their demonstration catalog, preserving the standalone disabled Calculator. The basic and badge-count MultiSelect examples share their grouped technology options; search, selection, badge counts and outer state remain caller-owned. Include the corresponding demo support module when copying examples manually; registry installation follows its dependency closure.

The Item size example now demonstrates actual md, sm and xs recipes instead of repeating the basic example. Its documentation now names the supported md default, not an unsupported default size.

Comparison galleries use scale-backed native sizing and intrinsic tracks rather than hardcoded grid lengths. Button matrix columns retain their minimum readable widths and horizontal scrolling on narrow screens. The palette editor uses standard fixed-height scrollable panes instead of viewport-percentage heights; its maximum dialog width and native CSS editing remain intact. The unknown-component state uses a standard spacing-scale minimum height. These are intentional layout standardizations.

dotStackedVariants is no longer exported from the general utility module. Use CountDot’s public stacked prop. Its existing halo geometry is owned by the CountDot recipe and remains unchanged. Do not copy the private recipe into consumer code.

Profile example field ownership and Bio validation

  • The lightweight lib/profile-form-fields registry item now owns substantive username, Bio and notification presentations plus the canonical profile schema/metadata. The rich lib/profile-form-example composes it; standalone Input/Textarea/Radio examples no longer copy Calendar/Checkbox/Popover/Select/Switch merely to share field UI.
  • Reinstall affected installed registry items through the CLI, including their dependents: Form/FormSimple, InputForm, TextareaForm, RadioGroupForm and the profile helpers. This is not an instruction to hand-edit copied implementations.
  • The canonical schema target is components/redpanda-ui/lib/profile-form-fields/profile-schema.ts; shared presentation installs at components/redpanda-ui/lib/profile-form-fields.tsx.
  • Standalone Bio now starts with the controlled empty-string default and shares the real 10–160-character canonical limits. The old maximum message incorrectly said 30; it now truthfully says 160. Each outer form still owns its controller, schema selection, validation and submission. Manual FormField versus SimpleFormField teaching is retained.
  • Shared notification presentation preserves native group name/value/blur, controller disabled state and first-radio error-focus ref. Instance-specific labels/errors remain associated without duplicate IDs.

Toast description native element

  • ToastDescription now defaults to a div, not a p, because its public React-node content legitimately includes block content such as preview pre or lists. This repairs invalid HTML/nesting warnings; it does not change the toast manager lifecycle or announcements.
  • Default native props/events/ref are now typed for HTMLDivElement. Update paragraph-specific ref annotations and selectors such as p[data-slot="toast-description"]. Prefer the stable slot or public testId contract over assuming an HTML tag.
  • Base UI description IDs/accessible association, state-aware className/style, and custom render composition remain supported. Custom rendering remains responsible for an appropriate semantic container; a p is only suitable for phrasing content.

Message and activity recipe ownership

  • MessageScrollerContent variant="conversation" owns the repeated padded chat layout (24px gap,16px padding). The unpadded default32px gap, used by useChat, remains unchanged.
  • MessageFooter owns metadata/action wrapping and 6px spacing. The simple message demo now uses the existing compact8px MessageGroup gap instead of its custom20px.
  • Marker status="running" owns the content shimmer. The caller owns work/status text and whether role="status" is appropriate; there is no invented async state.
  • Timeline variant="activity" is a compact rail-free row list. Its shared12px gap and 12px vertical padding replace the plain activity demo’s 10px vertical padding. Activity inside CardContent no longer adds another16px horizontal inset. Content uses foreground. The default stepper/control contract is unchanged.
  • TraceIcon variant="disclosure" identifies only the trailing rotating chevron; TraceCollapsibleTrigger supplies it automatically. Leading tool icons and default icon geometry remain unchanged. The equally scoped reduced-motion rule prevents the variant selector from winning over the motion preference.
  • The illustrative agent demo now copies real filename/response text. The sample download has no actual fixture, so its native button is disabled, as are the disconnected retry/feedback controls. Each has its own persistent explanation. These are availability corrections, not a backend integration. Explanations live outside Message so the assistant avatar remains aligned with its actual footer.

No exports were retired. Replace cross-component spacing/shimmer repainting with these owned recipes, not utility-shaped override props. Four component MDX pages were updated; Trace docs remain under the parent’s independent TraceLink ownership.

Table appearance is owned by real table concepts

  • Use Table variant="striped", not index-dependent row background classes. It owns alternating rendered body rows and the matching opaque pinned-column wash, without overriding existing hover, expanded or selected feedback.
  • TableRow pinned="top" | "bottom" represents rows the caller already orders into those sections. This adds the pinned-row rest treatment; it does not reorder data or imply sticky rows. It is separate from existing logical-edge column pinning.
  • Use TableCell variant="detail" with the actual visible-column colSpan and existing density for a full-width expanded detail cell. The detail cell owns its stable recessed ground. Defaults remain unchanged.
  • Compact severity labels are explicit Info/Warn/Error text, not lower-case data painted with CSS capitalization.

The local Dropzone table example now explicitly states that uploads are simulated and files stay in the browser. It exposes each actual failure reason with an inline live message and associated retry description; retry only clears its own error. Removing a selected file also removes it from the picker summary, while keeping independent rows. Native Download saves the original file contents.

The example uses the existing small Table typography, standard16px cell grid and destructive-ghost/icon-sm retry recipe. Its two bespoke inline-edge cell paddings are intentionally standardized;32px button targets remain and owned icons use their standard16px size. This is not exact pixel parity or a new arbitrary spacing/color API. No task-menu backend behavior or unrelated Card/Carousel contract is changed.

  • Set Carousel gap="sm" | "md" instead of repainting CarouselContent negative margins and CarouselItem padding. The owner coordinates both sides on the active scroll axis; the unchanged default is md (16px), while sm is 4px.
  • Numeric carousel examples use actual owned Card recipes. Three horizontal examples now reserve their existing arrow gutters inside the container rather than overflowing narrow galleries. The responsive example uses Card padding="none" for edge-to-edge square numeric frames; the vertical example deliberately uses the standard small Card inset. These are intentional specimen geometry changes, not universal pixel parity.
  • The interactive Card example remains a native link to https://redpanda.com/docs, with keyboard focus on that link and the standard outlined Card recipe. Its bespoke hover shadow/transition is removed.
  • Task row menus now perform real Copy task ID and expose clipboard failures. Edit, Make a copy, Favorite, Labels, and Delete are visibly disabled because the example has no backend for them. The nonfunctional Delete shortcut is no longer advertised. Row menu and sortable-column triggers use the standard ghost Button open-state treatment.

No Carousel or Card prop was retired; gap is additive. Backend operations were not invented, and existing task data, sorting, visibility, row selection, pagination, Embla API callbacks and ownership remain unchanged.

Overlay and consumer recipe ownership

DialogContent now owns close clearance for its direct headerless DialogBody. When the built-in close is shown and no DialogHeader exists, omit manual right padding overrides on the Body; its content reserves48px. Headered dialogs and showCloseButton={false} retain their existing16px body padding. The headerless example therefore gains16px usable width versus its old double-padded layout.

Use an adjacent CopyButton for a second independent credential action rather than adding another InputEnd beside Input’s password toggle. The read-only password example deliberately keeps reveal disabled and copies the example value through the existing accessible copy lifecycle.

Consumer examples now use standard owned CommandItem padding, DrawerFooter padding, PopoverHeader spacing and Tooltip typography. Static swatch actions use semantic figures and owned ghost icon actions; their accessible copy names end in “static swatch”. Side labels use actual literal casing. No new spacing props, public component removals or general Tooltip announcement changes in this slice.

Applications: Compiler-owned memoization

Lookout, Docs Changelog/InstallPicker and Playground ComponentIndex no longer use manual useMemo/useCallback (26 calls removed). No registry distribution/API/dependency change is introduced by this application-only slice. Native navigation, filtering, persisted choices and explicit request actions retain their contracts.

Lookout’s initial audit is now explicitly mount-owned: a local timer-fired Effect Event starts the initial request, with the pending timer canceled during StrictMode cleanup/unmount. Ordinary event handlers retain the actual explicit all-project/project rescan function. The Effect Event is never passed to children or called by UI handlers. This prevents initial fetching from depending on an incidental callback cache identity; don’t replace it with [load], function-ref storage or mirrored state.

Docs external-store subscription/read callbacks remain genuinely stable at module scope. Playground’s invariant alphabetic discovery list is module-derived. Reactive filters/groups/counts and ordinary UI callback optimization belong to React Compiler; they are not mirrored into state or cached manually.

Native ref cleanup and uncompiled Registry consumers

Input and MultiSelect popup refs now honor React 19 cleanup-returning callback refs on replacement and unmount. If a callback returns cleanup, that cleanup runs according to React’s protocol; do not also rely on a second null callback for the same cleanup. Object refs and actual native input/Popup render composition remain supported. No additional DOM wrappers or public props were introduced.

PopoverAnchor retains native anchor state and caller refs without a cached callback wrapper. MultiSelect preserves controlled/uncontrolled selection, metadata, cancellation, native keyboard/touch focus and portal lifetimes.

Manual memoization is removed from these owners and useControllableState. Do not use the custom dispatcher identity as an Effect scheduling or mount signal; use actual reactive state and event ownership. Controlled changes remain proposals: a parent ignoring a callback does not change the displayed value. React Compiler is an optimization, not a correctness requirement for copied Registry consumers; the primary Registry tests remain uncompiled.

Compiler-owned optimization and real lifecycle inputs

Manual useMemo, useCallback, and React.memo are no longer supported repository patterns. react-compiler:check rejects direct and aliased imports, runtime namespace access, capability re-exports, and captured dynamic/CommonJS React namespaces. Lexically unrelated bindings and genuine type-only references remain valid. Astro programs are parsed with the installed native parser; use explicit named React APIs there rather than passing a namespace into template expressions. Discarded side-effect module preloads do not expose a cache API and remain valid.

Do not recreate a manual cache using state, refs, a function cell, or a WeakMap. The removed useMemoizedArray helper has no compatibility alias: use the actual array and stable domain IDs. An object’s or command’s incidental reference identity is not a request, subscription, reset, or mount signal. Depend on real state/data inputs and own subscriptions and cleanup explicitly. React documents memoization as an optimization, not a semantic guarantee. Copied registry components remain behaviorally valid without a Compiler transform.

CodeTabs highlighting now reacts to its actual codes/items and language/theme inputs, not a cached normalization array. Selection and unrelated layout renders do not restart highlighting. Existing cancellation still prevents an older source result from replacing a newer one.

Authored declarations are checked, too

Both mandatory native compilers now force skipLibCheck=false and skipDefaultLibCheck=false; project configuration drift cannot re-enable either. This is necessary because TypeScript’s skipLibCheck applies to declaration files, not merely dependencies. Invalid authored .d.ts declarations, including declarations marked as default libraries, must fail the same gate as implementation source. Dependency-origin diagnostics are counted and reported separately; they cannot hide an authored diagnostic or an unrecognized compiler failure.

The playground no longer redeclares Rsbuild’s ImportMetaEnv.MODE with a conflicting union or modifier. The installed bundler owns that declaration. The validated runtime schema accepts Rsbuild’s none mode and the test runner’s test mode, and strictImportMetaEnv removes the permissive fallback for undeclared environment keys.

Standard control recipes and Alert actions

  • Use existing MiniCalendar spacing and surface recipes in the custom-navigation example; its bespoke card ground, shadow, 16px inset and 8px day gap are removed. Date selection and native navigation remain unchanged.
  • Field checkbox examples use the standard FieldGroup gap. Hard disks now really starts checked: the old defaultChecked was incorrectly placed on its label.
  • Notification examples use standard Card borders and whitespace rather than a selected-border override or consumer-added dividers. Skeleton cards use the owned variant="card" radius. These are deliberate appearance standardizations.
  • PaginationCard uses Card size="sm" and unmodified PaginationContent. Its number destinations are native PaginationLinks, not anchors rendered with button semantics; Space does not navigate and Enter follows the destination.
  • Chart filtering uses existing CardHeader/CardFooter separator recipes and the Card’s own content inset. Its wider plot area is deliberate, not exact pixel parity.
  • Links with a direct SVG own their inline alignment and 4px icon gap. Plain prose links retain normal inline flow. The MotionHighlight specimen uses its real row highlight recipe, without painting padding or a border onto the decorative indicator.
  • Ghost Button families own persistent aria-pressed and popup-open feedback using their respective token ramps. Disabled controls do not receive those active paints. CodeBlock wrapping uses that contract; CodeBlockActions visibility="always" replaces an opacity override for persistent floating actions.
  • Put independent Alert controls in AlertAction. It now occupies its own native grid track spanning the title and description, rather than absolute positioning. Remove hand-added right padding and absolute close positioning. The dismissible example preserves at least 8px text/action clearance at narrow and wide widths.

Automatic DataTable selection is native anatomy, not a fabricated data column

DataTable selectable still renders the familiar page and row checkboxes, with the same controlled selection, eligibility, row-activation guards and bulk-action APIs. It now passes the caller’s data-column array unchanged to TanStack and renders one dedicated leading native header/cell outside that schema. Grouped headers use a single selection header spanning the native header rows. Empty, loading and expanded cells span visible leaf data columns plus this selection cell.

Breaking metadata change: automatic selection no longer injects or reserves a TanStack column ID named select. getAllColumns(), header groups, column enumeration, sizing, visibility, order and pinning metadata describe caller data columns only. Remove synthetic getColumn('select'), columnOrder/columnPinning/columnSizing references and calculations assuming the automatic UI control is a vendor data column. A caller’s genuine data-column ID select is now supported without collision.

Use selectable to show/hide automatic controls and existing row.toggleSelected(), table.toggleAllPageRowsSelected(), row-selection state/options and eligibility predicates to control behavior. Automatic controls stay leading and unpinned; actual data-column logical start/end pinning and order remain vendor-owned. This is not a new sticky-pinning feature. Consumers needing a custom movable/pinned selection column can keep an explicit stable column schema and existing controller with DataTableView, which continues to render exactly its caller-owned columns and never adds automatic controls.

The prior array-normalization useMemo is removed rather than renamed into another cache. Caller Column instances remain stable on unrelated parent renders; selection updates still do not rerender unrelated data cells. Two actual flat-table demos are pixel/geometry unchanged across light/dark and desktop/narrow viewports. External-controller/manual selection examples are unchanged. No deprecated compatibility alias or new public styling API is added.

Form and filter commands use real lifecycle inputs

useProtoForm returns an immutable current-render snapshot rather than mutating RHF’s controller object. Its real control, registration, setters and form state remain RHF-owned. Helpers and the returned wrapper no longer promise stable reference identity. Call createMessage, setOneofValue and server-error commands from the actual user, submit or request operation. Read form.formState.errors during render for reactive validation, and use the current render’s getNestedErrors reader. Do not use helper or whole-wrapper identity as an Effect or subscription signal.

useTokenPagination commands and useDataTableFilter().actions are commands, not stable Effect dependencies. Depend on actual pagination/filter values and invoke commands at fetch completion or user action. Previously captured pagination commands still apply in order to the current reducer state.

AutoForm derives schema, field, default and context values without manual React caches. Schema providers must keep render-time parsing and default generation pure; exact invocation counts are not an API contract. Equivalent controlled values retain the native form, draft and dirty state. Actual schema changes retain the existing intentional reset boundary.

Text-filter edits keep their 200-millisecond deadline across parent renders and use the latest committed action handler. Accepted edits do not trigger recurring updates; unmount cancels pending work. Both text editors have the accessible name Filter <column display name>. A parent refusing a controlled change still determines the applied value and description.

Reinstall auto-form, use-proto-form, use-token-pagination, use-data-table-filter, data-table-filter and their installed dependents. The filter now uses useEffectEvent: retain its generated React and React DOM 19.2-or-newer dependency floor. No new entrypoint or retired file is introduced by this slice.

Opening a text editor without editing no longer emits an unchanged filter update. Option search matches human labels while preserving stored IDs, and loading options may be absent or explicitly undefined at runtime.

File-upload preview ownership

useFileUpload owns object URLs only for its mounted resource lifetime. Removal, clearing and effect cleanup revoke them; StrictMode and Activity reconnection restore retained files with fresh previews. Do not keep a hook-owned preview in a parent after unmount. Retain the File and let the longer-lived owner create and revoke its own URL. Caller-supplied initialFiles URLs remain caller-owned.

Generated file IDs are opaque, stable selection identities, not UUID-format data. They use Web Crypto random values without requiring HTTPS-only randomUUID, so ordinary file selection continues to work on plain-HTTP deployments.

One React Doctor policy owner

The repository-root doctor.config.ts is the only Doctor configuration. Nested copies, alternative root formats, the legacy react-doctor.config.json, and package.json#reactDoctor fail the source gate, even if they would make Doctor report no findings. Malformed or unreadable manifests also fail closed. Repair diagnostics in their actual owner rather than adding a configuration file that hides them.

Last updated on

Was this page helpful?