Changelog
1.8.0the sidebar: width, first paint, tooltips
Additive. /app/utils: APP_SIDEBAR_WIDTH ({ collapsed: 56, expanded: 208 }), SIDEBAR_ATTRIBUTE, sidebarInitScript(key, default = "collapsed"), Z_INDEX (also from /app; tokens --z-sticky … --z-tooltip-overlay in app/styles/tokens.css, category layer in APP_TOKENS). AppShell: expanded width 208 px (was 240); the width comes from <html data-sidebar> in CSS and the state starts from it (no collapse-then-open on a remount); the width transition runs only after a toggle; chrome prop (default true) for a shell mounted once in a root layout; the main landmark is <div id="main" role="main"> (was <main>; FocusRescue finds either). The rail's tooltips are the kit Tooltip (portalled, side="right"), no longer CSS spans under the page. Tooltip: z-index from --z-tooltip (75: above page content, sticky headers, toolbars and the sidebar; dialogs, popovers, sheets and toasts above it), data-layer="overlay" (--z-tooltip-overlay, 110) when its control is inside a dialog, menu, listbox or popover. Tests in test/app/w2-shell.test.tsx.
1.7.0Screens
Additive, no export added or removed.
- Five Screens (
kind: "screen", category "Screens"), full-page examples built only from kit exports with fictional data (@example.com), insrc/screens/: website (section: "blocks") Landing page (Navbar, Hero, a features Grid of Cards, StatsSection, a call-to-action Band, Footer) and Pricing page (Monthly | Yearly SegmentedControl, three PricingCards, an Accordion, Footer); product (section: "product") Application hub (StickyToolbar with ListSearch, QuickFilters, SortMenu and List | Board; ListRows or the Kanban; no Board on phones), Students list (toolbar with a Stage FilterDropdown, rows with PersonName, Badge and Tick, the SelectionBar) and Sign-in (a card with the email field and two Buttons). - Each has a catalogue entry with its description, phone and tablet notes, components and
source(the screen file);CATALOGUE_SECTIONSadds "Screens" to Website blocks and Product UI. - Tests:
test/screens.test.tsx(registered with their sections, catalogue fields, imports only kit code, React and lucide-react, only @example.com addresses, every Screen renders);test/catalogue.test.tsacceptskind: "screen"only for category "Screens".
1.6.0W2: the one app sidebar and shell
Additive. /app: AppShell, PhoneNav, SidebarAction, sidebarRowClass, useAppSidebar and the types AppNavItem, AppNavProgress, AppShellBrand, AppShellProps (README "App shell and the one sidebar"); ThemeProvider, useTheme, useColorScheme, DEFAULT_THEME_STORAGE_KEY and NavigationProgress, moved from candidate-portal; Menu bareTrigger. /app/utils: themeInitScript. The base Sidebar measures its pill through the shared lib/traveling-pill.ts (no change in look or API). Docs: the app-sidebar product demo (its CATALOGUE entry: Product UI, "Shell & navigation", with responsive and usedIn). Codemod codemods/waves/w2.json. Tests test/app/w2-shell.test.tsx.
1.5.0the catalogue
Additive, no export removed.
CATALOGUE,CATALOGUE_SECTIONS,KIT_APPS,catalogueSourceand their types (CatalogueEntry,DemoMeta,DemoKind,DemoSection,DemoStatus,DemoResponsive,KitApp) from the root entry and/internal-demo-registry. Every entry, base ones too, has a section, category (base categories copied once from docs-dev's old map), description, status, responsive note andusedIn. New section "blocks" (Website blocks): hero, overlap-hero, feature-showcase, segmented-sections, modal-cards, step-walkthrough, scroll-screens, device-frame, loop-video, pricing-card, stats-section, band, section-divider, sticky-mobile-cta, footer.meta.sectionmay now be "blocks" (readers that only know "product" treat it as base, as before).meta.kind("component" by default, or "screen"; no screens yet).- Product demo modules keep only
layout/previewHeightin their ownmeta; their section, category, import path and components come from the catalogue throughdemoRegistry(unchanged for readers). APP_TOKENS,THEMES,SCREEN_SIZES,TAILWIND_SCREENS.BREAKPOINTSis unchanged (moved tolib/screens.ts, still exported from the root).- Both Tailwind presets set
theme.screensfromBREAKPOINTS(identical to Tailwind's defaults). - docs-dev reads the catalogue: sections Website components, Website blocks, Product UI with categories in catalogue order; its copied
CATEGORIESandPRODUCTmaps are gone; the brand picker readsTHEMES. - Tests:
test/catalogue.test.ts.
1.4.0Product UI in the docs
Additive. The demo registry (@metalaunchlabs/ui/internal-demo-registry) now covers the components moved in from candidate-portal (K1, W1), as a separate Product UI section:
- 31 demo modules in
src/product-demos/(fictional data,@example.compeople), each rendering insideframe.tsx:UiHostProviderwith links and navigation kept on the page, in-memory storage and a demo viewer, andCandidateScope, so nothing needs Next./app:app-button(Button, ButtonLink),icon-button,app-badge,tag-chip(TagChip, TagChips, TagDot),person-name,app-empty-state,app-tooltip,app-scroll-fade,disabled-reason(ReasonTip, ReasonText, ReadOnlyReason),accessibility(FieldError, LiveStatus),app-tabs(Tabs, TabPanel, SectionSwitcher),segmented,kanban,list-row(ListRow, ListRows, RowCount),list-more(ListMore, RowSkeletons),list-toolbar(StickyToolbar, ListSearch, QuickFilters, SortMenu),filter-dropdown,filter-panel(FilterPanel, ActiveFilters),selection-bar(SelectionBar, Tick, SelectHit),menu,app-dialog(Dialog, ConfirmDialog),bottom-sheet,app-popover(PopoverPanel),toaster,save-bar(SaveBar, UnsavedChangesGuard);/resources:status-badge(StatusBadge, StatusPicker),application-labels(ChancesBadge, ChancesPicker, TopChoiceStar, TagChips),intake-select;/platform:level-badge,email-chips(EmailChips, ChipFieldFrame),people-picker. Product slugs that would clash with a base slug take anapp-prefix. - A module's
metamay now carry, all optional so the existing entries are unchanged:section("base" by default, or "product"),importPath(default@metalaunchlabs/ui, e.g.@metalaunchlabs/ui/app),category(set on every product entry: Primitives, Navigation, Lists & boards, Overlays, Feedback & saving, Forms & pickers, Resources, Platform) andcomponents(the exports the page covers). New exports from the registry entry: theDemoMetatype,DEFAULT_DEMO_SECTION,DEFAULT_DEMO_IMPORT_PATH. test/demo-coverage.test.tsx: every public component export of every entry has a demo (or a stated reason:ThemeScope,InputModality,UiHostProvider,CandidateScope,FocusRescue,SaveHeld), product entries name real exports in theirimportPath, and every product demo renders inside the candidate scope.- docs-dev: the sidebar and index show Components and Product UI sections; the crawler reads
src/product-demos/with a category and props source per slug.
The product demos look right only where the host loads the app layer's CSS compiled with the app Tailwind preset (README "Layers"); a precompiled, scoped stylesheet for docs and embedding hosts is still to come.
1.3.0W1: primitives, list pieces, badges, sharing pieces
Additive. Moved from candidate-portal verbatim, its couplings replaced by the host seams (links through useHostLink(), the guard's navigation through useHostRouter()), module-level state through singleton():
/appprimitives:Badge(BadgeTone),EmptyState,PersonNamewithpersonDisplay,personText(PersonKind,PersonInput; names with email),Segmented,Tabs(TabPanel,tabPanelProps; phones get theSectionSwitcherorphone="wrap"),SectionSwitcher,ListRow/ListRows/RowCount,PAGE_CONTAINER,Menu(anyMenuOpen,openCardMenuAt; a bottom sheet on phones),BottomSheet,Dialog/ConfirmDialog(a sheet on phones, full screen option),FocusRescue(focusTrail,noteFocus,rescueFocus),usePopover/PopoverPanel(a sheet on phones), the anchored-list helpers,IconButton,chipClass,TagChip/TagChips/TagDotwithtagTone,normaliseTag,nextRadioIndex;isTypingTarget(also in/app/utils)./appfeedback:Toaster(toasts with Undo),SaveBar(focusFirstInvalid),UnsavedChangesGuard./applists:FilterDropdown(name plus count),QuickFilters,SortMenu(icon),StickyToolbar,ListSearch,SelectionBar,usePagedList(ListPage,ListCounts,FetchPage); new, generic:FilterPanel(the Filter icon button and panel: search, toggles, groups with counts, Clear) andActiveFilters(removable chips), withfilterCount,FilterGroup,FilterGroupOption,FilterToggle./resources: the Application labels (APPLICATION_STATUSES,statusLabel,statusTone,APPLICATION_CHANCES,chancesLabel,chancesTone,isOutcome,INTAKE_KEY,intakeKeyLabel,sortIntakeKeys),StatusBadge,StatusPicker,ChancesBadge,ChancesPicker,TopChoiceStar,IntakeSelect./platform(sharing, new):LevelBadge(SHARE_LEVEL_LABELS,ShareLevel),ChipFieldFramewith the Add people chip classes,EmailChips, andPeoplePicker(type-to-search people as chips with Lead stars;normLeads,PickerPerson,PeopleWithLeads).
Codemod for candidate-portal: codemods/waves/w1.json (it also retires the W0 shims lib/toast and lib/staged).
1.2.0K1: Kanban
Additive. /app gains Kanban (with KanbanLane, KanbanCardContext, KanbanProps, kanbanCountText, DRAG_THRESHOLD_PX, EDGE_SCROLL_PX), taken from candidate-portal's ApplicationBoard (the Application card stays in candidate-portal as a render prop), and the primitives it needs, moved verbatim from candidate-portal ahead of W1: Button, ButtonLink (links through useHostLink()), buttonClasses, phoneLabelClasses, useIsPhone, Tooltip (its one-at-a-time slot is a singleton()), REASON_TOOLTIP_DELAY_MS, HOVER_GRACE_MS, LONG_PRESS_MS, the disabled-with-a-reason helpers, ScrollFade and its helpers, ListMore, RowSkeletons, useInfiniteScroll (LoadMoreState), Tick, SelectHit, useAnnouncer, LiveStatus, FieldError, describedBy, withContext, and cn (also in /app/utils). Codemod for candidate-portal: codemods/waves/k1.json.
1.1.2W0: CSS sync
Patch; no API change. app/styles/components.css takes candidate-portal's components-layer rules added since 1.1.1 (the 44 px touch hit areas for buttons and 16 px text fields on touch phones, the bottom sheet's 44 px rows, and the Board's always-visible .board-scrollbar), copied by scripts/sync-candidate-css.mjs --write, so W0's switch imports exactly today's rules.
1.1.1W0: CSS sync
Patch; no API change. app/styles/components.css takes candidate-portal's muted-control hover rules (candidate-portal 2119fee, sharing D26: .btn, .btn-ghost, .btn-danger, .btn-outline, .btn-secondary, .btn-destructive and .icon-btn with aria-disabled='true' keep their resting look on hover), copied by scripts/sync-candidate-css.mjs --write, so candidate-portal's W0 switch imports exactly the rules its globals.css has today.
1.1.0W0: the layers' foundations
Additive; the core entry is unchanged (ADR-0021, platform-hq efforts/candidate-ui-to-kit W0).
- New entries:
@metalaunchlabs/ui/app,/app/utils,/app/tailwind-preset,/app/styles.css,/app/styles/*.css,/platform,/candidate,/resources(README "Layers"). The layers build in a second tsup config intodist/layers/with a"use client"banner;/app/utilsand the preset without it. - The candidate look: candidate-portal's Tailwind theme as the app preset, and its tokens, base rules and component classes (
globals.csslines 22-626, verbatim) asapp/styles/*.css, scoped under[data-ui="candidate"]byscripts/build-layer-css.mjswith zero added specificity. - Host seams:
UiHostProvider(link, router, pathname, search params, storage, viewer, formatError; all optional, nested providers merge) with its hooks, andconfigureUiRuntime. - Runtimes moved from candidate-portal, behaviour unchanged and single-copy (
globalThissingletons):toast/useToasts/toastDuration,useStaged/useStagedForm/useStagedRegistration/useAnyDirty/SaveHeld/navigateGuarded/useNavRequest. CandidateScope,candidateScopeProps,UI_SCOPE_ATTRIBUTE,CANDIDATE_SCOPE.- New dependency:
zustand^4.5.5 (layers only). - Tests:
test/app-runtime.test.tsx,test/app-styles.test.ts. - Tooling (repo only, not in the bundle):
scripts/sync-candidate-css.mjs(drift between the app layer's CSS sources and candidate-portal'sglobals.cssuntil it imports them) andcodemods/waves/w0-globals.mjs(candidate-portal'sglobals.cssswitch).
1.0.0the semver promise
No API change from 0.9.1: the same exports, entry points, props and styles. What 1.0.0 adds is a promise (ADR-0022):
- Minors are additive and patches are fixes; breaking changes happen only in a major, and only for something deprecated (with a dev-time warning, a
DEPRECATIONS.mdrow and, where mechanical, a codemod) for at least two minors before. - Old versions stay published; every app upgrades on its own schedule with
pnpm kit:upgrade, and can roll back the same way. - Every release runs every consumer's typecheck, tests and build first (
npm run consumers:check).
Release tooling (not in the bundle): the public API snapshot (api/public-api.json, test/public-api.test.ts, npm run api:update / api:check), the internal deprecated() helper, DEPRECATIONS.md, codemods/ (shipped in the package), scripts/consumers-check.mjs and scripts/kit-upgrade.mjs (repo root).
0.9.1
FeatureShowcase: running text (the description and each item's answer) is 16 px on phones and 14 px fromsmup, so body copy is never under 16 px on a phone (mobile audit 2026-10-25; the site's design-qa phone check).
0.9.0mobile-native building blocks
New components (container- or touch-native, hydration-safe, axe clean):
SnapCarousel: a swipeable row on CSS scroll-snap (native momentum, no JS drag), the next card peeking (slideWidth, default 85%), dots up to 7 slides then a "2 / 5" counter (indicator), previous / next buttons with a fine pointer (controls="auto"), arrow keys and Home / End,aria-roledescription="carousel"with slides named "2 of 5", polite announcements, the ScrollFade edge fade,bleedto run to the screen edges on the page gutter. 44 px targets on touch.nearestSlide()exported. (Carouselstays for auto-advancing promo panels.)StepWalkthrough: an annotated screenshot shown one step at a time, zoomed into eachregion(source px) by a CSS transform (zoomTransform()), caption below, Back / Next, swipe and arrow keys, "Step 2 of 4" announced. Sharp: the defaultsizesasks for the viewport times the deepest zoom. From 640 px of its own width (container query,breakpoint) it shows the whole image with numbered markers and the steps listed. Reduced motion: no glide.StickyMobileCTA: a bottom bar for one or two actions on phones only (md:hidden), shown oncetrigger(the hero) has scrolled above, hidden whilehideWhenelements (default the footer; add the in-page CTA) are visible and while a text field has focus, padded by--safe-bottom; reservesscroll-padding-bottomwhile shown (WCAG 2.4.11).SegmentedSections: columns when its box is wide, a segmented control with one panel when narrow (pricing, comparisons); container query; tab semantics when narrow.LoopVideo: a muted looping product clip. The server renders only the poster<img>; apreload="none"video mounts after hydration and gets itssourcesonly withinrootMargin(200px) of the viewport; plays while visible, pauses off screen and in a hidden tab; never loads under reduced motion; a Pause / Play button always visible (WCAG 2.2.2).
Extended:
Sheet side="bottom"is a native bottom sheet: rounded top, a grab handle you drag down to close (dragToClose, default true;SHEET_DRAG_CLOSE_PX), up to 85dvh, padded above the home indicator.SegmentedControl:role="tablist"(withidPrefix;segmentedTabIds()) to switch panels, andsize="lg"(44 px). Default stays a radio group.- Hooks:
useMediaQuery,useBreakpoint,useIsMobile,BREAKPOINTS,minWidthQuery(lib/breakpoints.ts), for behaviour, not layout. - Tokens (new category
spacing):--gutter-mobile(20 px),--safe-top,--safe-right,--safe-bottom,--safe-left; preset spacinggutter-mobile,safe-*(px-gutter-mobile,pb-safe-bottom); classes.mll-gutter,.mll-safe-top,.mll-safe-bottom.
Fixes from the marketing site's mobile audit (platform-hq efforts/marketing-website/research/mobile-audit-2026-10-25.md):
FeatureShowcase: side gutters at every width.scheme="auto"had none, so its text ran to 0 px from the screen edges on phones; it now pads by--gutter-mobile(or the safe area), 32 px frommd. The dark scheme keeps 24 / 48 px and respects the safe area.StatsSection(and soFeatureShowcasestats): a stat already on screen at load shows its final number (no count from 0 mid-read); others count as soon as they enter, in 700 ms (was 1.2 s from 10% inside). Reduced motion: final numbers, as before.Band gutter(opt-in, off by default so existing layouts are unchanged): pads content by the phone gutter when it has no container of its own.
Tests: test/mobile-primitives.test.tsx, test/loop-video.test.tsx, test/audit-fixes.test.tsx; the hydration suite renders the new components. Checked in Chromium at 320, 390 and 1280 px, light and dark (axe clean).
0.8.0
Bandtonestintandtint-secondary: the light-mode versions of the emphasis tones. In light mode they are a soft primary (or secondary) tint, so a light page never breaks into a dark block; in dark mode they are the inverse (or secondary) surface. Content keeps the page's tokens; no pattern on them (one effect per section). Tokens--surface-tintand--surface-tint-secondaryin every theme, both modes; aliases--band-tintand--band-tint-secondary.FeatureShowcasescheme="auto": follows the page's mode on a transparent background, without its own dividers, for use inside aBand(default"dark"is unchanged).ButtonIcon: the signature arrow circle on its own, for links styled as buttons (<Link className={buttonVariants()}><span>Label</span><ButtonIcon /></Link>);Buttonuses it.- Tests:
test/tint-and-button-icon.test.tsx.
0.7.3
StatsSection: columns are chosen from the number of stats so every row is full (no stat left alone on a row): up to 3 sit in one row at every width (smaller type on phones), 4 are 2×2 on phones and one row frommd, larger counts use the widest even split (statsColumns(),data-stats-cols).FeatureShowcaseno longer overrides the grid. Tests:test/stats-columns.test.tsx.
0.7.2
FeatureShowcase: the frozen belt's text is full-strengthprimary(was 90%), so it passes 4.5:1 on the lighter dark-mode inverse surface from 0.7.1 (was 4.28:1).
0.7.1
FeatureShowcase:- The frozen tilted belt is 140vw and centred, so its ends always fall outside the section (no corners on screen), with clear space above the stats.
- Stats spread evenly across the content width.
- Labels (the top eyebrow and item eyebrows) are sentence case: no ALL-CAPS or letter-spacing.
- An item without a description renders as a plain row, not a trigger with a dangling
aria-controls. - axe is clean in light and dark.
- Dark surfaces, measured in CIE L* (page 17.8, muted 22.3):
--surface-inversedark is L* 31.5 (231 8% 30.6%, was 26.8), +9.2 from muted and +13.7 from the page, so curves between them read.- Other brands' dark inverse is muted +9 L*.
--surface-primarydark is78 32% 61.5%(L* 73.6, less chroma than 0.7.0's L* 74, which glared): ink 6.83:1, ink at 80% 4.53:1. A lime below about L* 73 cannot keep 80% ink at 4.5:1, so the glare is reduced through saturation rather than lightness.- Teal is unchanged (white 7.81:1).
- Tests:
test/showcase-and-tones.test.tsx(no caps classes, belt width, even stats, plain rows, dark-tone L* targets for every brand).vitesttestTimeoutraised to 20s: the hydration suite's cold start exceeds 5s on a loaded machine.
0.7.0
Bandedges:curve: "convex" | "concave"from the band's own view, correct on both sides (a concave top used to paint the neighbour as a lens on the wrong side, with a strip and a hard edge).edgeGeometry()is exported;BandEdge.flipis deprecated and keeps its old behaviour. Verified in a browser for every side × curve on four tone pairs, light and dark: only the two band colours at each seam.SectionDivider:fill: "below" | "above"(the complement of the shape).- Curves are shallower (32 px on phones to 80 px wide) and the padding next to an edge is its depth plus the normal padding.
Bandlift: content rises 24 px to rest on entry (not on bands with images).- Hydration-safe motion: new
useMotionAllowed()(false on the server and first client render).StatsSection,TextReveal,ScrollReveal,ScrollScreens,Cursor,ModalCards,HeroandBandrender the same markup on the server and the client whatever the reduced-motion setting (fixes React errors 418/423/425 and the root re-render that droppedhtml.dark); Band edges render at full depth on the server and never move under reduced motion. Test:test/hydration.test.tsx. - Surfaces by CIE L* (metalaunchlabs; light / dark): page 96.1 / 17.8; muted 94.1 / 22.3; inverse 17.8 / 26.8. New
--surface-primaryand--surface-secondary(light = the accent; dark = softened): lime dark78 51% 52.1%(6.3 L* softer), ink text 6.93:1 at 100% and 4.60:1 at 80%; teal dark181 23% 28.1%, white text 7.81:1 (light: lime 8.33:1 / 5.22:1 at 80%, teal 5.03:1). Preview brands:mono-slateandpreview-blueprimaries andsunset-coralandmidnight-violetsecondaries were already under 4.5:1 in light mode; unchanged here.
0.6.0
Band: one effect per section. Apatternrenders only on calm tones (background,muted,card); oninverse,primaryandsecondaryit is ignored, with a development warning. Docs and demos follow the rule.
0.5.1
Band:mutedno longer addsgrainautomatically. Edges are flat fills, so grain beside a curved edge showed a faint step; textures are now opt-in (pattern).
0.5.0
Band: a full-width section in one tone (background,muted,card,inverse,primary,secondary), optionally textured (grain,dots,grid,stripes,ruled; static), withSectionDivideredges on either or both sides painted in the neighbour's tone. Edges deepen from nearly flat as they scroll into view; static under reduced motion. Fills are frozen on<html>(--band-*indefaults.css) so an edge inside an inverse band paints the page's colour. Docs:band.mdx; tests:test/band.test.tsx.- Tokens
--surface-mutedand--surface-inverse(schema keyssurfaceMuted,surfaceInverse) in every brand, light and dark. BrowserFrame(address bar) andPhoneFramefor product screenshots.
0.4.1
Table: the actions column's sr-only "Actions" label is contained by its header cell, so it no longer escapes the horizontal scroller and widens the page on a phone.
0.4.0accessibility, WCAG 2.2 AA; audit in platform-hq `efforts/accessibility`
- Contrast. Default and
metalaunchlabsstatus tokens now pass 4.5:1 as text and with their foreground as a fill: light mode uses deep shades with white foregrounds (warning's foreground is now white), dark mode light shades with ink foregrounds.--secondaryis181 23% 38%(was 43%). Muted text in components is at least/70(was/40–/60), placeholders/70, decorative icons/60; Badgeprimaryand Avatar initials use ink on a tint. The preview brands (preview-blue, mono-slate, midnight-violet, sunset-coral) are unchanged. - Keyboard.
DialogandSheet:⌘/Ctrl+Enterruns the primary action. New<InputModality />setshtml[data-modality]; with it, base.css hides focus rings on buttons, links, tabs, options and menu items after a mouse action, and overlays always return focus to their trigger (a trigger-less Dialog returns it to what had focus). Render it from a Client Component. - Names.
Selecttakesaria-label,aria-labelledby,id,name,required.ProgressBaris named by itslabeloraria-label.TextInput/TextareatieerrorMessageto the field (aria-describedby).Table: search box named, Rows per page select named, row checkboxes named after the first column,aria-sort,scope="col", named actions column.Breadcrumbs,Pagination,Sidebartakearia-label. - Scrolling and targets.
ScrollFadeis a tab stop while it overflows;ScrollAreais a tab stop and takeslabel(role region). Carousel and ScrollScreens dots have 24px targets. - Reduced motion. Entrance, exit and reveal keyframes finish at once under
prefers-reduced-motion; spinners, pings and shimmer keep running. - Tests:
test/accessibility.test.tsx(axe-core structural rules in jsdom, keyboard, names).
0.3.0
ScrollFade+useScrollFade(+measureScrollFade,scrollFadeMask): a row that scrolls sideways fades out on the side where more content waits, nothing when it fits (fe.md §22). A CSS mask (correct in light and dark), inline styles only (works without the Tailwind preset), updates on scroll / resize / content change, RTL-aware, and keyboard focus scrolls the item clear of the fade (instant under reduced motion).Tabs: the tab list scrolls sideways with the fade when the tabs don't fit (triggers no longer wrap or squash).SegmentedControl:max-w-full; scrolls sideways with the fade when the segments don't fit.Table: the horizontal scroller is aScrollFade.
0.2.0
New components (app-screen patterns, first used by MetaLaunch, the candidate product):
PageHeader— title, one primary action, optional status / info / back slot / ⋯ menu.Nudge(+nudgeVariants,nudgeActionClassName) — the one attention line per screen;warning(default) orprimarytone.SaveStatus— Saving… / Saved / Couldn't save · Retry, in a polite live region.StarRating+StarRatingInput— read-only rating and an accessible five-star radio group.SegmentedControl— side-by-side mutually exclusive choices (radio-group semantics).
Extensions (all backwards compatible):
Button:variant="danger"(filled, for destructive confirmations).Badge: forwards HTML attributes (title,id,aria-*, …).Dialog:triggeroptional (fully controlled use),title/descriptionaccept nodes,eyebrow,hideDescription,role="alertdialog",dismissible,initialFocusRef; long bodies scroll inside the dialog.DropdownMenu: itemlabelaccepts nodes (+textValue,key),iconaccepts an element,onOpenChange.Tabs: itemcontentoptional (strip-only use),label(list name),listClassName, node labels.EmptyState:title/descriptionaccept nodes.InfoTooltip:label→ the trigger is named "About {label}".DropdownMenu:loop(arrow keys wrap) andaria-label.ToastProvider:viewportClassName(e.g. lift above a phone bottom nav).- Package tests (vitest) added.