Skeleton

In ProgressDark modeRTL

A loading placeholder with shimmer animation.


7:21

Installation

Copy the component into your project using the CLI:

$ronakcn add skeleton

Usage

Import and use the component in your Flutter widget tree:

lib/pages/example.dart
import 'package:flutter/material.dart';
import '../components/skeleton/skeleton.dart';

class ExamplePage extends StatelessWidget {
  const ExamplePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('RcnSkeleton Example')),
      body: Center(
        child: RcnSkeleton(
          // Configure props here
        ),
      ),
    );
  }
}

Variants

Skeleton ships with the following variants:

default

A rectangular skeleton block.

7:21

circle

A circular skeleton for avatars.

7:21

card

A full card skeleton layout.

7:21

Props

PropTypeDefaultDescription
widthdouble?nullWidth of the skeleton.
heightdouble?nullHeight of the skeleton.
borderRadiusdouble8Corner radius.
animatebooltrueEnables shimmer animation.

Source files

lib/components/ui/skeleton.dart
import 'package:flutter/material.dart';

class RcnSkeleton extends StatefulWidget {
  const RcnSkeleton({super.key, this.width, this.height, this.borderRadius = 8, this.animate = true});
  final double? width;
  final double? height;
  final double borderRadius;
  final bool animate;

  @override
  State<RcnSkeleton> createState() => _RcnSkeletonState();
}

class _RcnSkeletonState extends State<RcnSkeleton> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _anim;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 1200))..repeat(reverse: true);
    _anim = Tween<double>(begin: 0.4, end: 0.9).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
  }

  @override
  void dispose() { _controller.dispose(); super.dispose(); }

  @override
  Widget build(BuildContext context) {
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final base = isDark ? const Color(0xFF27272A) : const Color(0xFFE4E4E7);
    return AnimatedBuilder(
      animation: _anim,
      builder: (_, __) => Opacity(
        opacity: widget.animate ? _anim.value : 0.6,
        child: Container(
          width: widget.width,
          height: widget.height ?? 16,
          decoration: BoxDecoration(color: base, borderRadius: BorderRadius.circular(widget.borderRadius)),
        ),
      ),
    );
  }
}
Version 0.1.0 · display category