Skip to content

Skeleton [Draft]

Radoslav Karaivanov edited this page Apr 24, 2026 · 1 revision

Skeleton specification (DRAFT)

Owned By

Team Name: Design and Web Development

Developer name: Radoslav Karaivanov

Designer name:

Requires approval from:

Signed off by:

Revision history

Version Author Date Description
1 Radoslav Karaivanov 2026-04-24 Initial draft

Overview

The igc-skeleton component overlays animated placeholder shapes on top of projected content while it is in a loading state, then smoothly reveals the content once loading is complete. It uses absolute-positioned shapes measured from the bounding boxes of visible leaf elements inside the default slot, so no changes to the slotted content's markup are required. Placeholder shapes and the reveal transition are driven entirely by CSS custom properties and animations.

Acceptance criteria

The igc-skeleton must:

  • be WAI-ARIA compliant.
  • not alter the DOM structure or styles of slotted content.
  • overlay accurately measured placeholder shapes on top of every visible leaf element when loading.
  • clear all placeholder shapes when loading ends.
  • animate placeholder shapes with a configurable animation style.
  • smoothly animate slotted content into view after leaving the loading state.
  • re-measure shapes when slotted content changes dynamically.
  • re-measure shapes when the host element is resized.
  • expose styling hooks for the overlay layer, placeholder shapes, and content reveal transition through CSS custom properties.

User stories

End-user stories

As an end-user I expect to be able to:

  • see placeholder shapes that match the layout of the page content while data is loading.
  • see the real content smoothly appear once loading has completed.

Developer stories

As a developer I expect to be able to:

  • wrap any content in igc-skeleton and toggle the loading attribute to show or hide the skeleton overlay without modifying slotted content.
  • choose between multiple built-in animation styles (pulse, breathe, shimmer, wave, glow) to match the visual language of the application.
  • customize the overlay and shape colors through CSS custom properties.
  • rely on automatic re-measurement when slotted content or the component size changes.

Functionality

End-user experience

[Design Handoff]

Developer experience

Developers set the loading attribute (or property) to true to put the component into its loading state. The component then:

  1. Hides the slotted content (opacity 0, pointer-events none).
  2. Renders a semi-transparent overlay across the entire component.
  3. Measures every visible leaf element inside the default slot and renders an absolutely positioned placeholder shape for each one, using the same position, dimensions, and border-radius.

Setting loading back to false removes all placeholder shapes and overlay, and triggers a short CSS reveal animation on the content wrapper via the :state(revealing) custom state. The reveal runs for 600 ms and is implemented with ElementInternals.states so it does not pollute the element's attribute list.

The animation attribute selects the CSS animation applied to each placeholder shape. For the wave animation, shapes are staggered by 100 ms per index using the --_wave-delay CSS custom property.

Shapes are re-measured automatically whenever:

  • Slotted content changes (via a MutationObserver on childList and subtree).
  • The host element is resized (via a ResizeObserver).

The checkVisibility({ checkOpacity: false }) guard is applied during measurement so that display: none and visibility: hidden leaf nodes are excluded. Elements that are opacity-0 are intentionally included, as they may be part of a transition.

Localization

None applicable.

Keyboard navigation

None applicable. The component is non-interactive and non-focusable by default.

API

Properties

Property Attribute Reflect Type Default Description
loading loading Yes boolean false Puts the component into a loading state, rendering animated placeholder shapes over the projected content.
animation animation No 'pulse' | 'breathe' | 'shimmer' | 'wave' | 'glow' 'breathe' The animation style applied to each placeholder shape while in the loading state.

Methods

None applicable.

Events

None applicable.

Slots

Slot Description
(default) The content to measure and overlay with skeleton placeholder shapes.

CSS Shadow Parts

Part Description
content The wrapper element around the slotted content. Hidden while loading; animated in on reveal.
overlay The semi-transparent layer rendered over the entire component during loading.
shape An individual placeholder shape rendered over a visible leaf element. Also receives the animation part name (e.g. shape breathe).

CSS Variables

Variable Description
--ig-skeleton-overlay-color Background color of the overlay layer.
--ig-skeleton-shape-color Background color of the placeholder shapes.
--border-radius Border radius applied to the overlay and shapes when the measured element has no explicit border-radius. Falls back to a radius derived from --ig-radius-factor.

CSS Custom States

State Description
:state(revealing) Applied to the host for 600 ms immediately after loading transitions from true to false. Used to trigger the content reveal animation.

Test scenarios

Automation

  • Verify that the component renders and is accessible in the idle state (shadow DOM and light DOM a11y audits).
  • Verify that the component renders and is accessible in the loading state (shadow DOM and light DOM a11y audits).
  • Verify that loading defaults to false and animation defaults to 'breathe'.
  • Verify that the loading attribute is reflected to the DOM when set to true.
  • Verify that no placeholder shapes are rendered when loading is false.
  • Verify that placeholder shapes are rendered when loading is true.
  • Verify that all placeholder shapes are removed when loading returns to false.
  • Verify that each shape carries the correct animation part name for every animation value.
  • Verify that --_wave-delay is set per-shape index for the wave animation and absent for all other animations.
  • Verify that the :state(revealing) custom state is applied immediately when loading transitions from true to false.
  • Verify that the :state(revealing) custom state is removed after 600 ms.
  • Verify that :state(revealing) is not applied when loading was never true.
  • Verify that a pending reveal timeout is cancelled when loading is set back to true.
  • Verify that disconnecting the element clears the reveal timeout without throwing.
  • Verify that display: none and visibility: hidden leaf nodes are excluded from measurement.
  • Verify that the correct number of leaf shapes is measured for a known content structure.

Accessibility

While loading, aria-busy="true" is set on the host element via ElementInternals to signal to assistive technologies that the region is updating. The overlay and individual shapes carry aria-hidden="true" so they are invisible to screen readers.

No other intrinsic WAI-ARIA roles or properties are applied; it is the developer's responsibility to add any additional labels or live-region attributes required by the specific application context.

References

RTL

The component should work in a Right-To-Left context without additional setup or configuration. Shape positions are derived from getBoundingClientRect() relative to the host, so RTL layout is handled automatically by the browser.

Assumptions and limitations

  • Only visible leaf elements (elements with no child elements that pass checkVisibility()) receive placeholder shapes. Container or wrapper elements are intentionally skipped.
  • Shapes are measured against the component's own bounding rect. Any content that is positioned outside the host's bounds (e.g. via overflow: visible or position: fixed children) will produce shapes that fall outside the overlay area.
  • Shadow DOM roots of nested custom elements are not traversed; only light-DOM leaf nodes are measured.
  • The component does not support skeleton overlays for content rendered inside <input>, <textarea>, or other replaced/form elements, as their layout is opaque to the getBoundingClientRect-based measurement approach.

Clone this wiki locally