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
- SettingsSkeleton - Settings page skeleton with labels and switches
- SettingsPagesSkeleton - Multi-section settings page skeleton
Content Skeletons
- CircleTextSectionSkeleton - Profile/avatar skeleton with text
- ReportsShimmer - Grid-based reports skeleton
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 (
<Box>
<Heading>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}>
<Box>
<Heading size='md'>Personal Information</Heading>
{loadingStates.personal ? <FormSkeleton /> : <PersonalInfoForm />}
</Box>
<Box>
<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}>
<Heading>Welcome to WorkPay</Heading>
<Text>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}>
<Step>
<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}>
<FormControl>
<FormLabel>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 (
<Box>
<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
| Prop | Type | Required | Description |
|---|---|---|---|
| ...rest | object | - | 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>
<Text>{user.department}</Text>
<Text>{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}>
<Heading>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}>
<TabList>
<Tab>General</Tab>
<Tab>Security</Tab>
<Tab>Notifications</Tab>
</TabList>
<TabPanels>
<TabPanel>
{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>
<Box>
{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 <Box>{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
- Content Matching: Choose skeletons that match the actual content structure
- Proportional Sizing: Ensure skeleton dimensions approximate real content
- Consistent Spacing: Use consistent spacing patterns across skeletons
- Visual Hierarchy: Maintain proper visual hierarchy in skeleton layouts
Performance Considerations
- Lazy Loading: Only render skeletons when needed
- Reusable Components: Use skeleton components consistently across the app
- Minimal Animations: Keep skeleton animations lightweight
- Memory Management: Clean up skeleton states properly
User Experience
- Appropriate Duration: Don't show skeletons for too long
- Smooth Transitions: Ensure smooth transitions from skeleton to content
- Progressive Loading: Show content as it becomes available
- 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.