# 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






## 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
John Doe
Software Engineer
```
## 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
`,
})
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 `