Scroll Area
A scrollable region with block scrollbars, reachable by keyboard.
Installation
npx shadcn@latest add @runqora/craft-scroll-areaUsage
import { ScrollArea } from "@/components/craft/scroll-area";
<ScrollArea className="h-64" label="Saved worlds">
...
</ScrollArea>;The area needs a size to scroll within, so set a height for vertical scrolling or a width for horizontal scrolling.
Examples
Horizontal
Props
Prop
Type
Accessibility
- The scrolling region can be reached with Tab and scrolled with the arrow keys.
- Give it a
labelthat says what it holds, since a screen reader user lands on it directly. - The custom scrollbars work with a pointer. The keyboard and touch use the browser's native scrolling.
Customization
The scrollbar is as wide as five units, and the thumb uses the secondary tokens.
<ScrollArea className="[--craft-secondary:#8c8d90]">...</ScrollArea>Dependencies
@base-ui/react- shadcn's
utils, andcraft-theme
Source
"use client";
import { ScrollArea as BaseScrollArea } from "@base-ui/react/scroll-area";
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";
function Scrollbar({
orientation,
}: {
orientation: "horizontal" | "vertical";
}) {
return (
<BaseScrollArea.Scrollbar
className={cn(
"bg-craft-sunken flex",
orientation === "vertical"
? "border-craft-outline w-[calc(var(--craft-unit)*5)] border-l-(length:--craft-unit)"
: "border-craft-outline h-[calc(var(--craft-unit)*5)] border-t-(length:--craft-unit)"
)}
data-slot="scroll-area-scrollbar"
orientation={orientation}
>
<BaseScrollArea.Thumb
className={cn(
"craft-bevel [--bevel-depth:0px] [--bevel-edge:transparent] [--bevel-face:var(--craft-secondary)] [--bevel-hi:var(--craft-secondary-hi)] [--bevel-lo:var(--craft-secondary-lo)] hover:[--bevel-face:var(--craft-secondary-hover)]",
orientation === "vertical" ? "w-full" : "h-full"
)}
data-slot="scroll-area-thumb"
/>
</BaseScrollArea.Scrollbar>
);
}
export interface ScrollAreaProps
extends ComponentProps<typeof BaseScrollArea.Root> {
/**
* Names the scrolling region for screen readers. It can be reached with Tab
* and scrolled with the arrow keys, so it should say what it holds.
*/
label?: string;
/** Which directions show a scrollbar. */
orientation?: "vertical" | "horizontal" | "both";
}
export function ScrollArea({
children,
className,
label,
orientation = "vertical",
...props
}: ScrollAreaProps) {
return (
<BaseScrollArea.Root
className={cn(
"border-craft-outline relative flex min-h-0 overflow-hidden border-(length:--craft-unit)",
className
)}
data-slot="scroll-area"
{...props}
>
<BaseScrollArea.Viewport
aria-label={label}
className="focus-visible:craft-focus size-full outline-none focus-visible:-outline-offset-2"
role={label ? "group" : undefined}
tabIndex={0}
>
<BaseScrollArea.Content>{children}</BaseScrollArea.Content>
</BaseScrollArea.Viewport>
{orientation !== "horizontal" && <Scrollbar orientation="vertical" />}
{orientation !== "vertical" && <Scrollbar orientation="horizontal" />}
{orientation === "both" && (
<BaseScrollArea.Corner className="bg-craft-sunken" />
)}
</BaseScrollArea.Root>
);
}