https://github.com/darula-hpp/shimmer-from-structure
A structure-aware skeleton loader that mirrors your rendered UI at runtime. Zero layout duplication. Built for modern frameworks.
https://github.com/darula-hpp/shimmer-from-structure
angular angularjs developer-experience loading-ui react shimmer shimmer-effect skeleton-loading solidjs svelte sveltejs vue vuejs
Last synced: 8 days ago
JSON representation
A structure-aware skeleton loader that mirrors your rendered UI at runtime. Zero layout duplication. Built for modern frameworks.
- Host: GitHub
- URL: https://github.com/darula-hpp/shimmer-from-structure
- Owner: darula-hpp
- License: mit
- Created: 2026-01-20T18:59:27.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-02-21T08:19:42.000Z (18 days ago)
- Last Synced: 2026-02-21T16:08:47.948Z (18 days ago)
- Topics: angular, angularjs, developer-experience, loading-ui, react, shimmer, shimmer-effect, skeleton-loading, solidjs, svelte, sveltejs, vue, vuejs
- Language: TypeScript
- Homepage: https://shimmer-from-structure-docs.vercel.app
- Size: 6.33 MB
- Stars: 743
- Watchers: 2
- Forks: 14
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
- fucking-awesome-angular - shimmer-from-structure - A React, Vue, Svelte, & Angular shimmer/skeleton library that automatically adapts to your component's runtime structure. (Third Party Components / Loaders)
- awesome-angular - shimmer-from-structure - A React, Vue, Svelte, & Angular shimmer/skeleton library that automatically adapts to your component's runtime structure. (Third Party Components / Loaders)
README
# Shimmer From Structure
A structure-aware skeleton loader 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](https://shimmer-from-structure-docs.vercel.app)






## 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
import { ref } from 'vue';
import { Shimmer } from '@shimmer-from-structure/vue';
const isLoading = ref(true);
John Doe
Software Engineer
```
## Svelte
### Static Content
```svelte
import { Shimmer } from '@shimmer-from-structure/svelte';
let isLoading = $state(true);
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
import { ref } from 'vue';
import { Shimmer } from '@shimmer-from-structure/vue';
import UserCard from './UserCard.vue';
const loading = ref(true);
const userTemplate = {
name: 'Loading...',
role: 'Loading role...',
avatar: 'placeholder.jpg',
};
```
**Svelte**
```svelte
import { Shimmer } from '@shimmer-from-structure/svelte';
import UserCard from './UserCard.svelte';
let { user } = $props();
let loading = $state(true);
const userTemplate = {
name: 'Loading...',
role: 'Loading role...',
avatar: 'placeholder.jpg',
};
```
**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
import { provideShimmerConfig } from '@shimmer-from-structure/vue';
provideShimmerConfig({
shimmerColor: 'rgba(56, 189, 248, 0.4)',
backgroundColor: 'rgba(56, 189, 248, 0.1)',
duration: 2.5,
fallbackBorderRadius: 8,
});
```
### Svelte (setShimmerConfig)
```svelte
import { setShimmerConfig } from '@shimmer-from-structure/svelte';
import Dashboard from './Dashboard.svelte';
// Must be called at the top level during component initialization
setShimmerConfig({
shimmerColor: 'rgba(56, 189, 248, 0.4)',
backgroundColor: 'rgba(56, 189, 248, 0.1)',
duration: 2.5,
fallbackBorderRadius: 8,
});
```
### 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
---
Made with ❤️ for developers tired of maintaining skeleton screens