Skip to content

Add SliverClipRect and SliverClipRRect to the widgets library #179002

Closed

Description

@manu-sncf

1. Overview

This proposal introduces new widgets, SliverClipRect and SliverClipRRect. Those widgets serve as the Sliver equivalent of the existing ClipRect and ClipRRect widgets. It clips its child sliver using a rectangle / rounded rectangle and include specific logic to handle SliverConstraints.overlap, allowing for the visual removal of content that scrolls underneath pinned headers.

2. Background & Motivation

The Problem

Currently, the ClipRect and ClipRRect widgets only support RenderBox protocol.

There are two primary scenarios where clipping is essential in the Sliver world, but currently difficult to achieve:

  1. Visual Styling: Applying a hard clip to a group of slivers (e.g., inside a SliverMainAxisGroup or SliverDecoratedBox).
  2. Overlap Management: When using pinned headers (like SliverAppBar or SliverPersistentHeader) with transparent or semi-transparent backgrounds. By default, scrolling content paints underneath the pinned header. If the header is transparent, the scrolling content is visible, which is often undesirable. Developers need a way to "clip" the scrolling content exactly at the point where it overlaps with the pinned header.

The Solution

SliverClipRect/SliverClipRRect solve this by creating a RenderSliverClipRect/RenderSliverClipRRect. Those render object:

  1. Accept CustomClipper<Rect> and CustomClipper<RRect>.
  2. Optionally accounts for constraints.overlap to automatically clip the portion of the child obstructed by pinned headers.
  3. Correctly handles hit-testing, ensuring that clipped/overlapped areas do not consume touch events.

3. Design

API Surface

The API mirrors the existing ClipRect and ClipRRect but adds the clipOverlap property specific to the Sliver protocol.

class SliverClipRect extends SingleChildRenderObjectWidget {
  const SliverClipRect({
    super.key,
    super.child,
    this.clipper,
    this.clipBehavior = Clip.hardEdge,
    this.clipOverlap = true,
  });

  /// If non-null, determines which clip rectangle to use.
  final CustomClipper<Rect>? clipper;

  /// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
  final Clip clipBehavior;

  /// Whether to automatically clip the portion of the child that is overlapped
  /// by preceding pinned slivers.
  ///
  /// If true, the clip rect will be adjusted to exclude the area defined by
  /// [SliverConstraints.overlap].
  final bool clipOverlap;
}
class SliverClipRRect extends SingleChildRenderObjectWidget {
  /// Creates a sliver that clips its child using a rounded rectangle.
  const SliverClipRRect({
    super.key,
    required Widget sliver,
    this.borderRadius = BorderRadius.zero,
    this.clipper,
    this.clipBehavior = Clip.antiAlias,
    this.clipOverlap = true,
  }) : super(child: sliver);

  /// The border radius of the rounded corners.
  ///
  /// Values are clamped so that horizontal and vertical radii sums do not
  /// exceed width/height.
  ///
  /// This value is ignored if [clipper] is non-null.
  final BorderRadiusGeometry borderRadius;

  /// If non-null, determines which clip to use.
  final CustomClipper<RRect>? clipper;

  /// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
  final Clip clipBehavior;

  /// Whether to automatically clip the portion of the child that is overlapped
  /// by preceding pinned slivers.
  ///
  /// If true, the clip rect will be adjusted to exclude the area defined by
  /// [SliverConstraints.overlap].
  final bool clipOverlap;
}

Rendering Logic

The RenderSliverClipRect/RenderSliverClipRRect calculate the clip rect/rrect based on the child's paint bounds. If clipOverlap is true, it intersects the calculated clip rect with the visible portion of the viewport (excluding the overlap defined in constraints).

4. Use Cases

Use Case 1: Grouped Decoration with Hard Edges

This scenario involves a group of slivers wrapped in a decoration (e.g., a gradient). We want to ensure that if the decoration has specific bounds, the children are clipped strictly to those bounds, ignoring overlap logic.

Use Case 2: Transparent Pinned Header

This scenario involves a design where the SliverAppBar (or pinned header) has no background (transparent), but the Scaffold has a complex background (e.g., a gradient).

Without clipping, as items scroll up, they would become visible behind the text of the SliverAppBar because the App Bar is transparent. Using SliverClipRect with clipOverlap: true (the default) ensures items disappear the moment they hit the overlap point.

Example

The example below combines the two previous use cases and works with PR #179003.

Scaffold(
  body: DecoratedBox(
    decoration: BoxDecoration(
      gradient: LinearGradient(
        begin: Alignment.topCenter,
        end: Alignment.bottomCenter,
        colors: <Color>[Colors.blue.shade200, Colors.blue.shade800],
      ),
    ),
    child: CustomScrollView(
      slivers: <Widget>[
        const SliverAppBar(
          pinned: true,
          title: Text('Group Clipping'),
          backgroundColor: Colors.transparent,
        ),
        SliverSafeArea(
          top: false,
          sliver: SliverPadding(
            padding: const EdgeInsets.all(16),
            sliver: SliverClipRRect(
              borderRadius: BorderRadius.circular(12),
              sliver: DecoratedSliver(
                decoration: const BoxDecoration(
                  gradient: LinearGradient(
                    begin: Alignment.topCenter,
                    end: Alignment.bottomCenter,
                    colors: <Color>[Colors.blue, Colors.purple],
                ),
                sliver: SliverMainAxisGroup(
                  slivers: <Widget>[
                    const PinnedHeaderSliver(
                      child: ListTile(
                        title: Text('Pinned Header', style: TextStyle(color: Colors.white)),
                      ),
                    ),
                    SliverClipRect(
                      sliver: SliverList.builder(
                        itemCount: 20,
                        itemBuilder: (BuildContext context, int index) => ListTile(
                          title: Text('Item $index', style: const TextStyle(color: Colors.white)),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ],
    ),
  ),
)
demo.webm

Optional proposal

In a new PR, we eventually can add : SliverClipOval, SliverClipPath and SliverRSuperellipse.

Activity

  1. changed the title [-]Add `SliverClipRect` and `SliverClipRRect` to the Widgets Library[/-] [+]Add `SliverClipRect` and `SliverClipRRect` to the widgets library[/+] on Nov 24, 2025
  2. tirth-patel-nc commented on Nov 24, 2025

    @tirth-patel-nc
    Member

    @manu-sncf thanks for the report. labelling it as a new proposal. thanks for contributing as well!

  3. added
    c: new featureNothing broken; request for a new capability
    frameworkflutter/packages/flutter repository. See also f: labels.
    f: scrollingViewports, list views, slivers, etc.
    c: proposalA detailed proposal for a change to Flutter
    c: new widgetMust also have "c: new feature". Request for a new widget.
    and removed
    in triagePresently being triaged by the triage team
    on Nov 24, 2025
  4. added
    ⏳Bot is counting down the days until it unassigns the issue
    on Apr 14, 2026
  5. flutter-triage-bot commented on Apr 14, 2026

    @flutter-triage-bot

    This issue is assigned to @manu-sncf but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!

  6. added
    ⏳Bot is counting down the days until it unassigns the issue
    and removed
    ⏳Bot is counting down the days until it unassigns the issue
    on May 22, 2026
  7. flutter-triage-bot commented on May 23, 2026

    @flutter-triage-bot

    This issue is assigned to @manu-sncf but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!

  8. flutter-triage-bot commented on Jul 1, 2026

    @flutter-triage-bot

    This issue was assigned to @manu-sncf but has had no status updates in a long time. To remove any ambiguity about whether the issue is being worked on, the assignee was removed.

  9. removed
    ⏳Bot is counting down the days until it unassigns the issue
    on Jul 1, 2026
  10. added a commit that references this issue on Aug 20, 2026
    b49ab60
  11. github-actions commented on Sep 3, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  12. locked as resolved and limited conversation to collaborators on Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P3Issues that are less important to the Flutter projectc: new featureNothing broken; request for a new capabilityc: new widgetMust also have "c: new feature". Request for a new widget.c: proposalA detailed proposal for a change to Flutterf: scrollingViewports, list views, slivers, etc.frameworkflutter/packages/flutter repository. See also f: labels.team-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework teamwaiting for PR to land (fixed)A fix is in flight

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions