Display

Edge Shadow Scroll

A scroll area where the edge shadows appear only on the side that still has content, done in pure CSS: four background layers instead of a scroll handler.

  • scrollarea
  • shadow
  • css
  • display

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/scrollarea-002?lang=en

Work queue
  • Пересобрать реестр
  • Проверить типы
  • Обновить снимки превью
  • Написать описание блока
  • Перевести metadata
  • Прогнать линтер
  • Свести конфликты
  • Выложить на стенд
  • Позвать на ревью
  • Закрыть задачу
Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "scrollarea-002" (Edge Shadow Scroll) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/scrollarea-002.json

Registry item: https://vibeui.ru/r/scrollarea-002.json
Installs to: components/vibeui/scrollarea-002.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
A scroll area where the edge shadows appear only on the side that still has content, done in pure CSS: four background layers instead of a scroll handler.

A scroll area with edge hints: the shadows are four background layers that reveal themselves, with no onScroll. Zero dependencies, one file, server rendered.

## 3. How to use it
import { Scrollarea002 } from "@/components/vibeui/scrollarea-002"

<Scrollarea002 title="Work queue" items={["Rebuild the registry", "Check the types"]} height="13rem" />

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-scrollarea-002-* palette — do not swap it for your theme tokens
- the order and attachment of the four background layers: reordering breaks the whole trick
- the opaque cover colour — on a transparent ground the shadow is always visible and the hint lies
- tabindex="0" and role="region" with aria-label on the scroller: without them keyboard users cannot scroll it
- overscroll-behavior: contain so the scroll does not fall through to the page
- its own light surface: the shadows are measured against its colour

## 6. You may change
- the heading through title
- the list content through items
- the area height through height
- the shadow strength through the shadow colour variable

## 7. Rules
- The trick only works on an opaque background: through a glass surface the shadow always shows.
- This variant draws no horizontal shadows — for a sideways rail the layers must be rewritten on X.
- Overly tall covers eat the first rows of the list: keep them at about two rows.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/scrollarea-002.json
https://vibeui.ru/r/scrollarea-002.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-scrollarea-002-*. Серверный: хуков и обработчиков нет. Приём держится на background-attachment: две «крышки» цвета подложки объявлены как local и едут вместе с содержимым, две радиальные тени объявлены как scroll и приколоты к рамке. Пока лента не сдвинута, крышка накрывает тень; при прокрутке крышка уезжает и тень открывается. Прокручиваемый узел получает tabindex="0" и role="region" с aria-label: без них область нельзя пролистать с клавиатуры и она не попадает в список ориентиров. overscroll-behavior: contain не даёт прокрутке провалиться на страницу.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Scrollarea002Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "children" | "title"
> & {
  title?: string
  items?: string[]
  height?: string
}

// Идея компонента: тени у краёв появляются только с той стороны, где ещё есть
// содержимое, и делает это чистый CSS. Четыре слоя фона: две «крышки» цвета
// подложки прокручиваются вместе с содержимым (background-attachment:local),
// две тени приколоты к рамке (scroll). У верхнего края крышка накрывает тень,
// пока лента не сдвинута, — и уезжает, открывая её. Никакого onScroll.
const STYLES = `
:where([data-vibeui-block="scrollarea-002"]){
--vibeui-scrollarea-002-bg:oklch(1 0 0);
--vibeui-scrollarea-002-fg:oklch(0.24 0.014 265);
--vibeui-scrollarea-002-muted:oklch(0.55 0.014 265);
--vibeui-scrollarea-002-border:oklch(0.9 0.006 265);
--vibeui-scrollarea-002-shadow:oklch(0.24 0.014 265 / 16%);
--vibeui-scrollarea-002-accent:oklch(0.55 0.17 265);
--vibeui-scrollarea-002-height:13rem;
--vibeui-scrollarea-002-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="scrollarea-002"]{
display:flex;flex-direction:column;
width:100%;max-width:20rem;box-sizing:border-box;overflow:hidden;
background:var(--vibeui-scrollarea-002-bg);
border:1px solid var(--vibeui-scrollarea-002-border);border-radius:0.875rem;
font-family:var(--vibeui-scrollarea-002-font);color:var(--vibeui-scrollarea-002-fg);
}
[data-vibeui-block="scrollarea-002"] [data-part="head"]{
padding:0.625rem 0.875rem;border-bottom:1px solid var(--vibeui-scrollarea-002-border);
font-size:0.8125rem;font-weight:650;
}
/* Четыре слоя: крышки едут с содержимым, тени приколоты к рамке. */
[data-vibeui-block="scrollarea-002"] [data-part="area"]{
height:var(--vibeui-scrollarea-002-height);
overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;
background:
linear-gradient(var(--vibeui-scrollarea-002-bg) 30%,transparent) center top,
linear-gradient(transparent,var(--vibeui-scrollarea-002-bg) 70%) center bottom,
radial-gradient(farthest-side at 50% 0,var(--vibeui-scrollarea-002-shadow),transparent) center top,
radial-gradient(farthest-side at 50% 100%,var(--vibeui-scrollarea-002-shadow),transparent) center bottom;
background-repeat:no-repeat;
background-size:100% 2.25rem,100% 2.25rem,100% 0.6875rem,100% 0.6875rem;
background-attachment:local,local,scroll,scroll;
}
[data-vibeui-block="scrollarea-002"] [data-part="area"]:focus-visible{
outline:2px solid var(--vibeui-scrollarea-002-accent);outline-offset:-2px;
}
[data-vibeui-block="scrollarea-002"] ul{margin:0;padding:0.25rem 0;list-style:none}
[data-vibeui-block="scrollarea-002"] li{
display:flex;align-items:center;gap:0.5rem;
min-height:2.125rem;padding:0 0.875rem;font-size:0.8125rem;
}
[data-vibeui-block="scrollarea-002"] li span{
flex:none;width:0.375rem;height:0.375rem;border-radius:9999px;
background:var(--vibeui-scrollarea-002-muted);
}
@media (prefers-reduced-motion:reduce){
[data-vibeui-block="scrollarea-002"] *{animation:none!important;transition:none!important}
}
`

const DEFAULT_ITEMS = [
  "Пересобрать реестр",
  "Проверить типы",
  "Обновить снимки превью",
  "Написать описание блока",
  "Перевести metadata",
  "Прогнать линтер",
  "Свести конфликты",
  "Выложить на стенд",
  "Позвать на ревью",
  "Закрыть задачу",
]

/**
 * Область прокрутки, где тени у краёв нарисованы фоном без JS.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Scrollarea002({
  title = "Очередь работ",
  items = DEFAULT_ITEMS,
  height = "13rem",
  className,
  style,
  ...props
}: Scrollarea002Props) {
  const palette = {
    "--vibeui-scrollarea-002-height": height,
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-scrollarea-002" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="scrollarea-002"
        className={className}
        style={palette}
      >
        <div data-part="head">{title}</div>
        <div data-part="area" tabIndex={0} role="region" aria-label={title}>
          <ul>
            {items.map((item) => (
              <li key={item}>
                <span aria-hidden="true" />
                {item}
              </li>
            ))}
          </ul>
        </div>
      </div>
    </>
  )
}