Menu

Tailwind CSS Overscroll Behavior

Overscroll behavior in CSS defines how the browser responds when you reach the end of a scrollable area. In simpler terms, it controls behaviors such as overscroll bouncing and whether child elements' scroll overflow impacts the parent container.

Tailwind CSS provides a rich set of utilities to handle overscroll behavior. By leveraging these predefined utilities, you can easily manage overscroll behavior. This guide explores the implementation and principles behind overscroll utilities in Tailwind CSS.

ClassPropertiesExample
overscroll-autooverscroll-behavior: auto;<div className="overscroll-auto"></div>
overscroll-containoverscroll-behavior: contain;<div className="overscroll-contain"></div>
overscroll-noneoverscroll-behavior: none;<div className="overscroll-none"></div>
overscroll-y-autooverscroll-behavior-y: auto;<div className="overscroll-y-auto"></div>
overscroll-y-containoverscroll-behavior-y: contain;<div className="overscroll-y-contain"></div>
overscroll-y-noneoverscroll-behavior-y: none;<div className="overscroll-y-none"></div>
overscroll-x-autooverscroll-behavior-x: auto;<div className="overscroll-x-auto"></div>
overscroll-x-containoverscroll-behavior-x: contain;<div className="overscroll-x-contain"></div>
overscroll-x-noneoverscroll-behavior-x: none;<div className="overscroll-x-none"></div>

Overview of Overscroll Behavior

Disabling the Parent Overscroll

The overscroll-contain ensures no scroll chaining occurs when you reach the end of a scrollable area. It still allows the default overflow effect(like bounce).

This is a live editor. Play around with it!
export default function OverscrollBehaviorDemo() {
  return (
    <div className="h-screen w-screen bg-gray-100">
      <div className="overscroll-contain overflow-scroll h-48 w-full bg-white p-4">
        <p className="mb-4">Scroll inside this area, but notice that no scroll chaining occurs.</p>
        <img
          src="https://images.unsplash.com/photo-1511467687858-23d96c32e4ae"
          alt="Example Illustration"
          className="h-96 w-auto"
        />
      </div>
    </div>
  );
}

Disabling the Overscroll Bounce

The overscroll-none ensures no scroll chaining as well as bounce effect occurs when you reach the end of a scrollable area.

This is a live editor. Play around with it!
export default function PreventParentOverscroll() {
  return (
    <div className="h-screen w-screen bg-gray-100">
      <div className="overscroll-none overflow-scroll h-48 w-full bg-white p-4">
        <p className="mb-4">Scroll inside this area, but notice that no bounce or scroll chaining occurs.</p>
        <img
          src="https://images.unsplash.com/photo-1511467687858-23d96c32e4ae"
          alt="Example Illustration"
          className="h-96 w-auto"
        />
      </div>
    </div>
  );
}

Default Overscroll Behavior

The overscroll-auto maintains the default overscoll behavior when you reach the end of a scrollable area.

This is a live editor. Play around with it!
export default function PreventParentOverscroll() {
  return (
    <div className="h-screen w-screen bg-gray-100">
      <div className="overscroll-auto overflow-auto h-48 w-full bg-white p-4">
        <p className="mb-4">Scroll inside this area, the default overscroll behaviour will work.</p>
        <img
          src="https://images.unsplash.com/photo-1511467687858-23d96c32e4ae"
          alt="Example Illustration"
          className="h-96 w-auto"
        />
      </div>
    </div>
  );
}

States and Responsiveness

Overscroll behavior often varies depending on user interaction or screen size. Tailwind CSS includes state variants like hover and focus utilities, as well as responsive prefixes, to handle overscroll conditions dynamically.

Hover and Focus States

Use state modifiers like hover and focus to conditionally apply the overscroll behavior utilities.

This is a live editor. Play around with it!
export default function HoverScrollBehavior() {
  return (
    <div className="h-screen w-screen bg-gray-100">
      <div tabindex="0" className="focus:overscroll-none overflow-auto h-48 w-full bg-white p-4">
        <p className="mb-4">No bounce or scroll chaining occurs when you focus and scroll inside this area.</p>
        <img
          src="https://images.unsplash.com/photo-1511467687858-23d96c32e4ae"
          alt="Example Illustration"
          className="h-96 w-auto"
        />
      </div>
    </div>
  );
}

Breakpoint Modifiers

Responsive layouts demand flexible overscroll settings. Tailwind lets you apply modifiers like sm:, lg:, or xl: to adjust overscroll based on user devices.

This is a live editor. Play around with it!
export default function ResponsiveOverscrollBehavior() {
  return (
    <div className="h-screen w-screen bg-gray-100">
      <div tabindex="0" className="sm:overscroll-none md:overscroll-auto lg:overscroll-contain overflow-auto h-48 w-full bg-white p-4">
        <p className="mb-4">Overscroll behavior will be different on different screens when you scroll inside this area.</p>
        <img
          src="https://images.unsplash.com/photo-1511467687858-23d96c32e4ae"
          alt="Example Illustration"
          className="h-96 w-auto"
        />
      </div>
    </div>
  );
}

Real World Examples

A responsive product gallery with horizontal scrolling and overscroll behavior to prevent page bouncing.

This is a live editor. Play around with it!
export default function ProductGallery() {
  const products = [
    {
      id: 1,
      name: "Leather Backpack",
      price: "$129.99",
      src: "https://images.unsplash.com/photo-1548036328-c9fa89d128fa",
      alt: "Brown leather backpack"
    },
    {
      id: 2,
      name: "Minimalist Watch",
      price: "$199.99",
      src: "https://images.unsplash.com/photo-1524592094714-0f0654e20314",
      alt: "Silver analog watch"
    },
    {
      id: 3,
      name: "Wireless Earbuds",
      price: "$159.99",
      src: "https://images.unsplash.com/photo-1590658268037-6bf12165a8df",
      alt: "White wireless earbuds"
    },
    {
      id: 4,
      name: "Canvas Sneakers",
      price: "$79.99",
      src: "https://images.unsplash.com/photo-1525966222134-fcfa99b8ae77",
      alt: "White canvas sneakers"
    },
    {
      id: 5,
      name: "Sunglasses",
      price: "$89.99",
      src: "https://images.unsplash.com/photo-1572635196237-14b3f281503f",
      alt: "Designer sunglasses"
    },
    {
      id: 6,
      name: "Phone Case",
      price: "$24.99",
      src: "https://images.unsplash.com/photo-1541877944-ac82a091518a",
      alt: "Clear phone case"
    }
  ];

  return (
    <div className="w-full p-4">
      <div className="overscroll-x-contain overflow-x-auto flex space-x-4">
        {products.map((product) => (
          <div key={product.id} className="flex-none w-64">
            <img 
              src={product.src} 
              alt={product.alt}
              className="w-full h-48 object-cover rounded-lg"
            />
            <h3 className="mt-2 font-semibold">{product.name}</h3>
            <p className="text-gray-600">{product.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

Social Media Feed

A social media feed with smooth scrolling and overscroll behavior control.

This is a live editor. Play around with it!
export default function SocialFeed() {
  const posts = [
    {
      id: 1,
      author: "Jane Cooper",
      avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
      content: "Just finished my morning hike! 🏃‍♀️",
      image: "https://images.unsplash.com/photo-1551632811-561732d1e306",
      likes: 234,
      comments: 45
    },
    {
      id: 2,
      author: "John Smith",
      avatar: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e",
      content: "Perfect coffee morning ☕",
      image: "https://images.unsplash.com/photo-1495474472287-4d71bcdd2085",
      likes: 567,
      comments: 89
    },
  ];

  return (
    <div className="max-w-xl mx-auto p-4">
      <div className="overscroll-y-contain h-screen overflow-y-auto space-y-6">
        {posts.map((post) => (
          <div key={post.id} className="bg-white rounded-xl shadow-md p-4">
            <div className="flex items-center space-x-3">
              <img 
                src={post.avatar} 
                alt={post.author}
                className="w-10 h-10 rounded-full"
              />
              <span className="font-medium">{post.author}</span>
            </div>
            <img 
              src={post.image} 
              alt="Post content"
              className="mt-4 w-full h-64 object-cover rounded-lg"
            />
            <p className="mt-4">{post.content}</p>
            <div className="mt-4 flex space-x-4 text-gray-500">
              <span>{post.likes} likes</span>
              <span>{post.comments} comments</span>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

Dashboard Analytics Layout

A dual-scroll dashboard interface featuring a metrics sidebar and main content area.

This is a live editor. Play around with it!
const DashboardLayout = () => {
  const metrics = [
    {
      id: 1,
      category: "Revenue",
      items: [
        { id: "r1", name: "Monthly Growth", value: "+24.5%" },
        { id: "r2", name: "Year-to-Date", value: "$847,245" },
        { id: "r3", name: "Projected Q4", value: "$1.2M" },
        { id: "r4", name: "Average Order", value: "$95.20" },
        { id: "r5", name: "Refund Rate", value: "3.2%" },
        { id: "r6", name: "Net Revenue", value: "$782,150" }
      ]
    },
    {
      id: 2,
      category: "User Engagement",
      items: [
        { id: "u1", name: "Active Users", value: "24.3K" },
        { id: "u2", name: "Session Duration", value: "4m 12s" },
        { id: "u3", name: "Bounce Rate", value: "28.5%" },
        { id: "u4", name: "New Signups", value: "1,242" },
        { id: "u5", name: "Return Rate", value: "64.8%" },
        { id: "u6", name: "Pages/Session", value: "3.8" }
      ]
    },
    {
      id: 3,
      category: "Performance",
      items: [
        { id: "p1", name: "Load Time", value: "1.2s" },
        { id: "p2", name: "Server Uptime", value: "99.99%" },
        { id: "p3", name: "Error Rate", value: "0.05%" },
        { id: "p4", name: "API Latency", value: "145ms" },
        { id: "p5", name: "Cache Hit Rate", value: "94.2%" },
        { id: "p6", name: "CPU Usage", value: "42%" }
      ]
    }
  ];

  const reports = [
    {
      id: 1,
      title: "Revenue Breakdown",
      type: "chart",
      image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71",
      description: "Monthly revenue analysis with breakdown by product category"
    },
    {
      id: 2,
      title: "User Activity Patterns",
      type: "heatmap",
      image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f",
      description: "Hourly user engagement patterns across different time zones"
    },
    {
      id: 3,
      title: "Performance Metrics",
      type: "stats",
      image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71",
      description: "Key performance indicators and system health metrics"
    },
    {
      id: 4,
      title: "Geographic Distribution",
      type: "map",
      image: "https://images.unsplash.com/photo-1526778548025-fa2f459cd5c1",
      description: "User distribution and activity across global regions"
    },
    {
      id: 5,
      title: "Conversion Funnel",
      type: "funnel",
      image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71",
      description: "Step-by-step analysis of user conversion paths"
    },
    {
      id: 6,
      title: "Resource Usage",
      type: "gauge",
      image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f",
      description: "System resource utilization and capacity planning"
    }
  ];

  return (
    <div className="w-[450px] h-[400px] bg-gray-50 flex">
      {/* Scrollable Sidebar */}
      <div className="w-44 bg-white border-r border-gray-200 h-full overflow-y-auto overscroll-contain">
        <div className="pl-3 pt-2">
          <h2 className="text-lg font-semibold text-gray-800 mb-4">Metrics</h2>
          <div className="space-y-6">
            {metrics.map((section) => (
              <div key={section.id}>
                <h3 className="text-sm font-medium text-gray-500 mb-2">
                  {section.category}
                </h3>
                <div className="space-y-2">
                  {section.items.map((item) => (
                    <button
                      key={item.id}
                      className="w-full text-left px-2 py-1.5 rounded text-sm hover:bg-blue-50 hover:text-blue-600 transition-colors"
                    >
                      <div className="font-medium">{item.name}</div>
                      <div className="text-xs text-gray-500">{item.value}</div>
                    </button>
                  ))}
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Scrollable Main Content */}
      <div className="flex-1 h-full overflow-y-auto overscroll-contain">
        <div className="p-4">
          <div className="flex justify-between flex-col mb-6">
            <h1 className="text-md font-bold text-gray-800">Analytics Overview</h1>
            <button className="w-28 py-2 my-2 bg-blue-500 text-white rounded-lg text-sm hover:bg-blue-600 transition-colors">
              Export Data
            </button>
          </div>

          <div className="grid grid-cols-1 gap-4">
            {reports.map((report) => (
              <div
                key={report.id}
                className="bg-white rounded-lg shadow-sm p-3 hover:shadow-md transition-shadow w-40"
              >
                <div className="flex flex-col items-start gap-4">
                  <img
                    src={report.image}
                    alt={report.title}
                    className=" rounded object-cover"
                  />
                  <div>
                    <h3 className="font-semibold text-gray-800">{report.title}</h3>
                    <p className="text-sm text-gray-500 mt-1">{report.description}</p>
                    <div className="flex flex-col items-start gap-2 mt-2">
                      <button className="py-1 text-blue-500 hover:text-blue-600 text-xs">
                        View Details →
                      </button>
                    </div>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

export default DashboardLayout;

Photography Portfolio

A photography portfolio with vertical scrolling and contained overscroll behavior, perfect for photography portfolios.

This is a live editor. Play around with it!
const ImageGallery = () => {
  const images = [
    {
      id: 1,
      src: "https://images.unsplash.com/photo-1469474968028-56623f02e42e",
      alt: "Nature landscape",
      category: "Landscape",
      height: "h-64"
    },
    {
      id: 2,
      src: "https://images.unsplash.com/photo-1551316679-9c6ae9dec224",
      alt: "Street photography",
      category: "Urban",
      height: "h-48"
    },
    {
      id: 3,
      src: "https://images.unsplash.com/photo-1426604966848-d7adac402bff",
      alt: "Mountain view",
      category: "Landscape",
      height: "h-72"
    },
    {
      id: 4,
      src: "https://images.unsplash.com/photo-1470093851219-69951fcbb533",
      alt: "Portrait",
      category: "People",
      height: "h-56"
    },
    {
      id: 5,
      src: "https://images.unsplash.com/photo-1447752875215-b2761acb3c5d",
      alt: "Forest",
      category: "Nature",
      height: "h-64"
    },
    {
      id: 6,
      src: "https://images.unsplash.com/photo-1465146344425-f00d5f5c8f07",
      alt: "Architecture",
      category: "Urban",
      height: "h-48"
    }
  ];

  return (
    <div className="w-[370px] bg-black p-4">
      <h2 className="text-xl font-bold text-white mb-4">Photography Portfolio</h2>
      <div className="h-96 overflow-y-auto overscroll-contain px-2">
        <div className="columns-2 gap-4">
          {images.map((image) => (
            <div key={image.id} className="break-inside-avoid mb-4">
              <div className="relative group">
                <img 
                  src={image.src}
                  alt={image.alt}
                  className={`w-full ${image.height} object-cover rounded-lg`}
                />
                <div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-50 transition-all duration-300 rounded-lg">
                  <div className="absolute bottom-4 left-4 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300">
                    <p className="font-medium">{image.category}</p>
                  </div>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

export default ImageGallery;

Documentation Sidebar

A documentation sidebar with sections and controlled overscroll behavior.

This is a live editor. Play around with it!
export default function DocumentationSidebar() {
  const sections = [
    {
      id: 1,
      title: "Getting Started",
      items: [
        "Installation",
        "Quick Start",
        "Configuration",
        "Prerequisites",
        "Environment Setup",
        "Basic Usage"
      ]
    },
    {
      id: 2,
      title: "Core Concepts",
      items: [
        "Architecture",
        "Components",
        "State Management",
        "Routing",
        "Data Flow",
        "Security"
      ]
    },
  ];

  return (
    <div className="w-64 bg-gray-50 h-screen">
      <div className="p-4 border-b">
        <h2 className="text-xl font-bold">Documentation</h2>
      </div>
      
      <div className="overscroll-y-contain h-[calc(100vh-64px)] overflow-y-auto">
        {sections.map((section) => (
          <div key={section.id} className="p-4">
            <h3 className="font-semibold text-gray-700 mb-2">
              {section.title}
            </h3>
            <ul className="space-y-2">
              {section.items.map((item, index) => (
                <li 
                  key={index}
                  className="text-gray-600 hover:text-gray-900 cursor-pointer"
                >
                  {item}
                </li>
              ))}
            </ul>
          </div>
        ))}
      </div>
    </div>
  );
}

Best Practices

Maintain Design Consistency

Maintaining design consistency is crucial when applying overscroll behavior to your application. By ensuring that scrolling interactions are predictable and uniform across all components, you enhance the user experience.

Use consistent Tailwind CSS classes like overscroll-auto, overscroll-contain, or overscroll-none in components prone to scroll overflow, such as modals, scrollable sidebars, or nested containers. This consistency ensures that your user's expectations for scroll behavior are met across the board.

Leverage Utility Combinations

Tailwind CSS thrives on its ability to compose meaningful designs using combinations of utilities. Combine overscroll behavior utilities with complementary classes governing overflow, flexbox, or padding to achieve highly adaptable and reusable layouts. For instance, overscroll-none fits naturally with overflow-auto for isolating a container's scroll logic while ensuring its layout remains flexible.

Consider how overscroll-x-none pairs seamlessly with snap-x to create horizontally scrollable galleries where scroll snapping is active but bounce effects are suppressed. For vertical content, coupling overscroll-contain with overflow-y-scroll in modals eliminates container-parent conflicts. These pairings prioritize both usability and performance.

Accessibility Considerations

Enhance Readability and Navigability

Overscroll behavior plays a direct role in preserving text readability and content organization, especially for users with motor impairments or limited visual focus. A scrolled bounce effect can often disrupt users trying to read content, so using overscroll-contain enhances focus.

When designing navigable regions like documentation pages or multi-column articles, suppress overscroll propagation between containers (via overscroll-none) to create isolated, easily traversed sections. Use spacing and alignment classes like text-center, leading-loose, and p-4 to improve the relationship between scrollable elements and the content housed within.

Support Accessible Interactive Elements

Interactive UI elements, such as sliders, dropdowns, and expandable containers, benefit from tailored overscroll behavior to reduce motion-related fatigue. Applying overscroll-none to a parent dropdown limits scroll propagation, ensuring the interaction focuses purely on the dropdown logic rather than page displacement.

Well-structured overscroll utilities not only prioritize accessibility but can guide users smoothly through interactive components without visual or tactile disorientation.