All @lattice-ui/* packages version in lockstep, so a single release number describes the whole library. This page summarizes each release and its migration steps; the exact code changes live in Migration, and the full entry-by-entry history in the repository’s CHANGELOG.md.
Lattice UI is pre-1.0: minor releases (0.x) may include breaking changes,
and each release ships migration notes when they do. Read
Package stability for the
per-package stability tiers before upgrading.
0.8.1
The current stable release. A patch: three fixes to asChild’s handling of UI modifier siblings, no API change and nothing to migrate.
Fixes
- UI modifiers are recognised under both renderers.
Slotdecides which child to clone by looking each element’s type up in a table of UI modifiers, and that table held one spelling of each class at a time. Which spelling an element carries depends on who renders it:@rbxts/react’screateElementrewrites a host tag to its Roblox class name before building the element, so<uicorner />arrives as"UICorner", while a browser React renderer leaves the tag as written. Keyed by tag,asChildfailed on every styled subtree in Roblox; re-keyed by class name in 0.8.0, it failed the same way anywhere the library runs under browser React — which is what broke theasChildpreviews on this site. Both spellings are now listed. UIShadowcounts as a modifier. It is a creatableUIComponentthat a Tailwind-style transform emits forshadow-*, and it was absent under either spelling, so it failedasChildexactly like a casing miss.- Re-parented modifiers no longer collide keys with the element’s own children. Both child walks numbered from
.0, and React may resolve a duplicate key by dropping a child rather than by warning. Reachable from any modifier sibling on an element that has children of its own.
The 0.8.0 keying is correct for @rbxts/react, so a game shipped against it
was never affected by the first fix. The second and third are real anywhere:
a shadow-* class on an asChild part failed, and a modifier next to an
element with its own children fed React a duplicate key.
0.8.0
Every motion host that used to be a CanvasGroup is now a Frame, so no primitive flattens its content into an offscreen composited layer behind your back.
Migration — see Migration. In short:
Dialog.Content,Popover.Content,Tooltip.Content,Menu.Content,ContextMenu.Content,Select.Content,Combobox.ContentandToast.Rootforward props onto aFrame. TheCanvasGroup-onlyGroupTransparencyandGroupColor3no longer compile there.- A
transitionthat faded a host throughGroupTransparencyhas to animateBackgroundTransparencyinstead.createSurfaceRevealRecipeandcreatePopperEntranceRecipeare theFramecounterparts ofcreateCanvasGroupRevealRecipeandcreateCanvasGroupPopperEntranceRecipe, which stay exported for acanvasgroupyou slot yourself. Dialog.Contentis the one part with no direct replacement for a whole-surface fade, because its host spans the layer. PassasChildwith your owncanvasgroup, or move the fade to the overlay dim and your own panel elements.
A CanvasGroup composites its whole subtree into one offscreen layer so GroupTransparency can fade it as a unit. That is exactly right for the one effect, and wrong for everything else: children could not carry their own transparency, and every dialog, menu and tooltip paid for the layer whether or not it faded. Rendering a Frame makes the composite opt-in.
Highlights
Dialog.ContentacceptsasChild— it was the last content part without it. Pass acanvasgroupand it becomes the motion host, socreateCanvasGroupRevealRecipe()fades the surface as one layer again, opt-in rather than paid for by every dialog. The outside-press boundary moves down to that element’s first host child, which is the same panel a plain dialog renders directly underContent. See Dialog.createToastRevealRecipefadesBackgroundTransparencyto match its new host.- Popper measurement and placement, presence timing, exit-before-unmount, outside-dismissal boundaries, focus scoping and layer stacking are all unchanged.
Fixes
asChildworks in Roblox on a subtree a Tailwind-style transform has styled.UI_MODIFIER_TAGSwas keyed by the JSX tag, but@rbxts/reactrewrites a host tag to its Roblox class name before building the element —<uicorner />reachesSlotas"UICorner"— so every modifier missed the lookup, counted as a second target candidate, andasChildfailed with “expected exactly one child element besides any UI modifiers”. Any component recipe that emits aUIListLayoutor aUICornerhit this on every use. Re-keying it fixed Roblox and broke browser React, where the tag survives as written; 0.8.1 lists both spellings. See styling with Vela andasChildcomposition.
0.7.0
The release before it, and the largest breaking one so far. It strips every primitive down to behavior and rebuilds the CLI around the same idea.
Migration — see Migration for the full walkthrough. In short:
- Every primitive renders unstyled. Pass the appearance you want directly to the part — props now forward onto the instance it renders — or use
asChild. - Motion has no defaults. Pass
transitionto anything that should animate; presence timing and exit-before-unmount are unchanged. - Drop
trackColorMode,trackOnColor,trackOffColoranddisabledTrackColorfromSwitch.Root, andtransitionfromRadioGroup.ItemandToggleGroup.Item. - Supply labels as children on
Select.ItemandCombobox.Item;textValueno longer renders. - Render the toast queue yourself inside
Toast.Viewport, and supply the layout forMenu.GroupandContextMenu.Group.
It renders differently. Surfaces that looked finished on 0.6 come up flat on 0.7, because the appearance came from the primitive. Plan a visual pass, not just a typecheck.
Highlights
- Primitives now set behavior plus the minimum needed to neutralize Roblox’s own instance defaults. Geometry computed from state — progress fill ratios, slider thumb travel, popper position, scroll thumb size — stays owned by the primitive.
- Unknown props forward from every part onto the instance it renders, so styling no longer requires
asChild. Consumer props override the neutral defaults but never the behavior props, consumer event handlers compose with the primitive’s rather than replacing them, and refs compose too. - Forwarded props are type-checked against that instance, so a prop the element does not accept is a compile error instead of being silently absorbed.
- Highlight state is readable through
useMenuItemContext,useContextMenuItemContext,useSelectItemContextanduseComboboxItemContext, which return{ highlighted, disabled }—highlightedcovers hover or managed focus. useFocusNodegainsonFocusChangeandonActivate, so a node that never becomesGuiService.SelectedObjectcan still render a focus highlight and take activation.activateFocusedNode()drives the same path directly.- The CLI gains per-command help pages, did-you-mean suggestions, keyboard-driven selection prompts,
NO_COLOR/FORCE_COLOR, OSC 8 hyperlinks, a working--verbose, and a single connected run of output. See the CLI reference.
Fixes
- 16 leaf parts render
childrenagain — both separators, both text inputs, the labels, descriptions and messages onTextFieldandTextarea,Toast.Title,Toast.Description,Select.Value,Combobox.Value,Avatar.ImageandDialog.Overlay. Attaching aUICornerorUIPaddingto one of these previously did nothing. asChildaccepts Roblox UI modifiers as siblings of the child element, which is the shape a Tailwind-styleclassNametransform such as vela-rbxts emits. See styling with Vela.Menu.Triggerno longer runs its activation twice, andMenu.ItemfiresonSelectexactly once per activation.- A menu item stays highlighted while it holds keyboard focus.
- The switch thumb is centered in its track, and its travel derives from
AnchorPointandPositionrather than a declaredSize. lattice add context-menuandlattice add motionresolve instead of failing with “Unknown component”.--dry-runreports what it would do instead of claiming it ran.
0.6.2
A CLI-only patch. It changes no primitive behavior — every fix is about making a scaffolded project install and type-check cleanly on npm, where a peer conflict is a hard error rather than a warning.
Migration
- Use
npx lattice-uirather thannpx lattice.npxresolves the published package name, not the shorthand alias, sonpx latticenever worked on npm projects; the CLI’s own command hints now saylattice-ui. Thelatticebinary still works once the package is installed. - If your project still lists pre-0.6.1 package names, run
lattice init— it now rewrites them to theirreact-replacements instead of leaving both names to fight over peers.doctorreports them andupgradeskips them.
Highlights
createandinitrecord the resolved package manager as adevEnginespin, andadd,remove, andupgradeapply that pin before touching dependencies — so a project scaffolded with one manager cannot be quietly installed with another.typescriptis pinned to the version roblox-ts actually compiles with instead of being resolved from the latest dist-tag, anddoctorwarns when a project is already on an unsupported major.- A failed install no longer leaves a half-written scaffold behind:
initandcreateroll their file changes back. - The scaffolded
tsconfigskips lib checks, sotypecheckpasses on a freshly generated project.
0.6.1
A rename-only release: every package moved under a framework layer directory and gained a layer prefix in its published name, so future Vide and plain-Luau targets can sit beside React instead of one framework holding the unprefixed names. No runtime behavior, exports, or APIs changed.
Migration
- Add the
react-prefix to every Lattice import:@lattice-ui/<name>is now@lattice-ui/react-<name>(for example@lattice-ui/dialog→@lattice-ui/react-dialog). @lattice-ui/coreis now@lattice-ui/react-runtime— a rename of the package, not just a prefix.- CLI component names are mostly unchanged:
lattice add dialogstill works. Only thecoreregistry key was renamed, toruntime.
Highlights
- Packages now live at
packages/<layer>/<name>and publish as@lattice-ui/<layer>-<name>, so package name and path convert 1:1. - The layer is a real directory boundary, which lets the “core must not import React” rule be enforced as a path rule.
0.6.0
The 0.6 line hardens the layered surface rather than adding new components: gamepad- and keyboard-driven selection, presence-driven overlay motion, and more reliable dismissal and positioning.
Migration
Toast.Root’stransitionprop now takes aPresenceMotionConfig(initial/reveal/exit) instead of aResponseMotionConfig. Update any custom toast transition to the presence shape.
Highlights
- Overlay triggers and items are gamepad-selectable:
Selecttrigger/item, toggle-group item, and accordion trigger register focus nodes and route activation through a shared guard, so gamepad users can open menus, move between items, and commit without double-firing. - New
useActivationGuardin@lattice-ui/focuscollapses the gamepad/keyboardActivated+Return/Spacedouble-fire into a single activation. - Toast now runs on presence motion: it fades in on entry, stays visible while its exit animation completes before removal, starts the expiry timer when it enters the visible window (not at enqueue), and animates
clear()through the exit transition. Textgains an opt-intruncateprop that applies an end-ellipsis for single-line overflow.
Fixes
- Dialog hosts its overlay in its own
ScreenGuiso content renders above its dim. - Dismissable layers re-promote on open for correct z-order and dismissal order, dismiss on sunk input, and register their trigger as an inside ref so clicking it no longer dismisses; outside-click dismissal also works without an explicit content boundary ref.
- Popper resolves the
ScreenGuiviewport rect from the GUI inset for more accurate placement. - Combobox keeps its selected label, and Select keeps its selected value, after content unmounts.
- Avatar re-reports loaded status for cached textures on source change; Textarea preserves its X scale during auto-resize.
0.5.1
A patch on the 0.5 line:
- Correct Combobox selection, filtering, and clear behavior.
- Declare the loom-preview example-scene dependencies so the docs previews build from a clean install.
0.5.0
Extracts shared focus and motion helpers into dedicated packages, expands CLI project scaffolding, adopts placement-relative Popper positioning, and standardizes motion behavior across layered primitives.
Migration
- Update imports that used focus or motion helpers from
@lattice-ui/coreto@lattice-ui/focusand@lattice-ui/motion. - If you regenerate projects with the CLI, review the updated pnpm hoisting and theme provider setup before shipping the scaffold.
Highlights
- New
@lattice-ui/motionpackage for tween and presence helpers, and@lattice-ui/focusfor focus management. - Placement-relative Popper positioning options (
placement,sideOffset,alignOffset,collisionPadding) adopted across positioned overlays, with improved flip/clamp behavior near viewport edges. usePopperexposes resolved placement/position metadata (position,anchorPoint,placement,contentSize,isPositioned,update).- Interactive
initCLI command for existing projects with safe template merging;createandinitscaffold from maintained package templates. - Layered components share the same motion recipes and focus infrastructure — overlay content stays mounted while motion is active, so exit animations complete instead of flashing or unmounting early.
- Normalized slider and switch thumb anchors so wrapped layouts keep animated thumbs aligned.
0.4.x
The 0.4 line narrowed the workspace to the maintained UI surface and introduced the shared focus manager.
Migration
- Replace
@lattice-ui/cliwithlattice-uiin install commands, scripts, and automation (0.4.3–0.4.4 renamed the published CLI package). - The preview, compiler, preview-runtime, preview-engine, and layout-engine workspace packages were dropped in 0.4.0 — depend on the maintained component packages and CLI entrypoints only.
- The deprecated
SwitchThumbforceMountbehavior and legacy roving-focus example flows were removed in 0.4.0.
Highlights
- Core focus manager primitives coordinating focus registration, ordering, and restoration across accordion, dialog, menu, popover, radio group, select, and tabs (0.4.0).
createCLI command for scaffolding workspace packages, with automatic npm/pnpm/yarn detection (0.4.0–0.4.2).- Direct
ScrollAreatrack-click and thumb-drag interactions (0.4.0).