Changelog
1.6.1 latest
Section titled “1.6.1 latest”2026-07-31
A packaging fix for people who vendor the library instead of installing it. No change for anyone using npm or the CDN: phantom-ui.js and phantom-ui.cdn.js are byte-identical to 1.6.0.
The release assets now pair correctly. TypeScript resolves a declaration file to a module by exact filename, so the unminified build added in 1.6.0 was arriving with no types at all: it was named phantom-ui.standalone.dev.js and looked for a .dev.d.ts that does not exist.
The assets attached to this release are:
phantom-ui.standalone.js unminified, about 1500 lines, readablephantom-ui.standalone.min.js the same code, minifiedphantom-ui.standalone.d.ts types, no imports of any kindDownload the first and the third into a folder and import by path. Nothing else to configure, and it type-checks under strict with skipLibCheck: false in a project with no node_modules at all.
Heads up if you took the 1.6.0 asset: phantom-ui.standalone.js changes form here. It was the minified build, it is now the unminified one. Same code, larger file. Take phantom-ui.standalone.min.js if you want the small one.
Using it in Angular
Section titled “Using it in Angular”Two things that are easy to lose time on, neither specific to this release:
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";// ...schemas: [CUSTOM_ELEMENTS_SCHEMA],Without it Angular rejects the tag with NG0304. And bind through the attribute rather than the property:
<phantom-ui [attr.loading]="loading() ? '' : null">2026-07-31
Vendoring for people who cannot reach a registry, and an accessibility fix for React.
aria-busy carried the string "undefined" in React. React assigns properties rather than attributes on custom elements, so the common loading={cond || undefined} binding hands the element undefined, and that went straight into the attribute. An unrecognised value is treated as false by assistive technology, so nobody was stuck with a permanently busy region, but the attribute was wrong in the framework most consumers use. Solid was never affected, since attr:loading goes through the attribute converter.
This predates the refactor: 1.4.0 behaves the same.
An unminified standalone bundle, for anyone who has to read what they are vendoring before approving it.
phantom-ui.standalone.js 34.7 kB minifiedphantom-ui.standalone.dev.js 51.5 kB about 1500 lines, readablephantom-ui.standalone.d.ts 8.7 kB types, no imports of any kindSame code either way, both with Lit inlined and no external imports. Drop the .js and the .d.ts in a folder and import by path:
import "./phantom-ui.standalone.dev.js";import type { PhantomUi } from "./phantom-ui.standalone.dev.js";No package manager, no node_modules, and it still type-checks under strict with skipLibCheck: false.
All three files are attached to this release, so they can be downloaded from GitHub without touching npm. They are also on the CDNs, which are different hosts from the registry and often get through where it does not:
https://cdn.jsdelivr.net/npm/@aejkatappaja/phantom-ui@1.6.0/dist/phantom-ui.standalone.dev.jsOne limit worth stating: these are bundled output, not the authored TypeScript. The sources import Lit, so they cannot be compiled by a project that cannot install anything. The sources are still the better thing to read for a review, and they are in the repository.
Testing
Section titled “Testing”A new CI job drives two built example apps in a real browser and asserts that every shimmer block lands on an element that actually exists, that all text and media are hidden while loading, and that the overlay, the inert attributes and the hiding are all gone on reveal. The examples were previously only built, never rendered.
Thanks
Section titled “Thanks”@hshahi, who pushed back when the first answer did not work for his setup, and whose report is why both the overlay fix in 1.5.0 and the vendoring path exist.
2026-07-30
First release since 1.4.0. Nothing in the public API changed.
mode="overlay" now always shows an indicator. Overlay makes the block fill transparent on purpose so your content shows through and only a light glint is drawn on top. That glint was the only thing it painted, and animation="pulse", "breathe", "solid" and the reduced motion media query each suppressed it. With a transparent fill there was nothing left, so a refresh rendered no indication at all beyond the dim. Measured across the matrix, overlay painted something in 1 of its 8 combinations.
It now keeps a flat veil at --shimmer-color whenever the glint is suppressed. pulse and breathe animate the block, so they animate the veil with it. solid and reduced motion hold it static, so nothing moves and WCAG 2.3.3 is unaffected. Reported by @hshahi in #46.
pierce-shadow re-measures when a pierced component re-renders. Shadow roots reached by piercing are now observed, so a component that rebuilds its own internals gets measured again instead of leaving stale blocks behind. The documentation listed this as a limitation, and that entry is gone.
A broken link in the docs. The SSR Frameworks link on the TypeScript reference page was missing the site base path and returned a 404.
@aejkatappaja/phantom-ui/standalone, an ES module with Lit bundled in. 34 kB minified, 11.5 kB gzipped, no runtime dependency and no bare imports.
import "@aejkatappaja/phantom-ui/standalone";Vendoring without a registry. If you cannot install from npm at all, copy two files into your project and import by path:
phantom-ui.standalone.js # the component, Lit includedphantom-ui.standalone.d.ts # its types, no imports of any kindimport "./phantom-ui.standalone.js";import type { PhantomUi } from "./phantom-ui.standalone.js";That is the whole install. The declarations reference nothing external, so a project with no node_modules still type-checks under strict with skipLibCheck: false. Both files are also reachable over a CDN if your proxy allows one:
https://cdn.jsdelivr.net/npm/@aejkatappaja/phantom-ui@1.5.0/dist/phantom-ui.standalone.jshttps://cdn.jsdelivr.net/npm/@aejkatappaja/phantom-ui@1.5.0/dist/phantom-ui.standalone.d.tsChanged
Section titled “Changed”A large internal refactor: duplication removed, the four shimmer keyframe sets folded into two, the SSR stylesheet generated from a single source, type assertions replaced with runtime narrowing. No public API change.
Three attribute types that had gone missing from custom-elements.json are restored, with a check so they cannot drift again.
Testing
Section titled “Testing”99 tests with a coverage floor enforced in CI. The built bundle is also smoke tested in a real page, the six example apps are rebuilt on every pull request, and the published tarball is installed and type-checked in a throwaway project.
Thanks
Section titled “Thanks”@hshahi, whose report turned into the overlay fix and the standalone build, and whose earlier suggestion is why mode="overlay" exists at all.
2026-07-01
What’s new
Section titled “What’s new”Refresh mode: A new mode="overlay" keeps your existing content on screen, dimmed, while a structure-aware light glint sweeps over each element, for refresh / stale-while-revalidate states (a grid you already have data for and are refetching). Pair it with your data library’s two states: skeleton on the first load, overlay on refetch. During the refresh the content is not clickable, so users can’t act on stale data, but it stays readable and in the accessibility tree (aria-busy announces the update). Dim it with the new --phantom-content-opacity variable (default 0.5). The default mode="skeleton" is unchanged.
Thanks to the community discussion that suggested this.
Tooling: The published custom elements manifest now resolves the element tag name and constant-backed default values instead of emitting internal symbol names.
2026-06-13
What’s new
Section titled “What’s new”Accessibility: While loading, placeholder content is now made inert, so it stays out of the tab order and the accessibility tree and cannot be activated with the keyboard. Keyboard and screen reader users no longer reach invisible placeholder elements. Elements marked data-shimmer-ignore stay interactive.
Reconnection: Moving a loading <phantom-ui> in the DOM (keep-alive, virtual scroll, a node move) now re-initializes its observers and accessibility state instead of leaving it stale.
Thanks to @patrikpereira99 for reporting the accessibility gap.
2026-06-13
Numeric attributes: Removing a numeric attribute (duration, reveal) or setting an invalid value no longer emits a broken --shimmer-duration: nulls or NaNs inline. That had frozen the animation and shadowed inherited theming. Reported and fixed by @patrikpereira99.
Explicit setup: The library has no postinstall hook. The optional npx @aejkatappaja/phantom-ui init command (JSX types and SSR CSS import) is now documented as an explicit step, and the docs no longer imply it runs automatically on install.
2026-06-10
What’s new
Section titled “What’s new”Reduced-motion support - Under prefers-reduced-motion: reduce, every animation mode (shimmer, pulse, breathe) now degrades to the static solid look automatically. No configuration needed (WCAG 2.3.3).
Global theming now works - CSS custom properties set on the element (phantom-ui { --shimmer-color: ... }) were shadowed by inline defaults on the overlay and had no effect. They now apply as documented. Per-instance attributes still take precedence.
React 18 reveal - Toggling loading from true to false left slotted content permanently hidden when loading="false" was set as an attribute (React 18, attribute-based bindings). The stale attribute is now cleared so content reveals correctly.
Thanks to @SrPalha for reporting all three and contributing the reduced-motion fix.
2026-06-09
-
pierce-shadowattribute — measure inside the open shadow roots of slotted custom elements, for design systems built with Stencil, Lit, or other Web Component tooling. Resolves default and named slots to their projected content, treats slot-only containers as measurable leaves, and hides shadow content during loading. Closed shadow roots are not reachable. See the Design Systems guide. -
mask-image icon hiding — icons drawn with CSS
mask-imagetinted viabackground-color(including masks on::before/::afterpseudo-elements) are now detected at runtime and hidden like real media during loading, instead of leaking through the shimmer. Works in light DOM and pierced shadow roots.
- For inline SVGs that contain a text layer, use
data-shimmer-no-childrenon the wrapper to capture the icon as a single block.
Thanks to the community for testing this against a real Stencil design system.
2026-06-01
First stable release. The public API is now covered by semantic versioning guarantees — documented attributes, data attributes, CSS custom properties, and TypeScript types will not change in a breaking way without a major version bump.
Nothing changed in the runtime since 0.10.1. This release is the commitment to API stability: you can build on phantom-ui without things shifting under you.
Since the journey began
Section titled “Since the journey began”- Structure-aware skeleton generation by measuring the real DOM at runtime
- 4 animation modes (shimmer, pulse, breathe, solid) + shimmer direction
count/count-gaprepeat mode with container replicationstagger,reveal,debug,loading-label- Fine-grained
data-shimmer-*controls - SSR pre-hydration CSS + automatic framework setup CLI
- Safe to import multiple times (micro-frontends, lazy modules, HMR)
- Works with React, Vue, Svelte, Angular, Solid, Qwik, HTMX, and vanilla HTML
Thanks to everyone who starred, used it, and opened issues.
0.10.1
Section titled “0.10.1”2026-05-06
- Duplicate custom element registration (#10, #11). phantom-ui can now be imported multiple times without throwing `NotSupportedError`. The `@customElement` decorator was replaced with a guarded `customElements.define()` call, making the library safe for micro-frontends, lazy-loaded modules, dynamic imports, and HMR / hot reload environments.
First-load behavior is unchanged. Re-imports become a no-op.
0.10.0
Section titled “0.10.0”2026-05-02
- `debug` attribute that outlines each measured block with an index for visual inspection of how phantom-ui interprets the DOM. Leaf blocks get pink outlines with numeric indices, container blocks get blue outlines with C-prefixed indices.
Documentation
Section titled “Documentation”- HTMX added to the supported frameworks list
- Qwik SSR usage example
- `aria-busy` accessibility behavior documented
2026-04-13
New Features
Section titled “New Features”shimmer-directionattribute — control the direction of the shimmer sweep:ltr(default),rtl,ttb, orbtt. Only applies toanimation="shimmer".
Improvements
Section titled “Improvements”- Upgraded Biome to v2.4
- Upgraded TypeScript to v6.0
- Added HTMX framework guide and example app
- Added direction control to the interactive demo page
- Fixed Solid
attr:loadingto properly remove attribute using ternary pattern - Added data fetching example to Angular app
- 30 tests (5 new for shimmer-direction)
2026-04-10
Refactor
Section titled “Refactor”- Extracted light-DOM styles and JSX types into separate files for cleaner architecture
- Replaced inline style strings with Lit
styleMapin block rendering - Disconnected MutationObserver during measurement to prevent double re-trigger from table cell text measurement
Bug Fixes
Section titled “Bug Fixes”data-shimmer-widthordata-shimmer-heightset alone no longer silently skips the element- Removed dead
tagfield from ElementInfo - Removed
HRduplicate from VOID_TAGS (already in ALWAYS_LEAF_TAGS)
2026-04-10
Features
Section titled “Features”- Container replication on
count: Whencount > 1, repeated skeleton rows now replicate the slotted element’s background, border, border-radius, and box-shadow. Previously only the first row showed the card styling.
Performance
Section titled “Performance”Benchmarked the DOM measurement pipeline:
| Elements | Leaf nodes | Time |
|---|---|---|
| 100 | 334 | ~20ms |
| 500 | 1,667 | ~25ms |
| 1,000 | 3,334 | ~31ms |
- Added tests verifying container blocks are emitted for styled elements and skipped for transparent ones (25 total)
- Performance section in README and How It Works page
- Card-based count example in demo playground
- Favicon on standalone demo page
- Body scroll lock when mobile settings sheet is open
2026-04-10
DX Improvements
Section titled “DX Improvements”- JSX types: added
key,ref,data-*attributes andstyleobject toPhantomUiAttributesfor React/Solid compatibility - JSDoc on every prop: hover documentation for all attributes in supported IDEs
- Updated code examples: use
classinstead ofclassName(React 19+ supports it natively)
- React Query + phantom-ui interactive demo (StackBlitz)
- Redesigned demo page: ghost SVG hero, Get Started / GitHub stars nav
- Version badge in docs header (fetched from npm at build)
- Landing page button polish and social icon colors
2026-04-09
Features
Section titled “Features”-
loading="false"treated as falsy — No more|| undefinedworkaround. Pass booleans directly in React <19, Qwik, Solid, and vanilla HTML. React 19+, Vue, and Svelte already handled this natively. -
Auto-generated changelog — Docs now include a Changelog page pulled from GitHub releases at build time. Stays up to date automatically on every deploy.
Docs & Demo
Section titled “Docs & Demo”- Redesigned demo controls: ghost mascot toggle button with bounce animation
- Handwritten “click me!” chalk annotation on desktop
- Mobile: floating action button + bottom sheet for settings (swipe to close)
- Compact bottom sheet layout: 2-column grid for colors and sliders
- Responsive dashboard card grid (4→2 columns on mobile)
- Ghost-style back button with arrow icon
- Landing page cards with subtle colored borders and tinted backgrounds
- Matched docs background colors to demo page (dark/light)
- Improved data fetching guide: clarified that layout structure is the skeleton, no fake placeholder text needed
- Removed
|| undefinedpattern from all code examples across docs, README, and framework guides
2026-04-09
Bug Fixes
Section titled “Bug Fixes”- data-shimmer-ignore: elements with
data-shimmer-ignorenow properly stay visible during loading. Previously, light DOM styles (-webkit-text-fill-color: transparent,opacity: 0) were hiding text and media inside ignored zones.
- Added tests verifying
data-shimmer-ignorekeeps text and images visible during loading state.
- Standalone interactive demo page with 11 card types, sticky controls bar, and theme switcher
- Custom moon/sun theme toggle component for docs
- Package manager tabs (npm/pnpm/yarn/bun) on all install commands
- Data fetching guide with TanStack Query, SWR, and Vue composable patterns
- Improved light mode contrast and purple accent color
- Fixed default color values in README
2026-04-09
What’s new
Section titled “What’s new”SSR pre-hydration CSS — Prevents content flash before JavaScript hydrates. The postinstall script now auto-detects SSR frameworks (Next.js, Nuxt, SvelteKit, Remix, Qwik) and injects import "@aejkatappaja/phantom-ui/ssr.css" into your layout file.
data-shimmer-width / data-shimmer-height — Override measured dimensions for elements that have no size yet (images without explicit width/height, JS-filled containers). Elements with zero dimensions are normally skipped — these attributes force a skeleton block.
CLI tests — 54 bun tests covering framework detection, SSR detection, CSS injection, indentation preservation, and idempotency.
2026-04-08
Bug fix
Section titled “Bug fix”- Hide buttons during loading – buttons and
[role="button"]elements are now hidden (opacity: 0) during loading state, preventing colored backgrounds from bleeding through the shimmer blocks
2026-04-08
Bug fixes
Section titled “Bug fixes”- Fix border visibility during loading – borders using
currentColor(e.g.border: 2px solid) are no longer hidden by the loading styles - Neutral default colors – shimmer defaults changed from white-based to neutral gray, now visible on both light and dark backgrounds
Updated
Section titled “Updated”- Docs and playground updated to reflect new defaults
2026-04-08
New features
Section titled “New features”- Media load re-measurement — Skeleton blocks re-measure automatically when images/videos finish loading via a capture-phase
loadlistener. Images without explicitwidth/heightare picked up once they load. - Deep descendant hiding — All nested elements (not just direct slot children) are now hidden during loading. A light DOM stylesheet targets
phantom-ui[loading] *to hide deeply nested images, text, and media. - Reveal transition fix — The
revealfade-out now works correctly. The overlay stays in the DOM during the CSS opacity transition instead of being removed and recreated.
Improvements
Section titled “Improvements”- Animated SVG preview — Replaced the 2.7MB preview GIF in the README with lightweight animated SVGs.
- Browser test suite — 19 tests running on Chromium, Firefox, and WebKit via
@web/test-runner+ Playwright. - Interactive playground —
bun run playgroundserves a local test page with controls for every attribute. - Contributing guide — Added
CONTRIBUTING.mdwith setup, testing, and PR guidelines. - CI improvements — Test job added to CI pipeline, Playwright browsers cached for faster runs.
- Framework examples — README examples now showcase more attributes (animation, stagger, reveal, count) and include a React list example with repeat mode.
- Examples point to local package — All framework examples in
examples/now usefile:../..instead of a pinned npm version. - Input validation —
countclamped to minimum 1,count-gapclamped to minimum 0. - Repo cleanup — Removed unused
preview.gifandlogo.svg, moved README assets to.github/assets/.
2026-04-08
What’s new
Section titled “What’s new”countattribute — Generate multiple skeleton rows from a single template element. Useful for lists, tables, and feeds where data isn’t loaded yet.count-gapattribute — Control spacing (in pixels) between repeated skeleton rows.- Input validation:
countis clamped to minimum 1,count-gapto minimum 0.
Example
Section titled “Example”<phantom-ui loading count="5" count-gap="8"> <div class="user-row"> <img src="avatar.png" width="32" height="32" /> <span>John Doe</span> <span>john@acme.io</span> </div></phantom-ui>2026-04-07
New animation modes, stagger effect, reveal transition, and accessibility.
- 4 animation modes: shimmer (default), pulse, breathe, solid
- Stagger attribute: progressive delay between blocks for wave effect
- Reveal attribute: smooth fade-out when loading ends
- aria-busy automatically set on host element
- Updated demo page and documentation with new controls
2026-04-07
Fix shimmer animation on Firefox and improve loop smoothness.
- Gradient now uses block background color at edges instead of transparent, eliminating the visible gap between animation cycles
- Moved gradient declaration out of keyframes (Firefox cannot interpolate between full background shorthand values)
- Changed easing from ease-in-out to linear for seamless looping
- Fixed slotted leaf elements (p, img, span) not being captured as skeleton blocks
2026-04-06
Fix: slotted leaf elements (p, img, span) were not captured as skeleton blocks when they were direct children of the slot.
2026-03-30
Add repository field for webcomponents.org
2026-03-30
Structure-aware skeleton loader. One Web Component, every framework.
Highlights
Section titled “Highlights”- Wrap your real UI in
<phantom-ui loading>and get perfect shimmer placeholders automatically - Works in React, Vue, Svelte, Angular, Solid, Qwik, and plain HTML
- ~8kb gzipped CDN build (Lit included)
- Runtime DOM measurement via
getBoundingClientRect() - CSS custom properties for theming
data-shimmer-ignoreanddata-shimmer-no-childrenfor fine-grained control- TypeScript types with auto-generated JSX declarations
- Starlight documentation site
Install
Section titled “Install”bun add @aejkatappaja/phantom-ui