14 KiB
Implement Skeleton Loader Transitions for Language Switching
Implement professional skeleton loader animations when switching between English and Spanish languages in the CV application. The goal is to create a polished, modern user experience similar to FriendKit (reference screenshots provided) where content transitions through skeleton/placeholder states with pulsing animations.Visual Reference: The user provided screenshots showing gray pulsing placeholder boxes that appear during page transitions - these skeleton loaders make the transition feel smooth and intentional while new content loads.
This will transform the current instant language switch into a premium, modern web app experience.
**Current Implementation:** - Language switching uses HTMX with `/switch-language?lang={en|es}` endpoint - HTMX performs out-of-band swaps (`hx-swap-oob="innerHTML"`) to update: - `#language-selector` (primary target) - `#cv-inner-content-page-1` - `#cv-inner-content-page-2` - Currently uses `hx-swap="outerHTML"` which causes instant, jarring content replacement - No intermediate loading state - content just "pops" from one language to anotherReference Files: @templates/language-switch.html - Server response template with OOB swaps @templates/partials/navigation/language-selector.html - Language selector buttons @static/css/main.css - Existing CSS with some transitions already in place
Tech Stack:
- HTMX for dynamic content swapping
- Hyperscript for custom behaviors (already in use)
- Vanilla CSS for animations (no external animation libraries)
- Go templates for server-side rendering
Desired User Experience (from screenshots):
- User clicks language button (EN or ES)
- Current content fades out (200-300ms)
- Skeleton loaders appear - gray pulsing boxes matching the layout structure
- New language content loads from server
- Skeleton loaders fade out and new content fades in (200-300ms)
- Total smooth, professional transition feel
Why Skeleton Loaders Matter:
- Perceived performance: Users perceive loading as faster when they see progressive feedback
- Modern UX standard: Used by Facebook, LinkedIn, YouTube, and all modern web apps
- Reduces cognitive load: Shows users "something is happening" vs blank screen or instant swap
- Professional polish: Demonstrates attention to detail and quality
-
Three-Phase Transition:
- Phase 1 (Fade Out): Current content fades to opacity 0 (250ms)
- Phase 2 (Skeleton Display): Show skeleton loaders with pulse animation
- Phase 3 (Fade In): New content fades from opacity 0 to 1 (250ms)
- Total transition: ~500-700ms (feels premium, not sluggish)
-
HTMX Integration:
- Use HTMX swap timing modifiers:
swap:250ms settle:250ms - Leverage HTMX request lifecycle events for skeleton state management
- Use
htmx:beforeRequestto show skeleton - Use
htmx:afterSwapto hide skeleton and fade in content
- Use HTMX swap timing modifiers:
-
CSS Animations:
- Create
.skeleton-loadercomponent classes - Pulsing animation using CSS
@keyframes(opacity or background gradient shift) - Animation should be subtle: 1.5s-2s loop, infinite
- GPU-accelerated properties only (opacity, transform)
- Create
-
Layout Matching:
- Skeleton should mirror the actual CV layout structure
- Consider creating skeleton variants for:
- Header section (name, title, badges)
- Skills sidebar sections
- Experience entries
- Education entries
- Doesn't need to be pixel-perfect, just recognizable structure
-
Performance:
- Skeleton rendering should be instant (<16ms)
- Animations GPU-accelerated for smooth 60fps
- No layout thrashing during transitions
- Total transition under 700ms
-
Accessibility:
- Respect
prefers-reduced-motion- disable pulse animation - Add
aria-busy="true"during loading states - Ensure screen readers announce loading state
- Respect
-
Consistency:
- Use existing project timing patterns (0.2s-0.3s)
- Match existing design language and color palette
- Work on mobile and desktop viewports
Step 1: Create Skeleton Loader HTML Structure
Create a new template or partial: templates/partials/skeleton-loader.html
This should contain a simplified version of your CV layout with placeholder boxes:
- Header skeleton (circular avatar placeholder, horizontal bars for name/title)
- Left sidebar skeleton (rectangular blocks for skills)
- Main content skeleton (blocks for experience entries)
- Right sidebar skeleton (blocks for additional skills)
Step 2: Create Skeleton CSS Animations
Add to static/css/main.css or create static/css/skeleton.css:
/* Skeleton loader base styles */
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-pulse 1.5s ease-in-out infinite;
border-radius: 4px;
}
@keyframes skeleton-pulse {
0%, 100% { background-position: 200% 0; }
50% { background-position: 0 0; }
}
/* Skeleton container - hidden by default */
.skeleton-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: white;
z-index: 10;
opacity: 0;
pointer-events: none;
transition: opacity 250ms ease;
}
.skeleton-overlay.active {
opacity: 1;
pointer-events: all;
}
/* Skeleton shapes - create boxes matching your layout */
.skeleton-header { height: 120px; margin-bottom: 20px; }
.skeleton-sidebar-item { height: 60px; margin-bottom: 10px; }
.skeleton-content-item { height: 100px; margin-bottom: 15px; }
/* Reduce motion support */
@media (prefers-reduced-motion: reduce) {
.skeleton { animation: none; background: #e0e0e0; }
}
Step 3: Update Language Selector with HTMX Timing
Modify templates/partials/navigation/language-selector.html:
- Add
hx-swap="outerHTML swap:250ms settle:250ms" - Add
hx-indicator="#skeleton-loader"to show skeleton during request - Add hyperscript to manage skeleton visibility
Step 4: Add Skeleton Loader to Main Template
Insert the skeleton loader overlay into your main CV template:
- Position it absolutely over the content area
- Initially hidden with
opacity: 0 - Activated via HTMX events or hyperscript
Step 5: HTMX Event Handling with Hyperscript
Add hyperscript behavior to show/hide skeleton:
on htmx:beforeRequest from #language-selector
add .active to #skeleton-loader
end
on htmx:afterSwap from #language-selector
wait 100ms -- Brief delay to ensure content is rendered
remove .active from #skeleton-loader
end
Alternative Approach (Pure HTMX):
Use hx-indicator attribute with CSS to control visibility:
hx-indicator="#skeleton-loader"on language buttons- HTMX automatically adds
.htmx-requestclass during requests - CSS:
#skeleton-loader.htmx-request { opacity: 1; }
Step 6: Content Fade Transitions
Add fade-in/out to actual content sections:
.cv-page-content-wrapper {
transition: opacity 250ms ease;
}
.cv-page-content-wrapper.htmx-swapping {
opacity: 0;
}
.cv-page-content-wrapper.htmx-settling {
opacity: 1;
}
What to Prioritize:
- Get basic skeleton structure showing/hiding correctly
- Add pulsing animation
- Refine skeleton shapes to better match layout
- Polish timing and transitions
- Add accessibility features
What to Avoid:
- Don't create overly complex skeleton markup - simple boxes are fine
- Don't make the skeleton identical to real content - approximate is better
- Don't use JavaScript animations - stick to CSS for performance
- Don't make transitions too long - 500-700ms total maximum
- Don't forget to test rapid clicking (skeleton should handle interruptions gracefully)
Why These Constraints Matter:
- Simplicity: Complex skeletons are harder to maintain when layout changes
- Performance: CSS animations are GPU-accelerated; JS animations cause jank
- UX Research: 400-700ms is the sweet spot - longer feels broken, shorter feels pointless
- Resilience: Users click fast - implementation must handle interruptions without breaking
-
./templates/partials/skeleton-loader.html(NEW)- Skeleton loader HTML structure
- Simplified CV layout with placeholder boxes
- Should mirror the structure of CV content sections
-
./static/css/skeleton.css(NEW)- Skeleton loader styles
- Pulsing animation keyframes
- Skeleton overlay positioning and transitions
- Responsive skeleton layouts
- Accessibility overrides for
prefers-reduced-motion
-
./templates/partials/navigation/language-selector.html- Add HTMX swap timing modifiers
- Add skeleton loader indicator reference
- Add hyperscript for skeleton show/hide events
-
./templates/language-switch.html- May need to coordinate OOB swap timing
- Ensure skeleton works with all content updates
-
./static/css/main.css- Add content fade-in/out transitions
- Import skeleton.css if created separately
- Add HTMX animation class styles
-
Include skeleton loader in main template (wherever CV content lives)
- Add
<div id="skeleton-loader" class="skeleton-overlay">...</div> - Position over content area
- Initially hidden, shown during language switch
- Add
1. Visual Verification:
- Click EN button: Content fades out → Skeleton appears → New content fades in
- Click ES button: Same smooth transition with skeleton
- Skeleton boxes pulse/shimmer smoothly
- Skeleton roughly matches CV layout (recognizable structure)
- No flashing or jarring jumps during transition
2. Timing Verification:
- Total transition feels responsive (<700ms)
- Skeleton appears immediately when language button clicked
- Content fade-out is smooth (not instant)
- Content fade-in is smooth after skeleton disappears
3. Interaction Testing:
- Rapidly click between EN and ES - no broken states
- Click language button while skeleton is showing - handles gracefully
- Mobile viewport - skeleton works correctly
- Desktop viewport - skeleton works correctly
4. Performance Testing:
- Open DevTools Performance tab
- Record during language switch
- Verify 60fps animation (no dropped frames)
- No layout thrashing or long tasks
- GPU acceleration active for animations
5. Accessibility Testing:
- Enable "Reduce motion" in OS settings → Skeleton pulse disabled
- Screen reader announces loading state
- Keyboard navigation still works during transitions
- Focus management doesn't break
6. Browser Compatibility:
- Chrome: Smooth skeleton animations
- Firefox: Smooth skeleton animations
- Safari: Smooth skeleton animations
- Mobile browsers: Touch interactions work correctly
7. Edge Cases:
- Slow network simulation (throttle to 3G) - skeleton visible longer
- Fast network - skeleton doesn't flash too quickly (minimum display time?)
- First load vs subsequent switches - consistent behavior
- Page refresh during skeleton display - recovers gracefully
Success Indicators: ✅ Skeleton loaders appear during language transitions ✅ Pulsing animation is smooth and subtle ✅ Total transition time feels professional (500-700ms) ✅ No jarring content jumps or flashes ✅ Works consistently across browsers and devices ✅ Respects accessibility preferences ✅ Handles rapid interactions gracefully
<success_criteria>
- Skeleton loaders with pulsing animation appear during language switching
- Three-phase transition: fade-out → skeleton → fade-in
- Total transition time: 500-700ms (feels premium, not sluggish)
- Skeleton structure roughly matches CV layout (recognizable)
- Animations are GPU-accelerated and buttery smooth (60fps)
- Respects
prefers-reduced-motionaccessibility setting - Handles rapid clicking without breaking or visual glitches
- Works consistently across modern browsers and device sizes
- Code follows existing project patterns and conventions
- Implementation is maintainable and doesn't overcomplicate the codebase </success_criteria>
Skeleton Loader Patterns:
- Facebook-style skeleton screens
- Modern progressive loading UX patterns
- LinkedIn content placeholders
Visual Reference (provided by user):
- FriendKit example pages showing gray pulsing placeholder boxes
- Boxes animate with subtle shimmer/pulse effect
- Content appears progressively after skeleton state
<additional_context> If you can provide the FriendKit code: If you have access to the actual CSS/HTML from the FriendKit examples, examining their skeleton loader implementation would be valuable for:
- Exact timing values they use
- Gradient/animation patterns for the pulse effect
- How they structure skeleton markup
- Any clever performance optimizations
However, we can implement an excellent skeleton loader without that code - the visual reference is sufficient to create a premium experience. </additional_context>