Ir al contenido
Contenido y datos

ScrollArea

Una caja con scroll y una barra propia, discreta: aparece al pasar el mouse o al scrollear.

import { ScrollArea, ScrollAreaScrollbar } from "sebs7n-ui/scroll-area"

Ejemplos

Una lista larga dentro de un panel

La caja necesita un alto propio: sin límite no hay desborde y no hay nada que scrollear. El padding va en `contentClassName`, no en el viewport, para que el contenido no se corte contra la barra.

import { ScrollArea } from "sebs7n-ui/scroll-area"
import { Separator } from "sebs7n-ui/separator"

function Basico() {
  return (
    <ScrollArea className="h-56 w-full max-w-sm rounded-surface border border-separator bg-surface" contentClassName="p-3">
      <div className="flex flex-col">
        {MOVIMIENTOS.map((movimiento, indice) => (
          <div key={movimiento.detalle + movimiento.fecha}>
            {indice > 0 && <Separator className="my-2" />}
            <div className="flex items-baseline justify-between gap-4">
              <div className="flex min-w-0 flex-col">
                <span className="truncate text-callout text-label">{movimiento.detalle}</span>
                <span className="text-callout text-label-secondary">{movimiento.fecha}</span>
              </div>
              <span className="shrink-0 text-mono-body text-label-secondary">{movimiento.monto}</span>
            </div>
          </div>
        ))}
      </div>
    </ScrollArea>
  )
}

Los dos ejes

`orientation="both"` agrega la barra de abajo y la esquina entre las dos. Se pone solo cuando de verdad desborda en los dos ejes; si no, sobra una barra.

import { ScrollArea } from "sebs7n-ui/scroll-area"

function DosEjes() {
  const columnas = ["Cliente", "CUIT", "Condición IVA", "Provincia", "Último pago", "Saldo"]
  const filas = [
    ["Acme S.A.", "30-71234567-8", "Responsable Inscripto", "Santa Fe", "01/09/2026", "$ 0"],
    ["Bruma SRL", "30-71987654-3", "Monotributo", "Buenos Aires", "29/08/2026", "$ 96.000"],
    ["Cortina SA", "30-70111222-9", "Responsable Inscripto", "Córdoba", "24/08/2026", "$ 0"],
    ["Delta", "27-33444555-1", "Exento", "Mendoza", "18/08/2026", "$ 74.500"],
    ["Everest SRL", "30-71555444-2", "Monotributo", "Santa Fe", "11/08/2026", "$ 33.900"],
    ["Fénix SA", "30-70999888-7", "Responsable Inscripto", "Neuquén", "04/08/2026", "$ 128.000"],
  ]
  return (
    <ScrollArea
      className="h-48 w-full max-w-sm rounded-surface border border-separator bg-surface"
      contentClassName="p-3"
      orientation="both"
    >
      <table className="w-max border-separate border-spacing-x-6 border-spacing-y-1 text-left">
        <thead>
          <tr>
            {columnas.map((columna) => (
              <th className="text-footnote whitespace-nowrap text-label-secondary" key={columna} scope="col">
                {columna}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {filas.map((fila) => (
            <tr key={fila[0]}>
              {fila.map((celda) => (
                <td className="text-callout whitespace-nowrap text-label" key={celda}>
                  {celda}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </ScrollArea>
  )
}

Solo horizontal

Una fila de tarjetas que se corre de costado. Con `orientation="horizontal"` no se dibuja la barra vertical, que acá no tendría nada que hacer.

import { ScrollArea } from "sebs7n-ui/scroll-area"

function Horizontal() {
  const meses = ["Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre"]
  return (
    <ScrollArea className="w-full max-w-sm" contentClassName="pb-3" orientation="horizontal">
      <div className="flex w-max gap-3">
        {meses.map((mes, indice) => (
          <div className="flex w-40 flex-col gap-1 rounded-surface border border-separator bg-surface p-4" key={mes}>
            <span className="text-footnote text-label-secondary">{mes}</span>
            <span className="text-title-3 text-label">$ {(420 + indice * 37).toLocaleString("es-AR")}k</span>
          </div>
        ))}
      </div>
    </ScrollArea>
  )
}

Props

Generadas del TypeScript del paquete. Las propias del componente, más las heredadas del primitivo que tienen algo que explicar —marcadas «heredada de Base UI»—. El resto está en la línea «hereda de».

ScrollArea

Hereda las props de ScrollArea.Root.

PropTipoPor defectoDescripción
contentClassNamestring—Clases del contenido, dentro del viewport. Ahí va el padding.
orientation"horizontal" | "vertical" | "both""vertical"vertical (default) · horizontal · both, que agrega la esquina.
viewportClassNamestring—Clases del viewport: el elemento que scrollea y recibe el foco.
classNamestring—Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana.

ScrollAreaScrollbar

Hereda las props de ScrollArea.Scrollbar.

PropTipoPor defectoDescripción
classNamestring—Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana.

Teclado

Tab
Llega al viewport cuando hay desborde.
↑ ↓ ← →
Scrollean, como en cualquier caja con overflow.
Re Pág · Av Pág · Inicio · Fin
Saltan de a una pantalla o a los extremos.

Accesibilidad

  • Adentro hay un div con overflow nativo: la rueda, el trackpad y el arrastre táctil funcionan como siempre. Lo único que cambia es que se oculta la barra del sistema.
  • Base UI le pone tabIndex={0} al viewport cuando hay desborde, así que se llega con Tab y se scrollea con las flechas. Por eso el foco es visible.
  • En táctil la barra propia no se muestra: ahí la nativa ya es un overlay que aparece y se va.
  • overscroll-contain evita que el scroll se escape a la página al llegar al final.

Reglas de uso

  • No para la página entera. El scroll del documento es del navegador; esto es para una caja: una lista dentro de un panel, un log, una tabla ancha.
  • La caja necesita un alto (o un ancho) propio: sin límite no hay desborde y no hay nada que scrollear.
  • El padding va en contentClassName, no en el viewport: si no, el contenido se corta contra la barra.
  • orientation="both" solo cuando de verdad desborda en los dos ejes; si no, sobra una barra.

Relacionados