Skip to content

Slivers

Understand the building blocks of scrollable content in Flutter.


What is it?

Slivers are the fundamental building blocks of scrollable areas in Flutter. They are pieces of a scrollable view that can be combined in various ways to create complex scrolling effects. Unlike regular widgets, slivers have special properties that allow them to be laid out lazily and efficiently in a scrolling viewport.


Why does it exist?

Slivers exist to:

  • Enable efficient lazy loading of scrollable content
  • Create complex scrollable layouts
  • Support custom scrolling effects
  • Combine different scrollable elements
  • Optimize performance for large lists
  • Enable scrollable app bars and headers
  • Create advanced scrolling experiences

Understanding Slivers

Slivers are scrollable building blocks.

// Basic sliver usage
class BasicSliverExample extends StatelessWidget {
  const BasicSliverExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        // Each sliver is a scrollable piece
        SliverToBoxAdapter(
          child: Container(
            height: 100,
            color: Colors.blue,
            child: const Center(child: Text('Regular Widget')),
          ),
        ),

        SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) => Container(
              height: 50,
              color: Colors.blue[100 * (index % 9 + 1)],
              child: Center(child: Text('Item $index')),
            ),
            childCount: 20,
          ),
        ),
      ],
    );
  }
}

// Sliver characteristics:
// 1. Scrollable - Part of a scroll view
// 2. Lazy - Can build children on demand
// 3. Flexible - Can be combined freely
// 4. Efficient - Optimized for scrolling
// 5. Customizable - Various types available

What's happening here? - Slivers are scrollable building blocks - Combine multiple slivers in CustomScrollView - Each sliver has specific behavior - Lazy loading for performance


Sliver Types

Common sliver types and their uses.

// Different sliver types
class SliverTypesExample extends StatelessWidget {
  const SliverTypesExample({super.key});

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        // 1. SliverAppBar - Scrollable app bar
        SliverAppBar(
          title: const Text('Sliver Types'),
          expandedHeight: 150,
          flexibleSpace: const FlexibleSpaceBar(
            background: ColoredBox(
              color: Colors.blue,
              child: Center(
                child: Text(
                  'App Bar',
                  style: TextStyle(color: Colors.white),
                ),
              ),
            ),
          ),
          pinned: true,
        ),

        // 2. SliverToBoxAdapter - Regular widget
        const SliverToBoxAdapter(
          child: Padding(
            padding: EdgeInsets.all(16),
            child: Text(
              'Section 1: List',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),

        // 3. SliverList - List of children
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) => ListTile(
              title: Text('List Item $index'),
              leading: CircleAvatar(child: Text('${index + 1}')),
            ),
            childCount: 10,
          ),
        ),

        // 4. SliverToBoxAdapter - Section header
        const SliverToBoxAdapter(
          child: Padding(
            padding: EdgeInsets.all(16),
            child: Text(
              'Section 2: Grid',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),

        // 5. SliverGrid - Grid of children
        SliverGrid(
          delegate: SliverChildBuilderDelegate(
            (context, index) => Container(
              color: Colors.green[100 * (index % 9 + 1)],
              child: Center(child: Text('Grid $index')),
            ),
            childCount: 12,
          ),
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            crossAxisSpacing: 4,
            mainAxisSpacing: 4,
          ),
        ),

        // 6. SliverPadding - Adds padding
        SliverPadding(
          padding: const EdgeInsets.all(16),
          sliver: SliverToBoxAdapter(
            child: Container(
              height: 50,
              color: Colors.orange,
              child: const Center(child: Text('Padded Widget')),
            ),
          ),
        ),

        // 7. SliverFillRemaining - Fill remaining space
        const SliverFillRemaining(
          child: Center(
            child: Text(
              'Fills remaining space',
              style: TextStyle(fontSize: 18),
            ),
          ),
        ),
      ],
    );
  }
}

What's happening here? - SliverAppBar: scrollable app bar - SliverToBoxAdapter: regular widget - SliverList: scrollable list - SliverGrid: scrollable grid - SliverPadding: padding around sliver - SliverFillRemaining: fills remaining space


SliverList and Delegate

SliverList uses delegates for efficient building.

// SliverList delegates
class SliverListDelegates extends StatelessWidget {
  const SliverListDelegates({super.key});

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        const SliverAppBar(
          title: Text('SliverList Delegates'),
          pinned: true,
        ),

        // 1. SliverChildListDelegate - Fixed children
        SliverList(
          delegate: SliverChildListDelegate(
            [
              for (int i = 0; i < 5; i++)
                Container(
                  height: 50,
                  color: Colors.blue[100 * (i % 9 + 1)],
                  margin: const EdgeInsets.all(4),
                  child: Center(child: Text('List $i')),
                ),
            ],
          ),
        ),

        // 2. SliverChildBuilderDelegate - Lazy building
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) => Container(
              height: 50,
              color: Colors.green[100 * (index % 9 + 1)],
              margin: const EdgeInsets.all(4),
              child: Center(child: Text('Builder $index')),
            ),
            childCount: 50,
            // Performance optimizations
            addRepaintBoundaries: true,
            addSemanticIndexes: true,
          ),
        ),

        // 3. SliverFixedExtentList - Fixed height
        SliverFixedExtentList(
          delegate: SliverChildBuilderDelegate(
            (context, index) => Container(
              color: Colors.orange[100 * (index % 9 + 1)],
              child: Center(child: Text('Fixed $index')),
            ),
            childCount: 10,
          ),
          itemExtent: 60, // All items are 60px tall
        ),
      ],
    );
  }
}

// Delegate comparison:
// SliverChildListDelegate: All children built at once
// SliverChildBuilderDelegate: Children built lazily
// SliverFixedExtentList: Optimized for fixed-height items
// Use builder delegate for large lists

What's happening here? - SliverChildListDelegate: all children built - SliverChildBuilderDelegate: lazy building - SliverFixedExtentList: fixed height items - Choose delegate based on needs


SliverGrid and Delegate

SliverGrid provides grid layouts in scroll views.

// SliverGrid delegates
class SliverGridDelegates extends StatelessWidget {
  const SliverGridDelegates({super.key});

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        const SliverAppBar(
          title: Text('SliverGrid Delegates'),
          pinned: true,
        ),

        // 1. Fixed cross axis count
        SliverGrid(
          delegate: SliverChildBuilderDelegate(
            (context, index) => Container(
              color: Colors.blue[100 * (index % 9 + 1)],
              child: Center(child: Text('$index')),
            ),
            childCount: 20,
          ),
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            crossAxisSpacing: 4,
            mainAxisSpacing: 4,
          ),
        ),

        // 2. Max cross axis extent
        SliverGrid(
          delegate: SliverChildBuilderDelegate(
            (context, index) => Container(
              color: Colors.green[100 * (index % 9 + 1)],
              child: Center(child: Text('$index')),
            ),
            childCount: 20,
          ),
          gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 100,
            crossAxisSpacing: 4,
            mainAxisSpacing: 4,
          ),
        ),

        // 3. Custom aspect ratio
        SliverGrid(
          delegate: SliverChildBuilderDelegate(
            (context, index) => Container(
              color: Colors.orange[100 * (index % 9 + 1)],
              child: Center(child: Text('$index')),
            ),
            childCount: 20,
          ),
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            crossAxisSpacing: 4,
            mainAxisSpacing: 4,
            childAspectRatio: 1.5, // Wider than tall
          ),
        ),
      ],
    );
  }
}

What's happening here? - Fixed cross axis count: fixed number of columns - Max cross axis extent: adaptive columns - Custom aspect ratio: control item proportions - Efficient lazy loading


Real-World Examples

Common patterns using slivers.

// 1. Weather app
class WeatherApp extends StatelessWidget {
  const WeatherApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          // Weather header
          SliverAppBar(
            expandedHeight: 200,
            pinned: true,
            flexibleSpace: FlexibleSpaceBar(
              background: Container(
                decoration: const BoxDecoration(
                  gradient: LinearGradient(
                    begin: Alignment.topLeft,
                    end: Alignment.bottomRight,
                    colors: [Colors.blue, Colors.purple],
                  ),
                ),
                child: const Padding(
                  padding: EdgeInsets.all(16),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(
                        '72°F',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 64,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      Text(
                        'Sunny',
                        style: TextStyle(
                          color: Colors.white70,
                          fontSize: 24,
                        ),
                      ),
                      Text(
                        'New York, NY',
                        style: TextStyle(
                          color: Colors.white70,
                          fontSize: 16,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),

          // Hourly forecast
          SliverToBoxAdapter(
            child: Container(
              padding: const EdgeInsets.all(16),
              child: const Text(
                'Hourly Forecast',
                style: TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),

          SliverToBoxAdapter(
            child: SizedBox(
              height: 100,
              child: ListView.builder(
                scrollDirection: Axis.horizontal,
                itemCount: 24,
                itemBuilder: (context, index) {
                  return Container(
                    width: 60,
                    margin: const EdgeInsets.symmetric(horizontal: 4),
                    child: Column(
                      children: [
                        Text('${index}:00'),
                        const SizedBox(height: 4),
                        Icon(
                          index % 2 == 0 ? Icons.wb_sunny : Icons.cloud,
                          color: Colors.orange,
                        ),
                        const SizedBox(height: 4),
                        Text('${65 + index}°F'),
                      ],
                    ),
                  );
                },
              ),
            ),
          ),

          // Daily forecast
          const SliverToBoxAdapter(
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Text(
                '7-Day Forecast',
                style: TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),

          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
                return ListTile(
                  leading: Text(days[index % days.length]),
                  title: Row(
                    children: [
                      Icon(
                        index % 2 == 0 ? Icons.wb_sunny : Icons.cloud,
                        color: Colors.orange,
                      ),
                      const SizedBox(width: 8),
                      Text('${65 + index}°F'),
                    ],
                  ),
                  trailing: Text('${70 + index}°F'),
                );
              },
              childCount: 7,
            ),
          ),
        ],
      ),
    );
  }
}

// 2. E-commerce product page
class ProductPage extends StatelessWidget {
  const ProductPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          // Product image
          SliverAppBar(
            expandedHeight: 300,
            pinned: true,
            flexibleSpace: FlexibleSpaceBar(
              background: Container(
                color: Colors.blue[100],
                child: const Center(
                  child: Icon(
                    Icons.image,
                    size: 100,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
            actions: [
              IconButton(
                icon: const Icon(Icons.favorite_border),
                onPressed: () {},
              ),
              IconButton(
                icon: const Icon(Icons.share),
                onPressed: () {},
              ),
            ],
          ),

          // Product info
          SliverToBoxAdapter(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Product Name',
                    style: TextStyle(
                      fontSize: 24,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: [
                      const Text(
                        '\$99.99',
                        style: TextStyle(
                          fontSize: 20,
                          color: Colors.blue,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(width: 8),
                      Text(
                        '\$149.99',
                        style: TextStyle(
                          fontSize: 16,
                          color: Colors.grey[600],
                          decoration: TextDecoration.lineThrough,
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 16),
                  const Text(
                    'Product description goes here. '
                    'This describes the product in detail.',
                    style: TextStyle(
                      fontSize: 16,
                      height: 1.5,
                    ),
                  ),
                ],
              ),
            ),
          ),

          // Related products
          const SliverToBoxAdapter(
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Text(
                'Related Products',
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),

          SliverPadding(
            padding: const EdgeInsets.all(8),
            sliver: SliverGrid(
              delegate: SliverChildBuilderDelegate(
                (context, index) => Card(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Expanded(
                        child: Container(
                          width: double.infinity,
                          color: Colors.blue[100 * (index % 9 + 1)],
                          child: const Center(
                            child: Icon(
                              Icons.shopping_bag,
                              color: Colors.white,
                              size: 30,
                            ),
                          ),
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.all(8),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              'Product ${index + 1}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                            ),
                            Text(
                              '\$${19.99 + index * 10}',
                              style: const TextStyle(
                                color: Colors.blue,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
                childCount: 10,
              ),
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 8,
                mainAxisSpacing: 8,
                childAspectRatio: 0.7,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

What's happening here? - Weather app with header and forecasts - E-commerce product page with images - Mixed sliver types for complex layouts - Real-world scrolling patterns


Best Practices

Use Lazy Delegates

// Good - Lazy loading
SliverList(
  delegate: SliverChildBuilderDelegate(
    (context, index) => Text('Item $index'),
    childCount: 1000,
  ),
)

// Bad - All children built at once
SliverList(
  delegate: SliverChildListDelegate(
    List.generate(1000, (index) => Text('Item $index')),
  ),
)

Use Appropriate Sliver Types

// Good - Appropriate sliver for the job
SliverAppBar(...),  // For app bars
SliverList(...),    // For lists
SliverGrid(...),    // For grids
SliverToBoxAdapter(...), // For regular widgets

Optimize Performance

// Good - Performance optimizations
SliverList(
  delegate: SliverChildBuilderDelegate(
    (context, index) => Container(...),
    childCount: 1000,
    addRepaintBoundaries: true,
    addSemanticIndexes: true,
  ),
)

Common Mistakes

Not Using Builder Delegate

Wrong:

// Inefficient for large data
SliverList(
  delegate: SliverChildListDelegate(
    List.generate(1000, (index) => Container()),
  ),
)

Correct:

// Efficient lazy loading
SliverList(
  delegate: SliverChildBuilderDelegate(
    (context, index) => Container(),
    childCount: 1000,
  ),
)

Mixing Sliver and Non-Sliver

Wrong:

// Error - cannot mix directly
CustomScrollView(
  slivers: [
    Container(), // Error
    ListView(), // Error
  ],
)

Correct:

// Wrap in SliverToBoxAdapter
CustomScrollView(
  slivers: [
    SliverToBoxAdapter(child: Container()),
    SliverToBoxAdapter(child: ListView()),
  ],
)


Summary

Slivers are the building blocks of scrollable views in Flutter. They enable efficient lazy loading, complex scrolling effects, and mixed content types. Use different sliver types for different purposes: SliverList for lists, SliverGrid for grids, SliverAppBar for headers, and SliverToBoxAdapter for regular widgets.


Next Steps


Did You Know?

  • Slivers enable lazy loading
  • SliverList and SliverGrid support large datasets
  • SliverAppBar creates scrollable app bars
  • SliverToBoxAdapter wraps regular widgets
  • SliverPadding adds padding to slivers
  • SliverFillRemaining fills remaining space
  • Slivers can be combined freely
  • Slivers are optimized for performance