Refined UI

An opinionated, customizable superset of shadcn/ui

AgentsPrimitivesSkillsSkins
Guides
  • Overview
  • Get started
  • Shadcn compatibility
  • Architecture
  • Agent surface
Primitives
  • Accordion
  • Alert
  • Alert dialog
  • Aspect ratio
  • Autocomplete
  • Avatar
  • Badge
  • Button
  • Button group
  • Calendar
  • Card
  • Checkbox
  • Checkbox Group
  • Code
  • Code Diff
  • Collapsible
  • Color Picker
  • Combobox
  • Command
  • Date Picker
  • Dialog
  • Drawer
  • Empty
  • Field
  • Form
  • Gradient Editor
  • Hover card
  • Input
  • Input group
  • Input OTP
  • Item
  • Kbd
  • Markdown
  • Menu
  • Menubar
  • Meter
  • Native select
  • Navigation menu
  • Number Field
  • Pagination
  • Popover
  • Progress
  • Radio group
  • Resizable
  • Scroll area
  • Select
  • Sidebar
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Table
  • Table of contents
  • Tabs
  • Textarea
  • Toast
  • Toggle
  • Toolbar
  • Tooltip
  • Tree
  • Trigger Menu
  • Typography
Hooks
  • Use Chat Message
  • Use Chat Input
  • Use User Ask
  • Use Audio Recorder
  • Use Environment Variables
  • Use Copy To Clipboard
  • Use Tool Call
  • Use Sidebar Resize
Utils
  • cn
  • Skin
  • Contracts
  • Serialize
Extensions
  • Control Effects
  • View Transition
  • Send Aurora
  1. Primitives
  2. Scroll area
Primitives

Scroll area

Scroll container with overlay scrollbars and edge fades.

Composition

The preferred shape for composing the installed primitive from its exported parts.

Root

Single-slot surface with no nested parts.

ScrollArea

The refined source installs this primitive from src/registry/sources/refined/ui/scroll-area.tsx. Install it on its own with the command above, or inspect the source below.

Registry command
npx shadcn@latest add http://127.0.0.1:3000/r/refined/scroll-area.json
See registry manifest

Raw code

Primary installed primitive source

Base UI Scroll area slot
src/registry/sources/refined/ui/scroll-area.tsx
"use client";
​
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
import type { CSSProperties } from "react";
import type { ScrollAreaProps } from "@/components/refined-ui/contracts";
import { cn } from "@/components/refined-ui/lib/cn";
import { skinSlot } from "@/components/refined-ui/skin";
​
// Edge fade driven by Base UI overflow flags — fades only sides that actually clip content.
const maskBaseClasses = cn(
"[mask-image:linear-gradient(to_right,var(--scroll-mask-x-start,#000),#000_var(--scroll-fade-size),#000_calc(100%_-_var(--scroll-fade-size)),var(--scroll-mask-x-end,#000)),linear-gradient(to_bottom,var(--scroll-mask-y-start,#000),#000_var(--scroll-fade-size),#000_calc(100%_-_var(--scroll-fade-size)),var(--scroll-mask-y-end,#000))]",
"[mask-composite:intersect] [-webkit-mask-composite:source-in]",
);
​
const maskXOverflowClasses = cn(
"data-[overflow-x-start]:[--scroll-mask-x-start:transparent]",
"data-[overflow-x-end]:[--scroll-mask-x-end:transparent]",
);
​
const maskYOverflowClasses = cn(
"data-[overflow-y-start]:[--scroll-mask-y-start:transparent]",
"data-[overflow-y-end]:[--scroll-mask-y-end:transparent]",
);
​
const scrollbarVisibilityClasses = {
always: "opacity-100",
hover: "opacity-0 transition-opacity duration-[var(--duration-fast)] data-[hovering]:opacity-100 data-[scrolling]:opacity-100",
scroll: "opacity-0 transition-opacity duration-[var(--duration-fast)] data-[scrolling]:opacity-100",
};
​
function Scrollbar({
orientation,
visibility,
}: {
orientation: "vertical" | "horizontal";
visibility: ScrollAreaProps["scrollbarVisibility"];
}) {
return (
<ScrollAreaPrimitive.Scrollbar
orientation={orientation}
data-ui="agent"
data-slot="scroll-area-scrollbar"
className={cn(
"m-px flex touch-none select-none",
orientation === "vertical" ? "w-1.5 justify-center" : "h-1.5 flex-col",
scrollbarVisibilityClasses[visibility ?? "scroll"],
skinSlot("scroll-area-scrollbar", { orientation }),
)}
>
<ScrollAreaPrimitive.Thumb
data-ui="agent"
data-slot="scroll-area-thumb"
className={cn(
"flex-1 rounded-full bg-foreground/25 transition-colors hover:bg-foreground/40",
orientation === "vertical" ? "w-full" : "h-full",
skinSlot("scroll-area-thumb", {}),
)}
/>
</ScrollAreaPrimitive.Scrollbar>
);
}
​
export function ScrollArea({
className,
viewportClassName,
maxHeight,
mask = true,
lockAxis,
scrollbarVisibility = "scroll",
children,
...props
}: ScrollAreaProps) {
const lockX = lockAxis === "x" || lockAxis === "both";
const lockY = lockAxis === "y" || lockAxis === "both";
const viewportStyle: CSSProperties | undefined =
maxHeight || lockX || lockY
? {
...(maxHeight ? { maxHeight } : {}),
...(lockX ? { overflowX: "hidden" } : {}),
...(lockY ? { overflowY: "hidden" } : {}),
}
: undefined;
​
return (
<ScrollAreaPrimitive.Root
data-ui="agent"
data-slot="scroll-area"
className={cn("relative overflow-hidden", skinSlot("scroll-area", {}), className)}
{...props}
>
{/* Overscroll left at default (chaining) so wheel falls through to parent when viewport can't scroll further; `overscroll-contain` would trap it. */}
<ScrollAreaPrimitive.Viewport
className={cn(
"h-full w-full rounded-[inherit] outline-none",
mask && maskBaseClasses,
mask && !lockX && maskXOverflowClasses,
mask && !lockY && maskYOverflowClasses,
viewportClassName,
)}
style={viewportStyle}
>
<ScrollAreaPrimitive.Content style={{ minWidth: 0 }}>{children}</ScrollAreaPrimitive.Content>
</ScrollAreaPrimitive.Viewport>
{!lockY && <Scrollbar orientation="vertical" visibility={scrollbarVisibility} />}
{!lockX && <Scrollbar orientation="horizontal" visibility={scrollbarVisibility} />}
{!lockX && !lockY && (
<ScrollAreaPrimitive.Corner
data-ui="agent"
data-slot="scroll-area-corner"
className={cn("bg-transparent", skinSlot("scroll-area-corner", {}))}
/>
)}
</ScrollAreaPrimitive.Root>
);
}
​

On this page

  • Preview
  • Composition
  • Installation
  • Source
  1. 01Streamed the first tokens back to the client.
  2. 02Resolved the tool call against the registry.
  3. 03Normalized the attachment into plain markdown.
  4. 04Grouped the reasoning steps by intent.
  5. 05Checked each candidate against the contract.
  6. 06Kept the smallest change that satisfied all of them.
  7. 07Re-ran the validator on the generated fixture.
  8. 08Mirrored the manifest into the public registry.
  9. 09Synced the installed fixture from the refined source.
  10. 10Measured the panel height for the open animation.
  11. 11Faded the scroll edges that actually clip content.
  12. 12Committed the result and moved to the next turn.