Skip to content

SkeletonFooter

SkeletonFooter replaces the standard loading spinner in infinite-scroll scenarios with animated shimmer skeleton placeholders — the same pattern used by Facebook, LinkedIn, and YouTube while content loads. This technique shows users a preview of the incoming layout rather than a spinner, reducing perceived loading time.


Skeleton footers are typically used with enablePullUp: true for infinite loading:

SmartRefresher(
controller: _controller,
enablePullDown: true,
enablePullUp: true, // infinite scroll
header: const ClassicHeader(),
footer: SkeletonFooter(
child: Column(
children: List.generate(
3,
(_) => const SkeletonListTile(), // built-in skeleton card
),
),
),
onRefresh: _onRefresh,
onLoading: _onLoading,
child: ListView.builder(...),
)

PropertyTypeDefaultDescription
childWidgetRequiredThe skeleton layout that will shimmer while loading.
heightdouble60.0Minimum height reserved for the footer.
baseColorColorColors.grey.shade300Base color of the shimmer animation.
highlightColorColorColors.grey.shade100Bright highlight color that sweeps across.
directionShimmerDirection.ltrDirection the shimmer sweeps — left-to-right or right-to-left.
periodDuration1500msDuration of one full shimmer sweep.

smart_refresher ships a small library of pre-built skeleton cards you can compose:

Mimics a ListTile with a leading circle, and two text lines:

const SkeletonListTile(
hasLeading: true,
lineCount: 2,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
)

A circular or rectangular shimmer placeholder:

SkeletonAvatar(
style: SkeletonAvatarStyle(
shape: BoxShape.circle,
width: 56,
height: 56,
),
)

A single shimmer line with configurable width:

SkeletonLine(
style: SkeletonLineStyle(
width: 200,
height: 16,
borderRadius: BorderRadius.circular(8),
),
)

ValueSweep direction
ShimmerDirection.ltrLeft → Right (default)
ShimmerDirection.rtlRight → Left
ShimmerDirection.ttbTop → Bottom
ShimmerDirection.bttBottom → Top

SkeletonFooter(
baseColor: const Color(0xFFE0E0E0),
highlightColor: const Color(0xFFF5F5F5),
period: const Duration(milliseconds: 1200),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
for (int i = 0; i < 3; i++)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
const SkeletonAvatar(
style: SkeletonAvatarStyle(width: 48, height: 48, shape: BoxShape.circle),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SkeletonLine(style: SkeletonLineStyle(height: 14, width: double.infinity)),
const SizedBox(height: 8),
SkeletonLine(style: SkeletonLineStyle(height: 14, width: 160)),
],
),
),
],
),
),
],
),
),
)