# Shimmer From Structure A structure-aware skeleton generator that mirrors your rendered UI at runtime. Automatically generates responsive shimmer states with zero layout duplication. Built for React, Vue, Angular, Svelte and SolidJS. **Documentation:** Access Full Docs ![React](https://img.shields.io/badge/React-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) ![Vue](https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D) ![Svelte](https://img.shields.io/badge/Svelte-ff3e00?style=for-the-badge&logo=svelte&logoColor=white) ![Angular](https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white) ![SolidJS](https://img.shields.io/badge/SolidJS-%232c4f7c?style=for-the-badge&logo=solid&logoColor=white) ![Shimmer From Structure Demo](https://github.com/darula-hpp/shimmer-from-structure/raw/main/example/preview.gif) ## Why This Library? Traditional shimmer libraries require you to: - Manually create skeleton components that mirror your real components - Maintain two versions of each component (real + skeleton) - Update skeletons every time your layout changes **Shimmer From Structure** eliminates all of that: - ✅ **Works with React, Vue, Svelte, Angular & SolidJS** - Simple, framework-specific adapters - ✅ Automatically measures your component's structure at runtime - ✅ Generates shimmer effects that match actual dimensions - ✅ Zero maintenance - works with any layout changes - ✅ Works with complex nested structures - ✅ Supports dynamic data with `templateProps` - ✅ Preserves container backgrounds during loading - ✅ Auto-detects border-radius from your CSS ## Installation ```bash npm install shimmer-from-structure # or yarn add shimmer-from-structure # or pnpm add shimmer-from-structure ``` ## 🎯 Framework Support Shimmer From Structure provides dedicated packages for **React and Vue**. ### React React support is built into the main package for backward compatibility: ```javascript // React projects (or @shimmer-from-structure/react) import { Shimmer } from 'shimmer-from-structure'; ``` ### Vue 3 Vue support requires importing from the specific adapter: ```javascript // Vue 3 projects import { Shimmer } from '@shimmer-from-structure/vue'; ``` ### Svelte Svelte support is provided via its own adapter: ```javascript // Svelte projects import { Shimmer } from '@shimmer-from-structure/svelte'; ``` ### Angular Angular support requires importing from the specific adapter: ```typescript // Angular projects import { ShimmerComponent } from '@shimmer-from-structure/angular'; ``` ### SolidJS SolidJS support requires importing from the specific adapter: ```tsx // SolidJS projects import { Shimmer } from '@shimmer-from-structure/solid'; ``` --- # 📖 Basic Usage ## React ### Static Content For components with hardcoded/static content: ```tsx import { Shimmer } from 'shimmer-from-structure'; function UserCard() { return (

John Doe

Software Engineer

); } ``` ## Vue ### Static Content ```vue ``` ## Svelte ### Static Content ```svelte

John Doe

Software Engineer

``` ## Angular ### Static Content ```typescript import { Component, signal } from '@angular/core'; import { ShimmerComponent } from '@shimmer-from-structure/angular'; @Component({ selector: 'app-user-card', standalone: true, imports: [ShimmerComponent], template: `

John Doe

Software Engineer

`, }) export class UserCardComponent { isLoading = signal(true); } ``` ## SolidJS ### Static Content ```tsx import { createSignal } from 'solid-js'; import { Shimmer } from '@shimmer-from-structure/solid'; function UserCard() { const [isLoading, setIsLoading] = createSignal(true); return (

John Doe

Software Engineer

); } ``` --- ### Dynamic Content with `templateProps` For components that receive dynamic data via props, use `templateProps` to provide mock data for skeleton generation: **React** ```tsx import { Shimmer } from 'shimmer-from-structure'; // Your component that accepts props const UserCard = ({ user }) => (

{user.name}

{user.role}

); // Template data for the skeleton const userTemplate = { name: 'Loading...', role: 'Loading role...', avatar: 'placeholder.jpg', }; function App() { const [loading, setLoading] = useState(true); const [user, setUser] = useState(null); return ( ); } ``` **Vue** ```vue ``` **Svelte** ```svelte ``` **Angular** ```typescript import { Component, signal } from '@angular/core'; import { ShimmerComponent } from '@shimmer-from-structure/angular'; import { UserCardComponent } from './user-card.component'; @Component({ selector: 'app-root', standalone: true, imports: [ShimmerComponent, UserCardComponent], template: ` `, }) export class AppComponent { loading = signal(true); user = signal(null); userTemplate = { name: 'Loading...', role: 'Loading role...', avatar: 'placeholder.jpg', }; } ``` **SolidJS** ```tsx import { createSignal } from 'solid-js'; import { Shimmer } from '@shimmer-from-structure/solid'; import { UserCard } from './UserCard'; function App() { const [loading, setLoading] = createSignal(true); const [user, setUser] = createSignal(null); const userTemplate = { name: 'Loading...', role: 'Loading role...', avatar: 'placeholder.jpg', }; return ( ); } ``` The `templateProps` object is spread onto the first child component when loading, allowing it to render with mock data for measurement. ## 🎨 API Reference ### `` Props | Prop | Type | Default | Description | | ---------------------- | ------------------------- | -------------------------- | --------------------------------------------------------- | | `loading` | `boolean` | `true` | Whether to show shimmer effect or actual content | | `children` | `React.ReactNode` | required | The content to render/measure | | `shimmerColor` | `string` | `'rgba(255,255,255,0.15)'` | Color of the shimmer wave | | `backgroundColor` | `string` | `'rgba(255,255,255,0.08)'` | Background color of shimmer blocks | | `duration` | `number` | `1.5` | Animation duration in seconds | | `fallbackBorderRadius` | `number` | `4` | Border radius (px) for elements with no CSS border-radius | | `templateProps` | `Record` | - | Props to inject into first child for skeleton rendering | ### Example with All Props **React** ```tsx ``` **Vue** ```vue ``` **Svelte** ```svelte ``` **Angular** ```typescript ``` **SolidJS** ```tsx ``` ## 🔧 How It Works 1. **Visible Container Rendering**: When `loading={true}`, your component renders with transparent text but **visible container backgrounds** 2. **Template Props Injection**: If `templateProps` is provided, it's spread onto the first child so dynamic components can render 3. **DOM Measurement**: Uses `useLayoutEffect` to synchronously measure all leaf elements via `getBoundingClientRect()` 4. **Border Radius Detection**: Automatically captures each element's computed `border-radius` from CSS 5. **Shimmer Generation**: Creates absolutely-positioned shimmer blocks matching measured dimensions 6. **Animation**: Applies smooth gradient animation that sweeps across each block ### Key Features - **Container backgrounds visible**: Unlike `opacity: 0`, we use `color: transparent` so card backgrounds/borders show during loading - **Auto border-radius**: Circular avatars get circular shimmer blocks automatically - **Fallback radius**: Text elements (which have `border-radius: 0`) use `fallbackBorderRadius` to avoid sharp rectangles - **Dark-mode friendly**: Default colors use semi-transparent whites that work on any background ## Examples ### Dashboard with Multiple Sections Each section can have its own independent loading state: **React** ```tsx function Dashboard() { const [loadingUser, setLoadingUser] = useState(true); const [loadingStats, setLoadingStats] = useState(true); return ( <> {/* User profile section */} {/* Stats section - with custom colors */} ); } ``` **Vue** ```vue ``` **Svelte** ```svelte ``` **Angular** ```typescript @Component({ template: ` `, }) export class DashboardComponent { loadingUser = signal(true); loadingStats = signal(true); // ... } ``` ### Transactions List **React** ```tsx ``` **Vue** ```vue ``` **Svelte** ```svelte ``` **Angular** ```typescript ``` ### Team Members Grid **React** ```tsx ``` **Vue** ```vue ``` **Svelte** ```svelte ``` **Angular** ```typescript ``` ## 🔄 Using with React Suspense Shimmer works seamlessly as a Suspense fallback. When used this way, `loading` is always `true` because React automatically unmounts the fallback and replaces it with the resolved component. ### Basic Suspense Pattern ```tsx import { Suspense, lazy } from 'react'; import { Shimmer } from 'shimmer-from-structure'; const UserProfile = lazy(() => import('./UserProfile')); function App() { return ( } > ); } ``` ### Why `loading={true}` is Always Set When using Shimmer as a Suspense fallback: 1. **Suspend**: React renders the fallback → Shimmer shows with `loading={true}` 2. **Resolve**: React **replaces** the entire fallback with the real component 3. The Shimmer is **unmounted**, not updated — so you never need to toggle `loading` ### Performance Tips for Suspense **Memoize the fallback** to prevent re-renders: ```tsx const ShimmerFallback = React.memo(() => ( )); // Usage }> ; ``` **Keep templates lightweight** — the DOM is measured synchronously via `useLayoutEffect`, so avoid complex logic in your template. ## Global Configuration You can set default configuration for your entire app (or specific sections) using the context/provider pattern. This is perfect for maintaining consistent themes without repeating props. ### React (Context API) ```tsx import { Shimmer, ShimmerProvider } from '@shimmer-from-structure/react'; function App() { return ( // Set global defaults ); } ``` ### Vue (Provide/Inject) ```vue ``` ### Svelte (setShimmerConfig) ```svelte ``` ### Angular (Dependency Injection) ```typescript // main.ts or bootstrapApplication import { bootstrapApplication } from '@angular/platform-browser'; import { provideShimmerConfig } from '@shimmer-from-structure/angular'; import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent, { providers: [ provideShimmerConfig({ shimmerColor: 'rgba(56, 189, 248, 0.4)', backgroundColor: 'rgba(56, 189, 248, 0.1)', duration: 2.5, fallbackBorderRadius: 8, }), ], }); ``` ### SolidJS (ShimmerProvider) ```tsx import { Shimmer, ShimmerProvider } from '@shimmer-from-structure/solid'; function App() { return ( ); } ``` --- Components inside the provider automatically inherit values. You can still override them locally: **React** ```tsx // Inherits blue theme from provider // Overrides provider settings ``` **Vue** ```vue ``` **Svelte** ```svelte ``` **Angular** ```typescript ``` **SolidJS** ```tsx ``` ### Accessing Config in Hooks/Composables If you need to access the current configuration in your own components: **React** ```tsx import { useShimmerConfig } from 'shimmer-from-structure'; function MyComponent() { const config = useShimmerConfig(); return
...
; } ``` **Vue** ```javascript import { useShimmerConfig } from '@shimmer-from-structure/vue'; const config = useShimmerConfig(); console.log(config.value.backgroundColor); ``` **Svelte** ```javascript import { getShimmerConfig } from '@shimmer-from-structure/svelte'; const config = getShimmerConfig(); console.log(config.backgroundColor); ``` **Angular** ```typescript import { Component, inject } from '@angular/core'; import { injectShimmerConfig } from '@shimmer-from-structure/angular'; @Component({ selector: 'app-my-component', template: `
...
`, }) export class MyComponent { config = injectShimmerConfig(); } ``` **SolidJS** ```tsx import { useShimmerConfig } from '@shimmer-from-structure/solid'; function MyComponent() { const config = useShimmerConfig(); return
...
; } ``` ## Best Practices ### 1. Use `templateProps` for Dynamic Data When your component receives data via props, always provide `templateProps` with mock data that matches the expected structure. ### 2. Match Template Structure to Real Data Ensure your template data has the same array length and property structure as real data for accurate shimmer layout. ### 3. Use Individual Shimmer Components Wrap each section in its own Shimmer for independent loading states: ```tsx // ✅ Good - independent loading // ❌ Avoid - all-or-nothing loading ``` ### 4. Consider Element Widths Block elements like `

`, `

` take full container width. If you want shimmer to match text width: ```css .title { width: fit-content; } ``` ### 5. Provide Container Dimensions For async components (like charts), ensure containers have explicit dimensions so shimmer has something to measure. ## ⚡ Performance Considerations - Measurement happens only when `loading` changes to `true` - Uses `useLayoutEffect` for synchronous measurement (no flicker) - Minimal re-renders - only updates when loading state or children change - Lightweight DOM measurements using native browser APIs - Lightweight DOM measurements using native browser APIs ## 🛠️ Development This is a monorepo managed with npm workspaces. Each package can be built independently: ```bash # Install dependencies npm install # Build all packages npm run build # Build individual packages npm run build:core npm run build:react npm run build:vue npm run build:svelte npm run build:main # Run tests npm test ``` ## 📝 License MIT ## 🤝 Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## 🐛 Known Limitations - **Async components**: Components that render asynchronously (like charts using `ResponsiveContainer`) may need explicit container dimensions - **Zero-dimension elements**: Elements with `display: none` or zero dimensions won't be captured - **SVG internals**: Only the outer `` element is captured, not internal paths/shapes ## 🏗️ Monorepo Structure This library is organized as a monorepo with four packages: | Package | Description | Size | | --------------------------------- | ------------------------------------------- | -------- | | `@shimmer-from-structure/core` | Framework-agnostic DOM utilities | 1.44 kB | | `@shimmer-from-structure/react` | React adapter | 12.84 kB | | `@shimmer-from-structure/vue` | Vue 3 adapter | 3.89 kB | | `@shimmer-from-structure/svelte` | Svelte adapter | 4.60 kB | | `@shimmer-from-structure/angular` | Angular adapter | 6.83 kB | | `@shimmer-from-structure/solid` | SolidJS adapter | 4.01 kB | | `shimmer-from-structure` | Main package (React backward compatibility) | 0.93 kB | The core package contains all DOM measurement logic, while React, Vue, Svelte, Angular and SolidJS packages are thin wrappers that provide framework-specific APIs. ## 🚧 Roadmap - [x] Dynamic data support via `templateProps` - [x] Auto border-radius detection - [x] Container background visibility - [x] **Vue.js adapter** - [x] **Svelte adapter** - [x] **Angular adapter** - [x] **SolidJS adapter** - [ ] Better async component support - [ ] Customizable shimmer direction (vertical, diagonal) - [ ] React Native support ## 📚 Featured In - Build your own shimmer skeleton that never goes out of sync - Deep dive blog post on the implementation - What's new in Svelte: March 2026 - Featured in Svelte's official blog - React Status Issue #459 - Featured in React Status newsletter --- Made with ❤️ for developers tired of maintaining skeleton screens