Scroll Area
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.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
contentClassName | string | — | Clases del contenido, dentro del viewport. Ahí va el padding. |
orientation | "horizontal" | "vertical" | "both" | "vertical" | vertical (default) · horizontal · both, que agrega la esquina. |
viewportClassName | string | — | Clases del viewport: el elemento que scrollea y recibe el foco. |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ScrollAreaScrollbar
Hereda las props de ScrollArea.Scrollbar.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | 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
divconoverflownativo: 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-containevita 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.