HextaUI

Scroll area

Minimal bir kaydırma çubuğuyla yerel kaydırma, yalnızca görülecek daha fazla şey olan yerde solan kenarlar ve son öğeyi ikiye bölen isteğe bağlı bir göz atma payı.

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

const names = [
  "Olivia Martin",
  "Jackson Lee",
  "Isabella Nguyen",
  "William Kim",
  "Sofia Davis",
  "Liam Patel",
  "Emma Garcia",
  "Noah Wilson",
]
const roles = ["Design", "Engineering", "Product", "Support"]

const people = Array.from({ length: 40 }, (_, index) => ({
  id: index + 1,
  name: names[index % names.length],
  role: roles[index % roles.length],
}))

export function ScrollAreaDemo() {
  return (
    <ScrollArea peek className="h-80 w-full max-w-sm rounded-lg border">
      <ul className="flex flex-col gap-1 p-2">
        {people.map((person) => (
          <li
            key={person.id}
            className="flex items-center justify-between gap-3 rounded-md px-3 py-2.5 text-sm hover:bg-muted"
          >
            <span className="truncate font-medium">
              {person.id}. {person.name}
            </span>
            <span className="text-muted-foreground">{person.role}</span>
          </li>
        ))}
      </ul>
    </ScrollArea>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/scroll-area.json

Bileşeni, HextaUI tema token'larını ve bileşenin bağımlı olduğu tüm HextaUI bileşenlerini ekler.

Kaydırma alanına sabit bir yükseklik veya genişlik verin. Ötesindeki içerik yerel kaydırma davranışıyla kayar ve hover ya da kaydırma sırasında ince bir kaydırma çubuğu belirir.

import { ScrollArea } from "@/components/ui/scroll-area"
<ScrollArea className="h-72 rounded-lg border">
  <div className="p-4">{/* long content */}</div>
</ScrollArea>

Peek

İki listenin yüksekliği aynıdır. Sağdaki peek ile kutu kendini kırpar; böylece son görünen öğe yarıda kesilir ve kimse denemeden önce kaydırılacak daha fazlası olduğunu gösterir. Kutuyu yüksekliğinin yarısının altına asla kırpmaz.

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

const names = [
  "Olivia Martin",
  "Jackson Lee",
  "Isabella Nguyen",
  "William Kim",
  "Sofia Davis",
  "Liam Patel",
  "Emma Garcia",
  "Noah Wilson",
]
const roles = ["Design", "Engineering", "Product", "Support"]

const people = Array.from({ length: 40 }, (_, index) => ({
  id: index + 1,
  name: names[index % names.length],
  role: roles[index % roles.length],
}))

function PeopleList() {
  return (
    <ul className="flex flex-col gap-1 p-2">
      {people.map((person) => (
        <li
          key={person.id}
          className="flex items-center justify-between gap-3 rounded-md px-3 py-2.5 text-sm hover:bg-muted"
        >
          <span className="truncate font-medium">
            {person.id}. {person.name}
          </span>
          <span className="text-muted-foreground">{person.role}</span>
        </li>
      ))}
    </ul>
  )
}

export function ScrollAreaPeek() {
  return (
    <div className="grid w-full max-w-xl grid-cols-2 gap-4">
      <ScrollArea className="h-80 rounded-lg border">
        <PeopleList />
      </ScrollArea>
      <ScrollArea peek className="h-80 rounded-lg border">
        <PeopleList />
      </ScrollArea>
    </div>
  )
}

Metin

Kenarlar yalnızca kaydırılacak daha fazla içerik olan yerde solar. Üstteyken yalnızca alt solar ve bir kenardan uzaklaştıkça solma büyür.

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

export function ScrollAreaText() {
  return (
    <ScrollArea className="h-48 w-full max-w-md rounded-lg border">
      <div className="flex flex-col gap-3 p-4 text-sm leading-6">
        {Array.from({ length: 6 }, (_, index) => (
          <p key={index}>
            Vernacular architecture is building done outside any academic
            tradition, and without professional guidance. It reflects local
            traditions, materials and climate, and makes up most of the world’s
            built environment.
          </p>
        ))}
      </div>
    </ScrollArea>
  )
}

Alttan başla

viewportRef size kayan öğeyi verir. Bir sohbeti en son mesajda başlatmak için onun scrollTop değerini ayarlayın; böylece yalnızca üst kenar solar.

"use client"

import * as React from "react"

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

export function ScrollAreaChat() {
  const viewportRef = React.useRef<HTMLDivElement>(null)

  React.useLayoutEffect(() => {
    const viewport = viewportRef.current
    if (viewport) {
      viewport.scrollTop = viewport.scrollHeight
    }
  }, [])

  return (
    <ScrollArea
      viewportRef={viewportRef}
      aria-label="Messages"
      className="h-56 w-full max-w-sm rounded-lg border"
    >
      <div className="flex flex-col gap-2 p-3 text-sm">
        {Array.from({ length: 24 }, (_, index) => (
          <p
            key={index}
            className={
              index % 3 === 0
                ? "self-end rounded-lg bg-primary px-3 py-2 text-primary-foreground"
                : "self-start rounded-lg bg-muted px-3 py-2"
            }
          >
            Message {index + 1}
          </p>
        ))}
      </div>
    </ScrollArea>
  )
}

Yatay

scrollbars="horizontal" ayarlayın ve içeriğin kutunun ötesine büyüyebilmesi için ona w-max verin.

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

const tags = [
  "Design systems",
  "Motion",
  "Accessibility",
  "Typography",
  "Color",
  "Layout",
  "Forms",
  "Data tables",
  "Charts",
  "Navigation",
  "Overlays",
  "Feedback",
]

export function ScrollAreaHorizontal() {
  return (
    <ScrollArea
      scrollbars="horizontal"
      className="w-full max-w-md rounded-lg border"
    >
      <div className="flex w-max gap-2 p-3">
        {tags.map((tag) => (
          <span
            key={tag}
            className="rounded-full border px-3 py-1 text-sm whitespace-nowrap"
          >
            {tag}
          </span>
        ))}
      </div>
    </ScrollArea>
  )
}

Her iki eksen

scrollbars="both" her iki kaydırma çubuğunu ve kesiştikleri bir köşeyi gösterir ve dört kenarı da soldurur.

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

export function ScrollAreaBoth() {
  return (
    <ScrollArea
      scrollbars="both"
      className="h-64 w-full max-w-md rounded-lg border"
    >
      <div className="grid w-max grid-cols-[repeat(10,6rem)] gap-2 p-3">
        {Array.from({ length: 100 }, (_, index) => (
          <div
            key={index}
            className="grid aspect-square place-items-center rounded-md bg-muted text-sm"
          >
            {index + 1}
          </div>
        ))}
      </div>
    </ScrollArea>
  )
}

Dinamik içerik

Öğeler eklendikçe veya kaldırıldıkça peek ve solmalar güncellenir. Kaydırılacak kadar öğe yoksa kutu yüksekliğini korur ve hiçbir şey solmaz.

"use client"

import * as React from "react"

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

export function ScrollAreaDynamic() {
  const [count, setCount] = React.useState(4)

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <ScrollArea peek className="h-48 rounded-lg border">
        <ul className="flex flex-col gap-1 p-2">
          {Array.from({ length: count }, (_, index) => (
            <li key={index} className="rounded-md bg-muted px-3 py-3 text-sm">
              Item {index + 1}
            </li>
          ))}
        </ul>
      </ScrollArea>
      <div className="flex gap-2">
        <Button size="sm" onClick={() => setCount(count + 1)}>
          Add item
        </Button>
        <Button
          size="sm"
          variant="ghost"
          onClick={() => setCount(Math.max(0, count - 1))}
        >
          Remove item
        </Button>
      </div>
    </div>
  )
}

Renkli bir yüzeyde

Solma, bindirme rengi değil içerik üzerinde bir maskedir; bu yüzden her arka planda çalışır.

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

const names = [
  "Olivia Martin",
  "Jackson Lee",
  "Isabella Nguyen",
  "William Kim",
  "Sofia Davis",
  "Liam Patel",
  "Emma Garcia",
  "Noah Wilson",
]
const roles = ["Design", "Engineering", "Product", "Support"]

const people = Array.from({ length: 40 }, (_, index) => ({
  id: index + 1,
  name: names[index % names.length],
  role: roles[index % roles.length],
}))

export function ScrollAreaColoredSurface() {
  return (
    <div className="w-full max-w-sm rounded-xl bg-muted p-2">
      <ScrollArea className="h-56">
        <ul className="flex flex-col gap-1 p-2">
          {people.map((person) => (
            <li
              key={person.id}
              className="flex items-center justify-between gap-3 rounded-md px-3 py-2.5 text-sm hover:bg-background"
            >
              <span className="truncate font-medium">
                {person.id}. {person.name}
              </span>
              <span className="text-muted-foreground">{person.role}</span>
            </li>
          ))}
        </ul>
      </ScrollArea>
    </div>
  )
}

Sheet içinde

Bir flex sütunu içinde onu min-h-0 flex-1 olan bir öğeyle sarın ve kalan alanı doldurması için kaydırma alanına h-full verin.

import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/ui/scroll-area"
import {
  Sheet,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"

const names = [
  "Olivia Martin",
  "Jackson Lee",
  "Isabella Nguyen",
  "William Kim",
  "Sofia Davis",
  "Liam Patel",
  "Emma Garcia",
  "Noah Wilson",
]
const roles = ["Design", "Engineering", "Product", "Support"]

const people = Array.from({ length: 40 }, (_, index) => ({
  id: index + 1,
  name: names[index % names.length],
  role: roles[index % roles.length],
}))

export function ScrollAreaSheet() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="outline" />}>
        Open members
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Members</SheetTitle>
        </SheetHeader>
        <div className="min-h-0 flex-1 px-4 pb-4">
          <ScrollArea peek className="h-full">
            <ul className="flex flex-col gap-1 p-2">
              {people.map((person) => (
                <li
                  key={person.id}
                  className="flex items-center justify-between gap-3 rounded-md px-3 py-2.5 text-sm hover:bg-muted"
                >
                  <span className="truncate font-medium">
                    {person.id}. {person.name}
                  </span>
                  <span className="text-muted-foreground">{person.role}</span>
                </li>
              ))}
            </ul>
          </ScrollArea>
        </div>
      </SheetContent>
    </Sheet>
  )
}

Solmasız

Kenar solmalarını fade={false} ile kapatın.

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

const names = [
  "Olivia Martin",
  "Jackson Lee",
  "Isabella Nguyen",
  "William Kim",
  "Sofia Davis",
  "Liam Patel",
  "Emma Garcia",
  "Noah Wilson",
]
const roles = ["Design", "Engineering", "Product", "Support"]

const people = Array.from({ length: 40 }, (_, index) => ({
  id: index + 1,
  name: names[index % names.length],
  role: roles[index % roles.length],
}))

export function ScrollAreaWithoutFade() {
  return (
    <ScrollArea fade={false} className="h-40 w-full max-w-sm rounded-lg border">
      <ul className="flex flex-col gap-1 p-2">
        {people.map((person) => (
          <li
            key={person.id}
            className="flex items-center justify-between gap-3 rounded-md px-3 py-2.5 text-sm hover:bg-muted"
          >
            <span className="truncate font-medium">
              {person.id}. {person.name}
            </span>
            <span className="text-muted-foreground">{person.role}</span>
          </li>
        ))}
      </ul>
    </ScrollArea>
  )
}

Sağdan sola

İçerik sağ kenardan başlar; kaydırma çubuğu ve solmalar okuma yönünü izler, yön sayfadan alınır.

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

const tags = [
  "أنظمة التصميم",
  "الحركة",
  "إمكانية الوصول",
  "الطباعة",
  "الألوان",
  "التخطيط",
  "النماذج",
  "جداول البيانات",
  "الرسوم البيانية",
  "التنقل",
]

export function ScrollAreaRtl() {
  return (
    <div dir="rtl" className="w-full max-w-md">
      <ScrollArea scrollbars="horizontal" className="rounded-lg border">
        <div className="flex w-max gap-2 p-3">
          {tags.map((tag) => (
            <span
              key={tag}
              className="rounded-full border px-3 py-1 text-sm whitespace-nowrap"
            >
              {tag}
            </span>
          ))}
        </div>
      </ScrollArea>
    </div>
  )
}

Viewport sekme sırasına yalnızca kaydıracak bir şeyi olduğunda girer. Odaklandığında tarayıcının yerel tuşlarıyla kayar.

TuşAction
Tabİçeriği taştığında viewport'a odaklanır.
↑↓Dikey kaydırır.
←→Yatay kaydırır.
Page UpPage DownSpaceBir sayfa kadar kaydırır.
HomeEndBaşa veya sona atlar.
  • Amacı çevresindeki içerikten anlaşılmıyorsa, sohbet örneğindeki “Messages” etiketi gibi, kaydırma alanına bir aria-label verin.
  • Odak halkası kaydırma alanının kendisine çizilir, böylece solmuş kenarların üzerinde görünür kalır.

Base UI scroll area üzerine kuruludur. ScrollArea kök, viewport, içerik ve kaydırma çubuklarını birlikte işler.

PropTürVarsayılan
scrollbars
"vertical" | "horizontal" | "both""vertical"
fadeDaha fazla içeriği olan kenarları soldurur.
booleantrue
peekSon görünen öğe yarıda kesilsin diye yüksekliği kırpar. Hangi öğelerin sayılacağını seçmek için öğeleri data-scroll-area-item ile işaretleyin.
booleanfalse
viewportRefKayan öğe.
Ref<HTMLDivElement>–
overflowEdgeThresholdBir kenarın taşıyor sayılması için kaydırılacak piksel.
number | { xStart, xEnd, yStart, yEnd }0
className
string | (state) => string–
render
ReactElement | (props, state) => ReactElement<div>
ÖznitelikAçıklama
data-slot="scroll-area"Kök.
data-slot="scroll-area-viewport"Kayan öğe.
data-slot="scroll-area-content"İçeriğinizi viewport içine sarar.
data-peekpeek açıkken bulunur.
data-peekingpeek yüksekliği kırparken bulunur.
data-scrollingKullanıcı kaydırırken bulunur.
data-has-overflow-xİçerik viewport'tan genişse bulunur.
data-has-overflow-yİçerik viewport'tan uzunsa bulunur.
data-overflow-y-startÜstte daha fazla içerik olduğunda bulunur. Diğer kenarlar için eşleşen -y-end, -x-start ve -x-end nitelikleri vardır.
--scroll-area-fadeKenar solmasının boyutu. Varsayılan 2.5rem.
--scroll-area-overflow-y-startÜst kenardan piksel cinsinden uzaklık. Eşleşen -y-end, -x-start ve -x-end değişkenleri de vardır.

ScrollArea tarafından sizin için işlenir. Yalnızca Base UI parçalarını kendiniz birleştiriyorsanız dışa aktarın.

PropTürVarsayılan
orientation
"vertical" | "horizontal""vertical"
render
ReactElement | (props, state) => ReactElement<div>
ÖznitelikAçıklama
data-slot="scroll-area-scrollbar"Kaydırma çubuğu izi.
data-slot="scroll-area-thumb"Sürüklenebilir başparmak.
data-orientationvertical veya horizontal.
data-hoveringİmleç alanın üzerindeyken bulunur.
data-scrollingKullanıcı kaydırırken bulunur.
--scroll-area-thumb-heightBaşparmağın yüksekliği.
--scroll-area-thumb-widthBaşparmağın genişliği.