image

06 Jan 2026

9K

35K

Creating a Loading Skeleton Widget for ListView in Flutter

In modern application development, user experience (UX) is paramount. When dealing with asynchronous data loading, simply displaying a spinning progress indicator can sometimes lead to a feeling of sluggishness or uncertainty for the user. A more engaging and perceptually faster approach is to use a "loading skeleton" — a minimalistic placeholder of the UI that will eventually be displayed, gradually filling in with actual content as data arrives.

This article will guide you through creating a professional and animated loading skeleton widget specifically designed for a ListView in Flutter, enhancing your application's perceived performance and user satisfaction.

Why Use a Loading Skeleton?

  • Improved Perceived Performance: Skeletons give users a sense of progress and anticipation, making the loading time feel shorter than it actually is.
  • Reduced Cognitive Load: By showing the layout of the upcoming content, users can anticipate the structure of the information, rather than staring at an abstract spinner.
  • Seamless Transition: The transition from a skeleton to actual content can be much smoother and less jarring than a sudden appearance of content after a spinner disappears.

Core Components of Our Skeleton Widget

Our loading skeleton will consist of a few key elements:

  1. Placeholder Shapes: Simple rectangles or circles that mimic the size and position of actual text, images, or other UI elements.
  2. Neutral Color Palette: Typically shades of gray to indicate a temporary, uninitialized state.
  3. Animation (Shimmer Effect): A subtle, continuous animation that draws the user's eye and indicates an active loading state without being distracting. For this, we'll leverage the popular shimmer package.

Step 1: Create an Individual Skeleton Item

First, let's design a single skeleton item that represents how each item in your ListView will look while loading. For this example, we'll create a layout with a circular avatar placeholder and two lines of text, mimicking a typical list item.


import 'package:flutter/material.dart';

class SkeletonItem extends StatelessWidget {
  const SkeletonItem({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Placeholder for an avatar/image
          Container(
            width: 48.0,
            height: 48.0,
            decoration: BoxDecoration(
              color: Colors.grey[300], // Light grey background
              shape: BoxShape.circle,
            ),
          ),
          const SizedBox(width: 16.0), // Spacer
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // Placeholder for the main title text
                Container(
                  width: double.infinity, // Takes full available width
                  height: 12.0,
                  decoration: BoxDecoration(
                    color: Colors.grey[300],
                    borderRadius: BorderRadius.circular(4.0),
                  ),
                ),
                const SizedBox(height: 8.0), // Spacer
                // Placeholder for the subtitle text
                Container(
                  width: MediaQuery.of(context).size.width * 0.5, // Simulate a shorter second line
                  height: 12.0,
                  decoration: BoxDecoration(
                    color: Colors.grey[300],
                    borderRadius: BorderRadius.circular(4.0),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Step 2: Implement the Animated Skeleton ListView

Next, we'll create a ListView that populates with multiple instances of our SkeletonItem. To add the shimmering effect, we'll use the shimmer package. First, add it to your pubspec.yaml file:


dependencies:
  flutter:
    sdk: flutter
  shimmer: ^3.0.0 # Add this line

Then, create the SkeletonListView widget:


import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart'; // Import the shimmer package

// Assuming SkeletonItem is defined in the same file or imported from 'skeleton_item.dart'

class SkeletonListView extends StatelessWidget {
  final int itemCount;

  const SkeletonListView({Key? key, this.itemCount = 5}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Shimmer.fromColors(
      baseColor: Colors.grey[300]!, // Base color of the skeleton
      highlightColor: Colors.grey[100]!, // Highlight color for the shimmer effect
      child: ListView.builder(
        itemCount: itemCount,
        itemBuilder: (context, index) {
          return const SkeletonItem(); // Display our individual skeleton item
        },
      ),
    );
  }
}

Step 3: Integrate with Your Data Loading Logic

Finally, we integrate the SkeletonListView into our main UI. We'll use a simple boolean flag (_isLoading) to toggle between displaying the skeleton and the actual data. For a real-world scenario, you might fetch data from an API or a local database.


import 'package:flutter/material.dart';
// Assuming SkeletonItem and SkeletonListView are in separate files
// import 'skeleton_item.dart';
// import 'skeleton_list_view.dart';

class MyDataScreen extends StatefulWidget {
  const MyDataScreen({Key? key}) : super(key: key);

  @override
  _MyDataScreenState createState() => _MyDataScreenState();
}

class _MyDataScreenState extends State {
  bool _isLoading = true; // State to control loading display
  List _data = []; // Placeholder for actual data

  @override
  void initState() {
    super.initState();
    _loadData(); // Trigger data loading when the screen initializes
  }

  // Simulate an asynchronous data loading process
  Future _loadData() async {
    // Simulate a network delay
    await Future.delayed(const Duration(seconds: 3)); 
    setState(() {
      _data = List.generate(10, (index) => 'Actual Item ${index + 1}'); // Populate with dummy data
      _isLoading = false; // Set loading to false once data is ready
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Data List with Skeleton'),
      ),
      body: _isLoading
          ? const SkeletonListView(itemCount: 7) // Display skeleton while loading
          : ListView.builder(
              itemCount: _data.length,
              itemBuilder: (context, index) {
                // Display actual data items
                return Card(
                  margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
                  elevation: 2,
                  child: ListTile(
                    leading: CircleAvatar(
                      backgroundColor: Colors.blueAccent,
                      child: Text('${index + 1}', style: const TextStyle(color: Colors.white)),
                    ),
                    title: Text(_data[index]),
                    subtitle: Text('This is the subtitle for ${_data[index]}'),
                    trailing: const Icon(Icons.arrow_forward_ios, size: 16.0),
                  ),
                );
              },
            ),
    );
  }
}

// Example main function to run the app
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Skeleton Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MyDataScreen(),
    );
  }
}

Conclusion

By implementing a loading skeleton, you can significantly improve the user experience of your Flutter applications, especially when dealing with data fetching. This approach makes loading times feel shorter, provides visual context, and creates a more polished and professional interface. The flexibility of Flutter allows you to customize your skeleton widgets to match the exact layout of your content, ensuring a seamless and visually appealing transition from loading state to loaded data.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is