Changelog
Release history for @luminpdf/ds. Click a version heading or section to collapse it.
SelectContent's popover had no inset of its own — the 4px padding lived on SelectGroup instead. Any Select built without a SelectGroup wrapper (e.g. a flat list of SelectItems) rendered flush against the popover edge, missing the 4px inset Figma specifies (node 118:2540, DS-101-1 Foundations).
- Moved the
p-1inset fromSelectGrouponto theSelectContentviewport, matching howdropdown-menu,context-menu, andmenubaralready pad their content instead of their groups. - Grouped menus (
SelectGroup+SelectLabel) render pixel-identical to before, minus an erroneous 8px seam between adjacent groups that came from double-padding (4px from each group).
No breaking changes — ungrouped menus gain the missing 4px inset; grouped menus only lose the incorrect extra gap between groups.
A tooltip anchored to a trigger inside a dialog, menu, or popover could get stuck open after that overlay closed. Closing an overlay restores focus to its trigger, and Radix's TooltipTrigger opens on any focus event — including that programmatic restore — so the tooltip reopened right as the user was leaving.
TooltipTriggernow gates its focus-open on:focus-visible(mirrors MUI): real keyboard-driven focus still opens the tooltip, but focus restored programmatically by a closing overlay no longer does.- Added e2e coverage for both focus modalities in
e2e/tooltip.spec.ts.
No breaking changes — tooltips still open on keyboard focus and hover; only the phantom re-open on programmatic focus restore is gone.
Icons in a large button now render at 20px instead of 16px, matching Figma (node 34:6). The cva base sets a 16px icon for every size and only the *smaller* sizes overrode it, so a 36px-tall lg button carried the same glyph as the 32px default — visibly under-weighted.
- The
lgandicon-lgsizes now add[&_svg:not([class*='size-'])]:size-5, following the patternxs/smalready use. Height, gap, padding and radius are unchanged. - The
[class*='size-']exclusion is untouched, so an explicitsize-*on a consumer's icon still wins. Spinnernow carriesdata-slot="spinner", and the large sizes size it to 20px via[&_[data-slot=spinner]]:size-5—Spinnersets its ownsize-4, which the icon selector deliberately skips, so a loading large button would otherwise have shown a 16px spinner beside 20px icons.- No API changes, but this is a visual change: any existing
size="lg"orsize="icon-lg"button that doesn't set its own icon size will grow its glyph by 4px.
<Button variant="link"> had the ends of its underline shaved into a wedge. overflow: clip clips to the padding box honoring border-radius, and underline-offset-4 puts the underline within ~1px of the bottom edge — right where the 8px corner arcs cut inward. Most visible at call sites that collapse the box onto the text with h-auto p-0.
- The
linkvariant now addsoverflow-visible, unsetting the baseoverflow-clip. It has no background and a transparent border, so there was never anything to clip. - Keeps the 8px radius, so the
focus-visiblering stays rounded and still matches Figma (node 60:244). Also holds up inside aButtonGroup, where the small sizes re-applyrounded-mdafter the variant classes. - No breaking changes — the box geometry is unchanged; only clipping is relaxed.
AvatarFallback no longer renders a flat gray. When it shows text (initials) it now picks a card-<hue> background + card-<hue>-foreground text from the 17-hue design-token ramp, deterministically hashed from the initials, so members without a photo get distinct, on-brand colors instead of the washed-out bg-muted gray.
- Text fallbacks resolve to one of the 17
card-*hues via a char-code hash of the initials (same initials → same color every render). - Icon-only fallbacks (non-text children) keep the previous
bg-muted/text-muted-foregroundgray, and an explicitclassNamestill overrides viatwMerge. - Hues are listed as literal class strings so Tailwind's source scan emits every one.
- Added shuffled-fallback demos (distinct-initials grid, stable-across-sizes row, fallback-only group) to the docs playground.
No breaking changes — text fallbacks recolor; icon-only fallbacks and className overrides are unchanged.
AvatarBadge (status dot / icon badge) sits at the avatar's bottom-right edge and was being clipped by overflow-hidden on the Avatar root.
- Dropped
overflow-hiddenfrom theAvatarroot (matching the design-parity spec'soverflow: visible). The image still self-clips to the circle via its ownrounded-[inherit], so the avatar shape is unchanged — only the badge is now fully visible.
An indeterminate checkbox now renders a filled MinusIcon (a dash) matching the checked state, instead of an unfilled CheckIcon, per the Figma spec (node 23174:7682).
- Root cause:
CheckboxPrimitive.Indicatorrendered<CheckIcon />unconditionally, and every fill selector was scoped todata-[state=checked]only. Radix setsdata-state="indeterminate"for that state, so it showed a checkmark on a transparent box. - Fix: render both icons and toggle via the indicator's own
data-state(works for controlled and uncontrolleddefaultChecked="indeterminate"), and mirror eachdata-[state=checked]fill selector (bg/border/text, incl.aria-invalid) with adata-[state=indeterminate]equivalent so the box is filled like the checked state. - No breaking changes.
Drawer/DrawerContent now work correctly for a non-modal bottom sheet that must leave the rest of the page interactive (e.g. a resizable preview pane).
Drawer— Fixed a vaul bug where a fully-controlledopenprop combined withmodal={false}leavesdocument.body.style.pointerEventsstuck at'none'forever (vaul's own reset only fires throughDrawer.Trigger's internal state change, not a controlledopenprop). Now watches for this viaMutationObserverand reverts it back to'auto'for as long as such a drawer is open. See emilkowalski/vaul#509 (open as of this writing).DrawerContent— Added anoverlayprop (defaulttrue) to optionally skip the dimming backdrop for non-modal use. Forwards its ref, so callers can measure/drive the sheet's DOM node directly (e.g. resizing it without a re-render). Addedoverflow-hiddenso square-cornered content flush against an edge no longer visually pokes past the sheet's rounded corners.- No breaking changes — both additions are opt-in and default to prior behavior.
Cleared all 59 advisories (33 high, 21 moderate, 5 low) reported by pnpm audit --ignore-workspace against the standalone lockfile.
next: lockfile refreshed to 15.5.19 (was stale at 15.5.9), resolving the bulk of thenextadvisories.- Added range-scoped
pnpm.overridesfor transitive deps, each capped to its current major to avoid breaking bumps:@modelcontextprotocol/sdk(1.26.0),tar,path-to-regexp,qs,ajv,js-yaml,body-parser,postcss,@babel/core,diff,flatted,minimatch,picomatch,brace-expansion,@isaacs/brace-expansion,uuid. - No runtime or component changes.
DialogContent now stays within the viewport instead of overflowing on short screens.
- Added
max-h-[calc(100dvh-2rem)]+overflow-y-autoso a tall dialog scrolls inside itself rather than scrolling the page (dvhaccounts for mobile browser chrome). - Changed the mobile width guard from
max-w-[calc(100%-2rem)]tomax-w-[calc(100vw-2rem)]so the 16px side gutter is always viewport-relative (the previous100%resolved against the portal container, letting the modal touch the screen edge on mobile/tablet).
Matched the Figma "DropdownMenu / Item" spec (node 336:4988).
- The leading icon now defaults to
muted-foreground(gray) instead of inheriting the foreground color. - The leading icon and
DropdownMenuShortcutstay muted on hover/focus — dropped thefocus:**:text-accent-foregrounddescendant override and the shortcut'sgroup-focusoverride so only the item label takes the accent/destructive color. - Destructive variant unchanged (icon + label stay
destructive). - Added an "Item states & icon color" demo to the docs playground and an
ItemStatesStorybook story.
Color tokens on /tokens showed the previous theme's value after toggling light/dark (e.g. dark mode displayed rgba(255,255,255,.898) instead of rgba(10,10,10,.898)).
- Root cause:
useResolvedCssVarsre-read on the next-themesresolvedTheme, but the provider applies the.darkclass in an effect that runs after the consumer effect, sogetComputedStyleread one commit behind. - Fix: both hooks now re-read via a
MutationObserveron<html>(class/style), so values resolve after the DOM reflects the theme. Fixes every token section via the shared hook.
Added a new /icons page to browse all 1627 icons from @luminpdf/icons.
- Search — Real-time filter by icon name
- Weight selector — Switch between
thin,light,regular,bold,fill,duotone - Size slider — Adjust preview size from 16–80 px
- Color picker — Custom color with a
currentColortoggle - Infinite scroll — Grid lazy-loads 240 icons at a time
- Click-to-copy — Clicking any icon copies the import snippet (e.g.
import { AgreementGenIcon } from "@luminpdf/icons") with a toast confirmation - Keyboard shortcuts —
/focuses search,Escclears it - Icon names displayed as-is (e.g.
AgreementGen) without word-splitting
All 1627 icons are code-split into individual chunks for webpack safety. The copy handler falls back to execCommand('copy') in non-secure contexts (e.g. LAN IP).
Added a /changelog page with a lightweight Markdown renderer (headings, lists, tables, inline code, bold, links), sidebar navigation, collapsible sections, and active version tracking.
Upgraded the /tokens reference page so every token swatch now exposes its Tailwind utility alongside the CSS variable.
Color palette & semantic colors:
- Each swatch now shows three one-click copy buttons —
bg-*,text-*,border-*— via a newColorUtilityButtonscomponent - Swatch click still copies
var(--…); the--varlabel is now a separate clickable button - Checkerboard background behind swatches to expose transparency
Radius section:
- Card click copies
rounded-*Tailwind class (e.g.rounded-lg) - Separate inner button to copy
var(--radius-*)CSS variable
Responsive section — Expanded with usage guidance:
- Breakpoints table now includes the Tailwind responsive prefix (e.g.
sm:*) and a description of when it activates - Container tokens now show the corresponding
max-w-*class and a plain-language usage example per size - Responsive spacing, section padding, and title-gap tokens show live resolved values and copy buttons for both Tailwind utility and CSS variable
New custom Tailwind utilities added to app/theme.css:
px-container→padding-inline: var(--responsive-container-padding-x)py-section→padding-block: var(--responsive-section-padding-y)gap-section-title-xl/lg/md/sm→ corresponding--responsive-section-title-gap-*variables
New `useResolvedCssVarsResponsive` hook — like useResolvedCssVars but also re-reads on window.resize, for tokens whose values change at breakpoints.
New `copyTailwindClass` helper — writes a Tailwind class name to clipboard with a toast.
Tokens layout (app/tokens/layout.tsx) — wraps the tokens subtree in <TooltipProvider>.
Added scripts/generate-icon-names.ts and a generate:icons npm script to regenerate app/_icons/icon-names.ts from the installed @luminpdf/icons package.
Added min-h-8 (32px) to every interactive item in the five menu-family components so that single-line rows maintain a consistent touch target regardless of content, matching the Figma DS spec (EXP-70).
| Component | Sub-elements updated |
|---|---|
| Combobox | ComboboxItem |
| Command | CommandItem |
| ContextMenu | ContextMenuItem, ContextMenuSubTrigger, ContextMenuCheckboxItem, ContextMenuRadioItem |
| DropdownMenu | DropdownMenuItem, DropdownMenuSubTrigger, DropdownMenuCheckboxItem, DropdownMenuRadioItem |
| Menubar | MenubarItem, MenubarSubTrigger, MenubarCheckboxItem, MenubarRadioItem |
No API changes — min-h-8 is purely additive and does not override explicit h-* classes passed via className.
- Added
MenubarItem,MenubarCheckboxItem,MenubarRadioItem,MenubarSubTriggernode mappings toscripts/figma-registry.tsfor upcoming design-parity coverage.
- Renamed CSS variables from
--components-card-{hue}/--components-card-{hue}-foregroundto `--card-{hue}` / `--card-{hue}-foreground`. - Tailwind utilities are now
bg-card-{hue}andtext-card-{hue}-foreground(e.g.bg-card-blue,text-card-blue-foreground). Migrate anyvar(--components-card-…)orbg-components-card-*usages. - Semantic shadcn tokens `--card` / `--card-foreground` and utilities `bg-card` / `text-card-foreground` are unchanged.
- Added 34
--components-card-{hue}/--components-card-{hue}-foregroundpairs (17 hues). Superseded in 0.2.4 by--card-{hue}naming. - Registered all 34 in
@theme inlineso Tailwind utilities likebg-components-card-bluework out of the box. Superseded in 0.2.4 bybg-card-blue.
- Added
ComponentCardsSectionto the tokens reference page, showing bg/fg swatch pairs for all 17 hues.
- Replaced Radix
AvatarPrimitivewith a single<img>element and localAvatarContextfor loading-status coordination, preventing duplicate requests for uncacheable URLs (e.g. signed S3 URLs).
- Updated surface tokens, max width, typography, arrow color, and Kbd shortcut styling to match DS-shadcn-001 node
17103:809.
- Updated tooltip/legend styling to match DS-shadcn-001 node
533:8856; added registry, docs, playground, Storybook, E2E, and design-parity wiring.
- Registered Toggle node mappings for DS-shadcn-001 node
21133:11461; added playground/docs/story/E2E/design-parity wiring.
- Added Figma state registry mappings for DS-shadcn-001 node
18684:85575, plus docs, playground, Storybook, E2E, and design-parity wiring.
- Updated toast container, action/cancel controls, and icon sizing to match DS-shadcn-001 nodes
18482:48401,17375:198044, and17375:198096.
- Updated backdrop, side dimensions, spacing, and close icon states to match DS-shadcn-001 node
220:4633.
- Updated Table header/cell API for Figma state/variant parity; replaced Data Table node mappings with
190:901,23177:2258, and17089:45580.
- Added Field component Figma registry, docs, playground, Storybook, E2E, and design-parity wiring.
- Bumped version to 0.2.2. Updated
peerDependenciesand registry metadata.
- Initial 0.2.x publish with updated registry and peer dependency declarations.
- Added AI Elements section to the main navigation and documentation layout.
- Added initial AI Elements component library with example patterns for AI-powered UIs.
- Improved registry build pipeline and component export verification.
- Renamed all custom design token CSS variables to use the
--custom-*prefix. - Updated
theme.cssand all consumer usages accordingly.
- Added
scripts/design-paritytooling for automated Figma-to-code comparison.
- Removed unused registry entries and standardised component registration format.
- Added
LOCAL_PUBLISH_GUIDE.mdwith step-by-step instructions for local package publishing.
- Registry now exports all UI components via a wildcard pattern for easier consumption.
- Added 24 previously unregistered components to the registry.
- Introduced the
ai-elementspackage with initial set of AI-focused UI primitives.
- Replaced inline SVG icons in AI Elements with
@luminpdf/iconsimports.
- Configured AI Elements for npm publishing and registry inclusion.
- Fixed tab line indicator position regression introduced in 0.1.0.
- Updated Tabs styling to match Figma DS-shadcn-001 specification.
- Removed
backdrop-blurfrom Dialog overlay per updated Figma spec.
- Moved the consumer-facing guide to
README.mdso it appears on the npm package page.
- Final pre-publish cleanup: verified registry, exports, and peer dependencies.