Silky Scroll

SilkyScroll is a Flutter package that provides natural and smooth scroll animations for mouse wheel, trackpad, and touch inputs.

✨ SilkyScroll vs ↔ Default comparison

✨ SilkyScroll   ↔   Default


Features

  • Smooth Scroll Animation — Interpolates mouse wheel input for seamless scrolling
  • Smart Input Detection — Automatically detects mouse, trackpad, and touch to apply appropriate physics
  • Edge Locking & Nested Forwarding — Locks scrolling at boundaries and automatically forwards touch/trackpad deltas to the parent scroll view
  • Stretch Effect — Supports Android stretch and iOS bounce overscroll effects
  • Horizontal Scroll — Supports both horizontal and vertical directions
  • Preset Widgets — Drop-in SilkyListView, SilkyGridView, SilkyCustomScrollView, SilkySingleChildScrollView with zero boilerplate
  • All Platforms — Android, iOS, Web, Windows, macOS, Linux

Getting Started

flutter pub add silky_scroll

Basic Usage

Simply wrap your scrollable widget with SilkyScroll.

SilkyScroll(
  builder: (context, controller, physics, pointerDeviceKind) => ListView(
    controller: controller,
    physics: physics,
    children: [...],
  ),
)

Preset Widgets

Preset widgets are drop-in replacements for Flutter's scrollable widgets. They eliminate the builder boilerplate by automatically wiring controller and physics internally.

Preset Widget Wraps
SilkyListView ListView
SilkyListView.builder ListView.builder
SilkyListView.separated ListView.separated
SilkyGridView GridView
SilkyGridView.builder GridView.builder
SilkyGridView.count GridView.count
SilkyGridView.extent GridView.extent
SilkyCustomScrollView CustomScrollView
SilkySingleChildScrollView SingleChildScrollView

Before (builder pattern)

SilkyScroll(
  builder: (context, controller, physics, _) => ListView.builder(
    controller: controller,
    physics: physics,
    itemCount: 100,
    itemBuilder: (context, i) => ListTile(title: Text('Item $i')),
  ),
)

After (preset)

SilkyListView.builder(
  itemCount: 100,
  itemBuilder: (context, i) => ListTile(title: Text('Item $i')),
)

SilkyCustomScrollView

SilkyCustomScrollView(
  slivers: [
    SliverAppBar(title: Text('Header')),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (context, i) => ListTile(title: Text('Item $i')),
        childCount: 50,
      ),
    ),
  ],
)

SilkySingleChildScrollView

SilkySingleChildScrollView(
  padding: EdgeInsets.all(16),
  child: Column(children: [...]),
)

Customizing SilkyScroll behavior

Preset widgets accept all SilkyScroll parameters directly, or via a shared SilkyScrollConfig:

// Individual parameters
SilkyListView.builder(
  scrollSpeed: 1.5,
  animationCurve: Curves.easeOutCubic,
  itemCount: 100,
  itemBuilder: (context, i) => Text('$i'),
)

// Shared config (overrides individual parameters when provided)
final config = SilkyScrollConfig(scrollSpeed: 1.5);

SilkyListView.builder(
  silkyConfig: config,
  itemCount: 100,
  itemBuilder: (context, i) => Text('$i'),
)

Detecting pointer device kind

SilkyListView.builder(
  itemCount: 100,
  itemBuilder: (context, i) => Text('$i'),
  onPointerDeviceKindChanged: (kind) {
    // PointerDeviceKind.mouse, .trackpad, .touch
  },
)

Custom Usage

You can connect an external ScrollController and fine-tune animation properties.

class _ScrollExampleState extends State<ScrollExample> {
  late final ScrollController _scrollController;

  @override
  void initState() {
    super.initState();
    _scrollController = ScrollController();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SilkyScroll(
        controller: _scrollController,
        physics: const BouncingScrollPhysics(),
        silkyScrollDuration: const Duration(milliseconds: 2000),
        scrollSpeed: 1.5,
        animationCurve: Curves.easeOutQuart,
        direction: Axis.vertical,
        builder: (context, controller, physics, pointerDeviceKind) {
          return ListView.builder(
            controller: controller,
            physics: physics,
            itemCount: 50,
            itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
          );
        },
      ),
    );
  }
}

Using SilkyScrollConfig

You can share the same configuration across multiple SilkyScroll widgets.

const config = SilkyScrollConfig(
  silkyScrollDuration: Duration(milliseconds: 1000),
  scrollSpeed: 1.5,
  animationCurve: Curves.easeOutCubic,
  enableStretchEffect: true,
);

SilkyScroll.fromConfig(
  config: config,
  builder: (context, controller, physics, pointerDeviceKind) => ListView(...),
)

Web: Overscroll Behavior Control

On web, SilkyScroll automatically sets overscroll-behavior-x: none while the widget is mounted (controlled by the blockWebOverscrollBehaviorX parameter, default true). This blocks browser back/forward swipe gestures.

When all SilkyScroll widgets with blocking enabled are disposed, the CSS is automatically restored to auto.

Per-widget control

// Disable browser swipe blocking for this widget
SilkyScroll(
  blockWebOverscrollBehaviorX: false,
  builder: (context, controller, physics, pointerDeviceKind) => ListView(...),
)

Global control

You can also set a global block flag that persists independently of widgets:

import 'package:silky_scroll/silky_scroll.dart';

// Force block browser swipe gestures globally
SilkyScrollGlobalManager.instance
    .setBlockOverscrollBehaviorX(true);

// Remove the global block (widgets may still block individually)
SilkyScrollGlobalManager.instance
    .setBlockOverscrollBehaviorX(false);

The actual CSS is set to none when any widget requests blocking or the global flag is true. It reverts to auto only when both are inactive.

On non-web platforms this is a no-op.


Parameters

Parameter Type Default Description
controller ScrollController? null External scroll controller
silkyScrollDuration Duration 850ms Scroll animation duration
scrollSpeed double 1 Scroll speed multiplier
animationCurve Curve Curves.easeOutQuart Animation curve
direction Axis vertical Scroll direction
physics ScrollPhysics ScrollPhysics() Scroll physics
edgeLockingDelay Duration 650ms Lock delay after reaching edge
overScrollingLockingDelay Duration 700ms Overscroll lock delay
enableStretchEffect bool true Overscroll stretch effect
edgeForwardingMode EdgeForwardingMode EdgeForwardingMode.sameAxisOnly Edge delta forwarding to ancestor
decayLogFactor double 12 Smooth-scroll convergence speed
blockWebOverscrollBehaviorX bool true Block browser swipe on web
setManualPointerDeviceKind Function? null Manual pointer device override
onScroll Function(double)? null Scroll event callback
onEdgeOverScroll Function(double)? null Edge overscroll callback
debugMode bool false Debug logging

License

MIT

Libraries

silky_scroll
Silky Scroll — smooth scrolling for Flutter.