# Scroll fade

> Edges of a scroll area that fade only when there's more to see, driven by the scroll position in CSS alone.

Docs: https://hextaui.com/docs/scroll-fade
Markdown: https://hextaui.com/docs/scroll-fade.md

```tsx title="components/examples/scroll-fade/demo.tsx"
const releases = Array.from({ length: 24 }, (_, index) => `v2.${24 - index}.0`)

export function ScrollFadeDemo() {
  return (
    <div className="w-full max-w-xs rounded-xl bg-muted">
      <ul
        tabIndex={0}
        aria-label="Releases"
        className="h-64 scroll-fade overflow-y-auto rounded-xl p-2 text-sm outline-none focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden"
      >
        {releases.map((release) => (
          <li
            key={release}
            className="flex h-9 items-center justify-between rounded-md px-3"
          >
            <span className="font-mono">{release}</span>
            <span className="text-muted-foreground">Released</span>
          </li>
        ))}
      </ul>
    </div>
  )
}
```

## Installation

Scroll fade is a set of Tailwind utilities from shadcn's `tailwind.css`. The HextaUI theme already imports it, so if you've added the theme or any component, you have it.

```bash
npx shadcn@latest add https://hextaui.com/r/theme.json
```

Without the theme, install `shadcn` and import its CSS after Tailwind.

```bash
pnpm add shadcn
```

```css title="app/globals.css"
@import "tailwindcss";
@import "shadcn/tailwind.css";
```

## Usage

```tsx
<div className="scroll-fade h-64 overflow-y-auto">…</div>
<div className="flex scroll-fade-x overflow-x-auto">…</div>
```

Add it to any element that scrolls. An edge fades only while there is more content past it, so a cut-off row signals "keep scrolling" and a list at rest keeps crisp edges.

## How it works

The fade is a `mask-image`, so content dissolves into whatever is behind it. There's no overlay gradient to match to the background, and it works on images, tinted surfaces and glass. A CSS scroll-driven animation grows each edge's fade over the first and last 96px of scrolling. No JavaScript runs, and it never re-renders.

- Each fade is 12% of the container, capped at 40px.
- Browsers without scroll-driven animations show both fades all the time. It's a little less precise, but still reads as scrollable.
- `scroll-fade-x`, `scroll-fade-s` and `scroll-fade-e` follow the writing direction, so the start fade sits on the right in right-to-left layouts.

## Examples

### Horizontal

A row of filters that scrolls sideways. Pair it with `no-scrollbar`, also from shadcn's CSS, when the fade alone shows that there's more.

```tsx title="components/examples/scroll-fade/horizontal.tsx"
const topics = [
  "All",
  "Design",
  "Engineering",
  "Product",
  "Research",
  "Marketing",
  "Sales",
  "Support",
  "Operations",
  "Finance",
]

export function ScrollFadeHorizontal() {
  return (
    <div
      tabIndex={0}
      role="region"
      aria-label="Topics"
      className="no-scrollbar flex w-full max-w-sm scroll-fade-x gap-2 overflow-x-auto rounded-md outline-none focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden"
    >
      {topics.map((topic) => (
        <span
          key={topic}
          className="shrink-0 rounded-full bg-muted px-3 py-1.5 text-sm"
        >
          {topic}
        </span>
      ))}
    </div>
  )
}
```

### One edge

A chat starts at the bottom, so only older messages above need a hint. `scroll-fade-t` fades the top alone, and `scroll-fade-t-16` makes it taller.

```tsx title="components/examples/scroll-fade/edge.tsx"
"use client"

const messages = Array.from({ length: 16 }, (_, index) => ({
  id: index,
  text: index % 3 === 0 ? "Sounds good, ship it." : "Pushed the fix to main.",
}))

export function ScrollFadeEdge() {
  return (
    <div className="w-full max-w-xs rounded-xl bg-muted">
      <ul
        ref={(node) => {
          if (node) {
            node.scrollTop = node.scrollHeight
          }
        }}
        tabIndex={0}
        aria-label="Messages"
        className="flex h-64 scroll-fade-t flex-col gap-2 overflow-y-auto rounded-xl p-3 text-sm outline-none scroll-fade-t-16 focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden"
      >
        {messages.map((message) => (
          <li
            key={message.id}
            className="w-fit max-w-[80%] shrink-0 rounded-2xl bg-background px-3 py-2 even:self-end even:bg-primary even:text-primary-foreground"
          >
            {message.text}
          </li>
        ))}
      </ul>
    </div>
  )
}
```

### Size and reveal distance

```tsx
<div className="scroll-fade scroll-fade-16 overflow-y-auto">…</div>
<div className="scroll-fade scroll-fade-[20%] overflow-y-auto">…</div>
<div className="scroll-fade [--scroll-fade-reveal:12rem] overflow-y-auto">…</div>
```

Sizes take the spacing scale or any length or percentage. `--scroll-fade-reveal` sets how far you scroll before an edge reaches its full fade.

## Good to know

- The mask fades the element's own background and border too. Put the surface on a wrapper and the fade on the scrolling child, as the examples do.
- A mask hides everything near the edge, including focus rings. Give the scrolling element some padding so focused items aren't faded out.
- Use [Scroll area](https://hextaui.com/docs/scroll-area) instead when you also want custom scrollbars. It has its own fades, measured with JavaScript, which work in every browser.

## API reference

| Class | Description |
| --- | --- |
| `scroll-fade, scroll-fade-y` | Fades the top and bottom edges. |
| `scroll-fade-x` | Fades the start and end edges, following direction. |
| `scroll-fade-t, scroll-fade-b` | Fades only the top or bottom edge. |
| `scroll-fade-s, scroll-fade-e` | Fades only the start or end edge. |
| `scroll-fade-l, scroll-fade-r` | Fades only the left or right edge, ignoring direction. |
| `scroll-fade-<size>` | Size of every fade. |
| `scroll-fade-{t,b,s,e}-<size>` | Size of one edge's fade. |
| `scroll-fade-none` | Turns the fade off, for example at a breakpoint. |
| `--scroll-fade-reveal` | Scroll distance over which an edge fades in. Defaults to 96px. |

## Notes for AI assistants

- Install a component with the shadcn CLI: `npx shadcn@latest add https://hextaui.com/r/<name>.json`. It adds the source, the HextaUI theme tokens and any HextaUI components it depends on. `https://hextaui.com/r/all.json` installs every component.
- The code is then owned by the project, like shadcn/ui. There is no HextaUI npm package. HextaUI is MIT licensed and free for personal and commercial use.
- Behavior and accessibility come from Base UI (`@base-ui/react`). Compose with the `render` prop, not `asChild`.
- Styling uses Tailwind CSS v4 with theme tokens. Merge classes with `cn` from the `cn` package.
- Icons come from `@tabler/icons-react`.
- Import components from `@/components/ui/<name>`, hooks from `@/hooks/<name>` and utilities from `@/lib/<name>`.

Every HextaUI doc: https://hextaui.com/llms.txt
