Skip to content

Latest commit

 

History

History
568 lines (426 loc) · 14.7 KB

File metadata and controls

568 lines (426 loc) · 14.7 KB

Skeleton Component

Beautiful loading placeholders for Angular applications with smooth animations, multiple variants, and accessibility-first design.

Overview

The Skeleton component provides an elegant loading experience by showing placeholder content while actual data is being fetched. It includes multiple variants, animation types, and size options to match your design requirements perfectly.

Features

  • 🎭 Smooth Animations - Pulse, shimmer, and more animation types
  • 🎨 Flexible Variants - Text, avatars, and custom shapes
  • Accessible - Screen reader friendly with ARIA labels
  • Lightweight - Minimal bundle impact
  • 🔧 Customizable - Easy theming and configuration
  • 📱 Responsive - Works perfectly on all screen sizes

Installation

Install the skeleton component using the Angular SuperUI CLI:

npx ngsui-cli add skeleton

Quick Start

Import the Component

import { Component } from '@angular/core';
import { 
  SkeletonComponent,
  SkeletonText,
  SkeletonAvatar,
  SkeletonItem,
  SkeletonGroup,
  SkeletonService 
} from '@lib/skeleton';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [
    SkeletonComponent,
    SkeletonText,
    SkeletonAvatar,
    SkeletonItem,
    SkeletonGroup
  ],
  providers: [SkeletonService],
  template: `
    <SkeletonText width="full" height="20px" />
    <SkeletonText width="4/5" height="16px" />
    <SkeletonAvatar size="lg" />
  `
})
export class ExampleComponent {}

Basic Usage

<!-- Simple text skeleton -->
<SkeletonText width="full" height="20px" />
<SkeletonText width="4/5" height="16px" />

<!-- Avatar skeleton -->
<SkeletonAvatar size="lg" />

<!-- Custom shape skeleton -->
<SkeletonComponent width="full" height="200px" rounded="lg" />

Components

SkeletonText

Used for text content placeholders with customizable width and height.

SkeletonText Properties

Property Type Default Description
width 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'full' | 'auto' | '1/4' | '1/3' | '1/2' | '2/3' | '3/4' | '4/5' | '5/6' 'full' Width of the skeleton
height string '16px' Height of the skeleton
animation 'pulse' | 'none' 'pulse' Animation type
rounded 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full' 'md' Border radius

SkeletonText Examples

<!-- Different widths -->
<SkeletonText width="full" height="20px" />
<SkeletonText width="4/5" height="16px" />
<SkeletonText width="1/2" height="16px" />
<SkeletonText width="5/6" height="16px" />

<!-- Different heights -->
<SkeletonText width="5/6" height="12px" />
<SkeletonText width="4/5" height="14px" />
<SkeletonText width="4/5" height="16px" />
<SkeletonText width="3/4" height="20px" />
<SkeletonText width="2/3" height="24px" />

<!-- With animations -->
<SkeletonText width="full" height="20px" animation="pulse" />
<SkeletonText width="4/5" height="16px" animation="none" />

SkeletonAvatar

Circular or rounded avatar placeholders for user profile images.

SkeletonAvatar Properties

Property Type Default Description
size 'xs' | 'sm' | 'default' | 'lg' | 'xl' 'default' Size of the avatar
animation 'pulse' | 'none' 'pulse' Animation type
rounded 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full' 'full' Border radius

SkeletonAvatar Examples

<!-- Different sizes -->
<SkeletonAvatar size="xs" />
<SkeletonAvatar size="sm" />
<SkeletonAvatar size="default" />
<SkeletonAvatar size="lg" />
<SkeletonAvatar size="xl" />

<!-- Avatar with text combination -->
<div class="flex items-center space-x-4">
  <SkeletonAvatar size="lg" />
  <div class="flex-1 flex flex-col gap-2">
    <SkeletonText width="1/3" height="16px" />
    <SkeletonText width="1/2" height="14px" />
  </div>
</div>

SkeletonComponent

Generic skeleton component for custom shapes and layouts.

SkeletonComponent Properties

Property Type Default Description
width 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'full' | 'auto' | '1/4' | '1/3' | '1/2' | '2/3' | '3/4' | '4/5' | '5/6' 'full' Width of the skeleton
height string 'auto' Height of the skeleton
animation 'pulse' | 'none' 'pulse' Animation type
rounded 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full' 'md' Border radius

SkeletonComponent Examples

<!-- Image placeholder -->
<SkeletonComponent width="full" height="200px" rounded="lg" />

<!-- Circle shape -->
<SkeletonComponent width="md" height="60px" rounded="full" />

<!-- Button placeholder -->
<SkeletonComponent width="full" height="40px" rounded="md" />

<!-- Custom dimensions -->
<SkeletonComponent width="full" height="120px" rounded="lg" />

SkeletonService

Service for managing global skeleton states and configurations.

Methods

Method Parameters Description
setLoading(loading: boolean) loading: boolean Set global loading state
isLoading() - Get current loading state
setDefaultAnimation(animation: string) animation: string Set default animation type

Usage

import { inject } from '@angular/core';
import { SkeletonService } from '@lib/skeleton';

export class MyComponent {
  private skeletonService = inject(SkeletonService);

  startLoading() {
    this.skeletonService.setLoading(true);
  }

  stopLoading() {
    this.skeletonService.setLoading(false);
  }

  checkLoadingState() {
    return this.skeletonService.isLoading();
  }
}

Real-world Examples

Article Card Layout

<div class="rounded-2xl border border-gray-200 bg-white p-8 shadow-sm dark:border-gray-700 dark:bg-gray-800">
  <h3 class="mb-6 text-xl font-semibold text-gray-900 dark:text-white">
    Article Card
  </h3>
  <div class="flex flex-col gap-4">
    <!-- Image placeholder -->
    <SkeletonComponent width="full" height="200px" rounded="lg" />

    <!-- Content -->
    <div class="flex flex-col gap-3">
      <SkeletonText width="5/6" height="24px" />
      <SkeletonText width="full" height="16px" />
      <SkeletonText width="4/5" height="16px" />
      <SkeletonText width="2/3" height="16px" />
    </div>

    <!-- Author info -->
    <div class="flex items-center space-x-3 pt-4">
      <SkeletonAvatar size="sm" />
      <div class="flex-1 flex flex-col gap-1">
        <SkeletonText width="1/3" height="14px" />
        <SkeletonText width="1/4" height="12px" />
      </div>
    </div>
  </div>
</div>

User List Layout

<div class="rounded-2xl border border-gray-200 bg-white p-8 shadow-sm dark:border-gray-700 dark:bg-gray-800">
  <h3 class="mb-6 text-xl font-semibold text-gray-900 dark:text-white">
    User List
  </h3>
  <div class="flex flex-col gap-6">
    <div *ngFor="let item of Array(4).fill(0); let i = index"
         class="flex items-center space-x-4">
      <SkeletonAvatar [size]="i === 0 ? 'lg' : 'default'" />
      <div class="flex-1 flex flex-col gap-2">
        <SkeletonText [width]="getWidthForIndex(i, 60, 5)" height="16px" />
        <SkeletonText [width]="getWidthForIndex(i, 40, 3)" height="14px" />
        <SkeletonText *ngIf="i === 0" width="1/2" height="12px" />
      </div>
      <SkeletonComponent width="xl" height="32px" rounded="md" />
    </div>
  </div>
</div>

Component with Dynamic Width Helper

export class SkeletonDemoComponent {
  // Helper function to map dynamic width values to allowed types
  getWidthForIndex(index: number, baseWidth: number, decrement: number): 'full' | '5/6' | '4/5' | '3/4' | '2/3' | '1/2' | '1/3' | '1/4' {
    const calculatedWidth = baseWidth - (index * decrement);

    if (calculatedWidth >= 90) return 'full';
    if (calculatedWidth >= 75) return '5/6';
    if (calculatedWidth >= 65) return '4/5';
    if (calculatedWidth >= 55) return '3/4';
    if (calculatedWidth >= 45) return '2/3';
    if (calculatedWidth >= 35) return '1/2';
    if (calculatedWidth >= 25) return '1/3';
    return '1/4';
  }
}

Animation Types

Pulse Animation (Default)

The pulse animation creates a subtle breathing effect that draws attention without being distracting.

<SkeletonText width="full" height="20px" animation="pulse" />
<SkeletonText width="4/5" height="16px" animation="pulse" />
<SkeletonText width="1/2" height="16px" animation="pulse" />

Static (No Animation)

For cases where you want static placeholders without animation.

<SkeletonText width="full" height="20px" animation="none" />
<SkeletonText width="4/5" height="16px" animation="none" />
<SkeletonText width="1/2" height="16px" animation="none" />

Size Variants

Width Options

The skeleton components support the following width values:

  • Fractional: 1/4, 1/3, 1/2, 2/3, 3/4, 4/5, 5/6
  • Fixed sizes: xs, sm, md, lg, xl, 2xl, 3xl, 4xl
  • Special: full, auto

Height Options

Height can be specified as any valid CSS height value:

  • Pixels: 12px, 14px, 16px, 20px, 24px, etc.
  • Relative units: 1rem, 1.5rem, 2rem, etc.
  • Special: auto

Styling & Theming

CSS Custom Properties

The skeleton components use CSS custom properties for easy theming:

:root {
  --skeleton-base-color: #f3f4f6;
  --skeleton-shimmer-color: #e5e7eb;
  --skeleton-dark-base-color: #374151;
  --skeleton-dark-shimmer-color: #4b5563;
}

Dark Mode Support

The component automatically adapts to dark mode using Tailwind CSS dark mode classes:

<!-- Automatically switches between light and dark modes -->
<SkeletonText width="full" height="20px" />

Custom Animation

You can add custom animations by extending the CSS:

@keyframes skeleton-pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
}

.skeleton-pulse {
  animation: skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

Accessibility

Screen Reader Support

The skeleton components include proper ARIA labels and roles for screen reader compatibility:

<SkeletonText 
  width="full" 
  height="20px" 
  aria-label="Loading content" 
  role="progressbar" 
/>

Reduced Motion

The component respects the user's preference for reduced motion:

@media (prefers-reduced-motion: reduce) {
  .skeleton-component {
    animation: none;
  }
}

Performance

Bundle Size

The skeleton component library is designed to be lightweight:

  • Gzipped: ~2KB
  • Minified: ~5KB
  • Tree-shakable: Import only what you need

Rendering Performance

  • Uses CSS animations for optimal performance
  • No JavaScript animations
  • Minimal DOM manipulation
  • Efficient re-rendering with OnPush change detection

Best Practices

1. Match Content Structure

Design skeleton layouts that closely match your actual content structure:

<!-- Good: Matches the actual content layout -->
<div class="flex items-center space-x-4">
  <SkeletonAvatar size="lg" />
  <div class="flex-1 flex flex-col gap-2">
    <SkeletonText width="1/3" height="16px" />
    <SkeletonText width="1/2" height="14px" />
  </div>
</div>

<!-- Avoid: Generic placeholders that don't match content -->
<SkeletonText width="full" height="100px" />

2. Use Appropriate Timing

Show skeletons for loading states that take longer than 200ms:

export class DataComponent {
  isLoading = signal(true);

  loadData() {
    this.isLoading.set(true);
    
    this.dataService.getData().subscribe(data => {
      // Add small delay to prevent flashing for fast requests
      setTimeout(() => {
        this.isLoading.set(false);
      }, 200);
    });
  }
}

3. Progressive Loading

Show different skeleton states for progressive loading:

<div *ngIf="isLoading()">
  <SkeletonText width="full" height="24px" />
  <SkeletonText width="4/5" height="16px" />
</div>

<div *ngIf="!isLoading() && !hasError()">
  <!-- Actual content -->
</div>

<div *ngIf="hasError()">
  <!-- Error state -->
</div>

4. Responsive Design

Ensure skeletons work well on all screen sizes:

<div class="grid gap-8 lg:grid-cols-2">
  <div class="flex flex-col gap-4">
    <SkeletonComponent width="full" height="200px" rounded="lg" />
    <SkeletonText width="5/6" height="24px" />
    <SkeletonText width="full" height="16px" />
  </div>
</div>

Troubleshooting

Common Issues

1. TypeScript Errors with Width Values

Problem: Getting type errors when using percentage or pixel values for width.

Solution: Use the predefined width types instead:

// ❌ Wrong
<SkeletonText width="80%" height="16px" />
<SkeletonText width="200px" height="16px" />

// ✅ Correct
<SkeletonText width="4/5" height="16px" />
<SkeletonText width="xl" height="16px" />

2. Animations Not Working

Problem: Skeleton animations are not visible.

Solution: Check for conflicting CSS or reduced motion preferences:

/* Ensure animations are enabled */
.skeleton-component {
  animation: skeleton-pulse 2s ease-in-out infinite;
}

/* Check for reduced motion override */
@media (prefers-reduced-motion: reduce) {
  .skeleton-component {
    animation: none;
  }
}

3. Layout Shifts

Problem: Content jumps when skeleton is replaced with actual content.

Solution: Ensure skeleton dimensions match actual content:

<!-- Match the exact dimensions of your content -->
<SkeletonComponent width="full" height="200px" rounded="lg" />
<!-- Should match -->
<img class="w-full h-[200px] rounded-lg" src="actual-image.jpg" />

API Reference

Complete Type Definitions

export interface SkeletonTextProps {
  width?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'full' | 'auto' | '1/4' | '1/3' | '1/2' | '2/3' | '3/4' | '4/5' | '5/6';
  height?: string;
  animation?: 'pulse' | 'none';
  rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
}

export interface SkeletonAvatarProps {
  size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl';
  animation?: 'pulse' | 'none';
  rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
}

export interface SkeletonComponentProps {
  width?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'full' | 'auto' | '1/4' | '1/3' | '1/2' | '2/3' | '3/4' | '4/5' | '5/6';
  height?: string;
  animation?: 'pulse' | 'none';
  rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
}

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

This component is part of Angular SuperUI and is licensed under the MIT License. See LICENSE for details.