Flutter Pro Design

Small details that build taste in Flutter.

curated by Kamran BekirovKamran Bekirov

Make lists feel scrollable

Long lists aren't obviously scrollable. Items just sit there and users don't know there's more.

Fading the edges signals that it scrolls. ShaderMask does this with no extra package:

ShaderMask(
  shaderCallback: (Rect bounds) {
    return const LinearGradient(
      begin: .topCenter,
      end: .bottomCenter,
      colors: [
        Colors.transparent,
        Colors.white,
        Colors.white,
        Colors.transparent,
      ],
      stops: [0.0, 0.12, 0.88, 1.0],
    ).createShader(bounds);
  },
  blendMode: .dstIn,
  child: // your scrollable widget
)

Adjust stops to control the fade size. [0.0, 0.1, 0.9, 1.0] for subtle, [0.0, 0.3, 0.7, 1.0] for aggressive.

An example from expen.app, faded edges in a category picker:

Fading only the bottom edge usually works best. If they've scrolled down, they already know there's a top, they only need a hint that there's more below:

colors: [
  Colors.white,
  Colors.white,
  Colors.white,
  Colors.transparent,
],
stops: [0.0, 0.0, 0.88, 1.0],

Or top only:

colors: [
  Colors.transparent,
  Colors.white,
  Colors.white,
  Colors.white,
],
stops: [0.0, 0.12, 1.0, 1.0],

For horizontal lists, swap the gradient direction:

begin: .centerLeft,
end: .centerRight,

Or as a reusable widget:

class Edgy extends StatelessWidget {
  final Widget child;
  final Axis axis;
  final bool fadeStart;
  final bool fadeEnd;
  
  const Edgy({
    super.key,
    required this.child,
    this.axis = .vertical,
    this.fadeStart = true,
    this.fadeEnd = true,
  });
  
  @override
  Widget build(BuildContext context) {
    final bool isVertical = axis == .vertical;
  
    return ShaderMask(
      shaderCallback: (Rect bounds) {
        return LinearGradient(
          begin: isVertical ? .topCenter : .centerLeft,
          end: isVertical ? .bottomCenter : .centerRight,
          colors: <Color>[
            fadeStart ? const Color(0x00FFFFFF) : const Color(0xFFFFFFFF),
            const Color(0xFFFFFFFF),
            const Color(0xFFFFFFFF),
            fadeEnd ? const Color(0x00FFFFFF) : const Color(0xFFFFFFFF),
          ],
          stops: <double>[0.0, 0.12, 0.88, 1.0],
        ).createShader(bounds);
      },
      blendMode: .dstIn,
      child: child,
    );
  }
}

The mask is alpha-only, so this works the same in light and dark mode.