Display

Sticky Group Headers

Область прокрутки, где заголовок раздела липнет к её верху, а не к верху страницы: сама область и есть предок прокрутки, от которого считается sticky.

  • scrollarea
  • sticky
  • list
  • display

Превью

1440pxHost theme

Использовать с AI

  1. 1. Скопируйте ссылку.
  2. 2. Напишите агенту своими словами и вставьте её в предложение.
  3. 3. Агент откроет ссылку и поставит компонент из registry.

размести это в шапке: https://vibeui.ru/c/scrollarea-004

Контакты
Сегодня3
Анна Реброва
Игорь Ким
Мария Пак
Вчера4
Дмитрий Гнатюк
Лиза Орлова
Пётр Савин
Тимур Хан
На прошлой неделе4
Ольга Дюжева
Сергей Мун
Юлия Ким
Артём Волошин
Показать полную инструкцию

То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.

# Установка и размещение «scrollarea-004» (Sticky Group Headers) из VibeUI

## 1. Сначала установка — не пропускай, не пересоздавай
Выполни эту команду до того, как писать код:
npx shadcn@latest add https://vibeui.ru/r/scrollarea-004.json

Registry item: https://vibeui.ru/r/scrollarea-004.json
Ставится в: components/vibeui/scrollarea-004.tsx (точный путь следует алиасам components.json этого проекта).
npm-зависимости: нет.
Registry-зависимости: нет.

Ставь из реестра. Не пересоздавай по описанию, не подменяй похожим
компонентом из другой библиотеки и не переписывай под стиль проекта.

## 2. Что это
Область прокрутки, где заголовок раздела липнет к её верху, а не к верху страницы: сама область и есть предок прокрутки, от которого считается sticky.

Область с липкими заголовками разделов: sticky считается от самой области, разметка — один dl без лишних обёрток. Ноль зависимостей, один файл, серверный рендер.

## 3. Как использовать
import { Scrollarea004 } from "@/components/vibeui/scrollarea-004"

<Scrollarea004
  title="Контакты"
  groups={[{ title: "Сегодня", items: ["Анна Реброва"] }]}
  height="14rem"
/>

Полный список пропсов — в установленном файле.

## 4. Куда поставить
Это мелкий inline-компонент. Поставь его ровно туда, куда просил
пользователь, внутрь существующей разметки. Не создавай ради него
новую страницу, секцию или обёртку. Если на этом месте уже стоит похожий
контрол — замени его, а не добавляй второй.

Куда поставить: ___
(Эту строку заполняет пользователь. Если она осталась пустой — спроси,
а не угадывай.)

## 5. Сохранить как установлено
- локальную палитру --vibeui-scrollarea-004-* и не подменять её токенами вашей темы
- плоскую разметку dl без обёрток вокруг групп: любой предок с overflow отменяет прилипание
- непрозрачный фон заголовка — сквозь прозрачный просвечивают уезжающие строки
- фиксированную высоту и overflow-y на самой области: без них sticky считает верх от страницы
- tabindex="0" и role="region" с aria-label на прокручиваемом узле
- собственную светлую подложку: без неё тёмный текст пропадает на тёмном фоне

## 6. Можно менять
- заголовок области через title
- разделы и их содержимое через groups
- высоту области через height
- цвет и высоту липкой полосы в CSS

## 7. Правила
- Заголовок остаётся на месте до конца своего раздела и уступает место следующему: это поведение sticky, не анимация.
- z-index у заголовка нужен, иначе строки следующего раздела перекрывают его при прокрутке.
- Группы в dl обёрнуты фрагментами: не заменяйте их на div, прилипание сломается.
- Не выносите CSS-переменные в globals.css: компонент обязан оставаться одним файлом.

## 8. Проверка
- рендерится без ошибок в консоли;
- выглядит так же, как превью на странице VibeUI, откуда взята ссылка;
- если нет — ты изменил что-то из раздела 5, верни как было.

Для разработчика

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

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-scrollarea-004-*. Серверный: хуков нет. Список собран как один dl: dt — заголовок раздела с position: sticky и top: 0, dd — строки; группы обёрнуты фрагментами, поэтому между dl и dt нет лишнего узла с overflow, который отменил бы прилипание. Sticky считает верх от ближайшего предка с прокруткой, поэтому область обязана быть этим предком — отсюда фиксированная высота и overflow-y на ней. Прокручиваемый узел получает tabindex="0" и role="region" с aria-label. Непрозрачный фон заголовка обязателен: сквозь прозрачный просвечивают уезжающие строки.

Исходник компонента

Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.

import { Fragment } from "react"
import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Scrollarea004Group = {
  title: string
  items: string[]
}

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

// Идея компонента: заголовок раздела липнет к верху области, а не страницы.
// Sticky считает верх от ближайшего предка с прокруткой, поэтому область
// обязана быть этим предком, а сам заголовок — не иметь предка с overflow.
// Отсюда плоская разметка: секция не оборачивает список в лишний контейнер.
const STYLES = `
:where([data-vibeui-block="scrollarea-004"]){
--vibeui-scrollarea-004-bg:oklch(1 0 0);
--vibeui-scrollarea-004-fg:oklch(0.24 0.014 265);
--vibeui-scrollarea-004-muted:oklch(0.55 0.014 265);
--vibeui-scrollarea-004-border:oklch(0.9 0.006 265);
--vibeui-scrollarea-004-sticky:oklch(0.965 0.004 265);
--vibeui-scrollarea-004-accent:oklch(0.55 0.17 265);
--vibeui-scrollarea-004-height:14rem;
--vibeui-scrollarea-004-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="scrollarea-004"]{
display:flex;flex-direction:column;
width:100%;max-width:20rem;box-sizing:border-box;overflow:hidden;
background:var(--vibeui-scrollarea-004-bg);
border:1px solid var(--vibeui-scrollarea-004-border);border-radius:0.875rem;
font-family:var(--vibeui-scrollarea-004-font);color:var(--vibeui-scrollarea-004-fg);
}
[data-vibeui-block="scrollarea-004"] [data-part="bar"]{
padding:0.625rem 0.875rem;border-bottom:1px solid var(--vibeui-scrollarea-004-border);
font-size:0.8125rem;font-weight:650;
}
[data-vibeui-block="scrollarea-004"] [data-part="area"]{
height:var(--vibeui-scrollarea-004-height);
overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;
}
[data-vibeui-block="scrollarea-004"] [data-part="area"]:focus-visible{
outline:2px solid var(--vibeui-scrollarea-004-accent);outline-offset:-2px;
}
[data-vibeui-block="scrollarea-004"] dl{margin:0}
/* Липкий заголовок раздела: верх считается от области, а не от страницы. */
[data-vibeui-block="scrollarea-004"] dt{
position:sticky;top:0;z-index:1;
display:flex;align-items:center;justify-content:space-between;gap:0.5rem;
padding:0.375rem 0.875rem;
background:var(--vibeui-scrollarea-004-sticky);
border-bottom:1px solid var(--vibeui-scrollarea-004-border);
font-size:0.6875rem;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;
color:var(--vibeui-scrollarea-004-muted);
}
[data-vibeui-block="scrollarea-004"] dd{
margin:0;display:flex;align-items:center;
min-height:2.125rem;padding:0 0.875rem;font-size:0.8125rem;
}
[data-vibeui-block="scrollarea-004"] dd + dd{border-top:1px solid oklch(0.95 0.004 265)}
@media (prefers-reduced-motion:reduce){
[data-vibeui-block="scrollarea-004"] *{animation:none!important;transition:none!important}
}
`

const DEFAULT_GROUPS: Scrollarea004Group[] = [
  { title: "Сегодня", items: ["Анна Реброва", "Игорь Ким", "Мария Пак"] },
  {
    title: "Вчера",
    items: ["Дмитрий Гнатюк", "Лиза Орлова", "Пётр Савин", "Тимур Хан"],
  },
  {
    title: "На прошлой неделе",
    items: ["Ольга Дюжева", "Сергей Мун", "Юлия Ким", "Артём Волошин"],
  },
]

/**
 * Область прокрутки, где заголовок раздела липнет к её верху.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Scrollarea004({
  title = "Контакты",
  groups = DEFAULT_GROUPS,
  height = "14rem",
  className,
  style,
  ...props
}: Scrollarea004Props) {
  const palette = {
    "--vibeui-scrollarea-004-height": height,
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-scrollarea-004" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="scrollarea-004"
        className={className}
        style={palette}
      >
        <div data-part="bar">{title}</div>
        <div data-part="area" tabIndex={0} role="region" aria-label={title}>
          <dl>
            {groups.map((group) => (
              <Fragment key={group.title}>
                <dt>
                  <span>{group.title}</span>
                  <span>{group.items.length}</span>
                </dt>
                {group.items.map((item) => (
                  <dd key={item}>{item}</dd>
                ))}
              </Fragment>
            ))}
          </dl>
        </div>
      </div>
    </>
  )
}