Review release notes for Pluma components, icons, tokens, and the MCP package.
Changelogs for components, icons, mcp, and tokens. These, and other releases, can also be found on the Pluma repository
AspectRatio layout component for constraining content to a fixed width-to-height ratio.SplitView layout component (React + Ember) for building resizable, nestable config/preview split layouts with SplitViewPane and SplitViewResizer. Supports flexible and fixed panes, min/max sizes, hide/redistribute, snapping, resize callbacks, and full keyboard accessibility. Panes take an optional id for stable identity across conditional mounts, so each pane-set configuration keeps its own layout. Includes a useStoredSplitView hook (React + Ember) that persists a split's layout to storage (localStorage by default), with per-configuration layouts for conditional panels.useDrawers, useDrawer), mirroring the Modal manager. The Drawer component now accepts a state prop for use with the manager.withImmediateCallbacks in the modal manager's closeAll/closeAllOfType, matching close. Previously waitForClose promises for modals closed in bulk stayed pending until the close animation finished even when withImmediateCallbacks was true.markerStatusIndicatorColor to TimelineItem, passed through to the marker coin's status indicator.unsafe_isLegacy (Button, DatePicker, Link, Select, TextArea, TextField, ToggleButton) and the unsafe_useLegacy* PlumaProvider flags. Behavior is unchanged in this release — the flags still apply their overrides — and they will be removed in the next major version.onBeforeToggle and controlled isOpen to Accordion. onBeforeToggle({ isOpen }, event) runs before a toggle and can cancel it by returning (or resolving to) false; it may be async to defer the toggle. isOpen makes the accordion controlled, and onToggle now receives the triggering event as a second argument.6b96b73: Fix a Glimmer render crash (Cannot destructure property 'manager' of … undefined) when a Button, PlainButton, or ToggleButton is rendered with a component @as (a non-href @as={{SomeComponent}}).
The aria-disabled work (2.181) started currying the component @as to inject isDisabled — (component this.as isDisabled=…). When that curried component is invoked by the underlying Box (<Element ...attributes>) with the button's forwarded {{on "click"}} interaction modifiers, Glimmer can schedule a modifier/component instance whose manager is undefined, which throws during the render-transaction commit and takes down any page rendering such a button.
The non-href @as component is now rendered directly again (as it was before 2.181). Disabled state is still conveyed to it via the forwarded aria-disabled attribute, so no behavior is lost. The href branch (which curries PlumaPlainLink) is unchanged.
28e3a06: Add a size prop to Avatar (matching Coin's sm | md | lg | xl tiers, defaulting to md) and expose the initials typography via text-prefixed props: text, textSize, textWeight, textFamily, and textFontStyle. The initials text scales with size to stay balanced inside the coin (overridable via textSize/text).
Note: Avatar's size now derives entirely from the underlying Coin (default md, 32px). Previously the avatar was pinned to a fixed 28px, so avatars with no explicit size now render at 32px.
ticks, snapToTicks, snapToleranceFraction, and onTickClick props to Slider. Ticks render as marks along the track (with optional clickable labels below), and soft snap applies to the value on release only — continuous drag and keyboard nav are unaffected.bfbc150: Panel padding can now be customized per side. In addition to the existing panelPadding one-var override, Panel exposes panelPaddingBlockStart, panelPaddingBlockEnd, panelPaddingInlineStart, and panelPaddingInlineEnd CSS vars. Each side falls back to panelPadding, which falls back to the default, so panelPadding keeps working as a single-value override.
Set these vars on a panel (or a container above it) to customize the whole panel's padding, or on an individual PanelSection to override just that section's padding — the section's negative inset margins keep cancelling the panel's padding, so overriding a section no longer needs competing padding declarations and isn't sensitive to the order component stylesheets are imported in.
A Table inside a Panel now resolves its edge-cell padding from the matching per-side panel padding rather than a single value; for panels with uniform padding this is unchanged.
FileInput component (React and Ember). It supports a single-button mode and a drag-and-drop dropzone mode (dropToAdd), with loading and selected-image states. The dropzone is built on native HTML drag-and-drop and file input APIs.4106b80: Buttons now render aria-disabled="true" instead of the native disabled attribute when disabled (via isDisabled, isLoading, or a disabling Button context). This applies to Button, PlainButton, CloseButton, and ToggleButton.
A natively disabled element is removed from the accessibility tree and the tab order, so screen readers skip it and it can't be focused — which breaks tooltips and other affordances that explain why a button is disabled. Using aria-disabled keeps disabled buttons focusable and announced (as disabled), while click and keyboard activation are still blocked.
Behavior change to be aware of:
disabled attribute, and no longer set tabindex="-1" — they remain in the natural tab order. The disabled styling is unchanged.button[disabled] / :disabled in CSS, or asserting the disabled attribute in tests, should switch to [aria-disabled='true'].a789ffa: Add shouldShowOpenButton prop to Combobox for hiding the trailing dropdown-toggle button. Also fix onClear reopening the dropdown when shouldOpenOnFocus is enabled.
2bf386b: Radio, Checkbox, and Toggle now render the description prop. Previously the prop was part of their public API (inherited from the shared form-control type) but was never displayed. The description renders below the label, aligned with it, using the standard form description style.
90527b8: Add the Timeline component (Timeline / TimelineItem, and PlumaTimeline / PlumaTimelineItem in Ember) for rendering ordered sequences of events with markers and a connecting rail.
Each TimelineItem supports a markerIcon, markerIconSrc, or markerContent (text, any node, or an empty string for a plain coin); a title, description, and meta (leading or trailing via metaPosition); per-row color, trackColor (rail color independent of the marker), markerSize, shape, lineVariant, and isCurrent. Rows with no marker render full-width with the rail passing through them, for hosting a Banner or other context. Color and line variant carry across the seam to the next row, and unsafe_color / unsafe_colorAbove / unsafe_trackColor allow one-off custom rail colors. Includes Timeline and TimelineItem test helpers.
Europe/Kiev/Europe/Kyiv, America/Godthab/America/Nuuk, Asia/Rangoon/Asia/Yangon, and Asia/Saigon/Asia/Ho_Chi_Minh.unsafe_leftSectionComponent to Select, matching the leading-section prop already on TextField, Search, and NumberField. It renders custom content on the left side of the select trigger — before the selected value and after any icon — for cases the icon prop can't express, such as always-included tokens or a custom badge.position: initial on the Tabs tab element so its pseudo elements are positioned relative to the wrapper rather than the tab itself.elementBefore and elementAfter slots to NavItem for rendering custom content (e.g. an icon) in the leading and trailing parts of the item. In React these are node props; in Ember they can be passed as a component or via the :elementBefore / :elementAfter named blocks. Both compose with the built-in icon/arrow rather than replacing them: elementBefore renders after the icon, and elementAfter renders before the expand arrow when withArrow is set.e9ca77f: Avatar: added a withGravatar prop to control the email-based Gravatar lookup, and changed the default behavior so Gravatar is now off by default.
Previously, passing an email without a src always resolved a Gravatar image. Now the avatar derives initials/label from email but does not fetch a Gravatar unless you opt in with withGravatar (withGravatar={false} remains the default). This avoids surfacing third-party images for emails where that isn't appropriate (e.g. your customers' own contacts).
If you relied on email automatically showing a Gravatar, add withGravatar to those avatars:
Search (Ember): the className prop is now forwarded to the inner TextField, so it correctly applies to the component's outermost element. Previously it was dropped, matching the existing React behavior.PageHeader updates:
PageHeaderTitleAction slot. Renders inline to the right of PageHeaderTitle (after PageHeaderLabel if present) and is meant for inline action affordances that operate on the page subject (e.g. an edit button). Generic slot — pass any content, typically a single icon-only Button.PageHeaderLabel now renders to the right of PageHeaderTitle (previously to the left), matching the latest design spec. Existing consumers automatically pick up the new layout — no API change.PageBreadcrumbs. No layout change — it lives as a sibling of PageHeader inside Page, not inside the header grid, so existing pages render exactly as before. New code should render the standalone Breadcrumbs component above the page header instead. PageBreadcrumbs will be removed in the next major release.e42296b: Ember: stop overlays from throwing when their floating position is recomputed while the host is going away
Reading an overlay's floating position reads floatingUi.options, which calls
the host component's options function — and therefore the host's args and
getters. Those getters can touch state that has already gone away (e.g. a
destroyed Ember Data store, or an unloaded record), in which case they throw,
the error escapes the run loop, and it fails otherwise-unrelated work in the
host app (this surfaced widely after consumers upgraded @glimmer/component
to v2, which changed teardown ordering).
This happens via two routes:
FloatingUi#isTearingDown now also checks the owner — which, unlike the
resource or host component, is reliably flagged as destroying during that
flush.PositionFloating#position now bails with the last computed data if reading
the host options throws, instead of letting the error escape.Normal overlay open/close/positioning behaviour is unchanged.
4247bb4: Ember: stop overlays from re-evaluating their floating position during owner teardown
When a host application/engine instance is destroyed, it flags itself as
destroying and then flushes a final render queue before tearing down individual
components. That flush re-invokes the reference/floating modifiers, which read
PositionFloating#position — and reading position reads floatingUi.options,
i.e. the host component's args and getters. Those getters may already touch
torn-down state (e.g. a destroyed Ember Data store), so they throw, which fails
test teardown and cascades into unrelated tests.
The existing guard only checked the floating resource and host component, but
neither is flagged as destroying at flush time. FloatingUi#isTearingDown now
also checks the owner (application/engine instance), which is reliably flagged
as destroying during that flush, and PositionFloating#position returns the
last computed data instead of reading options while tearing down.
transitionend does not fire.icon prop to Select so React and Ember consumers can render a decorative leading icon in the trigger.ColorSchemePreference and ResolvedColorScheme from the React and Ember Pluma provider entrypoints.onRowExpansionChange callback to DataTable.xs size for Toggle.description rendering and child title/description composition components, with React and Ember codemods for migrating existing Banner children/yields into descriptions.2415d3b: Fix polymorphic type checking for as prop — JSX.IntrinsicElements → React.JSX.IntrinsicElements
The global JSX namespace doesn't exist in TS 5.x with jsx: "react-jsx". The emitted .d.ts referenced bare JSX.IntrinsicElements, which resolved to nothing in consumers, causing the conditional type to fall through and silently accept any prop when as was used (e.g. <Box as="div" garbage="hello" /> compiled without error).
$extensions.mode resolve to the referenced token's value for the same mode. The tokens build now emits Cobalt UI's internal {token#mode} reference format for generated mode aliases, matching the format Cobalt's Figma collection parser uses for mode-specific variable references, and the components theme build strips that suffix when converting original aliases back to CSS variables.DataTable showing a stray bottom border on the last visible row when virtualization is enabled and there are no more rows to scroll to. The virtualizer's end placeholder row now signals its empty state via a data-pluma-table-end-placeholder attribute, which TableTd uses to suppress the preceding row's bottom border.SegmentedControl icon shifting horizontally when the selected option changes. The icon and label now share the same fixed position across both the active (bold) and inactive (regular) states.label prop to DataTableHeaderColumnsMenu. When provided, the trigger button displays visible text alongside the icon instead of rendering as icon-only.date prop to accept null | undefined (renders — fallback). Treat 0 as missing. Auto-detect epoch seconds vs milliseconds for numeric inputs (values below 10_000_000_000 are converted to ms).4ade8ba: Button: add unsafe_withMigratedButtonVariants flag (also configurable
via PlumaProvider's componentConfig.PlumaButton). When set, button
variants render as if the upcoming major release that removes the
secondary variant has already shipped:
secondary renders with current tertiary stylestertiary renders with current subtle stylesIntended to be enabled together with running the
codemods/button-variant-rename codemod. With the flag on, the codemod
can be applied safely before the breaking-change major release — your
app looks the same before and after the rename. When the major
eventually ships, drop the flag.
This flag supersedes unsafe_withSoftDeprecatedSecondaryVariant: when
both are set (or just the new one), the secondary → tertiary mapping
is still applied.
4ade8ba: Add codemods (codemods/button-variant-rename/) that consumers can run
ahead of the upcoming Button variant renames: tertiary → secondary and
subtle → tertiary. Separate transforms ship for React (.tsx/.jsx)
and Ember (.gts/.gjs/.hbs); see the codemod README for instructions
on running it directly from this repo without publishing an npm package.
Pair this with the new unsafe_withMigratedButtonVariants flag (see
separate changeset) so the renamed source renders identically to the
pre-codemod look. When the major release that drops secondary ships,
drop the flag — your app keeps rendering the same.
c862593: Add Coin component — a shaped container (circle or rounded square) of fixed size that frames an Icon, Image, or short text content, with optional status indicator and label-token colorVariant. Coin propagates size, shape, imageFit, and color to descendants via context, so child Icon and Image components auto-size and Images can fill the coin edge-to-edge when imageFit="cover".
Deprecates LabelIcon (use <Coin><Icon /></Coin> instead) and Avatar's icon/iconSize props (compose with Coin for non-user-avatar use cases). Both will be removed in a future major version.
icon prop still suppresses the initials fallback as before.icon and iconSize props to AvatarconfirmationPhrase option to ConfirmationModal. When set, a text field appears requiring the user to type the exact phrase before the confirm button becomes enabled.SegmentedControl indicator briefly animating in from the default position on initial render. The first position update now uses an inline transition-duration: 0ms override instead of relying on the parent's :has()-driven CSS variable, whose cascade can fail to reach the indicator before style.transform is mutated.react/utils/* subpaths. The types field pointed to dist-react/react/public-utils/ but files are at dist-react/public-utils/.ed0c209: Adds Banner design an a11y docs
418c70f: Add early return with dev warning when icon name is undefined
76ab318: fix: replace raw palette refs with semantic tokens for dark mode compatibility
Components were referencing palette primitives directly instead of semantic color tokens, which breaks on dark themes since palette values don't change between modes.
palette['grey-100'] → color['border-minimal']palette['grey-100'] → color['surface-subtle']palette['grey-100'] → color['surface-subtle']palette['grey-100'] → color['surface-subtle']dataTableSelectedRowBackgroundColor and dataTableNestedSelectedRowBackgroundColor component vars with current palette values as fallbacksicon-white color token, and fix up tokens in Button and Snackbarvalue is an empty string.text-wrap: pretty to heading components to improve text wrapping and avoid orphaned words on the last line.3eacb12: Drawer: Add lg, xl, and xxl sizes and subtitle prop
5b419db: Remove legacy pseudo-element fade styles from Scrollable
Removes the fadeLeft, fadeRight, fadeTop, and fadeBottom pseudo-element
classes from the Scrollable component. These used sticky ::before/::after
elements with white-to-transparent gradients which didn't work well on
non-white backgrounds.
The withEdgeIndicators prop (using mask-image) is now the only approach.
The Select multi-value component has been migrated to use maskFadeRight.
color prop passing and adjust sizingwithEdgeIndicators pseudo-element approach with CSS mask-image classes. This fixes edge fade indicators for horizontal flex layouts and simplifies the implementation. The fade size can be overridden via the scrollable.vars.fadeSize CSS variable.a99dbc4: Add onLoadMore and onSearch support to DataTableHeaderColumnsMenu
3ca22c5: Expose state prop to ModalPrimitive
secondary button variantstyle to be passed in as an arg to TextFieldradius-4 to radius-6.KeyboardShortcut component that displays keyboard shortcuts with platform-aware formatting, powered by @tanstack/react-hotkeys. Supports isInverted prop for dark variant.withArrow prop to Tooltip component. When set to false, the arrow indicator on the tooltip content is hidden. Defaults to true to preserve existing behavior.9631f8c: fix(DataTable): stabilize config objects to prevent infinite re-renders
Add use*Config hook wrappers (usePaginationConfig, useSortingConfig, useSearchConfig, useFiltersConfig, useEmptyStateConfig) alongside the existing get*Config functions. The hooks memoize the result using the actual prop dependencies, keeping the plain functions intact for ember usage.
The get*Config functions return new objects every render. When used directly as contextValue useMemo dependencies, every render produced a new context, forcing DataTableTable to re-render infinitely.
102419f: fix(DataTable): fix infinite re-render loop in DataTableTable
table.getFlatHeaders() returns a new array reference on every render. Using it directly as a useLayoutEffect dependency caused setColumnSizing() to fire every render, which triggered a TanStack Table state update, causing another render — resulting in an infinite loop. This was triggered when consumers passed unstable prop references (e.g. inline filters or bulkActions objects).
Fixed by deriving a stable string key from header IDs to use as the effect dependency instead of the array reference.
6d7d6d0: fix(TextArea): pass onKeyDown and rows props to inner TextAreaInput
The React TextArea component accepted onKeyDown and rows in its type definition but silently dropped them — they were never forwarded to the inner TextAreaInput. This caused onKeyDown handlers to never fire and rows (without minRows) to have no effect.
isClearable to Select componentJsonField input*px format, when specifying widths in the data tableisAlwaysShown option to filters9cb19c7: Ensure the unix timestamp input adheres to any minDate/maxDate validations in the Datepicker when using withTime
Adjust PlumaModal test helper dialog getter to use queryByRole instead of getByRole to prevent test timeouts when checking modal state
onConfirm and onCancel callbacks to the ConfirmationModal6042268: DatePicker component withTime prop
withTime prop: Enable time selection in DatePicker componentreference prop: Allows for DST labels to be calculated correctly based on a reference date (either before or after DST)withTime={true}, component returns ZonedDateTime instances instead of CalendarDatetime-zone.ts for managing time selection stateSELECTABLE_TIME_ZONES and getSelectableTimeZones() from date-picker utilswithTime cannot be used together with isRange (mutually exclusive as seen in Figma)isActive stateisOpen and close function to the PopoverPrimitiveisActive prop to Button componentlabel prop to the Divider componentPopoverTriggerButton componentedgeThreshold and overflow=hidden option to Scrollable componentwithEdgeIndicators option to the Scrollable componentAvatar from shrinking in flexbox layoutssize option to LabelIconv2 sizing for icons, moving on from fly sizingisDraggable option to popover and popover primitive to make them draggableminRows and maxRows to the <TextArea /> componentisActive prop to <Blanket /> componentdirection prop to the <Divider /> componentname through inputstype="button" to Tabs when rendered as button elementsdescription option to the Select component<DropdownMenuFooter /> componentisPulsing to the StatusIndicator componentText props in the ember Truncate componentStatusIndicator componentScrollable componentisExternalTruncated componentisLoading and autoLoading to banner actionnull as a value for the TextField icon proppromotion variant to EmptyStatewithPadding property to panel sectionFilters componentsize and isInline props to the Image componentCloseButton componentalign meta option for data table configurationisDisabled prop to Tag componentDescription component to PanelProgressCircle componentvariant support for banner actions and added isOutlined prop for bannersPlainButton componentsize option to option cardtooltip prop to the DropdownMenuItemshouldCloseOnClick to DropdownMenu<Image /> componentdescription option to DropdownMenuItem componentminDate, maxDate, isDateDisabled to DatePicker componentonClick to breadcrumb itemsisLoading prop to <Page::Body /> component<PopoverPrimitive::Item/> component for creating navigable items inside popoversYield internal utility for react0 to autoCloseTimeout in the snackbar should prevent it from closing automatically<Banner /> to cautionerror and success variants to the snackbarstatusIndicatorColor option to <Avatar />shouldSubmitOnEnter to the TextArea component<Form /> componentwithOverlay option to disable overlays for drawersshouldAnimate option to disable animation<Avatar /> componentshape prop to <LabelIcon /> component<Drawer /> component<PlumaModalPrimitive /> componentautofocus attribute to TextField componenttruncate property to text componentsisError and replaced it with isDanger for the <Button /> component<FormLayout /> component<SkeletonShape /> component<SkeletonText /> componentsize option to <Modal /> componentonClick property for link component<ProgressBar /> component<Divider /> component<Select /> component when mixing keyboard and mouse navigationpageHeaderTitleText class<Select /> component<Select /> component<Search /> componentisLoading prop to <PlumaPage /><PlumaPage/> component
<PlumaPageHeader /> to <PlumaPage::Header /><PlumaPageHeader::Title /> to <PlumaPage::Header::Title /><PlumaPageHeader::Description /> to <PlumaPage::Header::Description /><PlumaPageHeader::Actions /> to <PlumaPage::Header::Actions /><PlumaPageHeader::Breadcrumbs /> to <PlumaPage::Breadcrumbs />onPageSizeChange and pageSizes options to the Pagination component. Together these render a Select component to switch the size of the pages the user paginates through.52f49d1: Update tokens and components to match post-brand-refresh Figma state.
This change introduces a few breaking changes. It modifies existing styles and changes the available props or variants on some components.
index.js