auto_skeleton

pub package License: BSD-3

Auto-generate skeleton/shimmer loading screens from your actual widget tree. No fake data needed — just wrap your widget and get a matching placeholder shape automatically.

Screenshots

Skeleton (Loading) Loaded (Content)

Annotations: Fine-grained Control

Annotation Skeleton Annotation Loaded
  • PlaceholderIgnore — the toggle switches are hidden during loading (not relevant to skeleton)
  • PlaceholderLeaf — the colored icon boxes are treated as solid rectangles (no child traversal)

Why auto_skeleton?

Building skeleton loading UIs manually is tedious and goes out of sync with your real layouts. auto_skeleton solves this by introspecting your widget tree at render time and generating matching bone shapes for every content widget (Text, Image, Icon, Button, etc.).

How It Compares

Feature auto_skeleton skeletonizer shimmer
Auto-detect widget shapes Yes Yes No
Zero fake data needed Yes No (needs mock data) No
Async builder (no setState) Yes No No
Future + Stream support Yes No No
Theme-aware colors Yes No No
Extension syntax .withSkeleton() Yes No No
Pre-built presets Yes No No
Multiple effects (shimmer, pulse, solid) Yes Yes Shimmer only
Annotation system Yes Yes No
Switch animation Yes Yes No
Dark mode auto-detection Yes Yes No

Comparison based on default features of each package as of March 2026. All packages are actively maintained and excellent in their own right.

Installation

dependencies:
  auto_skeleton: ^0.1.1
flutter pub get

Quick Start

Basic Usage

Wrap any widget with AutoSkeleton:

AutoSkeleton(
  enabled: _isLoading,
  child: Card(
    child: ListTile(
      leading: CircleAvatar(child: Icon(Icons.person)),
      title: Text('John Doe'),
      subtitle: Text('Software Developer'),
      trailing: Icon(Icons.chevron_right),
    ),
  ),
)

That's it! When enabled: true, the package scans the widget tree and renders matching skeleton bones with a shimmer animation. When enabled: false, your actual content is shown.

Colors are automatically derived from your app's theme — works in both light and dark mode with zero configuration.

AutoSkeletonBuilder — Zero setState

Handle async data loading with automatic skeleton. No setState, no _isLoading boolean:

AutoSkeletonBuilder<User>(
  future: fetchUser(),
  skeleton: ListTile(
    leading: CircleAvatar(child: Icon(Icons.person)),
    title: Text('Placeholder name'),
    subtitle: Text('Loading...'),
  ),
  builder: (context, user) => ListTile(
    leading: CircleAvatar(backgroundImage: NetworkImage(user.avatar)),
    title: Text(user.name),
    subtitle: Text(user.bio),
  ),
)

Works with Stream too:

AutoSkeletonBuilder<List<Post>>(
  stream: postStream(),
  skeleton: MyPostListSkeleton(),
  builder: (context, posts) => PostList(posts),
  errorBuilder: (context, error) => ErrorWidget(error),
)

Extension Syntax

Even simpler — use the .withSkeleton() extension:

Card(
  child: ListTile(
    title: Text('Hello World'),
    subtitle: Text('This is a subtitle'),
  ),
).withSkeleton(loading: _isLoading)

Migrating from Other Packages

From shimmer

// Before (shimmer) — manual layout, no auto-detection
Shimmer.fromColors(
  baseColor: Colors.grey[300]!,
  highlightColor: Colors.grey[100]!,
  child: Column(
    children: [
      Container(width: 48, height: 48, color: Colors.white),
      Container(width: 200, height: 16, color: Colors.white),
      Container(width: 150, height: 14, color: Colors.white),
    ],
  ),
)

// After (auto_skeleton) — one line, auto-detected
AutoSkeleton(
  enabled: _isLoading,
  child: myActualWidget,  // your real widget, real data
)

From skeletonizer

// Before (skeletonizer) — requires fake/mock data
Skeletonizer(
  enabled: _isLoading,
  child: ListTile(
    title: Text('Fake Name Here'),        // fake data!
    subtitle: Text('Fake email@test.com'), // fake data!
    leading: CircleAvatar(
      backgroundImage: NetworkImage('https://fake-url.com/img'), // fake!
    ),
  ),
)

// After (auto_skeleton) — zero fake data
AutoSkeleton(
  enabled: _isLoading,
  child: myActualWidget,  // same widget, real data, no mocks
)

Key differences when migrating:

  • No fake data needed — use your actual widgets
  • Colors are theme-aware by default (remove manual color setup)
  • Use .withSkeleton(loading: true) for even cleaner syntax
  • Use AutoSkeletonBuilder to eliminate setState + _isLoading boilerplate entirely

Color Customization

Layer 1: Theme-aware (zero config)

Colors are automatically derived from your app's ColorScheme. Just works in light and dark mode.

Layer 2: Global override

Set colors once at the app root:

AutoSkeletonConfig(
  data: AutoSkeletonConfigData(
    baseColor: Colors.grey.shade300,
    highlightColor: Colors.grey.shade100,
  ),
  child: MaterialApp(...),
)

Layer 3: Per-widget override

Override on a specific widget:

AutoSkeleton(
  enabled: _isLoading,
  effect: ShimmerEffect(baseColor: Colors.blue.shade200),
  child: myWidget,
)

Effects

Shimmer (Default)

AutoSkeleton(
  enabled: _isLoading,
  effect: ShimmerEffect(
    baseColor: Colors.grey.shade300,
    highlightColor: Colors.grey.shade100,
    duration: Duration(milliseconds: 1500),
    direction: ShimmerDirection.ltr,
  ),
  child: MyWidget(),
)

Pulse

A gentle breathing/fade animation:

AutoSkeleton(
  enabled: _isLoading,
  effect: PulseEffect(
    color: Colors.blue.shade100,
    duration: Duration(milliseconds: 1200),
  ),
  child: MyWidget(),
)

Solid

Static placeholder with no animation:

AutoSkeleton(
  enabled: _isLoading,
  effect: SolidEffect(color: Colors.grey.shade200),
  child: MyWidget(),
)

Annotations

Control how specific widgets are skeletonized:

PlaceholderIgnore

Hide a widget completely during loading — useful for interactive elements (switches, buttons) that don't make sense in a skeleton:

AutoSkeleton(
  enabled: _isLoading,
  child: ListTile(
    title: Text('Notifications'),
    subtitle: Text('Push & email alerts'),
    trailing: PlaceholderIgnore(
      child: Switch(value: true, onChanged: (_) {}),
    ),
  ),
)

PlaceholderLeaf

Mark complex widgets (charts, maps, custom painters) as a single solid bone instead of traversing their children:

PlaceholderLeaf(
  borderRadius: BorderRadius.circular(12),
  child: MyComplexChartWidget(),
)

PlaceholderReplace

Replace a widget with a completely custom placeholder:

PlaceholderReplace(
  replacement: Container(
    width: 48, height: 48,
    decoration: BoxDecoration(
      color: Colors.grey.shade300,
      shape: BoxShape.circle,
    ),
  ),
  child: CircleAvatar(
    backgroundImage: NetworkImage(user.avatarUrl),
  ),
)

Pre-built Presets

Ready-to-use skeleton patterns for common UI layouts:

// List with avatar, title, subtitle
SkeletonPresets.listTile(itemCount: 5)

// E-commerce product card
SkeletonPresets.productCard(width: 160.0)

// Food delivery restaurant card
SkeletonPresets.foodCard()

// Horizontal scrollable card row
SkeletonPresets.horizontalCardRow(itemCount: 4)

Global Configuration

Set defaults for your entire app:

AutoSkeletonConfig(
  data: AutoSkeletonConfigData(
    baseColor: Color(0xFFE8E8E8),
    highlightColor: Color(0xFFF8F8F8),
    textBorderRadius: 4.0,
    containerBorderRadius: 8.0,
    enableSwitchAnimation: true,
  ),
  child: MaterialApp(...),
)

Sliver Support

For use inside CustomScrollView:

CustomScrollView(
  slivers: [
    SliverAutoSkeleton(
      enabled: _isLoading,
      child: MyListContent(),
    ),
  ],
)

How It Works

  1. Layout Phase: The child widget tree is built and laid out (invisibly on the first frame).
  2. Scan Phase: WidgetTreeScanner walks the element tree and identifies content widgets.
  3. Bone Generation: For each content widget, a BoneRect is created matching its position and size.
  4. Paint Phase: BonePainter renders the chosen effect over each bone rectangle.
  5. Transition: When loading completes, the skeleton fades out and real content fades in.

Supported Widgets

The scanner automatically detects and creates bones for:

  • Text & RichText (with multi-line support)
  • Image, Icon, CircleAvatar
  • ElevatedButton, TextButton, OutlinedButton, IconButton
  • FloatingActionButton
  • Switch, Checkbox, Radio, Chip

Containers (Card, Container, Padding, etc.) are traversed to find their content children.

Example

Check the example directory for a complete demo app showing all features.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

BSD 3-Clause License
Copyright (c) 2026, Vaibhav Tambe

Libraries

auto_skeleton
Auto-generate skeleton/shimmer loading screens from your actual widget tree.