Skip to content

Pending Review

Pending Review from User Experience (UX)

Skeleton Component

Overview

The PWC Skeleton component provides a visual placeholder that mimics the shape and structure of content while it loads. Built on Lit web components, it features smooth pulsing animations and customizable dimensions to create seamless loading experiences that maintain layout stability and user engagement.

Core Capabilities

  • Animated Loading States - Smooth pulsing animation that provides visual feedback during content loading
  • Flexible Sizing - Five size variants from extra small to extra large with customizable width constraints
  • Dynamic Dimensions - Configurable minimum and maximum width properties for responsive layout adaptation
  • Performance Optimized - Lightweight CSS animations that don't impact page performance or user interaction
  • Layout Preservation - Maintains content structure and prevents layout shifts during loading transitions
  • Design Token Integration - Consistent styling through PWC design system colors and spacing tokens

When to use Skeleton:

  • Content Loading - Display placeholders while fetching data, images, or dynamic content from APIs
  • Progressive Enhancement - Provide immediate visual feedback before full content renders
  • List Placeholders - Show structured loading states for data tables, card grids, and content lists
  • Form Loading - Indicate loading states for form fields and input components during initialization

When not to use Skeleton:

  • Instant Content - Don't use for content that loads immediately or is already available
  • Error States - Use dedicated error components instead of skeleton placeholders for failed requests
  • Long Operations - Choose progress indicators for operations that take more than a few seconds

Basic Implementation

import React, { useState, useCallback, useEffect } from 'react';
import { PwcSkeleton, PwcButton, PwcBody } from '@progress-i360/pwc-react';

function ContentLoader() {
  const [isLoading, setIsLoading] = useState(true);
  const [loadingProgress, setLoadingProgress] = useState(0);

  // React 18: useCallback for optimization
  const simulateLoading = useCallback(() => {
    setIsLoading(true);
    setLoadingProgress(0);
  }, []);

  // React 18: useEffect for loading simulation
  useEffect(() => {
    if (!isLoading) return;

    const interval = setInterval(() => {
      setLoadingProgress(prev => {
        const newProgress = prev + 25;
        if (newProgress >= 100) {
          setIsLoading(false);
          return 100;
        }
        return newProgress;
      });
    }, 500);

    return () => clearInterval(interval);
  }, [isLoading]);

  const skeletonSizes = ['xs', 's', 'm'];

  return (
      <div style={{ padding: '20px' }}>
        {isLoading ? (
          <div>
            {skeletonSizes.map((size, index) => (
              <PwcSkeleton
                key={size}
                size={size}
                maxWidth={`${200 + index * 50}px`}
                style={{ marginBottom: '8px' }}
              />
            ))}
            <PwcBody 
              content={`Loading... ${loadingProgress}%`}
              size="xs"
              color="subtle"
            />
          </div>
        ) : (
          <div>
            <PwcBody content="Content Title" weight="bold" />
            <PwcBody content="Your content has loaded successfully!" />
            <PwcBody content="Additional details and information." size="s" />
          </div>
        )}

        <PwcButton
          label="Reload Content"
          variant="primary"
          onPwcClick={simulateLoading}
          style={{ marginTop: '16px' }}
        />
      </div>
  );
}
import { Suspense, use, useOptimistic, useState } from 'react';
import '@progress-i360/progress-web-components/skeleton';
import '@progress-i360/progress-web-components/button';
import '@progress-i360/progress-web-components/body';

// React 19+: Async data fetcher
const fetchContentItems = async (count) => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  return Array.from({ length: count }, (_, i) => ({
    id: i + 1,
    title: `Content Item ${i + 1}`,
    description: `Description for item ${i + 1}`
  }));
};

function SmartSkeletonList() {
  const [itemCount, setItemCount] = useState(3);
  const [contentPromise, setContentPromise] = useState(() => fetchContentItems(3));

  // React 19+: Optimistic item count updates
  const [optimisticCount, updateCount] = useOptimistic(
    itemCount,
    (current, newCount) => newCount
  );

  const loadMoreItems = () => {
    const newCount = optimisticCount + 2;
    updateCount(newCount);

    setItemCount(newCount);
    setContentPromise(fetchContentItems(newCount));
  };

  const resetItems = () => {
    updateCount(3);
    setItemCount(3);
    setContentPromise(fetchContentItems(3));
  };

  return (
    <div style={{ padding: '20px' }}>
      <Suspense
        fallback={
          <div>
            {Array.from({ length: optimisticCount }, (_, i) => (
              <pwc-skeleton
                key={i}
                size="m"
                max-width={`${150 + (i % 3) * 50}px`}
                style={{ marginBottom: '12px' }}
              ></pwc-skeleton>
            ))}
            <pwc-body content={`Loading ${optimisticCount} items...`} size="xs"></pwc-body>
          </div>
        }
      >
        <ContentDisplay promise={contentPromise} />
      </Suspense>

      <div style={{ marginTop: '16px' }}>
        <pwc-button
          label="Load More"
          variant="primary"
          onPwcClick={loadMoreItems}
        ></pwc-button>
        <pwc-button
          label="Reset"
          variant="outline"
          onPwcClick={resetItems}
          style={{ marginLeft: '8px' }}
        ></pwc-button>
      </div>
    </div>
  );
}

function ContentDisplay({ promise }) {
  const items = use(promise);

  return (
    <div>
      {items.map(item => (
        <div key={item.id} style={{ marginBottom: '12px' }}>
          <pwc-body content={item.title} weight="bold"></pwc-body>
          <pwc-body content={item.description} size="s"></pwc-body>
        </div>
      ))}
    </div>
  );
}
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "@progress-i360/progress-web-components/skeleton";
import "@progress-i360/progress-web-components/flex";
import "@progress-i360/progress-web-components/button";
import "@progress-i360/progress-web-components/body";
import "@progress-i360/progress-web-components/heading";

@Component({
  selector: 'skeleton-demo',
  template: `
    <pwc-flex direction="column" gap="m" padding="m">
      <pwc-heading content="Loading State Demo" size="l"></pwc-heading>
      <pwc-flex direction="column" gap="s">
        <pwc-skeleton [size]="skeletonSize" [maxWidth]="maxWidth" *ngIf="isLoading"></pwc-skeleton>
        <pwc-skeleton size="s" max-width="150px" *ngIf="isLoading"></pwc-skeleton>
        <pwc-skeleton size="xs" max-width="200px" *ngIf="isLoading"></pwc-skeleton>
        <pwc-body [content]="loadedContent" *ngIf="!isLoading"></pwc-body>
      </pwc-flex>
      <pwc-flex gap="s">
        <pwc-button label="Toggle Loading" variant="primary" (pwc-click)="toggleLoading()"></pwc-button>
        <pwc-button label="Change Size" variant="outline" (pwc-click)="changeSize()"></pwc-button>
        <pwc-button label="Change Width" variant="outline" (pwc-click)="changeWidth()"></pwc-button>
      </pwc-flex>
      <pwc-body [content]="'Size: ' + skeletonSize + ', Width: ' + maxWidth" color="subtle" size="xs"></pwc-body>
    </pwc-flex>
  `,
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class SkeletonDemo {
  isLoading = true;
  skeletonSize = 'm';
  maxWidth = '300px';
  loadedContent = 'Content has finished loading! This text replaced the skeleton placeholders.';

  sizes = ['xs', 's', 'm', 'l', 'xl'];
  widths = ['150px', '200px', '300px', '400px', '100%'];

  toggleLoading() {
    this.isLoading = !this.isLoading;
  }

  changeSize() {
    const currentIndex = this.sizes.indexOf(this.skeletonSize);
    this.skeletonSize = this.sizes[(currentIndex + 1) % this.sizes.length];
  }

  changeWidth() {
    const currentIndex = this.widths.indexOf(this.maxWidth);
    this.maxWidth = this.widths[(currentIndex + 1) % this.widths.length];
  }
}
import '@progress-i360/progress-web-components/skeleton';

const mediumSkeleton = document.createElement('pwc-skeleton');
mediumSkeleton.setAttribute('size', 'm');

const constrainedSkeleton = document.createElement('pwc-skeleton');
constrainedSkeleton.setAttribute('size', 's');
constrainedSkeleton.setAttribute('max-width', '200px');
constrainedSkeleton.setAttribute('min-width', '100px');

document.body.appendChild(mediumSkeleton);
document.body.appendChild(constrainedSkeleton);

Usage Patterns

  • Content Placeholders - Use appropriately sized skeletons to match the dimensions of expected content elements
  • List Loading States - Implement multiple skeleton elements to represent loading lists, tables, and grid layouts
  • Progressive Loading - Show skeletons while content loads incrementally to maintain user engagement
  • Responsive Adaptation - Utilize min/max width properties to ensure skeletons work across different screen sizes

Best Practices

Content Strategy Guidelines

  • Realistic Dimensions - Size skeleton elements to closely match the expected content dimensions and layout
  • Meaningful Structure - Use skeleton patterns that reflect the actual content hierarchy and organization
  • Appropriate Duration - Display skeletons for loading periods between 500ms and 3 seconds for optimal user experience
  • Consistent Patterns - Apply similar skeleton layouts for similar content types throughout the application

Performance Optimization

  • Lightweight Animation - Leverage CSS-only animations that don't impact JavaScript performance or user interactions
  • Efficient Rendering - Minimize DOM manipulation by reusing skeleton components when possible
  • Animation Control - Consider reducing animations for users with motion sensitivity preferences
  • Memory Management - Properly clean up skeleton components when content finishes loading

Integration Architecture

  • Loading State Management - Integrate with application loading states and data fetching patterns
  • Design System Alignment - Use consistent spacing and sizing that matches other PWC components
  • Accessibility Compliance - Ensure skeleton animations don't interfere with screen readers or assistive technology
  • Theme Compatibility - Maintain proper contrast and visibility across light and dark theme variations

Common Use Cases

Data Table Headers

  • Column Placeholders - Show skeleton elements in table headers while column definitions and data load
  • Row Loading - Display skeleton rows to indicate loading table data with proper column width preservation
  • Filter Skeletons - Use small skeleton elements for loading filter options and search functionality

Search Result Sections

  • Result Cards - Show skeleton placeholders that match search result card dimensions and layout structure
  • Category Loading - Display skeleton elements while search categories and filter options are being fetched
  • Pagination Skeletons - Use small skeleton elements for loading pagination controls and result counts

Dashboard Widget Headers

  • Widget Content - Show skeleton placeholders that match widget content dimensions and chart areas
  • Header Loading - Display skeleton elements for widget titles, controls, and configuration options
  • Data Visualization - Use appropriately sized skeletons for loading charts, graphs, and data displays

Troubleshooting

Common Issues

Animation Not Showing

Problem: Skeleton appears static without pulsing animation

Solution:

  • Verify CSS custom properties are loaded and animation keyframes are properly defined

Incorrect Sizing

Problem: Skeleton dimensions don't match expected content size

Solution:

  • Check size property is set correctly and adjust min/max width values for proper constraints

Layout Shifts

Problem: Content jumps or shifts when skeleton is replaced with actual content

Solution:

  • Ensure skeleton dimensions closely match final content dimensions and use proper CSS layout properties

Performance Issues

Problem: Multiple skeletons cause animation lag or performance degradation

Solution:

  • Limit concurrent skeleton animations and consider using simpler static placeholders for complex layouts

Implementation Support

For detailed implementation guidance:

  • Loading Integration - Seamless integration with data fetching and content loading patterns
  • Animation System - CSS-based pulsing animation with design system color tokens
  • Responsive Design - Flexible width constraints and size variants for different layout contexts

Resources

Storybook Documentation

For comprehensive API documentation, interactive examples, and testing tools: 📖 View Complete API Documentation in Storybook →