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-1 inset from SelectGroup onto the SelectContent viewport, matching how dropdown-menu, context-menu, and menubar already 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.

  • TooltipTrigger now 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 lg and icon-lg sizes now add [&_svg:not([class*='size-'])]:size-5, following the pattern xs/sm already use. Height, gap, padding and radius are unchanged.
  • The [class*='size-'] exclusion is untouched, so an explicit size-* on a consumer's icon still wins.
  • Spinner now carries data-slot="spinner", and the large sizes size it to 20px via [&_[data-slot=spinner]]:size-5Spinner sets its own size-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" or size="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 link variant now adds overflow-visible, unsetting the base overflow-clip. It has no background and a transparent border, so there was never anything to clip.
  • Keeps the 8px radius, so the focus-visible ring stays rounded and still matches Figma (node 60:244). Also holds up inside a ButtonGroup, where the small sizes re-apply rounded-md after 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-foreground gray, and an explicit className still overrides via twMerge.
  • 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-hidden from the Avatar root (matching the design-parity spec's overflow: visible). The image still self-clips to the circle via its own rounded-[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.Indicator rendered <CheckIcon /> unconditionally, and every fill selector was scoped to data-[state=checked] only. Radix sets data-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 uncontrolled defaultChecked="indeterminate"), and mirror each data-[state=checked] fill selector (bg/border/text, incl. aria-invalid) with a data-[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-controlled open prop combined with modal={false} leaves document.body.style.pointerEvents stuck at 'none' forever (vaul's own reset only fires through Drawer.Trigger's internal state change, not a controlled open prop). Now watches for this via MutationObserver and 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 an overlay prop (default true) 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). Added overflow-hidden so 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 the next advisories.
  • Added range-scoped pnpm.overrides for 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-auto so a tall dialog scrolls inside itself rather than scrolling the page (dvh accounts for mobile browser chrome).
  • Changed the mobile width guard from max-w-[calc(100%-2rem)] to max-w-[calc(100vw-2rem)] so the 16px side gutter is always viewport-relative (the previous 100% 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 DropdownMenuShortcut stay muted on hover/focus — dropped the focus:**:text-accent-foreground descendant override and the shortcut's group-focus override 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 ItemStates Storybook 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: useResolvedCssVars re-read on the next-themes resolvedTheme, but the provider applies the .dark class in an effect that runs after the consumer effect, so getComputedStyle read one commit behind.
  • Fix: both hooks now re-read via a MutationObserver on <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 currentColor toggle
  • 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, Esc clears 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 new ColorUtilityButtons component
  • Swatch click still copies var(--…); the --var label 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-containerpadding-inline: var(--responsive-container-padding-x)
  • py-sectionpadding-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).

ComponentSub-elements updated
ComboboxComboboxItem
CommandCommandItem
ContextMenuContextMenuItem, ContextMenuSubTrigger, ContextMenuCheckboxItem, ContextMenuRadioItem
DropdownMenuDropdownMenuItem, DropdownMenuSubTrigger, DropdownMenuCheckboxItem, DropdownMenuRadioItem
MenubarMenubarItem, 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, MenubarSubTrigger node mappings to scripts/figma-registry.ts for upcoming design-parity coverage.
  • Renamed CSS variables from --components-card-{hue} / --components-card-{hue}-foreground to `--card-{hue}` / `--card-{hue}-foreground`.
  • Tailwind utilities are now bg-card-{hue} and text-card-{hue}-foreground (e.g. bg-card-blue, text-card-blue-foreground). Migrate any var(--components-card-…) or bg-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}-foreground pairs (17 hues). Superseded in 0.2.4 by --card-{hue} naming.
  • Registered all 34 in @theme inline so Tailwind utilities like bg-components-card-blue work out of the box. Superseded in 0.2.4 by bg-card-blue.
  • Added ComponentCardsSection to the tokens reference page, showing bg/fg swatch pairs for all 17 hues.
  • Replaced Radix AvatarPrimitive with a single <img> element and local AvatarContext for 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, and 17375: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, and 17089:45580.
  • Added Field component Figma registry, docs, playground, Storybook, E2E, and design-parity wiring.
  • Bumped version to 0.2.2. Updated peerDependencies and 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.css and all consumer usages accordingly.
  • Added scripts/design-parity tooling for automated Figma-to-code comparison.
  • Removed unused registry entries and standardised component registration format.
  • Added LOCAL_PUBLISH_GUIDE.md with 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-elements package with initial set of AI-focused UI primitives.
  • Replaced inline SVG icons in AI Elements with @luminpdf/icons imports.
  • 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-blur from Dialog overlay per updated Figma spec.
  • Moved the consumer-facing guide to README.md so it appears on the npm package page.
  • Final pre-publish cleanup: verified registry, exports, and peer dependencies.