Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelog App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip
ARC UI ARC Radiant Components
v3.2 Docs Components Tokens Synthesizer
Getting StartedFrameworksServer Rendering Design TokensThemingTheme SynthesizerTypographyUtilities All ComponentsAccessibilityBrowser SupportChangelogContributingStats App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip

Changelog

Release history for ARC UI. All packages are versioned together.

v3.2.0 — Pinned Regions Stop Guessing At The Page

3.1.0 shipped four classes where there are two ideas. -soft was published as a sibling of the scheme, as though "dark" and "dark, softened" were independent things a consumer picks between. They are not: the lifted colour is simply where a dark pin lands when the page is light, because it is not allowed to go full light. Naming a resolved state alongside the choice that produces it turned one concept into a two-axis API and a pile of machinery underneath it.

Two classes now, each with two landing points.

The Two Classes

<header class="theme-fixed-dark">…</header>   <!-- near-black on dark, deep accent on light -->
<aside class="theme-fixed-light">…</aside>    <!-- near-white on light, deepened on dark -->

On a page of its own scheme a pinned region is plain — a dark nav on a dark page is near-black, nothing to do. On a page of the other scheme it lifts to a colour derived from your accent: the accent drawn over black for a dark region, over white for a light one. Deep navy is what ARC's blue produces; a teal accent gives a deep teal, a monochrome one stays grey. -soft is gone from the API entirely.

Each block restates every :root property that resolves against it — the semantic aliases, the glows, the focus recipes. A custom property substitutes where it is declared and then inherits the resolved value, so without that restatement --surface-base and its neighbours arrive carrying the page's colors and quietly ignore the scheme set above them. That list is computed to a fixpoint rather than hand-kept.

Breaking — .theme-fixed And The -soft Pair Are Gone

.theme-fixed was an alias for .theme-fixed-dark; there is no reason to carry two names for one thing. Rename it, and nothing else about it changes — including its behavior on a light page, which is what it always did.

.theme-fixed-dark-soft and .theme-fixed-light-soft shipped in 3.1.0 and are removed. Drop the suffix: .theme-fixed-dark now resolves to the lifted colour on a light page by itself.

The Softened Grounds Are Brighter

They were fitted to match the hand-tuned deep blue they replaced, which meant "softened" landed exactly where the old dark island already sat. bg-deep goes from L 0.187 to L 0.250.

A second bug stacked on that one: the mix took its source from the page's accent, and the light scheme's accent is deliberately darker than the dark scheme's — so the same percentage produced a darker region on a light page than on a dark one, which is backwards for the mode softening exists to serve. The source is now re-lit to the scheme's own accent lightness first. The page supplies hue, the scheme supplies lightness.

Borders were refitted with it. Taken from the contrast solve alone they landed on border-subtle being identical to bg-card — a card outlined in its own fill. They now sit above every surface step, spaced to reproduce the dark scheme's own border-to-card ratios.

Fixed — Gradients Faded To Black

Sixteen gradients in the token tree ended on the transparent keyword. transparent is rgba(0, 0, 0, 0) — transparent black — so each of them darkened on the way out instead of thinning, and left a hard edge where the gradient met its own box. Every divider, both glow hairlines, the page and section ambients, in both themes.

Nothing showed for as long as every surface was near-black, because black is what the fade was drifting toward. The softened schemes put those same gradients on a lit ground and the cuts appeared as rectangles. Each fade is now the adjacent stop's color at zero alpha, and pnpm check gradient-stops fails the build on a bare keyword.

Fixed — Your Accent Reaches The Glows

A pinned region restated --accent-primary-rgb and --accent-secondary-rgb as literals. Those channel triplets back every glow, tint and focus ring in the library, so an orange theme produced an orange nav wearing a blue focus ring. A triplet cannot be derived — relative color yields a color, not three bare numbers — so they inherit now. arc-top-bar also drops its translucent fill inside all four classes rather than the two it knew about.

Upgrading

Rename .theme-fixed to .theme-fixed-dark, and drop -soft from the two classes that carried it. That is the whole migration.

If you are coming from 3.0.0 or earlier, .theme-fixed behaved this way already — near-black on a dark page, a deep blue on a light one. The difference is that the deep blue was a hand-tuned palette with its own pinned accent and text ramp, and is now derived from yours.

v3.1.0 — One Contrast Contract, Four Schemes, and Pinned Regions That Follow Your Brand

v3.0.0 consolidated the token tree into one generated source. This is what using it revealed. A pinned nav — the always-dark bar over a light page — had been carried by a hand-tuned deep blue with its own accent literal, its own text ramp and its own borders, because a light-tuned accent on a dark ground lands at 3.55–4.00 and had to be rescued by hand. That island turned out not to be a palette at all. It is one of the two schemes the library already generates, applied to a subtree instead of to :root, and the moment it is written that way the rescue stops being necessary: the dark scheme's accent on the dark scheme's ground is 5.53 with nothing pinned.

Pulling that thread found the reason the rescues existed. The light theme shipped at the AA line with nothing to spare — its accent measured 4.30:1 as text on its own page, already under AA — so every variation attempted on top of it had to be hand-corrected somewhere. Same missing headroom, five different symptoms.

The Contrast Contract

One contract, four schemes: dark, light, and a softened variant of each. Every scheme solves its own foregrounds against its own ground, so none of them is a special case and none of them needs tuning by hand.

The text ramp takes the dark scheme's three ratios rather than a flat floor, because flooring secondary/muted/ghost at one number collapses them into each other. Borders take dark's own ratios — the contract for a divider is "as visible as it is in dark mode", not a WCAG number; the dark scheme's border-subtle on the old softened ground measured 1.06, present in the stylesheet and invisible on screen. Everything else clears 5.5, which is AA plus the headroom dark already carried.

The light palette moves as a result. Text, accents and status colors all lift: --accent-primary goes to rgb(40, 87, 215) at 5.48:1, --text-ghost to rgb(91, 94, 116) at 5.71, and the statuses land between 5.47 and 5.91. Dark moves exactly one value — --accent-secondary was 4.86, the only entry under the floor the rest of the tree already cleared. A build-time assertion now fails on any value that drifts under, so this cannot quietly come back.

Pinned Regions

Four classes, and .theme-fixed keeps its meaning as the dark one:

<header class="theme-fixed-dark">…</header>
<header class="theme-fixed-dark-soft">…</header>
<aside class="theme-fixed-light">…</aside>
<aside class="theme-fixed-light-soft">…</aside>

A pinned region declares nothing on a page of its own scheme. "Fixed dark" on a dark page means the page's dark mode, every token you retuned included. Restating the library's own literals there does not pin the scheme, it pins our palette — which was invisible until you themed the library, and then produced a warm page with two stubbornly neutral bars welded across the top of it. The scheme is forced only where the page is the opposite one.

The -soft variants are the escape hatch from a black slab against near-white. The same scheme, moved a tenth of the perceptual distance toward the page around it and tinted with your accent — literally the accent drawn over black for a dark region, over white for a light one. Deep navy is what ARC's blue produces; a teal accent gives a deep teal, with no second palette to keep in step.

Your accent reaches inside. A pinned region declares its scheme on its own element, which beats anything inherited — that is what makes the pin hold, and it is why :root { --accent-primary: … } never got in. The region cannot derive from a property it declares, so :root captures the accent under a private name pinned blocks never shadow. Override --accent-primary exactly as before; the nav follows. Only the lightness is pinned when re-lighting it — chroma and hue pass through, so a monochrome brand stays monochrome.

The derived path sits behind @supports (color: oklch(from red l c h)). Without relative color syntax a pinned region keeps the baked palette and simply stops following.

Fixed

Scoped overrides half-applied. A custom property substitutes where it is declared, so --divider-glow, --gradient-ambient, --gradient-page-ambient, --gradient-border-glow, both --glow-line-* and --gradient-accent-text were carrying the page theme's colors into pinned regions regardless of the scheme forced above them. The set of properties a region must restate is now computed to a fixpoint instead of hand-listed.

arc-top-bar drops its translucent fill inside every pinned class, not three of them. It paints --surface-base mixed 85% with transparent, which costs nothing when the region's scheme matches the page and renders the identical token visibly lighter when it does not — so a softened top bar sat at 85% over a light page while a plain div beneath it painted the same token flat. check pinned-schemes gates the list against the stylesheet that defines it.

The docs token reference read from a hand-copied table that had drifted independently of the source — it listed --text-secondary as rgb(138, 138, 150) against an actual rgb(150, 152, 162). It reads from shared/tokens.js now, as does the llms-full.txt token block.

Upgrading

Nothing to rename. .theme-fixed still means dark, and the three new classes are additive.

Two things will look different and both are deliberate. Light mode is darker in its text and accents, because it was under AA. And a pinned region on a page of its own scheme now inherits that page's tokens rather than the library's, so if you had retuned the theme and wondered why your nav ignored you, it no longer does.

v3.0.0 — One Event Contract, One Token Tree, and Every Component on the Server

v2 grew by addition. A component needed a close event, so it got one; the next component needed the same thing and got a different name for it. Multiply that by 163 components and the result is a library where every surface is defensible on its own and the whole is a dialect you have to learn per element — five names for "something was selected", four hand-rolled listboxes, 62 components each spelling their own glow, and a token layer maintained by hand in two places that had drifted at 19 of 81 values.

v3 is the consolidation. The event contract collapses to one vocabulary, the floating layer to one controller, motion and focus and glow to scales generated from shared/tokens.js, and every one of the 182 components server-renders. The breaking changes are the cost of that; each is mechanical, and MIGRATION.md records every one with the fix beside it.

Breaking — One Vocabulary For Events

User-initiated closes — Escape, backdrop, close button, outside click — now fire a cancelable arc-close before closing, across the 14 dismissible overlays, and honor preventDefault(). The 8 components that emitted arc-dismiss emit arc-close instead. The four selection events (arc-item-select, arc-row-select, arc-select-all, arc-selection-change) collapse into one arc-select carrying the selection in detail.value, and every arc-change gains a canonical detail.value alongside its component-specific keys. Calendar's month navigation becomes arc-month-change — it had been impersonating the router's arc-navigate — and navigation-menu dispatches from its host rather than document.

arc-input now means an edit and arc-change means a commit, everywhere. The last four controls firing arc-change per keystroke or per pointermove are split: arc-number-input commits on blur or Enter, arc-otp-input and arc-pin-input commit once the code is complete, arc-color-picker commits on drag release. Expensive work bound to arc-change ran per character before this; code inputs submitted every incomplete prefix.

check-event-conventions bans the retired names at build time, so a missed listener is a grep away rather than a silent no-op.

Breaking — The Floating Layer, Rebuilt

Fifteen floating components position through one shared controller and promote their panels with the native popover API, so panels render in the top layer. They are no longer clipped by an overflow: hidden ancestor and no longer fight the --z-* ladder. If a layout depended on a panel being clipped, it will now escape — that is the change working.

Listbox options are no longer focusable <button> elements. Virtual focus keeps DOM focus on the control, which is what the ARIA pattern asks for and what four components had each approximated differently.

Breaking — Tokens Reach Shadow DOM

The :host token layer is generated from shared/tokens.js rather than maintained by hand. 19 of 81 values had drifted; --text-3xl, --radius-xs and --label-inline-size change rendered output. base.css forwards 71 tokens into shadow DOM, so a :root override finally reaches components — re-check any override you had written off as a no-op, because it may take effect now.

194 of 295 physical CSS declarations became logical, so a layout mirrors under dir="rtl" without a second stylesheet. Date names come from Intl, with locale and first-day-of-week props.

Breaking — Packaging

@arclux/arc-ui moves from an exact-pinned hard dependency to a peer of all six wrappers, and lit becomes a peer of the web components package. The exact pin guaranteed dual installs and dual Lit registries on any version skew, with @lit/react holding a class identity from the copy the DOM never upgraded. Framework peers get real bounds: react ^18 || ^19, vue ^3.3, svelte ^5, solid ^1.8, preact ^10.19, @angular/core >=17 <22.

All six wrappers now compile real outputs — Svelte via svelte-package, Vue via a Vite lib build with declarations, Solid with a compiled fallback and a solid condition, Angular via ng-packagr APF, React and Preact via tsc. Exports maps are generated per framework with per-component subpaths, and the './*' wildcard that published every internal file as unversioned API is gone. pnpm smoke:wrappers packs each tarball and builds it in a consumer, which nothing verified before.

shiki and @shikijs/langs become optional peers, and arc-code-block leaves every barrel — 13.6 MB of highlighter was in every consumer's module graph for one component most never render. Reach it at @arclux/arc-ui/code-block. A missing shiki is a supported state: code renders uncolored with a one-time warning saying how to fix it.

Three props that shadowed DOM members are renamed — arc-diff before/afteroriginal/revised, arc-nav-item childrensubItems, arc-gradient-text animateanimated. A prop shadowing an HTMLElement member makes the element structurally not an HTMLElement, which hard-blocked compiled React output. And the 65 components with no slot of any kind are annotated @slot none, so their wrappers no longer advertise a children prop — <Spinner>hello</Spinner> type-checked while the content landed unassigned and vanished.

Breaking — arc-virtual-list Actually Windows

v2 built all N rows in the light DOM and hid all but a dozen: the scroll was virtual, the cost was not. Rows now come from renderItem(item, index) or from windowed item-N slots covering just the visible range, announced via arc-range-change. scrollToIndex() is new. The six wrappers are hand-authored and render the window in their own idiom — a renderItem prop in React, Preact and Solid, a row scoped slot in Vue, a row snippet in Svelte, a rowTemplate in Angular — and no longer accept arbitrary children.

Also folded: arc-toast-manager into arc-toast, and arc-form now delegates to its controls rather than reimplementing them.

New — Nineteen Components, and the Catalog Reaches 182

Shaped by what a downstream DAW needed, which is a better brief than a gap in a grid:

  • contentarc-lightbox, arc-image-compare, arc-image-hotspots (with arc-hotspot), arc-video
  • dataarc-gauge, arc-activity-heatmap, arc-json-tree, arc-uptime, arc-waveform, arc-level-meter, arc-clock
  • inputarc-signature-pad, arc-masked-input, arc-tree-select, arc-inline-edit, arc-knob
  • typographyarc-terminal, arc-keyboard-map
  • feedbackarc-conversation (with arc-message)

Every one server-renders to declarative shadow DOM, draws motion, focus and glow from the token tree, and the form controls ride form-control-mixin with the edit/commit contract above. arc-sidebar-link learns external links along the way.

New — Server Rendering For Every Framework

All 207 elements server-render, and CLIENT_ONLY is empty. arc-markdown was the last holdout and had two problems: a sanitiser that needed DOMParser, and a .innerHTML=${…} property binding the client applies after upgrade, so the server had nothing to serialize.

The larger change is that this is no longer Astro-shaped. Every framework integration for Lit SSR works from the component graph, which is why only React ever had one and five wrapper packages were documented as unsupported. Nothing about the problem needs the graph: a framework's server render produces HTML, and every <arc-*> in it can be rendered to a declarative shadow root from that markup alone. renderDeclarativeShadowDOM takes HTML and returns HTML, published as @arclux/arc-ui/ssr, so Nuxt, SvelteKit, Angular Universal, Next and Astro are one call each. @lit-labs/ssr is an optional peer.

This is not new code on trust — it is what renders this site, 200 pages and roughly 20,000 shadow roots per build, with the Astro integration reduced to file IO around it.

New — A Utility Layer Generated From The Tokens

Components own everything inside their shadow boundary and nothing outside it, so every consumer writes the same flex, gap and padding CSS around them — against 381 custom properties base.css already published with no ergonomic way to use them. 272 classes, 1.9 KB brotli, opt-in at @arclux/arc-ui/utilities.css.

What makes it worth shipping rather than pointing at Tailwind: these are generated from the same tokens.js the components compile against, so .arc-p-md and an arc-card's internal padding resolve to the same value, and overriding --space-md moves both. Every declaration reads a var() rather than a literal, which is the whole point. Logical properties throughout — x/y for the inline and block axes, s/e for start and end — so a layout built from these mirrors under RTL, which a utility layer emitting left/right would quietly undo at exactly the place consumers write the most CSS.

Scope is held to layout and surface. No responsive prefixes, no state variants, no arbitrary values; that is where a utility layer becomes a framework, and the ceiling is deliberate. Documented at /docs/utilities, including the fact that a class cannot cross a shadow boundary.

Changed — Motion, Focus and Glow Become Scales

Motion. Duration and curve are separate scales composed into the --transition-* shorthands: --duration-fast/base/slow/enter/exit and --ease-standard/out/in/spring. 356 transition sites had been using the browser-default ease keyword while the two published curves were referenced four times, and fifteen animating components had no prefers-reduced-motion guard at all. The guard now lives in shared-styles.js and shortens to 0.01ms rather than cancelling, so animationend still fires. check-motion-tokens fails the build on a bare timing keyword or a literal cubic-bezier.

Focus. The rule was already latent — 48 components had picked the glow and 13 the ring — and it is not arbitrary: glow for a bounded control, where light gathering around the box reads as the box being addressed; ring for an inline or dense target, where a 40px glow would wash over the neighbours instead of marking what is focused. Written down at tokens.focus. Every component agreed except arc-carousel, which had them inverted. Four hand-written recipes become tokens — --focus-error, --focus-inset, --focus-thumb — each a case the two treatments could not express. check-focus-ring fails the build on a hand-rolled focus shadow or an outline suppressed under a bare :focus.

Glow. A census rather than a line count: of 262 box-shadow declarations, two are cast shadows. The rest were glows — 62 literals at forty-odd distinct blur/alpha pairs — which is why the published glow tokens went unused. --glow-xs, --glow-sm and --glow-md take the exact values of the three clusters the census found, so fourteen literals collapse onto them and nothing moves on screen. The scale earns its place by being where the sixty-third glow goes.

Hover now adds light rather than moving an edge, at the eighteen sites that were changing a border and adding nothing: badge, tag, carousel arrow, copy-button, theme-toggle, top-bar menu, navigation-menu triggers, scroll-to-top, two grid checkboxes and the toggle track. These sit in tables and toolbars where a shifting border repeats on every pointer crossing. arc-badge and arc-tag drop from six hover rules each to one, both adopting statusVars and --glow-status — which required settling that info had been two different blues depending on which component you asked.

Changed — The Label Face

The label role — form labels, table headers, eyebrows, the small caps that run across the library — is set in Tomorrow. Tektur held it through v2.

It is not a rename. Tektur is a display face and Tomorrow is a proportional sans, so the fallback moves off the monospace stack it had been carrying: falling back from a proportional face to a monospace changes the width of every label on the page, which is the kind of difference that only shows up on a machine missing the font. The label tracking was tuned for a face that carried its own letter-fitting and comes back up to suit one that does not — sectionTitle from 1px to 2px, uiAccent from 0 to 0.5px.

The role is what components reference, so nothing in a consumer's code changes. Anyone who was styling around the old face's metrics will want to look at their labels once.

Changed — The Theme Solves Its Own Contrast

shared/color.js is new: sRGB and OKLCH conversion, WCAG luminance and contrast, alpha compositing so a role is measured against the tint it actually sits on rather than the page behind it, and two solvers — one that moves a seed's lightness until it clears a contrast target while holding hue and chroma, one that solves the color-mix percentage a blend needs.

The first thing it owns is --accent-text-mix, the amount of --text-primary blended into any accent used as text. That was 55% in the light theme and 0% in the dark, with nothing written down about where either came from. It is derived now, and deriving it was instructive: solved against the accents' own tints it came to 15%, against every surface 20%, and both broke pairings the 55% had been quietly carrying. The pairing that had failed in the first place was a grey — arc-tag takes a color as a prop and this site passes it greys for the component tiers — so solving against the six accents the library ships was answering a question nobody asked. The light theme sweeps the space it is really exposed to and lands back on 55%. The number does not move; it moves correctly from here.

The dark theme solves against the palette instead and comes to 10%. The sweep's answer there was 50%, which would wash out every accent that was already fine to rescue dark-saturated hues that were marginal before any of this. That trade is a policy — how much a library owes someone who hands it a color that was never going to read on their background — and v3's answer is that the color stays as given and arc-tag warns in the console when it cannot be read. The alternative is repainting a consumer's brand without telling them.

Fixed — The First slotchange, Which Declarative Shadow DOM Eats

Server rendering shipped earlier in this release, and it took a class of bug with it that only exists once markup arrives pre-rendered.

Under declarative shadow DOM the parser attaches the shadow root and assigns the light-DOM children before any script runs. The element then upgrades and Lit adopts that tree rather than constructing it, so no new slot element is created and no assignment ever changes — and slotchange, which fires on assignment changes, never fires at all. It is the DOM's behavior rather than any framework's: the same page rendered by any SSR path, or written by hand with a shadowrootmode template, breaks identically.

For most components that is harmless. It is not harmless for the ones that mirror their children into their own markup and hide the originals: they upgrade with nothing and render an empty mirror over hidden content, which is indistinguishable from the component not being there. arc-segmented-control was the first, as an 8px sliver on every server-rendered page including its own documentation. arc-navigation-menu was the second, and took this site's entire top-bar navigation with it — four links, gone, with the markup sitting correctly assigned in the HTML the whole time.

shared/hydrate-slots.js dispatches the event DSD ate, so each component's existing handler runs unchanged, and fires again on DOMContentLoaded when the document is still parsing — a component near the top of a page reaches its first render before the parser has reached the children it is waiting for, which is how arc-app-shell decided it had no table of contents and collapsed the rail. check-slot-hydration fails the build for any component that listens for slotchange without reading its slots on first render. It found thirty-one.

The rule is deliberately broader than the failures that prompted it. The first version only flagged components that hide a slot host, passed, and missed the one sizing a rail from the same read.

Fixed

  • arc-lightbox showed the previous image before the one you clicked. The closed viewer still renders an <img> at index 0, so swapping src on that element left the decoded frame on screen until the new source arrived. Each source gets its own element now, and fades in on load.
  • arc-waveform's playhead trailed the played/unplayed boundary. The boundary is an SVG clip that snaps to position while the playhead rode a transitioned rail — two readouts of one value with different latencies, and against a position advancing every frame the gap never closed.
  • arc-activity-heatmap carried ten pixels of dead space above its months row: an inline-grid inside an inline-block host sits in a line box and inherits the host's half-leading, which also gave it a sub-pixel height that put a scrollbar in any surrounding scroller.
  • arc-activity-heatmap and arc-uptime anchored their hover detail inside the track, so a year-wide heatmap in a horizontal scroller clipped a 57px bubble to a 12px sliver. Both use the shared position controller now and promote to the top layer, which retires two hand-rolled placement helpers — percentage placement with edge pinning was an approximation of flip and shift.
  • arc-video's control bar left entirely after two seconds of idle. It dims instead: still legible, still clickable, back to full strength when the pointer is anywhere over the player or a control takes focus.
  • arc-cta-banner's wash was inset: 0 inside an overflow: hidden host, so the glow stopped dead on the component's own top edge — a straight line across a dark page with the section above it unlit.
  • arc-scroll-spy's current-section link was 4.17:1 in the light theme. The accent on its own 6% tint costs 0.73 against the 4.90 the same blue manages on plain white, and it was the one state marking where the reader is. It reads through the theme's solved text mix now.
  • An immersive top bar stayed transparent behind an open mobile menu, so a filled, blurred panel hung below a bar still showing the hero through itself.
  • Five props declared with only a custom converter or attribute: false gave the generator no type to read, so the Angular wrappers typed them string against elements whose own declarations say boolean or object, and the package stopped compiling.
  • Status utilities pointed at tokens that never existed (--success for --color-success); all eight resolve now.
  • managedPanelStyles fed transition-duration a duration-plus-curve shorthand, which is invalid, so panels snapped instead of fading. Durations are durations again, and open and close each carry the curve the motion scale prescribes.
  • The glow and focus recipes sat on :host with dark alphas baked in, so light mode could never reach them. The forwarder holds back only shadow-private references, and the focus recipes gain their light values.
  • form-control-mixin's updated() hook ran in 2 of 22 consumers. Every consumer calls super first now, nine pure-duplicate overrides are gone, and a late required toggle finally resyncs validity.
  • arc-icon could never server-render a named icon.
  • arc-transfer-list groups its move controls by scope rather than direction.
  • arc-data-grid's column pinning was the one system the RTL pass left physical, and it was physical in three places at once: sticky offsets written as left:${n}px from JS, an edge shadow cast rightward, and a scrollLeft > 0 test that RTL's negative scrollLeft could never satisfy. Converting any one of them alone would have been worse than leaving all three, so they move together — inset-inline-start, a :dir(rtl) shadow, and a magnitude test. Column align follows: left and right stay the public spelling but resolve to the inline axis, so text no longer parts company with the column it sits in.
  • Four controls suppressed the focus outline under a bare :focus rather than :focus-visiblearc-data-grid's cell editor, arc-input, arc-masked-input and arc-password-input — and eight declarations across arc-avatar-group, arc-divider, arc-input and arc-masked-input were still physical. Both sets were invisible to the checks that exist to catch them: check-focus-ring and the RTL sweep each matched line by line, so a rule written on one line hid from the tool looking for it. Both now read selector/body pairs out of the css templates, which is the same fix check-motion-tokens needed for the same reason.

Also In This Release

Three new build-time checks close classes of silent failure. check-component-docs asserts a component's JSDoc doesn't claim a surface it lacks — a @csspart nothing renders, a @fires nothing dispatches, a @prop that is neither reactive property nor accessor. All 190 elements pass today, which is the useful result: it locks in a property the codebase already has rather than paying off a backlog. These claims flow outward into custom-elements.json, the VS Code and JetBrains data, the docs tables and six wrapper packages, so a false one gets repeated everywhere rather than staying in one file.

CI runs the full check suite instead of one hardcoded member, and release.yml gates on tests, checks and the wrapper smoke run before anything reaches npm — a tag on a red commit no longer publishes.

On the docs site: Getting Started is rebuilt around the path a reader actually takes — install first, tabbed across the package managers and the CDN rather than three stacked panels restating the same line, then something rendered in three steps, then the questions that only arise once it works. The registration-flash section becomes "Going to Production" and moves to the end, since it is an optimization rather than a step. Every code block across the guides is arc-code-block, which is where the copy buttons come from — there had been three different presentations in play, and the library's own documentation was the one place not using the library. docs.css names the measure its prose rules had been hard-coding, so a snippet stops running to the full width of the content column while the paragraph above it stops at 640px. A new /docs/stats page reports what the library is made of, every figure derived from the repository at build time through the module the landing page and the social card already share.

Also on the docs site: the theme synthesizer moves to /synth with a page of its own, the landing page gains a customization console that rewrites a theme.css live, search reads the content rather than the page titles, and the command palette gets fuzzy ranked search. @arclux/brand moved out to its own repository — it sits on its own 1.x and was being republished by every component release.

v2.x archive — 15 releases, v2.0.0 through v2.11.1

v2.11.1 — Every Button Gets Its Label Back

2.11.0 shipped framework wrappers that rendered a component's named slots and dropped its default one. <Button>Save</Button> rendered its prefix and suffix and no label. Fifteen components were affected in all six framework packages — 90 wrappers. If you are on 2.11.0, upgrade; there is no workaround short of the web components directly.

What Broke

The wrappers are generated by prism, whose 2.7.0 release added named-slot forwarding for Svelte and Vue — a genuine gap, since 104 named slots had been unreachable in those two packages. It replaced the default slot instead of adding alongside it:

  • svelte{@render children?.()} became {@render prefix?.()}{@render suffix?.()}
  • vue<slot /> became <slot name="prefix" /><slot name="suffix" />
  • angular<ng-content /> was dropped, removing all projection
  • solid / preact — the children passthrough was dropped from JSX
  • react — type-level only: children left the props interface, but @lit/react forwards it at runtime, so React consumers saw a TypeScript error rather than a missing label

Affected: arc-button, arc-accordion, arc-icon, arc-comparison, arc-list-item, arc-stepper, arc-context-menu, arc-multi-select, arc-segmented-control, arc-sortable-list, arc-breadcrumb, arc-footer, arc-navigation-menu, arc-scroll-spy, arc-sidebar. The common factor was a default <slot> that does not appear literally in the component's top-level template — arc-button's sits in a helper method returning a nested template — so the generator's template scan never saw it.

Fixed

prism 2.8.0 emits both the default and the named slots, and distinguishes a slot it observed as real markup from one it only knows about through a JSDoc tag — only the former can justify omitting anything. Named-slot forwarding is retained, so this is not a rollback of 2.7.0.

How This Reaches npm Less Often

Nothing in this repository consumes a generated wrapper, so no test could see the breakage, and the typecheck gate covers only the web components' own declarations. A run was entirely green while the files on disk were wrong. Two checks now close that:

  • check-wrapper-slots — every slot a component declares must reach every wrapper generated for it, with a per-framework notion of what "renders the default slot" means. 714 wrappers, part of pnpm generate, so it gates the release rather than reporting after it.
  • prism's own verify.js — reads each wrapper back after writing it. Previously every check inspected inputs, which is exactly how a green run produced broken output.

Also In This Release

  • 21 tests covering the 14 components whose 2.11.0 default-variant selectors were rewritten mechanically, plus the shared positioning stylesheet. Each asserts an unrecognized enum value matches the default and that a real member differs, so a test measuring the wrong property fails rather than passing green.
  • No behavioral change to the components themselves — 2.11.0's web components were correct; only the generated wrappers were affected.

v2.11.0 — Unknown Variants Fall Back, Weights You Assign

<arc-button variant="outline"> rendered a button with no background, no border and no padding, and said nothing about it. outline is a reasonable guess at a variant name — it is not one of the three this library has, and the default styling was selected by the absence of the attribute, so a reflected-but-unrecognized value matched no rule and could not reach the default either. This release makes every enum-driven default catch the values it does not recognize, across the whole library.

Fixed — An Unrecognized Enum Value No Longer Renders Nothing

Defaults were keyed on :host(:not([variant])). They are now keyed on "not any of the other members" — :host(:not([variant="secondary"]):not([variant="ghost"])) — which matches an absent attribute, the default, and anything unrecognized alike. 39 selectors across 18 components: arc-button, arc-icon-button, arc-divider, arc-icon, arc-inset, arc-modal, arc-sheet, arc-drawer, arc-tooltip, arc-toast, arc-snackbar, arc-progress, arc-progress-toast, arc-prose, arc-scroll-area, arc-scroll-to-top, arc-gradient-text, arc-textarea, and every panel using the shared position styles.

arc-gradient-text variant="custom" was a case of the same bug reachable without a typo: custom is a documented member whose gradient arrives as an inline style, so setting it without gradient left transparent text over no background — invisible. It now falls back to the accent gradient.

A new generate-time check, check-enum-fallbacks, fails the build on any enum prop that keys its default styling on attribute absence, so this cannot come back. Note that @arclux/arc-ui/dev already warned about invalid enum values at the console — the fix here is what keeps an unwarned production build looking right.

New — A Weight Per Typographic Role

The roles exposed a family and a fallback but not a weight, so the weight each role is set at was compiled into every component — arc-button at 600, and fifty-odd others besides. Assign a face whose heaviest cut is lighter than that and the browser synthesises the difference, which reads as smeared type rather than as a missing weight.

  • --font-body-weight — 500
  • --font-label-weight — 600, and what 62 declarations across the library now reference
  • --font-mono-weight — 400
  • --font-display-weight — 500. Display follows the Text role for its family but not for its weight; large type usually wants its own.
  • --font-quote-weight — 200

The existing context weights now reference the role rather than restating a number: --section-title-weight and --ui-accent-weight follow --font-label-weight; --heading-weight, --display-xl-weight and --wordmark-weight follow --font-display-weight; --body-weight follows --font-body-weight. Overriding a role reaches everything wearing it, which is the same rule the gradients and focus rings already followed. Defaults are unchanged, so assigning nothing changes nothing.

Fixed — Field Text Was Three Different Weights

arc-input set its field text at 300, arc-select and arc-textarea at 400, arc-number-input at 500 — the same kind of text, to a user, at three weights. All seven text-entry surfaces (adding arc-password-input, arc-multi-select and arc-combobox) now read one new token, --field-weight, defaulting to 400. Field text is its own context rather than the body weight, because a form value wants the weight a native input has.

New — Identifying an Editing Target Through a Shadow Boundary

A keypress inside arc-textarea's <textarea> is retargeted on the way out of the shadow root, so a document-level listener sees event.target as <arc-textarea> — tag name ARC-TEXTAREA, not TEXTAREA. The usual "am I typing?" guard fails, and a space typed into a textarea fires the play/pause shortcut. No selector reaches across a shadow boundary, so matches() could not rescue it either.

  • @arclux/arc-ui/shared/editing-targetisEditingTarget(eventOrNode) reads composedPath()[0], the pre-retargeting node, and accepts an event or a node. isEditingNode(node) is the node-only form. Both distinguish text-entry inputs from checkboxes, radios and ranges, where a bare-key shortcut is usually meant to fire.
  • data-arc-editing — every form control now carries this attribute on the host while a text field in its shadow root holds focus, so e.target.matches('[data-arc-editing]') works from a framework template or plain CSS with nothing imported.

arc-hotkey had this bug itself: it tested e.target.tagName and so fired while the user was typing in any ARC input. It now uses isEditingTarget, as does the shared menu keyboard controller. global still opts out on purpose.

New — arc-confirm Takes a Default Slot

The body came only from the message string, so a confirmation could not put a filename in <code> or emphasize the consequence. There is now a default slot that message is the fallback for — the imperative ArcConfirm.open() API is unchanged and still string-only. Slotted <code> and <strong> are styled to match the dialog.

Fixed — Menu Items Truncating At 320px

Menu item labels are flex: 1 with text-overflow: ellipsis, and the panel was capped at a hard-coded max-width: 320px — not enough for an ordinary label beside a shortcut, so "Ask for a change  ⌘K" ellipsed with the shortcut gutter still visible next to it. The cap was also unreachable except through ::part(menu), having no token behind it.

The panels now size to their widest item under a larger, viewport-relative cap you can override with --menu-max-width. Sizing to content also decouples them from shrink-to-fit, where an absolutely positioned box is measured against its containing block — which made the width a function of what the menu was nested in rather than of what it holds. Applied to arc-menubar, arc-dropdown-menu, arc-breadcrumb-menu and arc-toolbar's overflow panel. arc-popover, arc-hover-card and arc-notification-panel are deliberately unchanged — they hold arbitrary content, and sizing those to max-content would lay a paragraph out on one line.

Fixed — arc-number-input Above 128px

The bordered control stretched to the host's width while its contents stayed at 128px, leaving empty bordered box in any form column wider than that. The field now grows to absorb the width, so the control fills its column like every other input.

New — Per-Component Imports in Every Framework Package

The framework packages published a barrel per tier and nothing finer, so importing one component evaluated its whole tier — arc-ui-svelte/typography for Kbd pulled in all twelve typography components and everything they import. Each of the six packages now also exposes its components individually, as @arclux/arc-ui-svelte/typography/Kbd or @arclux/arc-ui-react/content/Card.

The tier barrels are unchanged and still resolve exactly as before — an explicit export key wins over the wildcard.

Docs — The Family Name Is Not the One You Expect

The typography page now covers the mistake that costs people the most time: Fontsource's variable packages register a Variable suffix, so @fontsource-variable/host-grotesk is "Host Grotesk Variable" and assigning 'Host Grotesk' silently does nothing. An unknown family name is not an error — it is skipped, the role falls through to its fallback stack, and system-ui looks close enough to a grotesque to read as success.

This site was making the neighbouring mistake. Host Grotesk and Tektur are variable fonts and were declared under five and three discrete weights, all pointing at the same file; a @font-face naming a single weight gives the browser no way to set the wght axis, so every weight rendered the font's default instance and the hierarchy was flat. Each is now declared once with its real axis range.

Build

Prism 2.7.0, whose new slot-name-remapped diagnostic reports the eight slots that deliberately share a name with a prop — the pairing that lets arc-cta-banner take an eyebrow string or an eyebrow slot. Svelte derives a snippet prop name from the slot, so it can only have one of them; those consumers use {#snippet eyebrow_()}. Recorded as accepted findings rather than renaming slots in every framework to suit one.

v2.10.0 — Typed to the Leaf, Typography You Assign

Building an app-specific component on the Arclight design system meant reaching past the package's exports boundary. The map published component register paths, ./tokens and ./base.css — everything in src/shared/ was private, so a component living in your own repo could not adopt tokenStyles or reuse the focus trap without copying it. Five subpaths are now public API.

New Subpath Exports

  • @arclux/arc-ui/shared-stylestokenStyles, the static token layer every component adopts into its shadow root. Only needed for shadow DOM; light-DOM components inherit the same tokens from base.css at :root.
  • @arclux/arc-ui/shared/form-control-mixinFormControlMixin, ElementInternals form participation: native <form> submission, formDisabledCallback, reset and bfcache restore.
  • @arclux/arc-ui/shared/focus-trapcollectFocusable, deepActiveElement, focusFirst, trapTabKey. Composed-tree aware, so it sees through shadow boundaries and slots.
  • @arclux/arc-ui/shared/scroll-locklockScroll, unlockScroll. Refcounted, so nested overlays release correctly.
  • @arclux/arc-ui/shared/click-outsideClickOutsideController, a Lit reactive controller for dismiss-on-outside-click.
  • @arclux/arc-ui/shared/time-scale — linear value↔pixel mapping for time-axis components. Pure functions over a frozen, structured-cloneable scale object: toPixels, toValue, spanToPixels, visibleRange, zoomAround, panBy, gridSnapper, gridLines, boundariesWithin, chooseStep. Axis-agnostic, so the same mapping drives a horizontal ruler and a vertical pitch gutter.

time-scale holds no viewport state on purpose. Offset and zoom belong to the application, which passes them down and clamps them; components stay viewport-stateless, which is what keeps a ruler and a grid aligned from one shared offset — and what makes either usable outside a timeline. Groundwork for arc-time-ruler and arc-timeline-grid; no component consumes it yet.

These are explicit entries rather than a ./shared/* wildcard, so overlay-mixin, menu-keyboard, position-styles and anchor-adoption stay internal and remain free to change. Additive only — nothing existing moved or was removed.

Every Subpath Now Publishes Types

Only the root entry carried a types condition. The other 212 subpaths were bare strings, so import { ArcLabel } from '@arclux/arc-ui/label' resolved to implicit any — the package looked typed because its root was, while every deep import silently was not. Invisible from inside this repo, since nothing here imports itself by package name.

  • 208 subpaths now carry types — element subpaths resolve to types/index.d.ts; non-element modules get their own declaration, generated from JSDoc by the new generate-module-types.js. A @typedef in source becomes an exported type automatically.
  • generate-exports.js now asserts instead of assuming — every subpath must point at a file that exists, and every JavaScript target must resolve to a declaration that exists. Either failure exits non-zero, failing pnpm generate and the generate-diff CI gate. Adding an untyped subpath is no longer something you can forget to notice.
  • The two wildcard subpaths are named in the output rather than quietly skipped, since an exemption nobody can see reads as coverage it isn't.

Fixes

  • @arclux/arc-ui/icons/lucide was unimportable — a hard SyntaxError, not a subtle one. Upstream ships casing aliases (ArrowDownAZ / ArrowDownAz, Axis3D / Axis3d) that the kebab-case conversion collapsed onto one name, emitting 14 duplicate export const declarations. Bodies were byte-identical, so the aliases are now collapsed rather than emitted twice. The generator throws if colliding icons ever have differing bodies, instead of silently dropping one.
  • Removed the broken ./tokens export — it pointed at src/tokens.js, deleted back in 286dbf3 when duplicate token modules were eliminated. Importing it has failed ever since. Tokens come from ./base.css.

Wrapper Types Repaired (Prism 2.4.0)

Prism derived every string union by scanning CSS :host([prop="value"]) selectors, which structurally cannot see a prop's default — the default variant is the base style and has no attribute selector — and sees nothing at all when variants are driven from JavaScript. It now reads the documented @prop {'a' | 'b'} name annotation, with the CSS scan as fallback.

  • 35 props regained their own default<Badge size="md" /> and <Badge variant="default" /> were type errors despite being the defaults. Same for avatar.shape, card.padding and 31 others.
  • 33 props regained a union that had collapsed to stringchart.type, alert.variant, qr-code.level, data-table.sortDirection and more, all styled from JavaScript rather than CSS.
  • Reserved-word props no longer emit broken wrappersarc-label's for was a hard syntax error in Svelte and Preact and an invalid expression in Vue; arc-text's as is a keyword in Angular's template grammar. Attribute names are unchanged; only the local bindings moved.
  • Two documentation bugs surfaced and fixedarc-cluster documented justify="between", which matches no CSS rule and has never done anything, while the values that do work (space-between, space-around) were undocumented. arc-cluster and arc-responsive-switcher both omitted gap sizes their CSS supports.
  • A new check:unions gate runs as part of pnpm generate and fails if any union reaches the wrappers collapsed, incomplete, or missing its default — whether it was documented on the class or inferred from CSS. 126 unions currently pass. pnpm generate also surfaces warnings from steps that succeed, instead of discarding their output.
  • Ten more props documented at sourcetag.size, checkbox.size, radio-group.size, select.size, toggle.size, container.padding, diff.mode, footer.align, link.underline and sidebar.position declared {string} with their values only in prose, so they fell to the CSS scan and lost their defaults the same way. <Tag size="md" /> type-checks again.

Object and Array Props Are Typed (Prism 2.5.0)

Lit's static properties can only say Array, so every prop carrying real shape arrived in the wrappers as unknown[] — and those are exactly the props a consumer most needs typed. Prism now emits a documented @prop object type verbatim, so eight of them describe themselves:

  • chart.seriesArray{label, data: number[]}
  • event-calendar.eventsdate, end?, label, color?
  • data-grid.columns and data-grid.sort, kanban.columns including its nested cards, transfer-list.options, date-range-picker.presets, menubar.items including its two levels of submenu

virtual-list.items stays unknown[] deliberately — it renders whatever you give it, so a shape there would be a fiction.

arc-column gains field

arc-column's key prop never reached the component in React or Preact: both reserve key for list reconciliation and strip it before the component is called, so <Column key="name" /> set the list key and the column silently rendered nothing — no error, no type error, and key= is what a developer writes by reflex on something that renders in a list.

field is the fix, and key stays as an alias. It works today in HTML, Vue, Svelte, Angular and Solid, so renaming would break five working consumers to repair two; field takes precedence when both are set. Prefer field in new work — in React and Preact it is the only one that functions.

Generated wrapper props also stopped accepting arbitrary names. The blanket [key: string]: unknown on the Svelte, Preact and Solid interfaces meant <Slider valu={3} /> type-checked; it is now pattern signatures for data-*, aria-* and on* plus an explicit list of global HTML attributes.

v2.9.0 — Two-Way Binding in Svelte, Vue & Angular

The Svelte, Vue and Angular wrappers passed props down but never listened for the component reporting a change back. Typing in an arc-input updated the element and nothing else — and because the framework's copy of value never changed, the next unrelated re-render pushed the stale value back onto the element and silently reverted what you had just typed. 38 components per framework now bind both ways.

Two-Way Binding

  • Svelte — bind:value works<Slider bind:value={v} />. Bindable props are declared with $bindable(). This was the worst affected of the three: the wrappers forwarded no events at all, so there was no manual workaround either, and bind: on a non-bindable prop is a hard error.
  • Vue — v-model works<Slider v-model:value="v" />. The wrappers relayed events but never declared the update:value emit that v-model listens for, so the binding looked correct and did nothing.
  • Angular — [(value)] works<arc-slider [(value)]="v">. The matching valueChange output did not exist, so the two-way form failed to compile. The local @Input() is updated as well as emitted, so one-way [value] consumers no longer have a stale value pushed back on the next change detection.
  • Existing handlers still fire — every wrapper still relays the underlying custom event, and the binding handler forwards to any handler you passed. onarc-input, @arc-input and (arc-input) behave exactly as before, with or without a binding.

Bindings are derived from the components themselves rather than a hand-maintained list: an event whose detail carries a key matching a declared prop is that prop's write-back path. Where two events carry the same key — arc-slider fires both arc-input and arc-change — both are bound, so a binding tracks the live drag as well as the commit.

React, Solid and Preact are unchanged. React's wrappers go through @lit/react, which already wires properties and events. Neither Solid nor Preact has a two-way binding form — the idiom there is an explicit handler, which already worked.

Fully backwards compatible. Binding is opt-in at the call site. A parent passing a plain value keeps working exactly as it did; in Svelte the write just stays local. Four components deliberately do not bind: arc-copy-button, arc-hotkey and arc-list-item only echo their own unchanged value back as event context, and arc-select reports the selected option's label, which is a different thing from the field's own label prop.

v2.8.0 — Links That Work Without JavaScript

With JavaScript disabled, ARC components rendered nothing. The stylesheet hid every custom element until it upgraded — and without JS nothing ever upgrades, so the guard never lifted. That is fixed, and link-bearing components can now carry a real anchor in the initial HTML so navigation survives with no JavaScript at all.

No-JS Rendering

  • The pre-upgrade hide is gated on scriptingbase.css wrapped its :not(:defined) rule in @media (scripting: enabled). Previously a no-JS visitor got a blank page wherever ARC components were used. Browsers without the scripting query simply skip the rule, trading a brief flash for never-hidden content.
  • The guard no longer hides elements it doesn't own — the rule was an unscoped :not(:defined), which matched any undefined element on the page, including your own custom elements and third-party ones. It is now scoped to the 187 arc-* tags, generated from the component sources.

Anchor Adoption

  • Slot a real <a> and the component adopts it<arc-button><a href="/start">Get started</a></arc-button> is a working, styled link before hydration and with JS off. On upgrade the component styles that anchor as its control instead of rendering a second one, so only one link ever reaches the accessibility tree. Supported on arc-button, arc-icon-button, arc-link, and arc-card.
  • Nav carriers read a child anchorarc-nav-item, arc-breadcrumb-item, and arc-sidebar-link resolve their destination and label from an anchor child, so menus, trails, and sidebars are working link lists before their parent upgrades.
  • href is unchanged — the attribute form still renders an <a> in the shadow root and always wins over a slotted anchor. Nothing that works today changes. Prefer href inside app shells where JS is guaranteed, and the slotted form on public or first-load-critical pages.

An href on the custom element cannot be made to work without JavaScript — the real <a> only exists in a shadow root that is never created — which is why the slotted form exists. Components that render from properties rather than slots, such as arc-feature-card, still have no light DOM to fall back to; see Without JavaScript.

Static HTML Package

  • Button styles are backarc-button has shipped with zero CSS in arc-ui.css since v2.6.0. Adding an @click handler to bridge form submit/reset across the shadow boundary tripped the generator's auto-detection into classifying the component as fully interactive, which excludes it from the bundle. A stale standalone button.css masked the gap. Same fix restores alert, copy-button, tag, chip, breadcrumb, pagination, sidebar, and the native-element form controls.
  • Classification moved out of JSDoc — component classification now lives in prism.config.js rather than @arc-prism doc comments. The tags had twice been erased by passes that rewrite JSDoc, silently reclassifying components and causing exactly the bug above. Config is validated, and entries matching no component now warn.
  • 117 stale files removed — see below.
Breaking for @arclux/arc-ui-html only. 62 stylesheets and 55 examples for interactive components have been removed, so imports like @arclux/arc-ui-html/css/modal.css will no longer resolve. These described shadow-DOM internals that hand-authored static markup cannot reproduce — the generator had already stopped maintaining them, leaving fossils that were last regenerated several releases ago. If you import one, drop it: it was not styling anything you can build statically. The Web Component and framework packages are unaffected.

v2.7.0 — Editor Autocomplete, Typed Events & Dev Warnings

A developer-experience release: the library now teaches its own API through your tooling. Every component documents its full surface — props with allowed values, events with typed payloads, slots, CSS parts — in custom-elements.json, and everything else is generated from it: editor autocomplete data, TypeScript declarations, JSX typings, runtime dev warnings, and the docs API tables themselves.

Editor Autocomplete

  • VS Code — the package ships vscode.html-custom-data.json and vscode.css-custom-data.json. One settings line gives tag, attribute, and attribute-value completion with hover docs in plain HTML: "html.customData": ["./node_modules/@arclux/arc-ui/vscode.html-custom-data.json"].
  • JetBrains IDEs — bundled web-types.json is picked up automatically; zero setup in WebStorm or IntelliJ.

TypeScript

  • Union prop typesvariant is now 'primary' | 'secondary' | 'ghost' instead of string, with per-prop hover documentation, across all 187 elements (164 components plus their sub-components).
  • Typed events — all arc-* event names are registered in GlobalEventHandlersEventMap, most with typed detail payloads, so addEventListener('arc-change', …) autocompletes and type-checks.
  • React 19 without the wrapper — add "types": ["@arclux/arc-ui/react-jsx"] to tsconfig and every arc-* tag is typed directly in JSX; variant="primry" fails to compile with a did-you-mean.

Runtime Dev Warnings

  • import '@arclux/arc-ui/dev' (development only) warns in the console — with a link to the right docs page — about invalid attribute values, camelCase property names used as attributes, and attribute-name typos.

Docs

  • API tables are generated — component pages and llms-full.txt read props, events, slots, and CSS parts from custom-elements.json, so API docs can no longer drift from component source. Event tables gained a typed Detail column; llms-full.txt gained Slots and CSS Parts sections.
  • llms-full.txt now includes the data and typography tiers — 33 components were missing from the LLM reference.

Changed

  • Quoted kebab property keys are now camelCase propertiesel['confirm-label']el.confirmLabel (same for cancelLabel, pauseOnHover, pauseEnd). HTML attributes are unchanged (confirm-label="…" still works); only direct JS property access is affected. Framework wrappers expose these four props for the first time.

v2.6.0 — Form Submission Across Shadow Boundaries

Forms now behave the way every developer expects, with zero wiring. Shadow-DOM buttons are never form-associated with an ancestor form, so <arc-button type="submit"> inside a form silently did nothing and Enter-to-submit was lost — both are fixed at the component level.

Forms

  • arc-button submit & reset work everywhere — clicking a type="submit" or type="reset" button finds the nearest arc-form or native <form> and submits/resets it. Native forms go through requestSubmit(), so constraint validation and the submit event still fire.
  • Enter submits — pressing Enter in a slotted single-line input submits the surrounding arc-form, matching standard browser behavior. Textareas and non-text input types are unaffected.
  • arc-form.submit() honors action — programmatic submits now route through the internal form's requestSubmit(), so action-mode forms actually navigate instead of only firing arc-submit.

If you wired a manual click-to-submit workaround around arc-form, remove it when upgrading — the component-level submit plus a workaround means double submission.

v2.5.0 — Keyboard-First Command Palette & Browser Tests

A fit-and-finish release for the overlays. Command Palette now delivers a genuinely keyboard-only experience — instant input focus on open, arrow navigation that keeps the highlighted item in view, a real empty state — and the focus bug it exposed is fixed across every overlay and guarded by the project's first real-browser test suite.

Command Palette

  • Search input autofocuses on open — opening the palette previously left focus on the page because the dialog's visibility transition made it unfocusable at the moment focus() ran. Overlays now flip visibility instantly on open and only delay it on close (Modal had the same latent bug and got the same fix).
  • Empty state that actually shows — a query with no matches now renders No results for "query"; the old CSS :empty approach never fired against Lit-rendered content.
  • Escape always closes — previously ignored when the result list was empty.
  • Arrow keys scroll the highlighted item into view in long result lists.
  • Item icons render at the right sizearc-icon now accepts numeric pixel sizes (size="16") alongside named sizes, fixing oversized icons in Command Palette, Context Menu, and any consumer passing numbers.

Testing

  • Real-browser component testspnpm test runs a web-test-runner suite in headless Chromium, wired into CI. The regressions in this release (focus vs. visibility transitions, computed icon sizing) are invisible to jsdom, so the suite tests what browsers actually do.

Docs

  • ⌘K toggles — pressing the shortcut with the palette open now closes it.
  • Fleshed-out package READMEs — every npm package now documents events, theming, SSR caveats, and correct registration entry points, with links that work on npm.

Static HTML Package

  • Tooltip works without JavaScript — the static export gains a pure-CSS :hover/:focus-within reveal. The Web Component disables the fallback at runtime, so its configured show delay is unaffected.
  • Callout, App Shell, Toolbar, and Tooltip join the static package as hybrids — the generator now maintains their examples (static presentation works; dismissal, overflow collapse, and drawer behavior still require JS).
  • Variant styling un-broken — Prism 2.0.4/2.0.5 fix a CSS-scoping bug that left size/variant/position rules dead in 26 components' standalone CSS (button sizes, card hover, dock positions, and more), plus example-generator fixes: real labels instead of blank or placeholder junk, sensible attribute defaults, and no shadow-DOM internals leaking into snippets.
  • Removed 72 stale example files — leftovers for interactive components (modal, drawer, combobox, …) that the generator had stopped maintaining, most of them empty shells. Interactive components are intentionally excluded from the static-HTML package.

Internal

  • Prism 2.0.5 — regenerated all wrapper output; 74 files stuck on a pre-rename header refreshed, Page Header's HTML example and slotted styles corrected.

v2.4.0 — Command Palette Groups & Keyword Search

Command Palette learns the two tricks that turn it into a real site-search primitive — group headings and hidden keyword aliases — and this docs site puts them straight to work with a ⌘K search across every component and guide. Fully backwards compatible.

Command Palette

  • New <arc-command-group> — wraps items under a small uppercase heading. Groups render as role="group" runs inside the listbox, items keep filtering and keyboard-navigating as one flat list, and a heading disappears when none of its items match the query.
  • keywords attribute on <arc-command-item> — extra space-separated terms the filter matches against but never displays, so keywords="dropdown picker" surfaces Select for the queries people actually type.

Docs

  • ⌘K / Ctrl+K site search — every component and guide, grouped by tier, searchable by name, tag (arc-select), or alias. Built on Command Palette itself; no search service, no client-side index.
  • Social cards everywhere — every component and guide page now ships its own OG image, and the homepage card derives its stats from the same source as the landing page so they can never drift.
  • Changelog RSS feed — subscribe at /rss.xml; one item per release, all the way back to 1.0.0.
  • Prev/next navigation and structured data — component pages chain together in sidebar order and emit TechArticle + BreadcrumbList JSON-LD.

v2.3.1 — Republish With Vendored Icons

Identical component code to v2.3.0. The v2.3.0 npm tarballs were missing the generated per-icon modules (icons/phosphor/, icons/lucide/), which broke consumer builds on import. The release pipeline now generates them in CI and verifies the tarball contents before publishing, so this class of failure can't ship again.

v2.3.0 — Zero Axe Violations, Prism 2.0 & Discoverability

The receipts release. Every component documentation page now passes axe-core with zero violations of any severity — dark and light themes, shadow roots included — and CI enforces it on every commit. The generator moved to Prism 2.0, and the docs site became properly crawlable. Backwards compatible, with a handful of deliberate behavior corrections listed below.

Accessibility, Verified

  • Automated audit in CI — a new pnpm audit:a11y harness runs axe-core against every built component page in both themes and fails the build on any serious or critical violation, with no rule exclusions. All 164 pages currently audit clean. Live results on the Accessibility page.
  • ~30 ARIA fixes across 20 components — date, time, and range pickers adopt the combobox-with-dialog pattern; Calendar's grid gained real row/columnheader/gridcell structure; Search uses a coherent combobox role with suggestions wired via aria-controls; Select triggers are always named, including the empty-placeholder state; Stepper Nav indicators announce "Step N: label"; Icon Button correctly adopts a host aria-label onto its internal button; Key Value items expose listitem; List accepts a label; Comparison marks its yes/no glyphs with role="img" and names its feature column; Markdown code blocks and Virtual List are keyboard-scrollable.
  • Docs chrome — every docs page now has a real <main> landmark, a light-DOM <h1>, correct heading order, and a labeled skip-link landmark.

Contrast & Theming

  • Dim-text ramps rebuilt--text-secondary, --text-muted, and --text-ghost now clear WCAG AA 4.5:1 on every surface token in both themes (dark ramp brightened one step; light ramp darkened).
  • New --accent-text-mix token — Tag, Badge, and Comparison blend colored text toward --text-primary (0% in dark, 55% in light), so accent and custom-color labels stay readable on their tinted backgrounds in light mode without dulling the dark theme.
  • Diff — added/removed line text mixes toward --text-primary for legibility in both themes; the removed-line opacity: 0.7 dim (which broke contrast) is gone.
  • Fixed nav regions — the light-mode royal-blue top bar brightened its text ramp and gained an accessible --accent-primary override.
  • Theme bug — the docs site's light-mode accent overrides were never mirrored into the auto theme's media query, so system-light visitors on the default theme got dark-theme accents on light backgrounds. Fixed, and the light teal darkened to pass AA as text.

New Props

  • label on Breadcrumb, Breadcrumb Menu, Navigation Menu, Sidebar, and List — override the default landmark/listbox names so multiple instances on one page stay distinguishable.
  • heading-level on Timeline (default 3) — item titles render as a proper heading at the level that fits your document outline instead of a hard-coded <h4>.
  • Page Header accepts slotted heading and description elements as an alternative to the attributes — slot a real light-DOM <h1> for SEO-critical pages.

Changed

  • Markdown prose links are always underlined (WCAG 1.4.1 — links must be distinguishable without color).
  • Standalone Chips render as role="button" with aria-pressed; the option/aria-selected semantics now apply only inside a listbox or group ancestor.
  • Infinite Scroll no longer hard-codes role="feed" (feed requires <article> children it can't guarantee); slot articles and add the role yourself for the full feed pattern.
  • Drawer's panel is a <div role="dialog"> instead of an <aside> (dialog is not a permitted role on aside). Styling hooks are unchanged.

Generator: Prism 2.0

  • All framework packages in this release are generated by @arclux/prism 2.0.0, a major hardening release of our code generator.
  • Security — the parser validates custom-element tag names and event identifiers, closing code-injection, path-traversal, and ReDoS vectors from malicious or malformed component sources.
  • Vue — emitted events are actually wired; @arc-* listeners on wrappers fire again.
  • Angular — array and object props bind as DOM properties instead of stringified attributes.
  • CSS extraction — the shadow→light transform scopes every selector in comma lists and bare element selectors, closing style leaks in the standalone CSS package; design tokens referenced multiple times resolve on every occurrence.
  • Barrels — missing tier barrels are created instead of silently skipped, and same-name-substring exports are no longer dropped.

Docs

  • Sitemap and robots.txt; canonical URLs, og:url, and absolute Open Graph images on every page.
  • Component pages render their title and overview as light-DOM text (real <h1> plus slotted markdown), so crawlers that don't execute JavaScript see actual content, with search-qualified page titles.
  • The component count is generated from the docs catalog into the README, homepage, and OG image — one source, no drift.

v2.2.0 — Dashboard Suite, Native Forms & Accessibility Overhaul

The largest release since 2.0: twelve new components, native form participation across every input, a library-wide accessibility remediation, and a rebuilt distribution pipeline with generated types. Fully backwards compatible — but several components now behave more correctly than before, so review the behavior changes below.

New Components

  • Chart (data) — SVG line, area, bar, and donut charts with a colorblind-validated categorical palette (--chart-1--chart-6), crosshair tooltips, and a hidden data table for screen readers.
  • Data Grid (data) — editable cells, pinned columns, Shift-click multi-sort, row selection, and virtualization with the full APG grid keyboard model.
  • Kanban (data) — drag-and-drop board with column limits and a complete keyboard move protocol with live-region announcements.
  • Event Calendar (data) — month and week views with event chips, multi-day spans, and overflow handling.
  • Date Range Picker (input) — two-panel range selection with hover preview and quick presets.
  • Tag Input (input) — free-text tokens with suggestions, paste splitting, and duplicate rejection.
  • Password Input (input) — visibility toggle plus an optional four-segment strength meter.
  • Transfer List (input) — dual listbox with checked-move controls and per-pane filtering.
  • Image Cropper (input) — eight-handle crop rect, zoom, aspect lock, and natural-pixel getCroppedBlob().
  • Menubar (navigation) — desktop-style menus with nested submenus, typeahead, and full APG 2D keyboard navigation.
  • Toast Manager (feedback) — queueing, stacking limits, and dedupe over arc-toast, plus a document-level event channel.
  • QR Code (content) — theme-aware SVG QR codes with a scan-safe contrast mode.
  • Toolbar gains opt-in responsive overflow collapse into a "More" menu.

Native Form Participation

  • All 17 value-bearing inputs are now form-associated custom elements (ElementInternals): they submit into native <form>/FormData, respond to form.reset() and <fieldset disabled>, and expose checkValidity()/reportValidity().
  • arc-multi-select, arc-transfer-list, and arc-tag-input submit one FormData entry per value, matching <select multiple> semantics.
  • arc-form now discovers any form-associated ARC control instead of a fixed six-component list.

Accessibility

  • Modal, sheet, drawer, and command palette gained composed-tree focus traps that see slotted content, initial-focus handling, and focus restore on close.
  • Dropdown menu, context menu, and command palette implement real focus models (roving tabindex / aria-activedescendant).
  • Tooltip, popover, hover card, and dropdown menu forward ARIA state onto the slotted trigger element where assistive tech can actually see it.
  • Toggle, radio group, and tree view announce correctly (role, name, and keyboard focus now live on the same element).
  • Multi-select reached combobox parity including keyboard tag removal; date picker gained full arrow-key day-grid navigation; time picker went from ~84 tab stops to 3.
  • Data table headers sort by keyboard; accordion panels are programmatically associated; alerts only interrupt for errors and warnings.

Fixed

  • Securityarc-markdown now enforces a URL-scheme allowlist, blocking javascript: link injection.
  • Framework wrappers register reliably — every generated wrapper imports its component's register subpath, so tree-shaken production builds can no longer strip element registration, and importing one component no longer pulls in the whole library.
  • Stacked overlays no longer unlock page scroll when the inner one closes (shared reference-counted scroll lock).
  • Typing spaces in the command palette no longer selects the first result.
  • Deterministic table sorting — mixed number/string columns previously produced a non-transitive comparator that let rows reorder arbitrarily; sort indicators now occupy a fixed-width slot so columns don't shift.
  • Light-mode fixes: navigation and tab hover states were hardcoded white; overlays and shadows now use theme-aware tokens throughout.
  • Internal Lit state properties (e.g. _hasFooter) no longer leak into framework wrapper APIs.
  • Registration is guarded against double customElements.define() crashes; all six wrapper packages gained the missing ./data and ./typography export subpaths.

Internal

  • TypeScript declarations are now generated from source for all 186 elements (previously 93, hand-maintained), including an HTMLElementTagNameMap for tag-name autocompletion.
  • New custom-elements.json manifest ships with the package for IDE and tooling integration.
  • CI now enforces that generated files match source, plus linting and type checks.
  • New design tokens: --chart-1 through --chart-6, a categorical data-visualization palette validated for contrast and color-vision deficiency in both themes.

v2.1.0 — Export Namespace Fix

Resolves a namespace collision where @arclux/arc-ui/input imported the input category barrel instead of the arc-input component.

Breaking Changes

  • Category barrel exports moved — Tier barrel imports like @arclux/arc-ui/input, @arclux/arc-ui/content, etc. are now under @arclux/arc-ui/categories/ (e.g. @arclux/arc-ui/categories/input). No known consumers were using the old paths.

Added

  • @arclux/arc-ui/input — Now correctly registers the arc-input component, matching the convention used by all other components.

v2.0.1 — Export Fixes & Build Pipeline

Fixes 43 missing package exports and consolidates the build pipeline into a single orchestrator with automatic export syncing so missing exports can never slip through again.

Fixed

  • 43 missing package exports — The exports map in @arclux/arc-ui was missing entries for all 14 new v2 components and 29 older components. Imports like @arclux/arc-ui/prose now resolve correctly.

Internal

  • Unified generate pipeline — Replaced 5 chained npm scripts with a single scripts/generate.js orchestrator. Runs tokens → registrations → brand → Prism → exports with per-step timing output and fail-fast error handling.
  • Automatic export syncing — New scripts/generate-exports.js scans all .register.js files and adds any missing entries to package.json exports during pnpm generate. Missing exports can no longer slip through.

v2.0.0 — Design System Overhaul

A major release adding 14 new components, theme presets, category-level package exports, a type scale overhaul, and a comprehensive quality pass across the entire library. Standardizes token usage, fixes subtle bugs, improves mobile interactions, and brings the full sci-fi luminescence vocabulary to every interactive surface.

New Data Components

  • arc-diff — Line-based text diff viewer with inline and side-by-side modes. Uses a longest-common-subsequence algorithm to highlight added, removed, and unchanged lines with color-coded backgrounds.
  • arc-key-value — Key-value pair list with horizontal and stacked layout modes. Pairs render with uppercase accent-font labels and optional dividers. Hover highlights each pair row.
  • arc-countdown-timer — Live countdown to a target date with segmented day/hour/minute/second cards. Numbers use gradient accent text with tabular-nums alignment. Dispatches arc-expired when the countdown reaches zero.
  • arc-comparison — Multi-column comparison table for pricing tiers and feature matrices. Accepts JSON-driven features and column values. Boolean values render as check/cross icons. Supports a highlighted column with accent styling.
  • arc-sparkline — Tiny inline SVG line or bar chart for embedding metrics in tables, stats, and cards. Supports area fill, custom colors, and a draw-in stroke animation on load.
  • arc-description-list — Structured term/detail pair list in a responsive grid. Supports multi-column layouts with automatic vertical dividers and collapses to a single column below 640px.

New Typography Components

  • arc-blockquote — Styled pull-quote with a top gradient accent line, decorative opening-quote glyph, and optional citation footer. Supports a default and accent variant with gradient text.
  • arc-prose — Long-form content wrapper that applies typographic rhythm to slotted HTML. Styles headings, paragraphs, lists, links, code blocks, images, and tables with consistent spacing and token-driven typography. Three size variants: sm, md, lg.
  • arc-typewriter — Character-by-character text reveal animation with configurable speed, delay, and looping. Blinking accent-colored cursor fades out on completion. Dispatches arc-complete and exposes a replay() method.
  • arc-gradient-text — Inline text wrapper that applies gradient fills declaratively. Five built-in variants (accent, display, sunset, ocean, custom) plus an animated mode that cycles the gradient position. Includes a subtle drop-shadow glow.
  • arc-number-format — Locale-aware number formatter powered by Intl.NumberFormat. Supports number, currency, percent, and compact notation types with configurable decimals and locale. Uses tabular-nums for column alignment.
  • arc-time-ago — Auto-updating relative time display ("3 minutes ago", "yesterday"). Uses Intl.RelativeTimeFormat with an adaptive update interval that slows as the timestamp ages. Renders a semantic <time> element with a full-date title tooltip.

New Input Components

  • arc-time-picker — Time selection dropdown with scrollable hour, minute, and AM/PM columns. Supports 12h and 24h display formats, configurable step increments (1/5/15/30 minutes), and min/max time constraints. Matches the DatePicker interaction pattern with keyboard navigation and outside-click dismiss.
  • arc-range-slider — Dual-thumb slider for selecting a value range. Custom-rendered track with pointer-capture drag, snap-to-step, and full keyboard support (ArrowKeys, Home/End). Both thumbs emit arc-input during drag and arc-change on release with { low, high } detail. Same glow effects as the single Slider.

Theme Presets

  • Minimal theme (@arclux/arc-ui/themes/minimal) — Strips all glow effects, gradient backgrounds, and depth shadows. Replaces the sci-fi aesthetic with a clean, flat look while preserving layout and spacing. Import after base.css.
  • High Contrast theme (@arclux/arc-ui/themes/high-contrast) — WCAG AAA compliant with 7:1+ contrast ratios. Brightened text, stronger borders, bolder focus rings (3px solid), and larger interactive targets (36px minimum). Includes both dark and light mode overrides.

Semantic Token Layer

A new semantic token layer adds ~25 CSS custom properties that describe purpose rather than appearance. All interactive colors now use --interactive (aliased to --accent-primary), all surface backgrounds use --surface-*, and feedback composites (error/success/warning/info borders, glows, and subtle backgrounds) are available as ready-made tokens. The semantic layer is additive — both old and new token names remain available — but all ~170 component files have been migrated to the semantic names internally.

  • Interactive tokens--interactive, --interactive-rgb, --interactive-hover, --interactive-active, --interactive-focus, --interactive-focus-ring, --interactive-muted. Override --interactive at :root to retheme every clickable element at once.
  • Surface tokens--surface-base, --surface-primary, --surface-raised, --surface-overlay, --surface-hover. A clear elevation hierarchy for page background → content → cards → dropdowns.
  • Divider tokens--divider and --divider-glow distinguish separator lines from component edge borders.
  • Feedback composites--feedback-{error,success,warning,info}-{subtle,border,glow}. Pre-composed background, border, and glow values for each feedback level, eliminating manual rgba() composition in components.
  • Theme preset integration — Minimal theme overrides flatten --interactive-hover/--interactive-active to none and simplifies --interactive-focus to a 2px ring. High Contrast theme strengthens all --interactive-focus and --feedback-*-glow values.

Category Package Exports

  • Import by category — New package entry points: @arclux/arc-ui/content, /data, /typography, /input, /feedback, /navigation, /layout, /shared. Import an entire tier with a single import statement. Existing per-component imports remain unchanged.

Vertical Tabs

  • New orientation prop on Tabsarc-tabs now accepts orientation="vertical" to render the tab list as a sidebar column with the panel to the right. Arrow key navigation automatically switches to up/down. Works with both underline and pills variants. Default remains horizontal.

Nested TOC & Sidebar Support

  • New level prop on SpyLink and SidebarLink — Both arc-spy-link and arc-sidebar-link now accept a level number prop (default 0) for visual nesting. Links with level > 0 render with progressive indentation and a smaller, dimmed font (--text-xs / --text-ghost). Active state highlighting overrides the dimmed color automatically. Zero breaking changes — existing flat lists default to level 0 and render identically.

Typography & Token Changes

  • Text size floor raised to 16px--text-sm bumped from 14px to 16px; --text-md and --body-size bumped from 15px to 17px. All body and secondary text across the library is now larger and more readable.
  • Code font size set to 14px--code-size is now a fixed 14px (was 13px). Slightly smaller than body text for clear visual distinction in inline code and code blocks.
  • Sidebar & TOC widened to 280pxarc-sidebar default width and arc-app-shell sidebar/TOC columns bumped from 260px to 280px to accommodate the larger text.

Visual Elevation

A systematic pass bringing the full sci-fi luminescence vocabulary — glow lines, entrance animations, inset shadows, and spring easing — to workhorse components that previously had minimal interaction treatments.

  • Glow line dividers — Card (body/footer), Accordion (between items), Modal (header & footer), and Tooltip (popup top) now use --glow-line-gradient pseudo-element dividers instead of flat border-color separators.
  • Dropdown entrance animations — Select, Combobox, and Multi-Select dropdowns now animate in with a 120ms slide-fade (opacity + translateY), matching the Date Picker. Each dropdown also gets a glow-line accent at the top edge. Respects prefers-reduced-motion.
  • Form field hover glow — Input, Select, Textarea, Combobox, and Multi-Select now show var(--glow-hover) on hover alongside the existing border-color change. Checkbox unchecked state gains a subtle accent glow on hover.
  • Inset shadow on recessed surfaces — New --shadow-inset token (inset 0 1px 3px rgba(0,0,0,0.25)) applied to all form field backgrounds, giving them a pressed-in depth. Combines with glow-hover and focus-glow via comma-separated box-shadows.
  • Checkbox spring-pop — Check icon now uses a check-pop keyframe (scale 0.5 → 1.15 → 1) with --ease-out-expo instead of a linear scale transition. Respects prefers-reduced-motion.
  • Toggle thumb spring easing — Toggle thumb transition uses 300ms var(--ease-out-expo) for a natural overshoot feel on slide.
  • Skeleton shimmer accent tint — Skeleton shimmer highlight now uses rgba(var(--accent-primary-rgb), 0.04) instead of var(--border-subtle), giving loading states a faint accent-colored pulse.
  • Active state spring easing — Button, Icon Button, and Theme Toggle release transitions now use var(--ease-out-expo) for the transform property, giving the scale-up a springy bounce-back.

Interaction Polish

A second visual pass adding hover glows, active press feedback, and pulse animations to 10 components that previously had flat or minimal interaction states. All enhancements are CSS-only and respect prefers-reduced-motion.

  • Accordion — Trigger hover glow, scale(0.98) active press, expo easing on chevron rotation.
  • Tabs — Active tab glow on both underline and pills variants, scale(0.95) press, expo transform easing.
  • ListItem — Hover glow, scale(0.98) press with expo easing, selected state inset glow.
  • Select — Option scale(0.97) active press, expo chevron easing, dropdown zoom-in entrance (scale(0.96) start).
  • Breadcrumb — Hover background with accent tint and glow, rounded corners, scale(0.95) press.
  • Pagination — Hover glow on inactive pages, scale(0.93) active press, expo transform easing.
  • Radio Group — Hover glow on radio circles with subtle border-color shift.
  • Sidebar — Section toggle hover glow, link hover inset glow.
  • Stepper — Completed step glow, active step pulse animation.
  • Avatarscale(1.05) hover lift, online status pulse animation.

Mobile Navigation Menu

  • Thematic mobile triggers — Mobile menu items are now bordered cards with accent glow, matching the desktop navigation style. Supports default, primary, and muted variants.
  • Tap feedback — All mobile triggers and child links show an instant scale(0.98) press state with accent tint on tap.
  • Swipe-to-close — Swiping up on the mobile panel (when scrolled to top) dismisses the menu.
  • GPU-accelerated animation — Panel open/close animation replaced from max-height to clip-path for smoother performance on mobile devices.
  • Smooth sub-menu expand — Replaced the max-height: 500px hack with CSS grid-template-rows: 0fr → 1fr for properly measured expand/collapse transitions.
  • ARIA improvements — Mobile panel now declares role="dialog", aria-modal="true", and aria-label.

Layout

  • Page Header top padding removedarc-page-header no longer adds var(--space-lg) top padding, eliminating the extra margin on component doc pages. The app shell content area already provides consistent top spacing.

Fixed

  • Sidebar toggle event collision — The arc-toggle rename to arc-sidebar-toggle fixes a latent bug where arc-toggle events from nested Collapsible or TreeView components could bubble up and incorrectly trigger the app-shell sidebar.
  • Breadcrumb active item alignment — The current (last) breadcrumb item now has matching padding and min-height with link items, fixing a visual offset.
  • Marquee ResizeObserver leak_setupResizeObserver now disconnects any existing observer before creating a new one, preventing duplicate observers.
  • Tree-view keyboard performance — Arrow key navigation now queries the DOM once per keypress instead of twice.
  • Pagination aria-current — Non-current pages now omit the aria-current attribute entirely instead of setting it to "false".
  • Hotkey renderrender() now returns Lit's nothing sentinel instead of undefined.
  • DatePicker lazy initnew Date() computation moved from constructor to connectedCallback for lazy initialization.
  • Timeline line gap — Fixed a 4px gap between timeline dots and their connecting lines.

Style Consistency

  • Transition token adoption — Replaced hardcoded transition durations in Accordion, Collapsible, Sidebar, DatePicker, and ThemeToggle with var(--transition-fast) or var(--transition-slow).
  • Hover glow standardization — Components that independently defined 0 0 12px rgba(accent, 0.15) now reference var(--glow-hover).
  • Breakpoint documentation — Added token-equivalent comments to 9 hardcoded media queries across 8 components.

New Tokens

  • --z-dropdown, --z-tooltip, --z-overlay, --z-modal, --z-toast, --z-max — Semantic z-index scale. All 22 components with z-index values now reference these tokens instead of hardcoding numbers.
  • --glow-hover — Standardized hover glow (0 0 12px rgba(var(--accent-primary-rgb), 0.15)). Used by Avatar, Tag, Badge, NavigationMenu, and available for consumer use.

Breaking Changes

  • Text size scale--text-sm is now 16px (was 14px), --text-md is now 17px (was 15px), --code-size is now 14px (was 13px). Components using these tokens will render larger text.

  • Z-index token restructure — Replaced the old numeric scale (--z-sticky, --z-drawer, --z-popover, values 100–700) with a semantic scale: --z-dropdown (1000), --z-tooltip (1100), --z-overlay (1200), --z-modal (1300), --z-toast (1400), --z-max (9999). If you referenced the old token names in your CSS, update them.

  • Disabled opacity standardized to 0.5 — 25 components previously used opacity: 0.4 for disabled states. All now use 0.5, matching the --opacity-disabled token. Disabled elements will appear slightly more visible than before.

  • Event renames — Three custom events have been renamed for clarity:

    • arc-triggerarc-hotkey-trigger (Hotkey)
    • arc-toggle (top-bar/app-shell sidebar) → arc-sidebar-toggle
    • arc-item-clickarc-item-select (List, ListItem)

    Note: arc-toggle events from Collapsible, Truncate, TreeView, and SidebarSection are unchanged.

Tree-Shaking

  • sideEffects: false — Added to all 7 framework packages (React, Vue, Svelte, Angular, Solid, Preact, HTML). Bundlers can now reliably tree-shake unused components from framework wrapper imports.

Docs

  • Front page overhaul — Tabbed code examples (React/Vue/Svelte/HTML), "What's New in v2" highlight section, animated stat counters, glow-line section dividers, and a Prism visual replacing the bare frameworks strip.
  • OG image refresh — Gradient title, framework pills, varied accent stat colors, and tighter layout spacing.

Internal

  • 22 components updated from hardcoded z-index values to semantic tokens.
  • 25 components updated from opacity: 0.4 to 0.5 in disabled states.
  • 5 components updated from hardcoded transition durations to token references.
  • 4 components updated to use var(--glow-hover) instead of inline shadow definitions.
v1.x archive — 26 releases, v1.0.0 through v1.11.0

v1.11.0 — Footer Containment & Layout Improvements

Adds a contained layout mode to the footer (matching the top bar) and converts the docs site footer to use arc-footer.

  • New contained attribute — Constrains footer content using arc-container with the same breakpoints as the top bar. Accepts sm, md (default), lg, or xl. The footer background stays full-width.
  • Built-in horizontal padding — Full-width mode now applies padding-inline: var(--space-lg) to the footer content.

Docs

  • Footer rewrite — Replaced the custom SiteFooter.astro markup with <arc-footer> using its slot-based API (logo, default columns, legal).
  • CSP headers — Added _headers file to allow blob: fonts and Cloudflare Insights under Content-Security-Policy.

v1.10.2 — Top Bar Containment & Brand Integration

Adds a contained layout mode to the top bar and replaces custom footer branding with @arclux/brand components.

Top Bar

  • New contained attribute — Constrains top-bar content to the same breakpoints as arc-container. Accepts sm (720px), md (1120px, default), lg (1400px), or xl (1600px). The bar background stays full-width.
  • Built-in horizontal padding — Full-width mode now applies padding-inline: var(--space-lg) to the content area, removing the need for external ::part(content) overrides.

Docs

  • Footer branding — Replaced the custom inline SVG "by Arclight" markup with the <arclight-by> component from @arclux/brand.

v1.10.1 — Tier Reorganization & API Cleanup

Splits the overloaded content/ tier into three focused categories, removes the last deprecated API, and aligns token naming between JavaScript and CSS.

Content Tier Split

  • New data/ tier (14 components) — Structured data display: animated-number, badge, data-table, list, list-item, meter, stat, step, stepper, table, tag, timeline, timeline-item, value-card.
  • New typography/ tier (6 components) — Text rendering & formatting: code-block, highlight, kbd, markdown, text, truncate.
  • content/ retains 27 presentational components (accordion, avatar, card, carousel, icon, etc.).
  • All framework wrappers (React, Vue, Svelte, Angular, Solid, Preact) regenerated with new directory structure.

Deprecation Removal

  • Tabs items property removed — The deprecated items array prop and its legacy rendering path have been deleted. Use <arc-tab> children exclusively.

Token Naming Alignment

  • Renamed JS token keys: accentBlueaccentPrimary, accentVioletaccentSecondary.
  • Renamed glow keys: glow.blueglow.primary, glow.violetglow.secondary, glowLine.blueglowLine.primary.
  • CSS variable names unchanged (--accent-primary, --accent-secondary) — JS now matches CSS.

Slot Naming Standardization

  • Empty State — Renamed slot actionactions (consistent with cta-banner, top-bar, fieldset).
  • Status Bar — Renamed slots left/rightstart/end (consistent with toolbar, RTL-friendly).

Code Block Improvements

  • Fixed copy button scroll — Copy button no longer scrolls with code content on horizontal overflow. It stays pinned to the top-right of the code area.
  • Copy button transparency — Copy button is now semi-transparent with backdrop blur by default, becoming fully opaque on hover. Code beneath is visible.
  • Basic variant layout — Copy button uses inline flex layout so the block grows naturally on mobile instead of overlapping.
  • Fixed window variant footer — Status bar metadata (language, line count) now renders correctly after slot rename.

Scroll Spy Improvements

  • Sticky heading — The "On this page" heading now sticks to the top of the TOC when scrolling through long link lists.
  • Heading redesign — Gradient accent text, surface background, border, and glow-on-hover treatment.
  • Scroll to top — Clicking the heading smooth-scrolls both the page and the TOC back to the top.

Docs

  • Homepage code blocks — Replaced custom hero install and code example markup with native <arc-code-block> components (basic and window variants).

v1.10.0 — The Big Component Update

40 new components in a single release. Layout primitives, navigation patterns, form helpers, content building blocks, and feedback overlays — basically everything we kept saying "we'll add that later" about. Later is now.

New Components — Layout

  • Center (arc-center) — Content centering primitive with max-width, intrinsic centering, and text-center modes.
  • Cluster (arc-cluster) — Flex-wrap primitive for tags, chips, and button groups with token gap spacing.
  • Dock (arc-dock) — Edge-snapped auto-hide panel with hover-reveal and spring easing.
  • Float Bar (arc-float-bar) — Floating toolbar anchored to viewport bottom with backdrop blur. For bulk actions and unsaved-changes prompts.
  • Aspect Grid (arc-aspect-grid) — CSS grid with uniform aspect-ratio cells.
  • Inset (arc-inset) — Padding primitive with optional negative-margin bleed.
  • Masonry (arc-masonry) — CSS-columns masonry layout. No JavaScript, just columns and gap.
  • Responsive Switcher (arc-responsive-switcher) — Container-query layout switcher: row above threshold, column below.
  • Sticky (arc-sticky) — Sticky wrapper with IntersectionObserver-based stuck detection and arc-stuck event.

New Components — Navigation

  • Anchor Nav (arc-anchor-nav) — In-page link bar with IntersectionObserver-driven active state. Horizontal or vertical.
  • Bottom Nav (arc-bottom-nav) — Mobile bottom navigation bar with icon + label items and backdrop blur.
  • Breadcrumb Menu (arc-breadcrumb-menu) — Breadcrumb segments that double as dropdown triggers for sibling navigation.
  • Command Bar (arc-command-bar) — Always-visible search input designed for toolbar placement.
  • Page Indicator (arc-page-indicator) — Dot-based position indicator for carousels and onboarding flows.
  • Rail (arc-rail) — Ultra-narrow icon-only vertical navigation strip. Think VS Code activity bar.
  • Skip Link (arc-skip-link) — Accessible skip-to-content link, invisible until focused.
  • Speed Dial (arc-speed-dial) — FAB that fans out secondary icon actions with staggered animation.
  • Stepper Nav (arc-stepper-nav) — Full wizard navigation controller with Back/Next/Skip and validation gates.

New Components — Content

  • Separator (arc-separator) — Semantic role="separator" with optional centered label text. Variants: line, dashed, dotted, fade.
  • Image (arc-image) — Responsive image with loading skeleton shimmer, fade-in transition, and error fallback.
  • Scroll Indicator (arc-scroll-indicator) — Sticky progress bar showing scroll position. rAF-throttled for smooth performance.
  • List (arc-list) — Container for list items with keyboard navigation and single/multiple selection.
  • List Item (arc-list-item) — List item with prefix/suffix/description slots.
  • Virtual List (arc-virtual-list) — Renders only visible items for 10K+ row datasets.

New Components — Input

  • Label (arc-label) — Form label with required indicator, description slot, and tooltip slot.
  • Fieldset (arc-fieldset) — Form group with native <fieldset> + <legend>, error messages, and card variant.
  • Switch Group (arc-switch-group) — Groups arc-toggle children with propagated size and disabled.
  • Button Group (arc-button-group) — Connected button borders with propagated size and variant.
  • Input Group (arc-input-group) — Prefix/suffix addon slots with connected borders and shared focus glow.
  • Hotkey (arc-hotkey) — Invisible keyboard shortcut listener with modifier combos and chord sequences.

New Components — Feedback

  • Announcement (arc-announcement) — ARIA live-region wrapper for screen-reader announcements. No visual output.
  • Banner (arc-banner) — Full-width persistent notification strip with status variants and dismiss.
  • Confirm (arc-confirm) — Programmatic confirmation dialog with static open() returning Promise<boolean>.
  • Connection Status (arc-connection-status) — Auto online/offline detector with amber pulse animation.
  • Guided Tour (arc-guided-tour) — Multi-step onboarding overlay composing spotlight + tooltip.
  • Inline Message (arc-inline-message) — Compact icon + text feedback with status variants.
  • Loading Overlay (arc-loading-overlay) — Semi-transparent loading cover with centered spinner.
  • Progress Toast (arc-progress-toast) — Toast with embedded progress bar and imperative show/update/complete API.
  • Snackbar (arc-snackbar) — Bottom-anchored notification with imperative show() API and action button.
  • Spotlight (arc-spotlight) — Element-highlighting overlay for onboarding and feature discovery.

Improved

  • Avatar — Image loading now shows a shimmer skeleton, fades in on load, and falls back to initials on error.
  • Data Table — New virtual and row-height props enable row virtualization for large datasets.
  • Dropdown Menu — Internal divider rendering now uses arc-separator.
  • Context Menu — Internal divider rendering now uses arc-separator.

See you in v1.10.1 when we inevitably fix half of these.

v1.9.2 — Top Bar Container Alignment

Fixed

  • Top bar content alignmentarc-top-bar now uses the same centering strategy as arc-container (max-width + margin-inline: auto + padding-inline) so nav content aligns pixel-perfect with page content at every viewport width. Consumers no longer need ::part(topbar) padding overrides.

v1.9.1 — Icon Loading Fix for Production Builds

Fixed

  • Icons broken in production — v1.9.0's dynamic imports used template literal paths that Vite/Rollup can't resolve from node_modules, causing 404s in production builds. Replaced with generated resolver maps containing static import() paths per icon, which bundlers can analyze and chunk correctly.

v1.9.0 — Per-Icon Lazy Loading

Icons are now loaded on demand — each icon is its own ~500-byte module, dynamically imported when first used. Previously, importing arc-icon eagerly pulled in both Phosphor (817KB) and Lucide (736KB) regardless of how many icons you actually used. Now, 0KB of icon data is loaded upfront, and only the icons your app renders are fetched.

Added

  • Per-icon lazy loading<arc-icon name="star"> dynamically imports only star.js (~500 bytes), not the entire library. Bundlers tree-shake unused icons automatically.
  • Per-icon subpath exports — Individual icons are available via @arclux/arc-ui/icons/phosphor/star and @arclux/arc-ui/icons/lucide/arrow-right for direct static imports.
  • Full-library opt-inimport icons from '@arclux/arc-ui/icons/phosphor' still works for use cases that need all icons (e.g. icon pickers).

Breaking Changes

  • iconRegistry.get() is now async — Returns a Promise<string | null> instead of a string. This only affects code that calls get() directly; <arc-icon> handles this internally.
  • iconRegistry.list() is now async — Returns a Promise<string[]>. Update any direct callers to await the result.

Bundle Impact

  • 0 icons used — Before: +1.6MB. After: +0KB.
  • 1 icon used — Before: +1.6MB. After: ~500 bytes (lazy).
  • 10 icons used — Before: +1.6MB. After: ~5KB (lazy).
  • Full library (explicit import) — Before: +1.6MB. After: +817KB (opt-in).

v1.8.6 — Theme Toggle Icon-Only Size Fix

Fixed

  • Theme toggle icon-only size — No, actually fixed this time. Restored 36px dimensions to match arc-icon-button, with --touch-min as a floor for mobile.

v1.8.5 — Theme Toggle Padding Fix

Fixed

  • Theme toggle padding — Restored desktop padding on arc-theme-toggle after v1.8.4 got a little too enthusiastic with the touch target tokens.

v1.8.4 — Mobile Touch Targets & Code Block Fix

Improved

  • Mobile touch targets — 8 interactive components now scale up on touch devices via --touch-min / --touch-pad tokens: arc-icon-button, arc-toggle, arc-input, arc-select, arc-pagination, arc-theme-toggle, arc-tag, and arc-chip.
  • Touch token tuning — Mobile touch targets set to 36px min-height / 8px padding (up from 24px/4px desktop defaults).

Fixed

  • Code block basic variant — Single-line code now vertically centers alongside the copy button instead of sitting at the top.

v1.8.3 — tokens.css → base.css Rename & FOUC Prevention

Breaking Changes

  • Renamed tokens.cssbase.css — The stylesheet contains design tokens, theme overrides, and touch media queries. Update imports from @arclux/arc-ui/tokens.css to @arclux/arc-ui/base.css.

Added

  • FOUC preventionbase.css adds :not(:defined) { opacity: 0 } and each component's :host includes transition: opacity 150ms ease, so unregistered elements are hidden and fade in smoothly when their JS loads.

Internal

  • Prism 1.2.2 — Updated @arclux/prism with renamed baseCSS config key.

v1.8.2 — Missing Subpath Exports

Adds missing per-component subpath exports and removes unused tier-level exports.

Fixed

  • Missing subpath exports — Added ./cta-banner and ./input to package.json exports so import '@arclux/arc-ui/cta-banner' and import '@arclux/arc-ui/input' resolve correctly for tree-shaking.

Internal

  • Removed tier exports — Dropped ./content, ./feedback, ./navigation, ./layout, and ./shared tier-level subpath exports. Use per-component imports (@arclux/arc-ui/button) or the barrel (@arclux/arc-ui) instead.

v1.8.1 — Tree-Shaking Bugfix

Fixes named barrel imports like import { ArcButton } from '@arclux/arc-ui' being tree-shaken away by Vite/Rollup, causing components to silently not render.

Fixed

  • Barrel now registers components — Tier index files (content/index.js, input/index.js, etc.) now re-export from .register.js files instead of pure class files. This means named imports through the barrel correctly call customElements.define() and are preserved by bundlers via the sideEffects field.
  • Stale docs imports — Fixed several docs pages still showing the pre-v1.8.0 import '@arclux/arc-ui' pattern which no longer registers components. Updated getting-started, frameworks, and data-table examples to use import '@arclux/arc-ui/register' or per-component imports.
  • CDN script tags — Fixed CDN <script> examples in getting-started and frameworks pages to point to @arclux/arc-ui/register instead of the bare entry point.

Docs

  • Import patterns callout — Getting-started page now documents the three import patterns and their tree-shaking trade-offs.

v1.8.0 — Tree-Shakeable Components

Component source files are now side-effect-free, enabling bundlers to tree-shake unused components. Registration is handled by generated .register.js files.

Breaking Changes

  • No auto-registrationimport '@arclux/arc-ui' no longer registers custom elements as a side effect. Named imports still work and are now tree-shakeable. If you relied on the side-effect registration, switch to import '@arclux/arc-ui/register'.

New Consumer API

  • import { ArcButton } from '@arclux/arc-ui' — named class import. Registers all components via the barrel (not tree-shakeable). Fixed in v1.8.1 to actually call customElements.define().
  • import '@arclux/arc-ui/register' — registers all components (replaces bare side-effect import).
  • import '@arclux/arc-ui/button' — single component, auto-registered with its dependencies.

Internal

  • Side-effect-free sources — Removed customElements.define() from all 117 component source files. Added @tag and @requires JSDoc annotations for metadata.
  • Registration generator — New scripts/generate-registrations.js produces per-component .register.js files and a top-level register.js during pnpm generate.
  • sideEffectspackage.json now declares "sideEffects": ["./src/*/*.register.js", "./src/register.js"] so bundlers can drop unused modules.
  • Prism v1.2.1 — Parser extracts tag names from @tag JSDoc with fallback to customElements.define(). Ignore patterns support leading wildcards.

Docs

  • Homepage live demo — Expanded from 5 showcase cards to 10, covering buttons, badges & tags, inputs, feedback, toggles & checks, rating & slider, navigation (tabs + breadcrumb), data (avatars + stepper), accordion, and controls (segmented control + pagination).

Fixed

  • Dashboard Grid — Fixed non-responsive layout when explicit columns attribute is set. The grid now uses auto-fill with a derived minimum column width so it wraps naturally on narrow viewports instead of forcing a fixed column count.

v1.7.2 — Themeable Syntax Highlighting

Improved

  • Code Block — Switched from baked hex Shiki themes to a CSS custom-properties theme. Syntax colors now derive from design tokens (--accent-primary, --accent-secondary, --color-success, etc.) so consumers can override them alongside the rest of the palette.
  • Code Block — Shiki core and regex engine are now lazy-loaded on first use, removing ~600KB from the initial bundle.

Internal

  • Removed 9 redundant per-page import '@arclux/arc-ui' script tags — BaseLayout already loads the library globally.

v1.7.1 — Token Consolidation

Internal

  • Single source of truth — Removed the duplicate tokens.js from the web-components package. shared/tokens.js is now the only token file.
  • Code Block — Fixed cross-package runtime import of shared/tokens.js that broke external consumers.

v1.7.0 — Code Block Variants

Code Block gains three visual variants and built-in Shiki syntax highlighting.

New Props

  • NavItem — Replaced muted boolean with a variant prop (default | primary | muted). The new primary variant renders accent-colored text and border in the resting state with a stronger glow on hover and active.
  • Code Block — Added variant prop with three modes:
    • default — Header bar with filename (left) and language label (top-right), no status bar footer.
    • window — macOS-style title bar with colored traffic-light orbs, centered filename, elevated shadow, and status bar footer with language + line count.
    • basic — Minimal: no header, no footer, compact padding, inline copy button. Ideal for single-line snippets.

Improved

  • Code Block — Integrated Shiki syntax highlighting using fine-grained imports (shiki/core + shiki/engine/javascript) — no WASM, tree-shakable, with lazy per-language loading from @shikijs/langs.
  • Code Block — Custom arc-dark and arc-light syntax themes derived from the design token palette. Keywords use accent blue, strings use success green, constants use accent violet, and all colors cascade from shared/tokens.js.
  • Code Block — Dual-theme support via CSS light-dark() — syntax colors switch automatically with the site theme, no re-render needed.
  • Code Blockfilename prop now reflects to the HTML attribute.
  • Code Block — Default variant moved language label from the status bar into the header bar (top-right) and removed the status bar footer for a cleaner look.
  • Code Block — Copy button repositioned inside the code body so it sits in the same spot across default and window variants.

Internal

  • Tokens — Added color-scheme: dark / color-scheme: light to the generated token CSS, enabling the CSS light-dark() function across all components.

Fixed

  • Copy Button — Replaced hardcoded dark backdrop (rgba(black, 0.6)) with theme-aware --bg-elevated token so the button renders correctly in light mode.

v1.6.0 — New Component Props

New props across six components — semantic status variants for Badge, layout controls for Divider, and form-critical props for Input, Button, and Select.

New Props

  • Badge — Added success, warning, error, and info semantic variants. Added color prop for custom RGB colors.
  • Divider — Added align prop (left/right) for asymmetric gradient origins and vertical prop for inline separators.
  • Input — Added value prop for controlled-component patterns and rows prop for multiline textarea height.
  • Button — Added type prop (button/submit/reset) for native form submission behavior.
  • Select — Added open prop for programmatic dropdown control.

Fixed

  • Accordion — Removed incorrect items prop from docs that never existed in the component.

v1.5.2 — Bug Fixes

Fixed

  • Accordion chevron — Replaced the tiny Unicode triangle character with an 18px SVG chevron icon for proper sizing and crisp rendering.

v1.5.1 — Bug Fixes

Fixed

  • Shadow DOM CSS reset — Added a universal reset (margin: 0; padding: 0; box-sizing: border-box) to the shared style module injected into every component. Consumer CSS resets (* { margin: 0 }) cannot cross shadow DOM boundaries, so browser user-agent defaults (e.g. h2 { margin: 0.83em 0 }) were leaking into component internals.
  • Boolean prop reflection — 11 boolean properties were missing reflect: true, preventing them from syncing back to HTML attributes when set via JavaScript. Affected props: Alert dismissible, Modal closable, Input required / multiline, DataTable sortable / selectable, ScrollToTop smooth, Form novalidate / error-summary, Link external, FileUpload multiple.

v1.5.0 — Utility Props Sweep

A comprehensive pass adding utility props across the entire component library. Most additions are pure CSS — no breaking changes.

New Props — High Impact

  • Buttonloading prop shows a spinner and disables the button for async operations.
  • Inputerror prop for inline validation messages with red border styling. size prop (sm/md/lg).
  • Cardpadding prop (none/sm/md/lg) and interactive boolean for clickable cards without href.
  • Tabsalign prop (start/center/end) and variant prop (underline/pills).
  • Avatarstatus indicator dot (online/offline/busy/away) and shape prop (circle/square/rounded).
  • Badge / Tagsize prop (sm/md/lg) for both components.
  • Containersize prop (sm/md/lg/xl/full) for max-width control. padding prop (none/sm/md/lg).
  • Breadcrumbseparator prop for custom separator characters (/, >, •).
  • TopBarnav-align prop (left/center/right) to pin navigation within the center slot.

New Props — Form Controls

  • Checkbox / Toggle / RadioGroupsize prop (sm/md/lg) for all three.
  • Selectsize prop (sm/md/lg) and error prop for validation state.
  • Textareasize prop (sm/md/lg) and auto-resize boolean that grows the textarea to fit content.

New Props — Content & Navigation

  • Dividerlabel prop for labeled dividers (e.g. "OR" between form sections).
  • Stattrend prop (up/down/neutral) with arrow indicator and change prop for percentage display.
  • Calloutdismissible boolean adds a close button and fires arc-dismiss.
  • Linkunderline prop (hover/always/never) for fine-grained underline control.
  • Progressshow-value boolean displays the current percentage.
  • Sidebarposition prop (left/right) moves the border to the opposite edge.
  • Footeralign prop (left/center) for centered footer layouts.

New Props — Feedback & Layout

  • Modalfullscreen boolean for full-viewport modals on mobile.
  • Alertcompact boolean reduces padding and font sizes for inline use.
  • Toastshow() now accepts actionLabel, action callback, and persistent to prevent auto-dismiss.
  • Accordionmultiple boolean allows multiple panels open simultaneously.
  • Skeletoncount prop renders multiple stacked skeleton items.
  • Paginationcompact boolean shows only prev/next with a "current / total" label.
  • Muted nav style — Reworked muted nav items: transparent background by default, white text on hover with soft glowing white pill, faint primary glowing pill for active state.

Docs

  • See Also links — Every component page now shows a "See Also" section linking to related components and guide pages. Guide pages also cross-link to each other. Helps users discover related components and navigate between docs naturally.

v1.4.0 — Component Composition & Token Infrastructure

Internal

  • Shared style modules — Extracted reusable style sheets into composable modules that multiple components import:
    • button-styles.js — variant styles (secondary, ghost, focus, disabled) shared by Button, IconButton, and Chip
    • card-styles.js — hover glow and gradient border shared by Card and FeatureCard
    • status-styles.js — status color CSS custom properties shared by Alert, Callout, and Toast
    • status-utils.js — status icon resolver shared by Alert, Callout, and Toast
  • Callout — Now uses status color tokens for border and background tinting instead of neutral card styling. Icon color matches the variant.
  • TopBar — Added inner content wrapper to separate backdrop/border from flex layout, exposing a content CSS part.
  • Token single source of truthshared/tokens.js is now the sole source. tokens.css is generated from it. pnpm generate runs token generation then Prism.
  • Fixed focus-glow drift — Shadow DOM fallbacks had different --focus-ring and --focus-glow values than the CSS file. Both now derive from tokens.focus.
  • Light theme completenesslightTokens now includes gradient, glow-line, and utility overrides that were previously only in hand-maintained CSS.

v1.3.0 — Visual Refinements

Improved

  • Badge — Switched to mono font with no uppercase for a quieter, code-like appearance. Added color prop accepting an RGB triplet (same API as Tag).
  • Tag — Increased vertical padding for more visual prominence. Removed min-height constraint so tags are naturally taller than badges when side by side.
  • Toggle — Label now uses the primary body font instead of the accent font, with normal casing.

Docs

  • Component page header: tier and interactivity labels now use arc-tag with custom color props instead of arc-badge.

v1.2.0 — Component Enhancements

Improved

  • Input — Added prefix and suffix slots for placing icons or inline adornments inside the field box. The input/textarea is now wrapped in a flex container that holds the border and focus styles.
  • Tag — Added color prop accepting an RGB triplet (e.g. "77, 126, 247") for custom category colors. Overrides variant colors for border, text, background, and hover glow.
  • Button — Added prefix and suffix slots for icons alongside the button label. Both <button> and <a> render paths support slots.

v1.1.0 — New Components & Enhancements

New Components

  • CTA Banner (arc-cta-banner) — Full-width call-to-action banner with gradient background, eyebrow text, headline, and actions slot. Replaces inline CSS patterns on landing pages.

Improved

  • Card — Added footer slot for action buttons, links, or metadata. Hides automatically when empty.
  • PageHeader — Replaced semantic slots (breadcrumb, actions, tabs) with positional slots (above, aside, below). Added border prop to opt-in to the bottom border (now off by default).
  • Form — Added loading prop to prevent double-submit, disabled prop that propagates to all child controls, error-summary with rendered error list, formData in event detail, and arc-reset event.
  • NavItem — Added muted prop for subdued navigation items that render with lighter text and no border. Supported in both desktop and mobile views.

Docs

  • Homepage CTA section now uses arc-cta-banner, removing ~50 lines of inline CSS.
  • Fixed navigation menu preview dropdown overflow and hover bleed.
  • Fixed container preview overflow by using simulated layout.
  • Dashboard grid preview: labels now use accent font (Tektur), numbers use body font (Host Grotesk).

v1.0.0 — Initial Release

The first stable release of ARC UI: Lit Web Components across 5 tiers, framework wrappers for React, Vue, Svelte, Angular, Solid, and Preact, standalone CSS output, and a comprehensive design token system with dark and light themes.