Documentation

Documentation

@lightleaplabs/ui 0.4.0

Release notes for @lightleaplabs/ui 0.4.0.

← 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:

  • accent and eyebrow on light: orange-600 → orange-900
  • solid button text on light: white → neutral-900
  • success on light: #10b981 → #047857
  • text-tertiary on light and dark
  • new badge-brand-text, badge-warning-text, badge-error-text, and input-error-text tokens, 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:

WasNow
--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 --eyebrow and --nav-link-dropdown-text-hover for their own cases.

    The feature-item icon was the subtle one: it sits on a bg-accent/15 chip, 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 in tests/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 exports target is missing from the packed tarball. This closes a real gap: ./properties is 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
Esc