← All @lightleaplabs/ui releases
Public Beta compatibility policy
Minor change
Add an alignment prop to <lll-form-block> ('start' | 'center', default
'start', reflected). center centers the input/button row, the note, and the
success message so a form can sit on the same centerline as a centered section
heading. Existing forms are unchanged. The allowed values and type are exported
from the new @lightleaplabs/ui/form-block/props entry point as
formBlockAlignmentValues and FormBlockAlignmentType.
<lll-input> now pins its error message to text-left, so a validation error
stays left-aligned under the field instead of inheriting centered text from a
centered form block or other ancestor.
Minor change
Certify the Phase 1 component surface: complete custom-elements metadata, stable Storybook registrations, and improved keyboard and ARIA behavior.
Rendered colors change. Light-surface contrast fixes alter the default value of several tokens, so components render differently even though the API is backwards compatible:
accentandeyebrowon light:orange-600→orange-900- solid button text on light: white →
neutral-900 successon light:#10b981→#047857text-tertiaryon light and dark- new
badge-brand-text,badge-warning-text,badge-error-text, andinput-error-texttokens, replacing shared colors that failed contrast on subtle backgrounds
Also adds a tabs-aria-label attribute to lll-feature-tabs so the tab list’s
accessible name can be localized.
Breaking change
Freeze and document the public @lightleaplabs/tokens contract for the Phase 1
beta.
Breaking: twelve themed token pairs became single-value tokens. A token is
themed because it has a meaningful per-theme answer, not because two values
happen to differ. Corner radius and the terminal mock’s chrome — which is
deliberately dark on any page theme — have no such answer, so publishing them as
-on-light / -on-dark pairs advertised an override point that could never
mean anything.
Migration — replace either suffixed name with the unsuffixed one:
| Was | Now |
|---|---|
--radius-button-on-light / --radius-button-on-dark | --radius-button |
--radius-card-on-light / --radius-card-on-dark | --radius-card |
--radius-input-on-light / --radius-input-on-dark | --radius-input |
--radius-surface-on-light / --radius-surface-on-dark | --radius-surface |
--terminal-*-on-light / --terminal-*-on-dark (8 tokens) | --terminal-* |
Pairs whose two sides merely coincide today — the gradients, the transparent button backgrounds — are deliberately left alone. They are rebrandable surfaces where a consumer may well want to differ by theme.
Breaking: @lightleaplabs/ui no longer exports ./footer/props. The subpath
pointed at Footer.props.js, which no build has ever produced — Footer has no
Footer.props.ts — so importing it could only fail. Footer’s props are declared
on the element itself.
tokens.docs.json is internal and no longer ships. It feeds an in-repo
tooling script, has no export subpath, and carries no compatibility promise.
Also in this release:
-
Light-surface accents stay vivid at Orange 600, which is tuned for fills and glows rather than for text: on white it is 3.59:1, fine for a shape and short of the 4.5:1 that normal-size text needs. So anything that renders accent content now goes through a new Orange 700 pair instead —
--accent-text(5.23:1 on white, 4.80:1 on the light page) for the accent badge, the animated words in<lll-heading>, the accent<lll-feature-item>icon, and the marketing form’s success copy, plus--eyebrowand--nav-link-dropdown-text-hoverfor their own cases.The feature-item icon was the subtle one: it sits on a
bg-accent/15chip, so Orange 600 on its own tint measured 2.95:1 — under the 3:1 that non-text content needs, even though the same colour passes 3:1 against the page. -
The supported export list, every public override target, the internal
--lll-src-mirrors, and the structural JS-only tokens are frozen intests/public-token-contract.ts, and checked against the built artifacts by the first test suite this package has had. -
The package-boundary verifier now fails when any
exportstarget is missing from the packed tarball. This closes a real gap:./propertiesis written by the ui build, so a tokens-only build left the subpath dangling. -
The README documents import order, light/dark behaviour, Tailwind usage, what is and is not overridable, and the 0.x compatibility rules — including that a token rename requires a migration note like this one.
Minor change
Add a secondary CTA variant to <lll-nav-link>, so a nav can carry two calls to
action instead of one. variant="action" remains the primary CTA — an outlined
pill on a transparent fill that gains an accent glow on hover — and the new
variant="action-secondary" is the same pill on a filled surface, brightening
its fill and warming its border toward accent on hover rather than taking a
glow.
Both CTAs are optional and independent. <lll-nav> now stacks its actions
slot full width in the mobile drawer, so a pair of CTAs no longer squeezes two
pills onto one phone-width row.
Dropdown panels are also anchored and sized differently. Every panel — one
column or three — now hangs off the link that opens it rather than being pinned
to the nav’s right edge: left-aligned under the trigger, flipping to sit
right-aligned on it when that would spill past the viewport, and falling back to
a gutter only when it fits on neither side. Panels are fluid as well: they ask
for the width their columns want but never exceed the viewport, and the columns
reflow (auto-fit tracks, 15rem minimum) so a squeezed three-column menu wraps
to two columns and then to one instead of being clipped. This replaces the
previous fixed widths and the lg breakpoint that collapsed a mega menu to a
single narrow stack.
Desktop dropdowns are now click-capable. A link that owns a dropdown renders a
single disclosure button — label and caret together, one target and one tab stop
at every breakpoint — instead of a link plus a hidden mobile-only trigger.
Hovering still opens the panel, but a click (or Enter/Space) pins it open so
it survives the pointer leaving, and a second click, Escape, a click outside,
or another dropdown closes it. Because the trigger opens the panel rather than
navigating, it is a button rather than a link, and the panel’s overview link to
the trigger’s own destination is now shown at every breakpoint, labelled by the
new overview-label prop. Hover opening is also gated on @media (hover: hover)
rather than a breakpoint, so a touchscreen laptop no longer fights a synthesized
hover with its own tap.
<lll-nav-link> no longer provides built-in submenu or overview labels.
Consumers must supply submenu-label and overview-label through their CMS or
template integration whenever the corresponding dropdown content is rendered.
The secondary CTA’s rest state now matches the agreed peach/orange design: a
filled surface, an accent border at 45%, and warm ink, rather than near-white
ink on a neutral outline. Colour moved off the shared CTA base onto each variant
— a shared text-/border- utility resolved to the same declaration and won
the cascade over whatever the secondary set.
It also gains a secondary-hover prop selecting one of four hover treatments
over that shared rest state: ring (default — a 3px accent halo with a 1px top
inner highlight), shimmer (a conic gradient sweeps once around the border),
nudge (the label shifts left as a trailing arrow slides right and brightens),
and deepen (the fill darkens and the pill eases down to 98% behind an inset
shadow, pressing further to 95% on click). Only nudge changes the
markup, adding a decorative arrow; the rest are pure CSS. The prop is ignored by
every other variant.
The shimmer treatment needs a custom property it can interpolate, so
@lightleaplabs/tokens gains a border-sweep keyframe and an
@property --lll-border-sweep-angle registration. The keyframe lives inside
@theme, so Tailwind emits it only into components that use the
animate-border-sweep utility, and the registration reaches the document
through tokens.properties.css like every other @property the library
depends on. No public token was added, renamed, or removed.
Minor change
Publish the free design tokens and Public Beta component library from public
npm in the @lightleaplabs scope, licensed under the MIT License. Include
third-party license notices in each published package, and support npm and pnpm
installs without repository-only Bun lifecycle guards.
Patch change
Publish the public issue tracker URL in both package manifests and direct support documentation to the runnable examples and issue-reporting repository.
Patch change
Require Bun 1.4.0, and make the requirement enforceable.
The engines.bun range moves from >=1.3.0 to >=1.4.0 across every workspace
package and every scaffolded template, so a generated project inherits the same
floor the repo itself enforces. scripts/sync-bun-preinstall.ts — which is what
keeps those fields in agreement — carries the new range, and the root
package.json now pins packageManager: bun@1.4.0.
The preinstall guard now checks the Bun version, not just that the runtime
is Bun. Neither engines.bun nor packageManager is enforced by Bun — both
are metadata, and an install on an older Bun against either one succeeds
silently. The floor was therefore advisory everywhere it appeared. The guard now
compares Bun.version against the range and fails the install with the
installed version and a remedy:
This repository requires Bun >=1.4.0. Installed: 1.3.14. Run: bun upgrade
For a project scaffolded by @lightleap/create, that turns an obscure
downstream failure on Bun 1.3.x into a clear message at install time.
@types/bun is also pinned to ^1.4.0 instead of floating on latest, which
had been resolving independently of the runtime in use.
Patch change
Bring the component documentation to production parity. Every Public Beta
component now has a canonical page in the docs site — including Background,
CodeBlock, FeatureTabs, FormBlock, NavLink, and NavDropdownColumn, which had
none — and the existing pages were corrected against the shipped API: stale
props (Card’s interactive, Divider’s variant, Grid’s columns, Stack’s
justify, Text’s align, Icon’s variant, Image’s objectFit) are gone, and
missing props, slots, events, methods, and CSS parts are documented.
A new parity test checks the docs against the Custom Elements Manifest in both directions, so a page can no longer fall behind an API change and a removed prop cannot linger in the docs.
<lll-heading> no longer exposes the currentBreakpoint property or its
currentbreakpoint attribute. It was written by an internal controller and
never read — the heading’s responsive sizing comes from CSS media queries, so
nothing rendered from it. Removing it also drops a resize listener per heading.
Patch change
Fix multi-column <lll-nav-link> dropdown panels overflowing the right edge of
the viewport. The panel’s horizontal shift was being computed correctly and then
silently discarded: it is applied through Tailwind’s translate-x-*, which
compiles to translate: var(--tw-translate-x) var(--tw-translate-y), and
--tw-translate-y gets its 0 from an @property registration. A shadow root’s
adopted stylesheet cannot register @property, so that variable resolved to
nothing, the whole translate declaration was invalid at computed-value time,
and every panel stayed left-aligned under its trigger.
The library already ships the fix for this class of bug —
@lightleaplabs/tokens/properties, which registers the --tw-* properties at
the document level — but nothing in this repo’s apps imported it, so it only
worked in Storybook. Consumers must load
@import '@lightleaplabs/tokens/properties' once on the document; without it,
every utility that reads a registered default silently drops inside a shadow
root. That also restores the nav-link label/arrow nudge treatment and the
secondary CTA’s hover:scale/active:scale, which were dead for the same
reason.
A panel’s shift is also recomputed while it is open. It is measured against the
viewport, so a reflow invalidates it: an open panel kept the offset the old width
earned it and could then hang off either edge. Recomputation runs off a new
shared ResizeObserver rather than a window resize listener, watching both
the document element — so a scrollbar appearing or a zoom counts, not only a
window resize — and the trigger’s own box, so a late font swap widening a label
counts too. One observer serves every subscriber and is disconnected when the
last panel closes.
A pure position change is still not covered: if the component sits in a
resizable split pane, dragging the divider moves it without the window resizing
or the document element changing width, and an open panel keeps its stale
offset. Catching that requires polling getBoundingClientRect on an animation
frame for as long as a panel is open, which is why comparable libraries ship it
as opt-in. Closing and reopening the panel realigns it.
Patch change
Point the README header at the live documentation site instead of “Documentation coming soon”, and add a direct component/token reference link alongside it.
Tag the npm-facing links back to lightleaplabs.com with utm_* parameters so
referrals from the package pages are attributable. Covers the README logo and
Website links and the homepage field in both packages; the campaign
distinguishes ui from tokens, and the medium distinguishes the rendered
README from npm’s own sidebar link. Documentation links are left untagged — the
docs site has no analytics for the tags to reach.
These links ship inside the published tarball, so the scheme is fixed at publish time — changing it later requires a republish.
Dependency updates (7)
Updated dependencies [4e53bff]
Updated dependencies [8ad2109]
Updated dependencies [e40382e]
Updated dependencies [d4ce392]
Updated dependencies [a0ea3a7]
Updated dependencies [27c33dc]
Updated dependencies [8bac5b4]
- @lightleaplabs/tokens@0.4.0