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.
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.tsxPaths 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.
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:
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.
form.setOneofValue('preferredContact', { case: 'preferredPhone', value: '+15551234567' });getNestedErrors(path) returns unknown, not a caller-selected generic type. Narrow the dynamic result before reading it:
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:
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.
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.
Menu item and label recipes
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.
<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 native props
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,shapeclassName,styleas,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.
<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 replacementchildren. Use a containing element for layout and a normal Link for navigation. No unstyled/style escape was added. - Date actions own
aria-pressed; select throughMiniCalendar’svalue,defaultValue, andonValueChangeinstead of writing that attribute. - A day
onClicknow composes with selection instead of replacing it. The caller runs first;event.preventDefault()orevent.preventBaseUIHandler()cancels selection. Existing handlers that implemented selection themselves should instead observeonValueChange, or explicitly cancel if replacing selection is intentional.
<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.
<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.
<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.
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
classNameand singularstyleremain 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.classNameand.styleremain DayPicker native protocol props, needed by custom day wrappers forwarding the framework’s props; no general Buttonvariant/size/render/navigation inheritance is introduced.components.DayButtonstill supports real custom native day content. buttonVariantstill 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.
Theme gallery demo imports
The aggregate @redpanda/registry/demo/theme exports ThemeDemo, not individual section demos. Import an individual demo directly from its existing dedicated entrypoint instead:
// 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.
<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:
<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
placeholderfrom 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:
<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.
Navigation Menu recipes
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:
<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:
<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-groupreplaces JSX children with readonly{id, content: ReactElement}items. The static AvatarGroup fromcomponents/avataris unchanged. - BadgeGroup replaces JSX children with readonly items containing required
id,contentand plain-texttextValue. 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.
Menu-triggered confirmation examples
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 anatomy
- 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}withaction={{ label: 'Select model', onPress: selectModel }}.onPresshas 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
HTMLDivElementrefs.actionowns role, tab order, accessible name, disabled state and optional controlledpressedstate; 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.disabledblocks pointer/keyboard activation and removes the root from the tab sequence. Native div keyboard handlers remain available: prevent Enter inonKeyDown, Space inonKeyUp; native click-capture cancellation is honored.- Use
action.pressedonly 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.tssource 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"andInputGroupButton shape="pill"instead of local--radiusor 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 andclassName="border-t"on block-end addons withseparator. 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; exposearia-pressedfor actual favorite state. - Use semantic
<code>for filenames insideInputGroupText. 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.
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. ScrollableTabsListstill 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.Stepmarker children with requiredtitleand optionaldescription/panelReactNode props. Rendered content is not discovered by component name or identity. Fragments, text and multiple nodes are preserved. ExistingStepper.Title,Description,PanelandControlsstill support Base UIrenderand native refs. titleis the visible step-label slot, not the native HTML tooltip attribute. Put status chips intodescription; 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
tabtobuttonnamed by the visible title, and selected-state checks toaria-current="step". Remove customrole,tabIndex,aria-selectedandaria-controlsprops; 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
variantkeeps the finite shared Button tone recipes but rejectsunstyledandnull. The owner preserves the separate current-step halo and native keyboard-focus ring.classNamebelongs 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.
<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.
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.
Sidebar actions and navigation
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:
<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:
// 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.
Breadcrumb standard anatomy
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.
Sidebar application examples: truthful navigation and shared chrome
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 / Integrationtext 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.
Trace links and example catalogs
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.
Gallery sizing and CountDot stacking
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-fieldsregistry item now owns substantive username, Bio and notification presentations plus the canonical profile schema/metadata. The richlib/profile-form-examplecomposes 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 atcomponents/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
ToastDescriptionnow defaults to adiv, not ap, because its public React-node content legitimately includes block content such as previewpreor 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 asp[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
rendercomposition remain supported. Custom rendering remains responsible for an appropriate semantic container; apis 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 byuseChat, remains unchanged.MessageFooterowns 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 whetherrole="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;TraceCollapsibleTriggersupplies 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-columncolSpanand 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.
Carousel spacing and native demo actions
- Set
Carousel gap="sm" | "md"instead of repaintingCarouselContentnegative margins andCarouselItempadding. The owner coordinates both sides on the active scroll axis; the unchanged default ismd(16px), whilesmis 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
defaultCheckedwas 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
separatorrecipes 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
rowhighlight recipe, without painting padding or a border onto the decorative indicator. - Ghost Button families own persistent
aria-pressedand 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.