Skip to main content

Skeleton Components

The Skeleton Components provide loading state placeholders for the WorkPayCore Frontend application. These components create visual placeholders that mimic the structure of content while data is loading, improving perceived performance and user experience.

Overview

This document covers all skeleton components that provide loading state placeholders for different types of content including forms, pages, settings, reports, and user profiles.

Components Overview

Form Skeletons

  • FormSkeleton - Form input skeleton with rows and action buttons
  • OnBoardingPages - Onboarding page skeleton with title and form fields

Page Skeletons

Content Skeletons


FormSkeleton

A skeleton component that mimics the structure of a typical form with input fields and action buttons.

Component Location

import FormSkeleton from 'components/Skeletons/formSkeleton';

Props

No props required - this is a stateless skeleton component.

Features

Form Structure Mimicry

  • Two rows of input fields (50% width each)
  • Action buttons row aligned to the right
  • Consistent spacing and proportions
  • Responsive 50% container width

Visual Hierarchy

  • 30px height for input field placeholders
  • 25% width for action buttons
  • Proper spacing between elements
  • Row-based layout structure

Usage Examples

Basic Form Loading

import FormSkeleton from 'components/Skeletons/formSkeleton';

function EmployeeForm() {
const [isLoading, setIsLoading] = useState(true);
const [formData, setFormData] = useState(null);

if (isLoading) {
return <FormSkeleton />;
}

return (
<form>
<Input name='firstName' placeholder='First Name' />
<Input name='lastName' placeholder='Last Name' />
<Button type='submit'>Save</Button>
</form>
);
}

Conditional Loading

import FormSkeleton from 'components/Skeletons/formSkeleton';

function ConditionalForm({ isLoading, data }) {
return (
&lt;Box&gt;
&lt;Heading&gt;Employee Information</Heading>
{isLoading ? <FormSkeleton /> : <EmployeeFormFields data={data} />}
</Box>
);
}

Multiple Form Sections

import FormSkeleton from 'components/Skeletons/formSkeleton';

function MultiSectionForm() {
const [loadingStates, setLoadingStates] = useState({
personal: true,
employment: true,
contact: true,
});

return (
<VStack spacing={8}>
&lt;Box&gt;
<Heading size='md'>Personal Information</Heading>
{loadingStates.personal ? <FormSkeleton /> : <PersonalInfoForm />}
</Box>

&lt;Box&gt;
<Heading size='md'>Employment Details</Heading>
{loadingStates.employment ? <FormSkeleton /> : <EmploymentForm />}
</Box>
</VStack>
);
}

Styling

  • Container Width: 50% of parent
  • Field Height: 30px
  • Field Spacing: 6 units
  • Button Width: 25% each
  • Layout: Row-based with proper alignment

OnBoardingPages

A skeleton component designed for onboarding pages with title, description, and form fields.

Component Location

import OnBoardingPages from 'components/Skeletons/onBoardingPages';

Props

No props required - this is a stateless skeleton component.

Features

Page Structure

  • Page title skeleton (300px width)
  • Description skeleton (350px width)
  • Three form field sections with labels
  • Action button at the bottom

Dynamic Content

  • Array-based field generation
  • Consistent spacing and proportions
  • Proper visual hierarchy

Usage Examples

Onboarding Form Loading

import OnBoardingPages from 'components/Skeletons/onBoardingPages';

function OnboardingStep() {
const [isLoading, setIsLoading] = useState(true);

if (isLoading) {
return <OnBoardingPages />;
}

return (
<VStack spacing={8}>
&lt;Heading&gt;Welcome to WorkPay</Heading>
&lt;Text&gt;Please complete your profile information</Text>
<OnboardingForm />
</VStack>
);
}

Multi-Step Onboarding

import OnBoardingPages from 'components/Skeletons/onBoardingPages';

function MultiStepOnboarding({ currentStep, isLoading }) {
if (isLoading) {
return <OnBoardingPages />;
}

return (
<Stepper index={currentStep}>
&lt;Step&gt;
<StepIndicator />
<StepSeparator />
</Step>
{/* More steps */}
</Stepper>
);
}

Styling

  • Title: 32px height, 300px width
  • Description: 22px height, 350px width
  • Labels: 20px height, 150px width
  • Fields: 32px height, full width
  • Button: 32px height, 160px width, rounded corners

SettingsSkeleton

A skeleton component for settings pages with labels, descriptions, and toggle switches.

Component Location

import SettingsSkeleton from 'components/Skeletons/settingsSkeleton';

Props

No props required - this is a stateless skeleton component.

Features

Settings Structure

  • Setting labels (120px width)
  • Setting descriptions (250px width)
  • Toggle switch placeholders (rounded)
  • Proper spacing between elements

Visual Elements

  • Mixed skeleton types (Skeleton, SkeletonText)
  • Rounded toggle switch appearance
  • Consistent vertical spacing

Usage Examples

Settings Page Loading

import SettingsSkeleton from 'components/Skeletons/settingsSkeleton';

function SettingsPage() {
const [isLoading, setIsLoading] = useState(true);
const [settings, setSettings] = useState(null);

if (isLoading) {
return <SettingsSkeleton />;
}

return (
<VStack spacing={4}>
&lt;FormControl&gt;
&lt;FormLabel&gt;Email Notifications</FormLabel>
<Switch isChecked={settings.emailNotifications} />
</FormControl>
{/* More settings */}
</VStack>
);
}

Section-based Settings

import SettingsSkeleton from 'components/Skeletons/settingsSkeleton';

function SettingsSection({ title, isLoading, children }) {
return (
&lt;Box&gt;
<Heading size='sm' mb={4}>
{title}
</Heading>
{isLoading ? <SettingsSkeleton /> : children}
</Box>
);
}

Styling

  • Labels: 12px height, 120px width
  • Descriptions: Single line, 250px width
  • Switches: 24px height, 40px width, rounded (16px radius)
  • Spacing: 5 units between elements

CircleTextSectionSkeleton

A skeleton component for profile sections with circular avatar and text content.

Component Location

import CircleTextSectionSkeleton from 'components/Skeletons/CircleTextSectionSkeleton';

Props

PropTypeRequiredDescription
...restobject-Additional props for Stack container

Features

Profile Structure

  • Circular avatar placeholder (size 20)
  • Multi-line text content (4 lines)
  • Proper spacing and padding
  • Flexible container props

Layout

  • Full width container
  • Padding of 6 units
  • Margin top of 4 units for text
  • Spacing of 4 units between text lines

Usage Examples

User Profile Loading

import CircleTextSectionSkeleton from 'components/Skeletons/CircleTextSectionSkeleton';

function UserProfile() {
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState(null);

if (isLoading) {
return <CircleTextSectionSkeleton />;
}

return (
<HStack spacing={4}>
<Avatar name={user.name} src={user.avatar} size='lg' />
<VStack align='start'>
<Text fontWeight='bold'>{user.name}</Text>
<Text color='gray.500'>{user.email}</Text>
&lt;Text&gt;{user.department}</Text>
&lt;Text&gt;{user.role}</Text>
</VStack>
</HStack>
);
}

Comment Section

import CircleTextSectionSkeleton from 'components/Skeletons/CircleTextSectionSkeleton';

function CommentsList() {
const [isLoading, setIsLoading] = useState(true);
const [comments, setComments] = useState([]);

if (isLoading) {
return (
<VStack spacing={4}>
{Array(3)
.fill()
.map((_, i) => (
<CircleTextSectionSkeleton key={i} />
))}
</VStack>
);
}

return (
<VStack spacing={4}>
{comments.map(comment => (
<CommentCard key={comment.id} comment={comment} />
))}
</VStack>
);
}

Custom Styling

import CircleTextSectionSkeleton from 'components/Skeletons/CircleTextSectionSkeleton';

function CustomProfileSkeleton() {
return <CircleTextSectionSkeleton bg='gray.50' borderRadius='md' p={8} />;
}

Styling

  • Circle: Size 20 (80px diameter)
  • Container: Full width, 6 units padding
  • Text: 4 lines, 4 units spacing, 4 units margin top

ReportsShimmer

A grid-based skeleton component for reports and dashboard cards.

Component Location

import ReportsShimmer from 'components/Skeletons/Reports';

Props

No props required - this is a stateless skeleton component.

Features

Grid Layout

  • 4 columns with responsive layout
  • Minimum child width of 280px
  • 8 skeleton cards by default
  • Consistent spacing and proportions

Card Structure

  • 200px height per card
  • Large border radius (lg)
  • Full width within grid item
  • Responsive grid behavior

Usage Examples

Dashboard Loading

import ReportsShimmer from 'components/Skeletons/Reports';

function Dashboard() {
const [isLoading, setIsLoading] = useState(true);
const [reports, setReports] = useState([]);

if (isLoading) {
return <ReportsShimmer />;
}

return (
<SimpleGrid columns={4} spacing={4} minChildWidth='280px'>
{reports.map(report => (
<ReportCard key={report.id} report={report} />
))}
</SimpleGrid>
);
}

Reports Page

import ReportsShimmer from 'components/Skeletons/Reports';

function ReportsPage() {
const [isLoading, setIsLoading] = useState(true);

return (
<VStack spacing={6}>
&lt;Heading&gt;Reports Dashboard</Heading>
{isLoading ? <ReportsShimmer /> : <ReportsGrid />}
</VStack>
);
}

Custom Grid Count

import { SimpleGrid, Skeleton } from '@chakra-ui/react';

function CustomReportsShimmer({ count = 12 }) {
return (
<SimpleGrid columns={4} spacing={4} minChildWidth='280px'>
{Array(count)
.fill('')
.map((_, i) => (
<Skeleton key={i} width='full' height='200px' borderRadius='lg' />
))}
</SimpleGrid>
);
}

Styling

  • Grid: 4 columns, 4 units spacing, 280px minimum width
  • Cards: 200px height, full width, large border radius
  • Count: 8 skeleton cards by default

SettingsPagesSkeleton

A skeleton component for multi-section settings pages with labels and input fields.

Component Location

import SettingsPagesSkeleton from 'components/Skeletons/SettingsPages';

Props

No props required - this is a stateless skeleton component.

Features

Multi-Section Layout

  • 3 sections by default
  • Section labels (300px width)
  • Input fields (500px width)
  • Consistent spacing and padding

Page Structure

  • Top and left padding
  • Section-based organization
  • Proper visual hierarchy

Usage Examples

Settings Page Loading

import SettingsPagesSkeleton from 'components/Skeletons/SettingsPages';

function CompanySettings() {
const [isLoading, setIsLoading] = useState(true);
const [settings, setSettings] = useState(null);

if (isLoading) {
return <SettingsPagesSkeleton />;
}

return (
<VStack spacing={8}>
<SettingsSection title='Company Information' />
<SettingsSection title='User Preferences' />
<SettingsSection title='Security Settings' />
</VStack>
);
}

Tabbed Settings

import SettingsPagesSkeleton from 'components/Skeletons/SettingsPages';

function TabbedSettings() {
const [activeTab, setActiveTab] = useState(0);
const [isLoading, setIsLoading] = useState(true);

return (
<Tabs index={activeTab} onChange={setActiveTab}>
&lt;TabList&gt;
&lt;Tab&gt;General</Tab>
&lt;Tab&gt;Security</Tab>
&lt;Tab&gt;Notifications</Tab>
</TabList>
&lt;TabPanels&gt;
&lt;TabPanel&gt;
{isLoading ? <SettingsPagesSkeleton /> : <GeneralSettings />}
</TabPanel>
{/* More panels */}
</TabPanels>
</Tabs>
);
}

Styling

  • Container: 9 units spacing, 7px top padding, 10px left padding
  • Labels: 20px height, 300px width
  • Fields: 38px height, 500px width
  • Sections: 5 units spacing between elements

Loading State Patterns

Progressive Loading

function ProgressiveLoadingComponent() {
const [loadingStates, setLoadingStates] = useState({
header: true,
content: true,
sidebar: true,
});

return (
<Grid templateColumns='1fr 300px' gap={6}>
<VStack spacing={6}>
{loadingStates.header ? <OnBoardingPages /> : <HeaderContent />}
{loadingStates.content ? <FormSkeleton /> : <MainContent />}
</VStack>
&lt;Box&gt;
{loadingStates.sidebar ? <SettingsSkeleton /> : <SidebarContent />}
</Box>
</Grid>
);
}

Conditional Skeleton Display

function ConditionalSkeletons({ data, isLoading }) {
const renderContent = () => {
if (isLoading) {
// Choose appropriate skeleton based on content type
switch (data?.type) {
case 'form':
return <FormSkeleton />;
case 'profile':
return <CircleTextSectionSkeleton />;
case 'reports':
return <ReportsShimmer />;
case 'settings':
return <SettingsSkeleton />;
default:
return <OnBoardingPages />;
}
}
return <ActualContent data={data} />;
};

return &lt;Box&gt;{renderContent()}</Box>;
}

Staggered Loading

function StaggeredLoading() {
const [phases, setPhases] = useState({
phase1: true,
phase2: true,
phase3: true,
});

useEffect(() => {
const timer1 = setTimeout(
() => setPhases(prev => ({ ...prev, phase1: false })),
500,
);
const timer2 = setTimeout(
() => setPhases(prev => ({ ...prev, phase2: false })),
1000,
);
const timer3 = setTimeout(
() => setPhases(prev => ({ ...prev, phase3: false })),
1500,
);

return () => {
clearTimeout(timer1);
clearTimeout(timer2);
clearTimeout(timer3);
};
}, []);

return (
<VStack spacing={6}>
{phases.phase1 ? <OnBoardingPages /> : <HeaderSection />}
{phases.phase2 ? <FormSkeleton /> : <FormSection />}
{phases.phase3 ? <ReportsShimmer /> : <ReportsSection />}
</VStack>
);
}

Best Practices

Skeleton Selection

  1. Content Matching: Choose skeletons that match the actual content structure
  2. Proportional Sizing: Ensure skeleton dimensions approximate real content
  3. Consistent Spacing: Use consistent spacing patterns across skeletons
  4. Visual Hierarchy: Maintain proper visual hierarchy in skeleton layouts

Performance Considerations

  1. Lazy Loading: Only render skeletons when needed
  2. Reusable Components: Use skeleton components consistently across the app
  3. Minimal Animations: Keep skeleton animations lightweight
  4. Memory Management: Clean up skeleton states properly

User Experience

  1. Appropriate Duration: Don't show skeletons for too long
  2. Smooth Transitions: Ensure smooth transitions from skeleton to content
  3. Progressive Loading: Show content as it becomes available
  4. Error Handling: Handle skeleton states during error conditions

Testing Skeletons

import { render, screen } from '@testing-library/react';
import FormSkeleton from 'components/Skeletons/formSkeleton';

describe('Skeleton Components', () => {
it('should render form skeleton with proper structure', () => {
render(<FormSkeleton />);

// Check for skeleton elements
const skeletons = screen.getAllByTestId('skeleton');
expect(skeletons).toHaveLength(6); // 4 fields + 2 buttons
});

it('should render reports shimmer with grid layout', () => {
render(<ReportsShimmer />);

const grid = screen.getByTestId('reports-grid');
expect(grid).toHaveStyle('grid-template-columns: repeat(4, 1fr)');
});
});

Custom Skeleton Creation

import { Skeleton, SkeletonText, SkeletonCircle } from '@chakra-ui/react';

function CustomSkeleton() {
return (
<Box p={6} borderRadius='lg' bg='white' shadow='sm'>
<HStack spacing={4}>
<SkeletonCircle size='12' />
<VStack align='start' flex={1}>
<Skeleton height='20px' width='200px' />
<SkeletonText noOfLines={2} spacing='2' />
</VStack>
</HStack>
<Skeleton height='100px' width='full' mt={4} />
</Box>
);
}

This comprehensive skeleton system provides consistent loading states that improve perceived performance and user experience throughout the WorkPayCore Frontend application.