draft
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart
index 0b3d784..ae33a76 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart
@@ -923,6 +923,80 @@
 
 typedef _PointerEventListener = dynamic Function(DomPointerEvent event);
 
+/// Tracks touch gesture state to determine if it's a scroll gesture.
+class _TouchGestureTracker {
+  double? _startX;
+  double? _startY;
+  double? _currentX;
+  double? _currentY;
+  num? _startTime;
+  bool _isScrollGesture = false;
+  
+  // Thresholds for detecting scroll gestures
+  static const double _scrollThreshold = 10.0; // pixels
+  static const double _scrollAngleThreshold = 0.5; // radians (~30 degrees)
+  
+  void onPointerDown(DomPointerEvent event) {
+    _startX = event.clientX.toDouble();
+    _startY = event.clientY.toDouble();
+    _currentX = _startX;
+    _currentY = _startY;
+    _startTime = event.timeStamp;
+    _isScrollGesture = false;
+    
+    if (_debugLogPointerEvents) {
+      print('[TOUCH_GESTURE] Down at ($_startX, $_startY)');
+    }
+  }
+  
+  void onPointerMove(DomPointerEvent event) {
+    _currentX = event.clientX.toDouble();
+    _currentY = event.clientY.toDouble();
+    
+    if (_startX != null && _startY != null) {
+      final double deltaX = (_currentX! - _startX!).abs();
+      final double deltaY = (_currentY! - _startY!).abs();
+      
+      // Check if movement exceeds threshold
+      if (deltaY > _scrollThreshold || deltaX > _scrollThreshold) {
+        // Check if it's primarily vertical movement
+        if (deltaY > deltaX * 1.5) {
+          _isScrollGesture = true;
+          if (_debugLogPointerEvents) {
+            print('[TOUCH_GESTURE] Detected vertical scroll gesture: deltaX=$deltaX, deltaY=$deltaY');
+          }
+        } else if (deltaX > deltaY * 1.5) {
+          // Horizontal scroll - also consider it a scroll gesture
+          _isScrollGesture = true;
+          if (_debugLogPointerEvents) {
+            print('[TOUCH_GESTURE] Detected horizontal scroll gesture: deltaX=$deltaX, deltaY=$deltaY');
+          }
+        }
+      }
+    }
+  }
+  
+  void reset() {
+    _startX = null;
+    _startY = null;
+    _currentX = null;
+    _currentY = null;
+    _startTime = null;
+    _isScrollGesture = false;
+  }
+  
+  bool get isScrollGesture => _isScrollGesture;
+  
+  bool get hasMovedSignificantly {
+    if (_startX == null || _startY == null || _currentX == null || _currentY == null) {
+      return false;
+    }
+    final double deltaX = (_currentX! - _startX!).abs();
+    final double deltaY = (_currentY! - _startY!).abs();
+    return deltaX > _scrollThreshold || deltaY > _scrollThreshold;
+  }
+}
+
 /// Adapter class to be used with browsers that support native pointer events.
 ///
 /// For the difference between MouseEvent and PointerEvent, see _MouseAdapter.
@@ -930,6 +1004,7 @@
   _PointerAdapter(super.owner);
 
   final Map<int, _ButtonSanitizer> _sanitizers = <int, _ButtonSanitizer>{};
+  final Map<int, _TouchGestureTracker> _touchTrackers = <int, _TouchGestureTracker>{};
 
   @visibleForTesting
   Iterable<int> debugTrackedDevices() => _sanitizers.keys;
@@ -997,20 +1072,43 @@
       _convertEventsToPointerData(data: pointerData, event: event, details: down);
       _callback(event, pointerData);
 
+      // Track touch gestures for scroll detection
+      final bool isTouchEvent = event.pointerType == 'touch';
+      if (isTouchEvent) {
+        final _TouchGestureTracker tracker = _touchTrackers.putIfAbsent(
+          device,
+          () => _TouchGestureTracker(),
+        );
+        tracker.onPointerDown(event);
+        
+        if (_debugLogPointerEvents) {
+          print('[TOUCH_PASSTHROUGH] Touch pointerdown - NOT calling preventDefault() to allow browser scroll');
+        }
+      }
+
       if (event.target == _viewTarget) {
         // Ensure smooth focus transitions between text fields within the Flutter view.
         // Without preventing the default and this delay, the engine may not have fully
         // rendered the next input element, leading to the focus incorrectly returning to
         // the main Flutter view instead.
         // A zero-length timer is sufficient in all tested browsers to achieve this.
-        event.preventDefault();
-        Timer(Duration.zero, () {
-          EnginePlatformDispatcher.instance.requestViewFocusChange(
-            viewId: _view.viewId,
-            state: ui.ViewFocusState.focused,
-            direction: ui.ViewFocusDirection.undefined,
-          );
-        });
+        //
+        // IMPORTANT: For touch events, we DON'T call preventDefault() to allow browser
+        // scrolling to work. This is part of the browser-driven scrolling solution.
+        // The trade-off is that focus transitions on touch might be slightly less smooth,
+        // but browser scrolling is more important for mobile UX.
+        if (!isTouchEvent) {
+          event.preventDefault();
+          Timer(Duration.zero, () {
+            EnginePlatformDispatcher.instance.requestViewFocusChange(
+              viewId: _view.viewId,
+              state: ui.ViewFocusState.focused,
+              direction: ui.ViewFocusDirection.undefined,
+            );
+          });
+        } else if (_debugLogPointerEvents) {
+          print('[TOUCH_PASSTHROUGH] Skipping preventDefault() for touch event to enable browser scroll');
+        }
       }
     });
 
@@ -1032,6 +1130,18 @@
       final _ButtonSanitizer sanitizer = _ensureSanitizer(device);
       final List<ui.PointerData> pointerData = <ui.PointerData>[];
       final List<DomPointerEvent> expandedEvents = _expandEvents(moveEvent);
+      
+      // Track touch gesture movement
+      if (moveEvent.pointerType == 'touch') {
+        final _TouchGestureTracker? tracker = _touchTrackers[device];
+        if (tracker != null) {
+          tracker.onPointerMove(moveEvent);
+          if (_debugLogPointerEvents && tracker.isScrollGesture) {
+            print('[TOUCH_PASSTHROUGH] Touch scroll gesture detected in pointermove');
+          }
+        }
+      }
+      
       for (final DomPointerEvent event in expandedEvents) {
         final _SanitizedDetails? up = sanitizer.sanitizeMissingRightClickUp(
           buttons: event.buttons!.toInt(),
@@ -1084,6 +1194,14 @@
           _callback(event, pointerData);
         }
       }
+      
+      // Clean up touch tracker
+      if (event.pointerType == 'touch') {
+        _touchTrackers.remove(device);
+        if (_debugLogPointerEvents) {
+          print('[TOUCH_PASSTHROUGH] Touch pointerup - cleaned up tracker for device $device');
+        }
+      }
     });
 
     // TODO(dit): Synthesize a "cancel" event when 'pointerup' happens outside of the flutterViewElement, https://github.com/flutter/flutter/issues/116561
@@ -1099,6 +1217,14 @@
         _convertEventsToPointerData(data: pointerData, event: event, details: details);
         _callback(event, pointerData);
       }
+      
+      // Clean up touch tracker
+      if (event.pointerType == 'touch') {
+        _touchTrackers.remove(device);
+        if (_debugLogPointerEvents) {
+          print('[TOUCH_PASSTHROUGH] Touch pointercancel - cleaned up tracker for device $device');
+        }
+      }
     }, checkModifiers: false);
 
     _addWheelEventListener((DomEvent event) {
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/full_page_embedding_strategy.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/full_page_embedding_strategy.dart
index 6733dec..3ec9098 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/full_page_embedding_strategy.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/full_page_embedding_strategy.dart
@@ -60,10 +60,11 @@
     setElementStyle(hostElement, 'user-select', 'none');
     setElementStyle(hostElement, '-webkit-user-select', 'none');
 
-    // This is required to prevent the browser from doing any native touch
-    // handling. If this is not done, the browser doesn't report 'pointermove'
-    // events properly.
-    setElementStyle(hostElement, 'touch-action', 'none');
+    // Allow touch scrolling (pan gestures) while preventing other touch actions
+    // like double-tap zoom. This enables browser-driven scrolling for better
+    // mobile UX while still allowing Flutter to handle other gestures.
+    // Changed from 'none' to 'pan-x pan-y' to fix Issue #157435.
+    setElementStyle(hostElement, 'touch-action', 'pan-x pan-y');
   }
 
   // Sets a meta viewport tag appropriate for Flutter Web in full screen.
diff --git a/packages/flutter/lib/src/widgets/browser_scroll_zone.dart b/packages/flutter/lib/src/widgets/browser_scroll_zone.dart
new file mode 100644
index 0000000..cf3e2ce
--- /dev/null
+++ b/packages/flutter/lib/src/widgets/browser_scroll_zone.dart
@@ -0,0 +1,281 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Web-specific widget for nested scrolling with boundary detection.
+///
+/// This file provides [BrowserScrollZone], which enables smooth nested scrolling
+/// on Flutter web by detecting scroll boundaries and allowing touch events to
+/// pass through to parent scrollables.
+library;
+
+import 'dart:js_interop';
+import 'dart:ui_web' as ui_web;
+
+import 'package:flutter/foundation.dart';
+import 'package:web/web.dart' as web;
+
+import 'basic.dart';
+import 'framework.dart';
+import 'platform_view.dart';
+import 'scroll_controller.dart';
+
+/// A widget that enables boundary detection for nested scrollables on web.
+///
+/// When a scrollable wrapped in [BrowserScrollZone] reaches its scroll
+/// boundaries (top or bottom), touch and wheel events will pass through to
+/// parent scrollables, enabling natural nested scrolling behavior.
+///
+/// This widget is only effective on web platforms. On other platforms, it
+/// simply returns its child without any modifications.
+///
+/// ## Use Cases
+///
+/// [BrowserScrollZone] is useful when you have a scrollable (like [ListView])
+/// nested inside another scrollable area and want smooth scrolling transitions
+/// at boundaries:
+///
+/// - Nested lists in a scrollable page
+/// - Modal dialogs with scrollable content
+/// - Embedded scrollable regions
+/// - Any nested scrolling scenario on web
+///
+/// ## How It Works
+///
+/// [BrowserScrollZone] creates an HTML platform view that sits above Flutter's
+/// canvas. This platform view intercepts touch and wheel events before they
+/// reach Flutter's rendering system, allowing conditional event handling based
+/// on the scroll position:
+///
+/// - **Not at boundary**: Events are prevented, Flutter handles scrolling
+/// - **At top + scrolling up**: Events pass through to parent
+/// - **At bottom + scrolling down**: Events pass through to parent
+///
+/// ## Requirements
+///
+/// - Requires a [ScrollController] to detect scroll position
+/// - Only works on web platform (no-op on other platforms)
+/// - Child should be a scrollable widget (ListView, GridView, etc.)
+///
+/// ## Example
+///
+/// ```dart
+/// final controller = ScrollController();
+///
+/// SingleChildScrollView(
+///   browserScrolling: true, // Enable browser scrolling for outer
+///   child: Column(
+///     children: [
+///       Text('Header'),
+///       SizedBox(
+///         height: 400,
+///         child: BrowserScrollZone(
+///           scrollController: controller,
+///           child: ListView.builder(
+///             controller: controller,
+///             itemCount: 30,
+///             itemBuilder: (context, index) => ListTile(
+///               title: Text('Item $index'),
+///             ),
+///           ),
+///         ),
+///       ),
+///       Text('Footer'),
+///     ],
+///   ),
+/// )
+/// ```
+///
+/// ## Important Notes
+///
+/// - The [ScrollController] must be attached to the child scrollable
+/// - Boundary detection only works when the controller has clients
+/// - The widget creates a platform view, which has some performance overhead
+/// - Works with both touch and mouse wheel events
+///
+/// See also:
+///
+/// * [Scrollable.browserScrolling], which enables browser-driven scrolling
+/// * [ScrollController], which is required for boundary detection
+/// * [HtmlElementView], which is used internally to create the platform view
+class BrowserScrollZone extends StatefulWidget {
+  /// Creates a [BrowserScrollZone] that enables boundary detection for nested scrolling.
+  ///
+  /// The [child] parameter must not be null and should be a scrollable widget.
+  ///
+  /// The [scrollController] parameter is required and must be the same controller
+  /// attached to the child scrollable widget. It is used to detect scroll position
+  /// and determine when to allow events to pass through to parent scrollables.
+  const BrowserScrollZone({
+    super.key,
+    required this.child,
+    required this.scrollController,
+  });
+
+  /// The scrollable widget to wrap.
+  ///
+  /// This should typically be a [ListView], [GridView], [SingleChildScrollView],
+  /// or any other scrollable widget.
+  final Widget child;
+
+  /// The scroll controller attached to the child scrollable.
+  ///
+  /// This controller is used to detect the current scroll position and determine
+  /// when the scrollable is at its boundaries (top or bottom).
+  ///
+  /// The same controller must be passed to both [BrowserScrollZone] and the
+  /// child scrollable widget for boundary detection to work correctly.
+  final ScrollController scrollController;
+
+  @override
+  State<BrowserScrollZone> createState() => _BrowserScrollZoneState();
+}
+
+class _BrowserScrollZoneState extends State<BrowserScrollZone> {
+  static int _nextViewId = 0;
+  late final String _viewId;
+
+  @override
+  void initState() {
+    super.initState();
+    _viewId = 'flutter-browser-scroll-zone-${_nextViewId++}';
+
+    // Only register platform view on web
+    if (kIsWeb) {
+      _registerPlatformView();
+    }
+  }
+
+  void _registerPlatformView() {
+    // Register a factory that creates a div with boundary detection
+    ui_web.platformViewRegistry.registerViewFactory(
+      _viewId,
+      (int viewId) {
+        final container = web.document.createElement('div') as web.HTMLDivElement;
+        container.style
+          ..width = '100%'
+          ..height = '100%'
+          ..touchAction = 'pan-y' // Allow vertical scrolling (controlled by JS)
+          ..overflow = 'hidden' // Prevent browser scrolling
+          ..pointerEvents = 'auto'; // Allow event interception
+
+        // Track touch start position for direction detection
+        double? touchStartY;
+
+        // Handle touch events for boundary detection
+        container.addEventListener('touchstart', (web.TouchEvent event) {
+          if (event.touches.length > 0) {
+            touchStartY = event.touches.item(0)!.clientY.toDouble();
+          }
+        }.toJS, web.AddEventListenerOptions(passive: true));
+
+        container.addEventListener('touchmove', (web.TouchEvent event) {
+          final scrollController = widget.scrollController;
+
+          // If no scroll controller or no clients, block all events
+          if (!scrollController.hasClients || event.touches.length == 0) {
+            event.preventDefault();
+            return;
+          }
+
+          final currentY = event.touches.item(0)!.clientY.toDouble();
+          final deltaY = touchStartY != null ? currentY - touchStartY! : 0.0;
+          final position = scrollController.position;
+
+          // Check if we're at boundaries and trying to scroll beyond
+          final isAtTop = position.pixels <= position.minScrollExtent;
+          final isAtBottom = position.pixels >= position.maxScrollExtent;
+          final isScrollingUp = deltaY > 0; // Positive deltaY = finger down = scroll up
+          final isScrollingDown = deltaY < 0; // Negative deltaY = finger up = scroll down
+
+          // Allow event to bubble to parent if:
+          // - At top and scrolling up, OR
+          // - At bottom and scrolling down
+          final shouldBubble = (isAtTop && isScrollingUp) || (isAtBottom && isScrollingDown);
+
+          if (!shouldBubble) {
+            event.preventDefault(); // Block event, Flutter handles it
+            if (kDebugMode) {
+              print('[BrowserScrollZone] Blocked touch event (scrollable can scroll)');
+            }
+          } else {
+            // Allow event to bubble to parent
+            if (kDebugMode) {
+              print('[BrowserScrollZone] Allowing touch event to bubble (at boundary)');
+            }
+          }
+        }.toJS, web.AddEventListenerOptions(passive: false)); // Must be non-passive to call preventDefault
+
+        container.addEventListener('touchend', (web.TouchEvent event) {
+          touchStartY = null;
+        }.toJS, web.AddEventListenerOptions(passive: true));
+
+        container.addEventListener('touchcancel', (web.TouchEvent event) {
+          touchStartY = null;
+        }.toJS, web.AddEventListenerOptions(passive: true));
+
+        // Handle wheel events for boundary detection (mouse scroll)
+        container.addEventListener('wheel', (web.WheelEvent event) {
+          final scrollController = widget.scrollController;
+
+          // If no clients, block all events
+          if (!scrollController.hasClients) {
+            event.preventDefault();
+            return;
+          }
+
+          final position = scrollController.position;
+          final deltaY = event.deltaY.toDouble();
+
+          // Check if we're at boundaries and trying to scroll beyond
+          final isAtTop = position.pixels <= position.minScrollExtent;
+          final isAtBottom = position.pixels >= position.maxScrollExtent;
+          final isScrollingUp = deltaY < 0; // Negative deltaY = scroll up
+          final isScrollingDown = deltaY > 0; // Positive deltaY = scroll down
+
+          // Allow event to bubble to parent if:
+          // - At top and scrolling up, OR
+          // - At bottom and scrolling down
+          final shouldBubble = (isAtTop && isScrollingUp) || (isAtBottom && isScrollingDown);
+
+          if (!shouldBubble) {
+            event.preventDefault(); // Block event, Flutter handles it
+            if (kDebugMode) {
+              print('[BrowserScrollZone] Blocked wheel event (scrollable can scroll)');
+            }
+          } else {
+            // Allow event to bubble to parent
+            if (kDebugMode) {
+              print('[BrowserScrollZone] Allowing wheel event to bubble (at boundary)');
+            }
+          }
+        }.toJS);
+
+        if (kDebugMode) {
+          print('[BrowserScrollZone] Created platform view with boundary detection');
+        }
+
+        return container;
+      },
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // On non-web platforms, just return the child
+    if (!kIsWeb) {
+      return widget.child;
+    }
+
+    // On web, stack the platform view behind the child
+    return Stack(
+      children: <Widget>[
+        Positioned.fill(
+          child: HtmlElementView(viewType: _viewId),
+        ),
+        widget.child,
+      ],
+    );
+  }
+}
+
diff --git a/packages/flutter/lib/src/widgets/scroll_configuration_web.dart b/packages/flutter/lib/src/widgets/scroll_configuration_web.dart
new file mode 100644
index 0000000..3eee2b8
--- /dev/null
+++ b/packages/flutter/lib/src/widgets/scroll_configuration_web.dart
@@ -0,0 +1,504 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Web-specific scroll configuration and browser scrolling integration.
+///
+/// This file provides automatic browser-driven scrolling for Flutter web apps,
+/// solving nested scrolling issues and providing native browser scrolling behavior.
+library;
+
+import 'dart:js_interop';
+import 'dart:math' as math;
+import 'dart:ui' as ui;
+import 'dart:ui_web' as ui_web;
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:web/web.dart' as web;
+
+import 'framework.dart';
+import 'scroll_controller.dart';
+import 'scroll_physics.dart';
+import 'scroll_position.dart';
+import 'scrollable.dart';
+import 'view.dart';
+
+/// Whether browser scrolling is enabled by default for Flutter web apps.
+///
+/// Defaults to false (opt-in). Set to true via [setBrowserScrollingDefault]
+/// to enable browser scrolling by default for all scrollables.
+bool _kDefaultBrowserScrollingEnabled = false;
+
+/// Gets the current default browser scrolling setting.
+///
+/// Returns true if browser scrolling is enabled by default.
+bool get kDefaultBrowserScrollingEnabled => _kDefaultBrowserScrollingEnabled;
+
+/// Sets the default browser scrolling behavior for all scrollables.
+///
+/// When enabled, Flutter will use native browser scrolling on web by default
+/// (unless explicitly disabled with browserScrolling: false), which provides:
+/// - Native touch and wheel scrolling behavior
+/// - Proper nested scrolling disambiguation
+/// - No iframe scroll blocking
+///
+/// Defaults to false. Set to true to opt-in all scrollables to browser scrolling.
+void setBrowserScrollingDefault(bool enabled) {
+  _kDefaultBrowserScrollingEnabled = enabled;
+}
+
+/// Interface for external scrollers that control scrolling via the browser DOM.
+///
+/// This is used internally by [BrowserScrollStrategy] to create and manage
+/// a placeholder DOM element that the browser scrolls, while Flutter renders
+/// at the appropriate scroll offset.
+abstract class ExternalScroller {
+  /// Sets up the external scroller (creates placeholder DOM, sets styles).
+  void setup();
+
+  /// Computes the currently visible rectangle based on browser scroll position.
+  ui.Rect computeVisibleRect();
+
+  /// Adds a listener for when the visible rect changes (browser scrolls).
+  void addVisibleRectListener(VoidCallback listener);
+
+  /// Removes a previously added visible rect listener.
+  void removeVisibleRectListener(VoidCallback listener);
+
+  /// Adds a listener for when the browser scroll event fires.
+  void addScrollListener(VoidCallback listener);
+
+  /// Removes a previously added scroll listener.
+  void removeScrollListener(VoidCallback listener);
+
+  /// Updates the height of the scrollable content.
+  void updateHeight(double height);
+
+  /// Enables boundary detection for nested scrolling.
+  /// 
+  /// When enabled, touch events are conditionally prevented based on scroll position.
+  /// This allows parent scrollables to receive events when this scrollable is at its boundaries.
+  void enableBoundaryDetection(ScrollController controller);
+
+  /// Disables boundary detection.
+  void disableBoundaryDetection();
+
+  /// Cleans up resources.
+  void dispose();
+}
+
+/// Implementation of [ExternalScroller] that uses JavaScript/DOM APIs.
+///
+/// This creates a placeholder `<body>` element with the correct height,
+/// while setting the actual Flutter view to `position: fixed`. The browser
+/// scrolls the placeholder, and Flutter renders based on `window.scrollY`.
+class JsViewScroller implements ExternalScroller {
+  JsViewScroller(this.viewId);
+
+  final int viewId;
+  late final web.HTMLElement _hostElement;
+  late final web.HTMLElement _placeholderElement;
+  final List<VoidCallback> _visibleRectListeners = <VoidCallback>[];
+  final List<VoidCallback> _scrollListeners = <VoidCallback>[];
+  late final web.EventListener _jsScrollListener;
+  web.ResizeObserver? _observer;
+  
+  // Boundary detection state
+  ScrollController? _boundaryDetectionController;
+  web.EventListener? _touchStartListener;
+  web.EventListener? _touchMoveListener;
+  web.EventListener? _touchEndListener;
+  web.EventListener? _touchCancelListener;
+  double? _touchStartY;
+
+  @override
+  void setup() {
+    _hostElement = ui_web.views.getHostElement(viewId) as web.HTMLElement;
+
+    // Create placeholder element (a clone of the host that will be scrollable)
+    _placeholderElement = _hostElement.cloneNode() as web.HTMLElement;
+    _hostElement.parentElement!.insertBefore(_placeholderElement, _hostElement);
+
+    // Set host element to fixed position (doesn't scroll with browser)
+    _hostElement.style
+      ..position = 'fixed'
+      ..top = '0'
+      ..left = '0'
+      ..right = '0'
+      ..bottom = '0';
+
+    // Ensure HTML element allows scrolling
+    (web.document.documentElement as web.HTMLElement).style
+      ..overflow = 'auto'  // Allow scrolling on <html>
+      ..height = 'auto'
+      ..margin = '0'
+      ..padding = '0';
+
+    // Set placeholder to static positioning and visible overflow
+    (_hostElement.parentElement! as web.HTMLElement).style
+      ..position = 'static'
+      ..overflow = 'visible';
+
+    if (kDebugMode) {
+      print('[BrowserScroller] Setup complete: placeholder created, host set to fixed');
+    }
+  }
+
+  @override
+  ui.Rect computeVisibleRect() {
+    final double scrollTop = web.window.scrollY;
+    final double windowHeight = web.window.innerHeight.toDouble();
+    return ui.Rect.fromLTWH(0, scrollTop, web.window.innerWidth.toDouble(), windowHeight);
+  }
+
+  @override
+  void addVisibleRectListener(VoidCallback listener) {
+    if (_visibleRectListeners.isEmpty) {
+      final int viewId = this.viewId;
+      _observer = web.ResizeObserver((JSArray<JSObject> entries, JSObject observer) {
+        for (final VoidCallback listener in _visibleRectListeners) {
+          listener();
+        }
+      }.toJS);
+      final web.Element? hostElement = ui_web.views.getHostElement(viewId) as web.Element?;
+      if (hostElement != null) {
+        _observer!.observe(hostElement);
+      }
+    }
+    _visibleRectListeners.add(listener);
+  }
+
+  @override
+  void removeVisibleRectListener(VoidCallback listener) {
+    _visibleRectListeners.remove(listener);
+    if (_visibleRectListeners.isEmpty) {
+      _observer?.disconnect();
+      _observer = null;
+    }
+  }
+
+  web.EventTarget get _scrollTarget => web.window;
+
+  @override
+  void addScrollListener(VoidCallback listener) {
+    if (_scrollListeners.isEmpty) {
+      _jsScrollListener = (web.Event event) {
+        for (final VoidCallback listener in _scrollListeners) {
+          listener();
+        }
+      }.toJS;
+      _scrollTarget.addEventListener('scroll', _jsScrollListener);
+    }
+    _scrollListeners.add(listener);
+  }
+
+  @override
+  void removeScrollListener(VoidCallback listener) {
+    _scrollListeners.remove(listener);
+    if (_scrollListeners.isEmpty) {
+      _scrollTarget.removeEventListener('scroll', _jsScrollListener);
+    }
+  }
+
+  @override
+  void updateHeight(double height) {
+    _placeholderElement.style.height = '${math.max(height, web.window.innerHeight.toDouble())}px';
+    if (kDebugMode) {
+      print('[BrowserScroller] Updated height: ${height}px');
+    }
+  }
+
+  @override
+  void enableBoundaryDetection(ScrollController controller) {
+    _boundaryDetectionController = controller;
+    
+    // Add touch event listeners to the host element for boundary detection
+    _touchStartListener = (web.TouchEvent event) {
+      if (event.touches.length > 0) {
+        _touchStartY = event.touches.item(0)!.clientY.toDouble();
+      }
+    }.toJS;
+    _hostElement.addEventListener('touchstart', _touchStartListener!, web.AddEventListenerOptions(passive: true));
+    
+    _touchMoveListener = (web.TouchEvent event) {
+      final scrollController = _boundaryDetectionController;
+      
+      // If no scroll controller, always block (defensive)
+      if (scrollController == null || !scrollController.hasClients || event.touches.length == 0) {
+        event.preventDefault();
+        return;
+      }
+      
+      final currentY = event.touches.item(0)!.clientY.toDouble();
+      final deltaY = _touchStartY != null ? currentY - _touchStartY! : 0.0;
+      final position = scrollController.position;
+      
+      // Check if we're at boundaries and trying to scroll beyond
+      final isAtTop = position.pixels <= position.minScrollExtent;
+      final isAtBottom = position.pixels >= position.maxScrollExtent;
+      final isScrollingUp = deltaY > 0; // Positive deltaY means finger moving down = scrolling up
+      final isScrollingDown = deltaY < 0; // Negative deltaY means finger moving up = scrolling down
+      
+      // Allow event to bubble to parent if:
+      // - At top and scrolling up, OR
+      // - At bottom and scrolling down
+      final shouldBubble = (isAtTop && isScrollingUp) || (isAtBottom && isScrollingDown);
+      
+      if (!shouldBubble) {
+        event.preventDefault(); // Block browser scrolling
+        if (kDebugMode) {
+          print('[BrowserScroller] Blocked touch event (scrollable can scroll)');
+        }
+      } else {
+        // Allow event to bubble to parent
+        if (kDebugMode) {
+          print('[BrowserScroller] Allowing touch event to bubble (at boundary)');
+        }
+      }
+    }.toJS;
+    _hostElement.addEventListener('touchmove', _touchMoveListener!, web.AddEventListenerOptions(passive: false)); // Must be non-passive to call preventDefault
+    
+    _touchEndListener = (web.TouchEvent event) {
+      _touchStartY = null;
+    }.toJS;
+    _hostElement.addEventListener('touchend', _touchEndListener!, web.AddEventListenerOptions(passive: true));
+    
+    _touchCancelListener = (web.TouchEvent event) {
+      _touchStartY = null;
+    }.toJS;
+    _hostElement.addEventListener('touchcancel', _touchCancelListener!, web.AddEventListenerOptions(passive: true));
+    
+    if (kDebugMode) {
+      print('[BrowserScroller] Boundary detection enabled');
+    }
+  }
+
+  @override
+  void disableBoundaryDetection() {
+    if (_touchStartListener != null) {
+      _hostElement.removeEventListener('touchstart', _touchStartListener!);
+      _touchStartListener = null;
+    }
+    if (_touchMoveListener != null) {
+      _hostElement.removeEventListener('touchmove', _touchMoveListener!);
+      _touchMoveListener = null;
+    }
+    if (_touchEndListener != null) {
+      _hostElement.removeEventListener('touchend', _touchEndListener!);
+      _touchEndListener = null;
+    }
+    if (_touchCancelListener != null) {
+      _hostElement.removeEventListener('touchcancel', _touchCancelListener!);
+      _touchCancelListener = null;
+    }
+    _boundaryDetectionController = null;
+    _touchStartY = null;
+    
+    if (kDebugMode) {
+      print('[BrowserScroller] Boundary detection disabled');
+    }
+  }
+
+  @override
+  void dispose() {
+    disableBoundaryDetection();
+    _observer?.disconnect();
+    if (_scrollListeners.isNotEmpty) {
+      _scrollTarget.removeEventListener('scroll', _jsScrollListener);
+    }
+  }
+}
+
+/// A scroll strategy that uses native browser scrolling instead of canvas-based scrolling.
+///
+/// This strategy creates a placeholder DOM element that the browser scrolls,
+/// while Flutter renders at the current scroll offset. This provides:
+/// - Native touch and wheel scrolling behavior
+/// - No iframe scroll blocking
+/// - Proper momentum scrolling
+/// - Better accessibility
+///
+/// This is automatically used on web when browser scrolling is enabled.
+class BrowserScrollStrategy {
+  BrowserScrollStrategy({
+    required this.scrollController,
+    required this.externalScroller,
+  });
+
+  final ScrollController scrollController;
+  final ExternalScroller externalScroller;
+
+  late ui.Rect _visibleRect;
+  bool _isInitialized = false;
+
+  /// Initializes the browser scroll strategy.
+  void initialize() {
+    if (_isInitialized) {
+      return;
+    }
+
+    externalScroller.setup();
+
+    _syncVisibleRect();
+    SchedulerBinding.instance.addPostFrameCallback((_) {
+      _syncScrollPosition();
+      _syncContentHeight();
+    });
+
+    _isInitialized = true;
+  }
+
+  void _syncVisibleRect() {
+    _visibleRect = externalScroller.computeVisibleRect();
+    externalScroller.addVisibleRectListener(_updateVisibleRect);
+  }
+
+  void _updateVisibleRect() {
+    final ui.Rect newVisibleRect = externalScroller.computeVisibleRect();
+    if (_visibleRect != newVisibleRect) {
+      _visibleRect = newVisibleRect;
+    }
+  }
+
+  void _syncScrollPosition() {
+    externalScroller.addScrollListener(() {
+      final ui.Rect visibleRect = externalScroller.computeVisibleRect();
+      if (scrollController.hasClients) {
+        final ScrollPosition position = scrollController.position;
+        if ((position.pixels - visibleRect.top).abs() > 1.0) {
+          position.jumpTo(visibleRect.top);
+        }
+      }
+    });
+  }
+
+  void _syncContentHeight() {
+    if (scrollController.hasClients) {
+      scrollController.position.addListener(_handleScrollPositionChange);
+      _handleScrollPositionChange();
+    }
+  }
+
+  void _handleScrollPositionChange() {
+    if (scrollController.hasClients) {
+      final ScrollPosition position = scrollController.position;
+      final double maxScrollExtent = position.maxScrollExtent;
+      final double totalHeight = maxScrollExtent + _visibleRect.height;
+      externalScroller.updateHeight(totalHeight);
+    }
+  }
+
+  /// Disposes of the browser scroll strategy.
+  void dispose() {
+    if (scrollController.hasClients) {
+      scrollController.position.removeListener(_handleScrollPositionChange);
+    }
+    externalScroller.removeVisibleRectListener(_updateVisibleRect);
+    externalScroller.dispose();
+  }
+}
+
+/// Widget that enables browser scrolling for its child.
+///
+/// This is automatically applied to scrollables on web when browser scrolling
+/// is enabled. It can also be used manually to wrap content that should use
+/// browser scrolling.
+///
+/// Example:
+/// ```dart
+/// BrowserScrollView(
+///   child: ListView.builder(...),
+/// )
+/// ```
+class BrowserScrollView extends StatefulWidget {
+  const BrowserScrollView({
+    super.key,
+    required this.child,
+    this.scrollController,
+  });
+
+  final Widget child;
+  final ScrollController? scrollController;
+
+  @override
+  State<BrowserScrollView> createState() => _BrowserScrollViewState();
+}
+
+class _BrowserScrollViewState extends State<BrowserScrollView> {
+  late final ScrollController _scrollController;
+  late final BrowserScrollStrategy _strategy;
+
+  @override
+  void initState() {
+    super.initState();
+    _scrollController = widget.scrollController ?? ScrollController();
+    
+    // Get the view ID for this widget's context
+    final int viewId = View.of(context).viewId;
+    
+    _strategy = BrowserScrollStrategy(
+      scrollController: _scrollController,
+      externalScroller: JsViewScroller(viewId),
+    );
+    
+    _strategy.initialize();
+  }
+
+  @override
+  void dispose() {
+    _strategy.dispose();
+    if (widget.scrollController == null) {
+      _scrollController.dispose();
+    }
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return widget.child;
+  }
+}
+
+/// Determines whether browser scrolling should be used for a given scrollable.
+///
+/// Returns true if:
+/// - Running on web platform
+/// - Browser scrolling is enabled globally
+/// - Not explicitly disabled for this scrollable
+/// - No custom scroll physics that conflict with browser scrolling
+bool shouldUseBrowserScrolling({
+  bool? explicitSetting,
+  ScrollPhysics? physics,
+}) {
+  if (!kIsWeb) {
+    return false;
+  }
+
+  if (explicitSetting != null) {
+    return explicitSetting;
+  }
+
+  if (!_kDefaultBrowserScrollingEnabled) {
+    return false;
+  }
+
+  // Check if physics would conflict with browser scrolling
+  // NeverScrollableScrollPhysics means the content shouldn't scroll at all
+  if (physics is NeverScrollableScrollPhysics) {
+    return false;
+  }
+
+  return true;
+}
+
+/// Creates a browser scroll strategy for the given view ID.
+///
+/// This is called by [ScrollableState] when browser scrolling is enabled.
+/// Returns an [ExternalScroller] that manages the DOM placeholder and
+/// syncs with browser scroll events.
+ExternalScroller createBrowserScrollStrategy(int viewId) {
+  return JsViewScroller(viewId);
+}
+
diff --git a/packages/flutter/lib/src/widgets/scroll_configuration_web_stub.dart b/packages/flutter/lib/src/widgets/scroll_configuration_web_stub.dart
new file mode 100644
index 0000000..7685b0e
--- /dev/null
+++ b/packages/flutter/lib/src/widgets/scroll_configuration_web_stub.dart
@@ -0,0 +1,71 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Stub implementation for non-web platforms.
+///
+/// This file provides empty implementations for web-specific browser scrolling
+/// features on non-web platforms.
+library;
+
+import 'package:flutter/foundation.dart';
+
+import 'framework.dart';
+import 'scroll_controller.dart';
+import 'scroll_physics.dart';
+
+/// Always returns false on non-web platforms.
+bool get kDefaultBrowserScrollingEnabled => false;
+
+/// No-op on non-web platforms.
+void setBrowserScrollingDefault(bool enabled) {
+  // No-op on non-web platforms
+}
+
+/// Always returns false on non-web platforms.
+bool shouldUseBrowserScrolling({
+  bool? explicitSetting,
+  ScrollPhysics? physics,
+}) {
+  return false;
+}
+
+/// Stub interface for external scrollers on non-web platforms.
+abstract class ExternalScroller {
+  void setup();
+  Object computeVisibleRect();  // Returns ui.Rect on web
+  void addScrollListener(VoidCallback listener);
+  void removeScrollListener(VoidCallback listener);
+  void updateHeight(double height);
+  void enableBoundaryDetection(ScrollController controller);
+  void disableBoundaryDetection();
+  void dispose();
+}
+
+/// Stub widget for non-web platforms.
+class BrowserScrollView extends StatelessWidget {
+  const BrowserScrollView({
+    super.key,
+    required this.child,
+    this.scrollController,
+  });
+
+  final Widget child;
+  final ScrollController? scrollController;
+
+  @override
+  Widget build(BuildContext context) {
+    return child;
+  }
+}
+
+/// Throws an error on non-web platforms.
+///
+/// Browser scrolling is only available on web.
+ExternalScroller createBrowserScrollStrategy(int viewId) {
+  throw UnsupportedError(
+    'Browser scrolling is only supported on web platforms. '
+    'This method should never be called on non-web platforms.'
+  );
+}
+
diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart
index d37cb17..a26a001 100644
--- a/packages/flutter/lib/src/widgets/scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/scroll_view.dart
@@ -123,6 +123,7 @@
     this.restorationId,
     this.clipBehavior = Clip.hardEdge,
     this.hitTestBehavior = HitTestBehavior.opaque,
+    this.browserScrolling,
   }) : assert(
          !(controller != null && (primary ?? false)),
          'Primary ScrollViews obtain their ScrollController via inheritance '
@@ -402,6 +403,9 @@
   /// Defaults to [HitTestBehavior.opaque].
   final HitTestBehavior hitTestBehavior;
 
+  /// {@macro flutter.widgets.scrollable.browserScrolling}
+  final bool? browserScrolling;
+
   /// Returns the [AxisDirection] in which the scroll view scrolls.
   ///
   /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
@@ -506,6 +510,7 @@
       semanticChildCount: semanticChildCount,
       restorationId: restorationId,
       hitTestBehavior: hitTestBehavior,
+      browserScrolling: browserScrolling,
       viewportBuilder: (BuildContext context, ViewportOffset offset) {
         return buildViewport(context, offset, axisDirection, slivers);
       },
@@ -857,6 +862,7 @@
     super.restorationId,
     super.clipBehavior,
     super.hitTestBehavior,
+    super.browserScrolling,
   });
 
   /// The amount of space by which to inset the children.
@@ -1298,6 +1304,7 @@
     super.restorationId,
     super.clipBehavior,
     super.hitTestBehavior,
+    super.browserScrolling,
   }) : assert(
          (itemExtent == null && prototypeItem == null) ||
              (itemExtent == null && itemExtentBuilder == null) ||
@@ -1375,6 +1382,7 @@
     super.restorationId,
     super.clipBehavior,
     super.hitTestBehavior,
+    super.browserScrolling,
   }) : assert(itemCount == null || itemCount >= 0),
        assert(semanticChildCount == null || semanticChildCount <= itemCount!),
        assert(
@@ -1489,6 +1497,7 @@
     super.restorationId,
     super.clipBehavior,
     super.hitTestBehavior,
+    super.browserScrolling,
   }) : assert(itemCount >= 0),
        assert(
          findItemIndexCallback == null || findChildIndexCallback == null,
@@ -1553,6 +1562,7 @@
     super.restorationId,
     super.clipBehavior,
     super.hitTestBehavior,
+    super.browserScrolling,
   }) : assert(
          (itemExtent == null && prototypeItem == null) ||
              (itemExtent == null && itemExtentBuilder == null) ||
diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart
index cf71c2f..2d1b1f5 100644
--- a/packages/flutter/lib/src/widgets/scrollable.dart
+++ b/packages/flutter/lib/src/widgets/scrollable.dart
@@ -16,6 +16,7 @@
 
 import 'dart:async';
 import 'dart:math' as math;
+import 'dart:ui' as ui;
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
@@ -32,6 +33,8 @@
 import 'restoration_properties.dart';
 import 'scroll_activity.dart';
 import 'scroll_configuration.dart';
+import 'scroll_configuration_web_stub.dart'
+  if (dart.library.js_interop) 'scroll_configuration_web.dart';
 import 'scroll_context.dart';
 import 'scroll_controller.dart';
 import 'scroll_physics.dart';
@@ -134,6 +137,7 @@
     this.scrollBehavior,
     this.clipBehavior = Clip.hardEdge,
     this.hitTestBehavior = HitTestBehavior.opaque,
+    this.browserScrolling,
   }) : assert(semanticChildCount == null || semanticChildCount >= 0);
 
   /// {@template flutter.widgets.Scrollable.axisDirection}
@@ -333,6 +337,36 @@
   /// to [ScrollView.clipBehavior] and is supplied to the [Viewport].
   final Clip clipBehavior;
 
+  /// Whether to use native browser scrolling on web.
+  ///
+  /// When `true`, Flutter uses the browser's native scrolling mechanism, which provides:
+  /// - Native touch and wheel scrolling behavior
+  /// - Proper nested scrolling disambiguation
+  /// - No iframe scroll blocking
+  /// - Better accessibility and integration with browser features
+  ///
+  /// When `false`, Flutter uses canvas-based scrolling (the traditional behavior).
+  ///
+  /// When `null` (default), uses canvas-based scrolling for backward compatibility.
+  /// The default may change to `true` on web in a future release.
+  ///
+  /// This parameter only affects web platforms. On other platforms, it is ignored.
+  ///
+  /// Example of enabling browser scrolling for nested scrolling scenarios:
+  /// ```dart
+  /// ListView.builder(
+  ///   browserScrolling: true, // Opt-in to browser scrolling
+  ///   itemBuilder: (context, index) => ListTile(...),
+  /// )
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * https://github.com/flutter/flutter/issues/156985 (nested scrolling)
+  ///  * https://github.com/flutter/flutter/issues/157435 (touch scrolling)
+  ///  * https://github.com/flutter/flutter/issues/113196 (iframe blocking)
+  final bool? browserScrolling;
+
   /// The axis along which the scroll view scrolls.
   ///
   /// Determined by the [axisDirection].
@@ -612,6 +646,7 @@
   late ScrollBehavior _configuration;
   ScrollController? _fallbackScrollController;
   DeviceGestureSettings? _mediaQueryGestureSettings;
+  ExternalScroller? _browserScrollStrategy;
 
   // Only call this from places that will definitely trigger a rebuild.
   void _updatePosition() {
@@ -655,6 +690,92 @@
     ServicesBinding.instance.restorationManager.flushData();
   }
 
+  bool get _shouldUseBrowserScrolling {
+    // Only on web platform
+    if (!kIsWeb) {
+      return false;
+    }
+    // Check widget parameter
+    if (widget.browserScrolling != null) {
+      return widget.browserScrolling!;
+    }
+    // Fall back to global default (currently false for opt-in)
+    return kDefaultBrowserScrollingEnabled;
+  }
+
+  void _initBrowserScrolling() {
+    if (!_shouldUseBrowserScrolling) {
+      return;
+    }
+
+    // Get the view ID from the current context
+    final int viewId = View.of(context).viewId;
+    _browserScrollStrategy = createBrowserScrollStrategy(viewId);
+    _browserScrollStrategy?.setup();
+
+    // Sync scroll position from browser to Flutter
+    _browserScrollStrategy?.addScrollListener(_syncScrollFromBrowser);
+
+    // Sync content height from Flutter to browser (in next frame after position is available)
+    SchedulerBinding.instance.addPostFrameCallback((_) {
+      if (_position != null) {
+        _position!.addListener(_syncHeightToBrowser);
+        _syncHeightToBrowser();
+        
+        // Enable boundary detection for nested scrolling
+        // This allows touch events to pass through when scrollable is at boundaries
+        _browserScrollStrategy?.enableBoundaryDetection(_effectiveScrollController);
+      }
+    });
+  }
+
+  void _syncScrollFromBrowser() {
+    if (_browserScrollStrategy == null || _position == null) {
+      return;
+    }
+    
+    // Get the scroll position from browser (only on web)
+    final ui.Rect visibleRect = _browserScrollStrategy!.computeVisibleRect() as ui.Rect;
+    final double browserScrollTop = visibleRect.top;
+    
+    // Update Flutter's scroll position to match browser
+    if ((_position!.pixels - browserScrollTop).abs() > 1.0) {
+      _position!.jumpTo(
+        clampDouble(
+          browserScrollTop,
+          _position!.minScrollExtent,
+          _position!.maxScrollExtent,
+        ),
+      );
+    }
+  }
+
+  void _syncHeightToBrowser() {
+    if (_browserScrollStrategy == null || _position == null) {
+      return;
+    }
+    
+    // Calculate total content height
+    final double maxScrollExtent = _position!.maxScrollExtent;
+    final double viewportHeight = _position!.viewportDimension;
+    final double totalHeight = maxScrollExtent + viewportHeight;
+    
+    // Update browser placeholder height
+    _browserScrollStrategy!.updateHeight(totalHeight);
+  }
+
+  void _disposeBrowserScrolling() {
+    if (_browserScrollStrategy != null) {
+      _browserScrollStrategy!.removeScrollListener(_syncScrollFromBrowser);
+      if (_position != null) {
+        _position!.removeListener(_syncHeightToBrowser);
+      }
+      _browserScrollStrategy!.disableBoundaryDetection();
+      _browserScrollStrategy!.dispose();
+      _browserScrollStrategy = null;
+    }
+  }
+
   @protected
   @override
   void initState() {
@@ -671,6 +792,8 @@
     _devicePixelRatio =
         MediaQuery.maybeDevicePixelRatioOf(context) ?? View.of(context).devicePixelRatio;
     _updatePosition();
+    // Initialize browser scrolling after position is created
+    _initBrowserScrolling();
     super.didChangeDependencies();
   }
 
@@ -727,6 +850,12 @@
     if (_shouldUpdatePosition(oldWidget)) {
       _updatePosition();
     }
+
+    // Handle changes to browserScrolling parameter
+    if (widget.browserScrolling != oldWidget.browserScrolling) {
+      _disposeBrowserScrolling();
+      _initBrowserScrolling();
+    }
   }
 
   @protected
@@ -741,6 +870,7 @@
 
     position.dispose();
     _persistedScrollOffset.dispose();
+    _disposeBrowserScrolling();
     super.dispose();
   }
 
diff --git a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
index 8b19273..8e8231a 100644
--- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
@@ -160,6 +160,7 @@
     this.hitTestBehavior = HitTestBehavior.opaque,
     this.restorationId,
     this.keyboardDismissBehavior,
+    this.browserScrolling,
   }) : assert(
          !(controller != null && (primary ?? false)),
          'Primary ScrollViews obtain their ScrollController via inheritance '
@@ -239,6 +240,9 @@
   /// [ScrollBehavior.getKeyboardDismissBehavior].
   final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
 
+  /// {@macro flutter.widgets.scrollable.browserScrolling}
+  final bool? browserScrolling;
+
   AxisDirection _getDirection(BuildContext context) {
     return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
   }
@@ -266,6 +270,7 @@
       restorationId: restorationId,
       clipBehavior: clipBehavior,
       hitTestBehavior: hitTestBehavior,
+      browserScrolling: browserScrolling,
       viewportBuilder: (BuildContext context, ViewportOffset offset) {
         return _SingleChildViewport(
           axisDirection: axisDirection,
diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart
index 4db5774..fc84757 100644
--- a/packages/flutter/lib/widgets.dart
+++ b/packages/flutter/lib/widgets.dart
@@ -116,6 +116,7 @@
 export 'src/widgets/router.dart';
 export 'src/widgets/routes.dart';
 export 'src/widgets/safe_area.dart';
+export 'src/widgets/browser_scroll_zone.dart';
 export 'src/widgets/scroll_activity.dart';
 export 'src/widgets/scroll_aware_image_provider.dart';
 export 'src/widgets/scroll_configuration.dart';
diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml
index c8fdce5..ae2cf75 100644
--- a/packages/flutter/pubspec.yaml
+++ b/packages/flutter/pubspec.yaml
@@ -17,6 +17,7 @@
   material_color_utilities: 0.11.1
   meta: 1.17.0
   vector_math: 2.2.0
+  web: any
   sky_engine:
     sdk: flutter