Scroll Area

Augments native scroll functionality for custom, cross-browser styling.

The Scroll Area component provides a custom-styled scrollable container. It hides native scrollbars and provides a clean, consistent scrolling experience across browsers. Built from scratch using React and native HTML elements. No dependencies on any UI library.

Code

TypeScript: Copy this code into components/ui/scroll-area.tsx:

tsx
"use client"

import * as React from "react"
import { cn } from "@/lib/utils"

interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
  children: React.ReactNode
}

const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
  ({ className, children, ...props }, ref) => {
    const viewportRef = React.useRef<HTMLDivElement>(null)
    const [showScrollbar, setShowScrollbar] = React.useState(false)

    React.useEffect(() => {
      const viewport = viewportRef.current
      if (!viewport) return

      const checkScroll = () => {
        const hasScroll = viewport.scrollHeight > viewport.clientHeight
        setShowScrollbar(hasScroll)
      }

      checkScroll()
      viewport.addEventListener("scroll", checkScroll)
      
      const resizeObserver = new ResizeObserver(checkScroll)
      resizeObserver.observe(viewport)

      return () => {
        viewport.removeEventListener("scroll", checkScroll)
        resizeObserver.disconnect()
      }
    }, [])

    return (
      <div
        ref={ref}
        className={cn("relative overflow-hidden", className)}
        {...props}
      >
        <div
          ref={viewportRef}
          className="h-full w-full rounded-[inherit] overflow-y-scroll scrollbar-hide"
        >
          {children}
        </div>
        {showScrollbar && (
          <ScrollBar orientation="vertical" />
        )}
      </div>
    )
  }
)
ScrollArea.displayName = "ScrollArea"

interface ScrollBarProps extends React.HTMLAttributes<HTMLDivElement> {
  orientation?: "vertical" | "horizontal"
}

const ScrollBar = React.forwardRef<HTMLDivElement, ScrollBarProps>(
  ({ className, orientation = "vertical", ...props }, ref) => {
    return (
      <div
        ref={ref}
        className={cn(
          "absolute right-0 top-0 z-10 flex touch-none select-none transition-colors",
          orientation === "vertical" &&
            "h-full w-2.5 border-l-2 border-foreground p-[1px]",
          orientation === "horizontal" &&
            "bottom-0 left-0 h-2.5 w-full flex-col border-t-2 border-foreground p-[1px]",
          className
        )}
        {...props}
      >
        <div className="relative flex-1 rounded-full bg-foreground/30" />
      </div>
    )
  }
)
ScrollBar.displayName = "ScrollBar"

export { ScrollArea, ScrollBar }

JavaScript: Copy this code into components/ui/scroll-area.jsx:

jsx
"use client"

import * as React from "react"
import { cn } from "@/lib/utils"

const ScrollArea = React.forwardRef(
  ({ className, children, ...props }, ref) => {
    const viewportRef = React.useRef(null)
    const [showScrollbar, setShowScrollbar] = React.useState(false)

    React.useEffect(() => {
      const viewport = viewportRef.current
      if (!viewport) return

      const checkScroll = () => {
        const hasScroll = viewport.scrollHeight > viewport.clientHeight
        setShowScrollbar(hasScroll)
      }

      checkScroll()
      viewport.addEventListener("scroll", checkScroll)
      
      const resizeObserver = new ResizeObserver(checkScroll)
      resizeObserver.observe(viewport)

      return () => {
        viewport.removeEventListener("scroll", checkScroll)
        resizeObserver.disconnect()
      }
    }, [])

    return (
      <div
        ref={ref}
        className={cn("relative overflow-hidden", className)}
        {...props}
      >
        <div
          ref={viewportRef}
          className="h-full w-full rounded-[inherit] overflow-y-scroll scrollbar-hide"
        >
          {children}
        </div>
        {showScrollbar && (
          <ScrollBar orientation="vertical" />
        )}
      </div>
    )
  }
)
ScrollArea.displayName = "ScrollArea"

const ScrollBar = React.forwardRef(
  ({ className, orientation = "vertical", ...props }, ref) => {
    return (
      <div
        ref={ref}
        className={cn(
          "absolute right-0 top-0 z-10 flex touch-none select-none transition-colors",
          orientation === "vertical" &&
            "h-full w-2.5 border-l-2 border-foreground p-[1px]",
          orientation === "horizontal" &&
            "bottom-0 left-0 h-2.5 w-full flex-col border-t-2 border-foreground p-[1px]",
          className
        )}
        {...props}
      >
        <div className="relative flex-1 rounded-full bg-foreground/30" />
      </div>
    )
  }
)
ScrollBar.displayName = "ScrollBar"

export { ScrollArea, ScrollBar }

Also add this CSS to your globals.css:

css
.scrollbar-hide {
  -ms-overflow-style: none;
  scrollbar-width: none;
}

.scrollbar-hide::-webkit-scrollbar {
  display: none;
}

Usage

TypeScript:

tsx
import { ScrollArea } from "@/components/ui/scroll-area"

function MyComponent() {
  return (
    <ScrollArea className="h-[200px] w-[350px] rounded-md border-2 border-foreground p-4">
      <div className="space-y-4">
        <p>Long content here...</p>
      </div>
    </ScrollArea>
  )
}

JavaScript:

jsx
import { ScrollArea } from "@/components/ui/scroll-area"

function MyComponent() {
  return (
    <ScrollArea className="h-[200px] w-[350px] rounded-md border-2 border-foreground p-4">
      <div className="space-y-4">
        <p>Long content here...</p>
      </div>
    </ScrollArea>
  )
}

Make sure you also have the lib/utils.ts file with the cn helper function.

Examples

Default

Things is a collection of beautifully designed components built from scratch. Each component is carefully crafted to provide a great user experience while maintaining accessibility and customization options.

All components are built using React and Tailwind CSS, with no external dependencies. This ensures fast performance and easy integration into your projects.

The design system follows a neobrutalism aesthetic with bold borders, shadows, and clean typography. Every component is fully accessible and follows web standards.

You can use these components as building blocks for your applications, customizing them to fit your specific needs. The code is clean, well-documented, and easy to understand.

With Custom Height

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20