Changelog
Release history for @luminpdf/ds. Click a version heading or section to collapse it.
Added Loader, a single component exposing the four animated brand marks (variant="lumin" | "pdf" | "sign" | "ag") so every team pulls one shared loader instead of copy-pasting markup.
- Self-contained inline SVG + CSS stroke-trim animation (2.5s linear loop) — no JS deps, no external assets, animates at first paint (works in
loading.tsx/ pre-hydration). The three single-path marks (lumin, pdf, sign) share oneloader-trimkeyframe; theagmark adds staggeredloader-star+loader-basekeyframes. - Props:
variant,size(number → px, or CSS length; default 48),color(defaults tocurrentColorso it themes with surrounding text / dark mode). Standard<div>props pass through. - Accessible (
role="status",aria-label="Loading") and reduced-motion aware (freezes to the fully-drawn mark underprefers-reduced-motion: reduce). - Import:
@luminpdf/ds/components/ui/loaderin-repo,@luminpdf/ds/loaderornpx shadcn add @lumin/loaderfor external consumers.
A solid-style Button used as a DropdownMenuTrigger inside a ButtonGroup kept the 1px transparent border every other button in the group had removed, so the trigger's content box was 2px narrower than its siblings and the seam next to the separator sat off by a pixel.
DropdownMenuTrigger passes its own data-slot="dropdown-menu-trigger" down through asChild, and Button spreads incoming props after its own attributes — so the rendered element loses data-slot="button", which is what the group's border-0 rule selects on.
buttonGroupVariantsnow applies the sameborder-0to[data-slot=dropdown-menu-trigger][data-variant]:not([data-variant=outline]). The[data-variant]guard keeps the rule to triggers actually rendered as aButton; a bare trigger is untouched.outlinetriggers keepborder-input, exactly likeoutlinebuttons.- Playground gained a "With a dropdown trigger" row (outline / default / secondary) and
e2e/button-group.spec.tsasserts the border widths for the secondary and outline triggers.
No API changes. Visual change only for solid-style dropdown triggers inside a ButtonGroup, which now match their sibling buttons.
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.