An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

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)

![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

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