[NEW PLUGIN] Auto Skeleton Loader — Pixel-perfect loading placeholders that auto-detect your layout

Hi Bubblers,

My dev partner and I built this because other skeleton loaders require you to list the IDs of elements you want covered, sometimes require drawing matching shapes, and then come back to it whenever the layout changes. We wanted the opposite:

Drop one element on the page that scans the DOM automatically, draws shimmer placeholders that match the real layout, and updates them whenever the layout changes.

That is what this plugin does.

You can still, of course, exclude elements by ID or only apply the skeleton to the desired elements.

You may have multiple skeletons per page, each configured differently (ideal for single-page apps).

Light mode, dark mode, and custom-color skeleton side by side

Here is the full features list:

  • Inverse-by-default targeting: by default, the plugin scans the whole page and shimmers every visible element it finds. You exclude what should stay visible (typically the header, footer, navigation, some Floating Groups), instead of listing every element to cover. For pages with focused loading, you can switch to specific containers (reloading a Repeating Group after a filter change, for example).
  • Real DOM scanning (not pre-declared shapes): the plugin reads your actual layout at runtime and draws placeholders that match the position, size, and corner radius of each element. If you redesign your page, the skeleton matches the new design without any change to the plugin configuration.
  • Pixel-perfect placement: round avatars stay round, pill buttons stay pill-shaped, cards keep their corners. The plugin reads each element’s actual border-radius, position, and size, and replicates them. Override globally with one number when you want a uniform look across a mixed-radius layout.
  • Live updates via MutationObserver: when your layout changes mid-load (a Repeating Group fills with cells, a conditional Group appears, a Floating Group toggles), the skeleton rescans and adapts within ~80ms. The shimmer is also anchored to the page, not the viewport: if a visitor scrolls during loading, the skeleton and the live content move together. This is the part that makes the plugin work for SPAs and dynamic content: a single mount handles the whole page lifecycle.
  • Manual show / hide / refresh actions: workflow actions let you trigger the skeleton from any event: filter changes, tab switches, search submit, paginated fetches, custom workflow runs. This is standard for skeleton plugins, but our implementation gives you the same automatic DOM scan in both modes (page-load and on-demand): the skeleton looks identical whether it appears at first paint or 30 seconds later when the user changes a filter.
  • Full workflow integration: three element actions (Show, Hide, Refresh), three exposed events (Skeleton Shown, Skeleton Hidden, Skeleton Error), three exposed states (Is Visible, Last Hide Reason with seven possible values, Display Duration in ms). Build perceived-performance analytics, react to the skeleton lifecycle, gracefully fall back when a strategy fails. Container IDs and Excluded Element IDs both accept Bubble dynamic expressions, so your skeleton can react to Current User, custom states, or any other data source.
  • Four loading detection strategies: auto-detect DOM stabilization (drop and forget), fixed duration in milliseconds (deterministic look), manual hide via workflow action (full control), or fixed minimum + manual hide (guaranteed display time with early exit when your data is ready). A safety cap prevents stuck skeletons; a configurable minimum display time prevents the “flash of skeleton” effect on fast loads.
  • Three disappearance modes: all-at-once, wait-for-repeating-groups (skeleton stays up until every RG has rendered its first cell), or progressive-by-section (each top-level section fades independently as it loads, which is what Facebook feed does).
  • Empty Repeating Groups stay informative: synthetic placeholder cells (typically 3 fake rows or columns) reserve the space and signal “data is loading here”, instead of an empty rectangle that abruptly fills mid-load. The skeleton swaps to real cells the moment Bubble inserts content.
  • Four animation styles: shimmer wave (the classic Facebook / LinkedIn look, a highlight band sweeping across each block), pulse (soft fade in and out, less distracting on long-loading pages), fade (one-time fade-in then static, lowest CPU), and static (no animation, automatically used when the user has Reduce Motion enabled at the OS level).
  • Three density modes: coarse for the canonical Facebook / LinkedIn / YouTube look (one shimmer per card silhouette), medium for the typical Bubble layout where cards with images break down into image plus text shimmers, and fine for maximum granularity (every text, icon, and button as its own rectangle). Plus an auto mode that picks medium and falls back to fine on sparse pages.
  • Frame preservation: parent containers (the page itself, your hero card, your section wrappers) keep their background, border, and box-shadow during loading. Only the descendants are flattened. This matches the canonical pattern from Facebook, LinkedIn, and YouTube, where the layout structure stays visible and only the content placeholders shimmer.
  • Auto light / dark mode: the plugin reads your page’s actual background (solid or gradient) at runtime and switches between light and dark shimmer palettes when luminance crosses 50%. You can override the colors entirely or disable auto-detection and lock to one palette.
  • Smart handling of Bubble specifics: the plugin recognizes Bubble Tables, populated and empty Repeating Groups, the Bubble Page element, and atomic visual elements like Image and Button (one shimmer per element, no double-rendering). Floating Groups outside the scan area are automatically promoted above the skeleton overlay so visitors can keep navigating during loading; the original z-index is restored on hide. Map and chart plugins are recognized as single opaque units. Bubble’s native loading bar is hidden while the skeleton is visible.
  • Improves Core Web Vitals (CLS): reserving space with placeholders eliminates the content jumps Google has been ranking against since 2021.
  • Accessibility: ARIA live regions announce the loading state to screen readers. The animation downgrades to a static placeholder when the user’s OS has Reduce Motion enabled.

AI-assisted setup

The plugin ships with a context document designed to be pasted into any LLM assistant. Once pasted, the assistant has every plugin field, event, and action in scope and can recommend configurations for your specific use case, write Bubble dynamic expressions for Container IDs and Excluded Element IDs, diagnose console output, and suggest patterns for combining multiple skeletons on the same page.
The link is in the docs, in the “Get help from an AI assistant” section, and at the top of the demo page.

We also built the user documentation we wish every Bubble plugin had.

Feedback welcome

If you notice a bug, a weird interaction with a third-party plugin, or there’s a specific business case you’d like the plugin to cover, don’t hesitate to reach out. We happily take any question or improvement request. Plus, we react quickly!

I searched a lot for something like this, and it seems you’ve built something really nice that works right out of the box :clap::clap::clap::clap:.

While visiting the plugin page, I noticed that the animation isn’t working for me. Everything else works correctly, but the skeleton remains static (there’s no shimmer wave, pulse, or fade animation).

I’m using Google Chrome.

This is common, even custom code running from an HTML element has issue as the amount of resources needed. The shimmer, if actually covering data heavy loads will be interrupted.

Yeah, but it is not working on the plugin’s demo page. At the very least, it should work there. :sweat_smile:

Did you get a chance to test it (visited the page)? It might be an issue only on my end…

Thanks @rpetribu, appreciate the careful look :folded_hands:

Quick context: I just tested the demo on Chrome across 2 Macs, 1 (old) PC, and 2 Android devices, and the animation is running on all of them. So it could be something specific to your environment.

The most likely cause: the plugin respects the OS-level prefers-reduced-motion setting. When that’s enabled, the animation is intentionally downgraded to a static placeholder (this is part of the WCAG 2.3.3 accessibility behavior).

Worth checking:

  • macOS: System Settings → Accessibility → Display → Reduce Motion
  • Windows 11: Settings → Accessibility → Visual Effects → Animation effects
  • Android: Settings → Accessibility → Remove animations

If that’s it, toggling Reduce Motion off and refreshing should bring the shimmer back.

If Reduce Motion is already off, two other possibilities worth ruling out before I dig further:

  • A browser extension that forces motion reduction (some privacy/accessibility extensions do this)
  • A “battery saver” or “low power mode” on the device, which can also pause CSS animations

If none of those apply, can you share which OS you’re on and whether you see the same on a different browser (Firefox, Safari)? That would narrow it down quickly.

The plugin’s animation is pure CSS (GPU-accelerated), running entirely independent of any data fetching or workload-unit activity. So the shimmer shouldn’t compete with data loading for resources, it would normally keep running smoothly even on heavy pages.

Aha! That solved the problem! Thank you. :folded_hands:

What worried me is that I don’t remember changing that setting. Is it enabled by default?

One more question, and probably the most important one: Do the animations and the skeleton mask automatically disappear once the page or the elements finish loading?

I’m asking because I noticed that on the demo page you added a 3-second timer (I assume so people can actually see the effect). My question is whether this timer is actually necessary, or if the plugin automatically handles showing and hiding the skeleton state on its own.

Great observation. You’re right, the 3-second timer is just so you can see it for a reasonable time in demo. The page otherwise loads almost instantly (static page with mock data) - the skeleton would disappear in less than a second.

In a real app, you’d typically use the Auto-detect DOM stabilization strategy (the default): the plugin watches the DOM and hides automatically when loading is done.

The Fixed duration the demo uses is one of four strategies. Three others give finer control: manual Hide via workflow, fixed minimum + manual hide (prevents the “flash of skeleton” on fast loads), or just the manual mode alone.

A safety cap (default 30s) prevents stuck skeletons.

You may find full details in user documentation, under “Loading detection strategy”.

Glad that fixed it!

To answer your question: I believe animation effects are enabled by default on a fresh Windows 11 install. The most common ways they get silently disabled are battery-saver mode (Windows turns animations off to preserve battery) or group policies on company-managed machines. Worth a look in either case.

On my iPhone 12 it doesn’t shimmer on the plugin demo…but I have prefer reduced motion enabled

Thanks for testing on iPhone @boston85719!

Glad to see the plugin behaves correctly. With “Reduce motion” enabled at the OS level, the animation is intentionally downgraded to a static placeholder (which makes us compliant with WCAG 2.3.3).
If you toggle it off in iOS Settings → Accessibility → Motion → Reduce motion and refresh the demo, you will see the shimmer.