Changelog
Release history of @plaud/design, organized by published version (newest first). Within each version, changes are grouped under ✨ Feature and 🐛 Bug Fix sub-sections, and listed per component. Version numbers come from the package's changeset changelog. Changes that predate the first published release are grouped under Pre-release.
v0.9.0
✨ Feature
-
- (WEB-1277) feat(Select): add a ghost trigger variant (Details)
Selectgains avariantprop ('outline' | 'ghost'), defaulting to'outline'— existing usage is unchanged. Consumers opt into ghost via<Select variant="ghost" />; the internalSelectTriggersubcomponent consumes it but stays internal and is not exported from the package barrelghostis a borderless, content-width, right-aligned trigger with a trailing sort (up/down) arrow and a gray hover/open background, for inline / toolbar select scenariosui/select.tsxSelectTriggergains an optionaliconprop to override the default chevron (internal capability passthrough)SortIconneutral state (sortDirection === null) now renders withcurrentColorso it follows the parent text color (lets the ghost disabled state dim correctly); asc/desc active highlight is unchanged- (WEB-1277) fix(Select): fix WCAG AA contrast issues in the ghost trigger
- Hover/open state text now deepens from
Labels/TertiarytoLabels/Secondaryon theGrays/Gray-1background, raising contrast from 4.47:1 (fails 4.5:1) to 9.11:1 - Keyboard focus indicator switches from the low-contrast gray background alone (1.19:1 against white, fails the 3:1 non-text contrast requirement) to a visible
Labels/Primaryfocus ring (21:1) - (WEB-1277) fix(Select): fix ghost trigger width bug where it stretched to fill the full line
- The ghost base class only overrode width (
w-full→w-auto) but notdisplay; the underlyingui/select.tsxprimitive is a block-levelflexcontainer, sowidth: autoalone still filled the parent width in normal document flow (outside a flex/grid parent), causing the hover/focus gray background to span the entire line instead of hugging the content - Added
inline-flexto the ghost base class alongsidew-auto, so the trigger is now an inline-level flex box that truly shrink-to-fits its content in any layout context
-
- (WEB-1310) feat(Select)!: support a left-checkmark option item variant, and rename
MultiSelect.menuItemVarianttooptionItemVariant(Details) - Breaking:
MultiSelect.menuItemVariantis renamed tooptionItemVariant(same meaning, same values, same default'checkmark') — a dropdown row is aSelectOptionItem, not a "menu item", so the prop is renamed to match the domain vocabulary and to share one name acrossSelectandMultiSelect SelectContent(the combinatorial subcomponent) gains anoptionItemVariant?: 'checkmark' | 'checkmark-right'prop, defaulting to'checkmark-right'(zero regression), propagated via context to everySelectItemrendered inside itSelectgains a matchingoptionItemVariantprop, threaded through the declarative non-searchableoptionsbranch to theSelectContentit renders- Deliberately not placed on
SelectItemitself: all rows in one dropdown must share the same checkmark placement (no Figma variant mixes left/right within a single menu), so a per-item prop would let a caller silently produce an inconsistent menu by forgetting it on one row checkmarkmoves the selected-state indicator from the right to the left of the label, matching the Ghost trigger menu item Figma variant; item padding shifts from a right reservation to a left reservation accordingly- Scope: combinatorial
SelectContent/SelectItemand the declarative non-searchable branch only —searchablesingle Select already covers left/right checkmark viaSearchableOptionItem
- (WEB-1310) feat(Select)!: support a left-checkmark option item variant, and rename
🐛 Bug Fix
-
- (WEB-1295) fix(Calendar): scroll & focus the current month/year when the month/year dropdown opens (Details)
- The month/year dropdowns (
CalendarMonthCaption) render with RadixDropdownMenu, which focuses the first list item on open, so the year dropdown (current year ±50 = 101 items) landed on the earliest year (1976) instead of the currently selected year (e.g. 2026); the month dropdown had the same latent issue - Tag the current month/year item with
data-current="true"and add apd-calendar__scroll-menumarker class to both dropdown content containers - Add a
useEffectthat, on open (after onerequestAnimationFrame), focuses the[data-current]item withpreventScrolland centers it within its scroll container by adjustingscrollToponly (no page scroll) - Add 2 unit tests locking the
data-currentmarker contract for the current year and month
-
- (WEB-1286) fix(DropdownMenu): make portaled overlay lists scrollable inside a modal Dialog/Popover (Details)
useStopWheelPropagationuseduseEffectto readref.currentwith a stable dependency, so it ran once at mount and returned early because Radix'sContentmounts its DOM lazily (Portal/Presence) — the wheel listener was never attached and never re-attached when the panel later opened- As a result, a
DropdownMenu(e.g. Calendar's month/year selector) portaled todocument.bodyinside a modal Dialog could not be scrolled: the Dialog'sreact-remove-scrollisolationpreventDefaults any wheel whose target is outside its shard allowlist, and the (never-attached)stopPropagationguard could not stop the event from reaching that document-level listener - Rewrite
useStopWheelPropagationto return a stable ref callback that binds/unbinds thewheelstopPropagationlistener exactly when the node mounts/unmounts, so it works with lazily-mounted portaled content - Apply the ref-callback form at all consumers:
DropdownMenu,ContextMenu,Menubar(Content + SubContent),Popover,Select - Add a unit test asserting the hook attaches on mount and detaches on
null
v0.8.0
✨ Feature
- Tree — suppress delayed color transition when switching light/dark theme (WEB-1207) (Details)
- Tree item's
transition-colors(for hover/selected background) also interpolated the whole CSS-variable flip on theme switch, so row background/border colors animated slowly during light↔dark toggles (the perceived "delayed color switch") - Add and export a shared
suppressThemeTransition(apply)util: it self-injects a one-off<style>(.pd-theme-switching * { transition: none !important }, no globals.css import needed), adds the class before runningapply, forces a synchronous style recalc so the variable flip commits with transitions disabled, then removes the class on the next animation frame — theme switching is instant while hover/selected transitions are unaffected DesignProvidernow wraps itsdata-theme/.darkflip withsuppressThemeTransition; consumers that toggle theme outsideDesignProvider(e.g. design-site's Docusaurus bridge, web4'sdarkMode.ts) can reuse the same util- Add unit tests for the util
- Tree item's
🐛 Bug Fix
- Dialog — use Grays/White background token so dark mode Modal is #292929 instead of near-black (WEB-1244) (Details)
- Select — stop the dropdown's top item being clipped when it auto-scrolls to the selected option (WEB-1194) (Details)
- Radix
SelectPrimitive.Viewportcarries an inlineoverflow: hidden autoandflex: 1, so it is the real scroll container (growing to the content'smax-h-[268px]). The dropdown's verticalpy-(--Spacing_8)used to sit on the viewport, i.e. inside the scroll region — when the list auto-scrolled to reveal a later selected option (e.g. display-language "中文(简体)", index 8) that top padding scrolled away and the first visible item ("Español") was clipped flush against the panel's top edge - Slim
SELECT_VIEWPORT_TOKEN_CLASSdown to just the semantic markerpd-select__viewport(drop the in-scrollpy-(--Spacing_8)and the redundantoverflow-y-autothat Radix's inline style already provides) - Move
py-(--Spacing_8)onto theSelectContentframe instead; Radix forcesdisplay:flex; flex-direction:columnon the popper content, so the padding lands on the non-scrolling flex box and stays fixed. Left the sharedSELECT_CONTENT_TOKEN_CLASSuntouched to avoid affecting the searchable Popover / MultiSelect that reuse it - Bump
SELECT_DROPDOWN_MAX_HEIGHTfrommax-h-[268px]tomax-h-[270px]: the content has a 1px border and Tailwind's preflight makesbox-sizing: border-box, so max-height includes the border. At 268px the viewport was only 268 − 2(border) − 16(padding) = 250px (< 7 × 36 = 252px), leaving a ~2px clip on scroll; counting the 2px border gives a 252px viewport = exactly 7 × 36px so scrolling snaps to item boundaries - Add regression tests asserting the padding lives on the content frame (listbox) not the viewport, and that the listbox is
max-h-[270px]
- Radix
- Toast — restore variant background by updating to renamed Accent tokens (WEB-1208) (Details)
v0.7.0
✨ Feature
- MultiSelect — expose menuItemVariant='checkmark' and default to it (left-aligned checkmark) (WEB-1201) (Details)
- Widen
MultiSelect.menuItemVariantfrom'checkmark-right' | 'checkbox'to'checkmark' | 'checkmark-right' | 'checkbox', unlocking the left-aligned checkmark thatSearchableOptionItemalready supports - Change the default
menuItemVariantfrom'checkmark-right'to'checkmark'(left-aligned check), matching the Figma default; callers that relied on the implicit right-aligned checkmark must now passmenuItemVariant="checkmark-right"explicitly - No rendering change:
'checkmark'renders the check on the left, and automatically moves to the right when the item has a leadingicon(existing Figma indicator-right rule) - Add unit tests and design-site doc cases (en + zh) for the left checkmark variant
- Widen
🐛 Bug Fix
- Button — prevent hover/active visual effects on disabled buttons (WEB-1203) (Details)
- Add
disabled:hover:anddisabled:active:class guards to all button variants (primary, secondary, tertiary, quaternary, destructive, destructive-outline, link-color, link-gray) - Ensures disabled buttons maintain consistent styling when clicked or hovered, preventing unintended border/background changes
- Overrides any hover/active pseudo-class effects that could slip through when
pointer-events-autois enabled on disabled state
- Add
- Dialog — align DialogTitle font-weight to Figma Modal (font-normal → font-semibold) (WEB-1193) (Details)
DialogTitleinui/dialogusedfont-normal(400); the Figma DS Modal title (node21231:6298, Size=Default) is H3/Emphasized: 18px / 24px / weight 600 (token--Font-Weight-Emphasized= Semibold)- Changed
font-normaltofont-semibold(600); font-size (18px) and line-height (--Line-Height-H3, 24px) unchanged - All dialogs using the structured
titleprop now render the title at weight 600, converging to the design
- DropdownMenu — dim disabled item icon/arrow to Labels/Disabled and show not-allowed cursor (WEB-1190) (Details)
- Add shared
DROPDOWN_ICON_DISABLED_COLOR_CLASSusing an ancestor[data-disabled]selector so the icon container followsLabels/Disabledwhen the item is disabled (the icon color was declared explicitly and did not inherit the item's disabled text color) - Apply it to
DropdownMenuItem's leading icon (viagetDropdownMenuItemIconColorClass),DropdownMenuSubTrigger's leading icon, and the trailing cascade arrow (DROPDOWN_SUB_ARROW_COLOR_CLASS) - Add
data-disabled:pointer-events-auto+data-disabled:cursor-not-allowedto the shared item base, overriding the ui layer'spointer-events-noneso disabled items show the not-allowed cursor (mirrors Button)
- Add shared
v0.6.8
🐛 Bug Fix
-
- (WEB-1183) fix(DropdownMenu): cascading submenu indicator arrow color changed to Grays/Gray-4 (design spec labels-gray4) (Details)
- the arrow icon (icon_list_chevron_right) was already switched in WEB-1129; this only changes its color, which was Labels/Primary before
- the arrow color is decoupled from the leading-icon color via a new DROPDOWN_SUB_ARROW_COLOR_CLASS; leading icons stay Labels/Primary
v0.6.7
🐛 Bug Fix
-
- (WEB-1181) feat(Toast): support action.actionProps to forward props to the underlying action button (Details)
- actionProps is spread after the default variant/size (so callers can override them) but before the composed onClick, so the click + auto-close behavior can never be overridden
- excludes children (carried by label) and onClick (composed by Toast); data-* index signature follows the Dialog okButtonProps convention
v0.6.6
🐛 Bug Fix
-
- (WEB-1176) feat(AiButton): add Loading and Disabled (reserved) states (Details)
- loading swaps the sparkle icon for a 16px Spinner, grays the label, keeps the white bg + gradient stroke, and blocks interaction via native disabled (aria-busy + not-allowed cursor)
- disabled (reserved) fills the button gray (
--Grays-Gray-2), drops the gradient stroke, and grays the sparkle icon
v0.6.5
🐛 Bug Fix
- Select — move the menu item selection indicator to the right when the item has a leading icon (WEB-1174) (Details)
- Generalized the "icon → indicator on the right" layout rule from the
checkboxvariant to also cover thecheckmarkvariant inSearchableOptionItem, matching the Figma spec (Show Icon=True→[icon][text][indicator])
- Generalized the "icon → indicator on the right" layout rule from the
v0.6.4
🐛 Bug Fix
v0.6.3
🐛 Bug Fix
- DropdownMenu — polish cascading submenu styling (WEB-1129) (Details)
- Add required
listChevronRightkey toIconRegistry(BREAKING: icon registries must now provideicon_list_chevron_right) - Cascading arrow uses
ListChevronRightIcon, 20px container, Labels/Primary SubContentdefaults tosideOffset={-8}/alignOffset={-8}(overlaps parent menu + aligns first item to parent option)SubTriggeropen-state background overridden to Grays/Gray-1 (fixes dark background on open)- Leading icon container defaults to Labels/Primary (business-injected node can override)
- Add required
v0.6.2
🐛 Bug Fix
- Dialog — overlay click no longer closes the dialog by default; added
closeOnOverlayClick?: booleantoDialogProps/DialogOpenOptions(defaultfalse); Escape key and close button behavior unchanged (WEB-1125) - Tooltip — hover delay defaults to 500ms;
delayDurationdefault changed from0to500; hide-on-leave behavior unchanged; removedTree's local 300ms workaround (WEB-1124)
v0.6.1
🐛 Bug Fix
- Button — update font-weight to Medium per Figma spec;
Primary/Secondary/Tertiary/Quaternary/Destructive/Destructive Outlinetext weight changed from Regular(400) to Emphasized/Medium(500);Link Color/Link Grayremain Regular(400) (WEB-1119)
v0.6.0
✨ Feature
- Select / MultiSelect — add icon support, menuItemVariant and showSearchIcon toggle;
SearchableOptionItemgains avariantprop ('checkmark-right' | 'checkmark' | 'checkbox') and an optional leadingicon;MultiSelectgainsmenuItemVariantto switch dropdown item visual style, with new chip hover/active/focus-visible states;SelectgainsshowSearchIcon(defaulttrue) and itscheckboxvariant now renders via the sharedCheckboxcomponent;Inputborder gains a hover state; Select item default text color changed fromLabels-SecondarytoLabels-Primary(WEB-1075, WEB-1076, WEB-1118)
🐛 Bug Fix
- DropdownMenu — align group label style with Select;
font-semibold→font-normal,text-(--Labels-Secondary)→text-(--Labels-Tertiary),py-(--Spacing_8)→pt-(--Spacing_8)(removes 6px bottom padding leak from shadcn defaultpy-1.5) (WEB-1116) - Select — fix conditional rendering and add aria-hidden to decorative icons; replace
&&JSX conditional rendering with explicit ternaries inMultiSelect,SearchableOptionItemandSelect; addaria-hidden="true"to decorative icon spans (WEB-1118)
v0.5.0
✨ Feature
- ImageViewer — add drag-to-pan when zoom > 1; mouse drag pans the image when zoomed in; cursor switches to grab / grabbing; boundary-clamped so image never fully leaves the viewport; auto-resets on image switch, zoom-out to default, or viewer close (WEB-1062)
🐛 Bug Fix
- Toast — correct Message form alignment for top-center position; fix left-offset of
w-fitMessage toasts by usingtranslateX(calc(210px - 50%))instead ofleft: 50%which shrank available width (WEB-1105)
v0.4.6
🐛 Bug Fix
- ui — migrate CSS variable class syntax from bracket to parenthesis for Tailwind v4 compatibility; all
xxx-[--radix-yyy]patterns replaced withxxx-(--radix-yyy)acrosspopover,hover-card,tooltip,navigation-menu,dropdown-menu,context-menu,menubar,select; fixes silent failures in v4 consumers (WEB-1108) - ui / Popover / DropdownMenu / ContextMenu / Select — fix wheel scroll blocked inside Dialog by react-remove-scroll;
useStopWheelPropagationhook attaches a nativewheellistener withstopPropagationto portaled overlay content nodes (WEB-1109) - Dialog — content area uses
flex-1to fill space between header and footer; footer stays pinned at bottom when content overflows
v0.4.5
🐛 Bug Fix
v0.4.4
🐛 Bug Fix
- Breadcrumb — Ellipsis menu now passes through
itemRender; newplacementfield added to distinguish dropdown rendering from inline (WEB-1044). Collapsed items previously bypasseditemRenderand fell back to a hardcoded<a href>, causing full-page navigation. They now correctly callitemRenderwithplacement: 'dropdown'. - Calendar — Selected date text invisible in dark mode fixed (WEB-1041). Text color changed from
--Foregrounds-White(always white) to--Labels-Reverse(white in light, black in dark), which always contrasts against the--Labels-Primaryselected background.
v0.4.3
🐛 Bug Fix
- Dialog — DialogTitle now reserves right-side spacing (
--Spacing_40) when close button is visible. WhenshowCloseistrue(the default), long titles are no longer obscured by the absolutely-positioned close button.
v0.4.2
🐛 Bug Fix
- Tooltip / HoverCard — Focus-open tightened to
:focus-visibleonly (DES-134). Mouse focus and programmatic focus restoration (e.g. after a nested DropdownMenu closes) no longer reopen the overlay. Keyboard-visible focus continues to open as before.
v0.4.1
🐛 Bug Fix
- ImageViewer — Default export downloads cross-origin images via blob (DES-133). The default export now fetches the image as a blob and uses a same-origin objectURL for downloads. This ensures cross-origin CDN images are properly downloaded instead of opening in a new tab. It falls back to the original URL if fetch fails or returns a non-2xx status, with delayed objectURL revocation.
v0.4.0
✨ Feature
- Tree — Tree title overflow detection switched to on-demand measurement (DES-132).
useTitleOverflowmeasures lazily on pointer-enter / focus instead of at mount, eliminating large-list layout thrashing. Contract change: long titles are wrapped as a Tooltip trigger only after first hover/focus, and theResizeObserveris removed. ... (rest of the file)
v0.3.1
🐛 Bug Fix
- ImageViewer — Toolbar optimizations for single image + accessibility fixes (DES-130). Hide prev/next navigation for a single image, restore
not-allowedcursor on disabled icons, addrole="status"/aria-live="polite"zoom announcements, and support keyboard zoom (+/=/-).
v0.3.0
✨ Feature
- Overlay — Composable triggers for Tooltip / DropdownMenu / Popover / HoverCard (DES-128). The four children-as-trigger overlays become
forwardRefand forward events / aria relationships / conditional id to the inner Trigger viaextractForwardableTriggerProps, enabling nesting like<Tooltip><DropdownMenu>…</DropdownMenu></Tooltip>.
v0.2.2
🐛 Bug Fix
- Input — Input family PR-review findings, documented in Input allowClear + visual cases.
useClearableInputnow clears via the native value setter + a realinputevent (completeSyntheticEvent);InputSearchclear button gains thefocus-visiblering; focus state is only managed whenallowClearis set; adds a localizableclearButtonAriaLabelprop.
v0.2.1
🐛 Bug Fix
- Input — Clear button (
allowClear) gains afocus-visiblekeyboard focus ring (ring-1+Labels/Primary, WCAG 2.4.7). See Input allowClear + visual cases.
v0.2.0
✨ Feature
- Input — new
allowClearone-click clear capability. - Skeleton — refactored to support multi-block rendering via
items, plus radius/color tokens and card-levelSkeletonGroupshimmer. - useFilePicker — new primitive, wired into ChatInput. (No design-site detail page.)
- Accessibility — a11y automation: vitest-axe unit layer + Playwright integration layer + 49-component audit.
- Button — icon-only interaction states aligned.
- Dialog — scrollable content with flex layout.
- Toast — per-item
closeButtonprop.
🐛 Bug Fix
- Button / Checkbox — disabled label color fixed via
:has(). - Typography — tokens aligned to the Figma design system.
v0.1.0
Skeleton overhaul and assorted component refinements (largely superseded by the more detailed v0.2.0 entries above).
✨ Feature
- Skeleton — refactored for multi-block rendering via
items, addedSkeletonGroupcard-level shimmer. - Button — aligned icon-only states.
- Dialog — support scrollable content.
- Checkbox / RadioGroup / Menu / Slider — style and behavior refinements.
- utils — added the
merge-refshelper, exported from the package entry.
🐛 Bug Fix
- Button — fixed disabled label styling.
v0.0.1
Initial release — publish design packages to the private registry. @plaud/design-tokens
and @plaud/design shipped as built dist artifacts (0.0.1).
Pre-release
Changes made before the changeset-based release flow — not individually versioned.
✨ Feature
- 2026-06-12 — Radio & Checkbox hit area expanded to 20×20 + visual cases (DES-96 / DES-97)
- 2026-06-12 — Breadcrumb title truncation for long text (DES-93)
- 2026-06-12 — Tree drag-and-drop visuals & row layout aligned to new Figma (DES-89)
- 2026-06-08 — Tree item spacing 4px + title/action layout refactor + gradient mask
- 2026-06-03 — RadioGroup & Checkbox add
labelprop to expand click area (DES-68) - 2026-06-03 — Dialog
okButtonProps/cancelButtonProps+ content-only mode + AlertDialog removed - 2026-06-02 — Sync 2026/06/01 component updates (DES-40)
- 2026-06-02 — Dialog adds Size=Emphasized variant (DES-37)
🐛 Bug Fix
- 2026-06-10 — Toast title font weight changed to Emphasized (Semibold)
- 2026-06-03 — Switch Toggle-On color updated to blue #1573D1 (light mode) (DES-69)
- 2026-06-03 — Typography font-size & line-height tokens updated (DES-67)
- 2026-06-03 — Typography Font-Weight-Emphasized token updated (DES-66)
- 2026-06-03 — PageControls Dot color tokens updated (DES-65)
- 2026-05-06 — Second round of fixes (designer feedback) (WEB-377)
- 2026-04-28 — First round of batch style fixes (WEB-377)
Related
- Linear project: Plaud Design