Goal: Bring the June 2026 shadcn/ui chat component set to shadcn-vue, preserving the same composable API surface and behavior semantics, adapted to Vue 3 / Reka UI / Tailwind CSS v4 conventions used by shadcn-vue.
Scope
Port the following components/utilities:
MessageScroller — headful + headless scroll container for chat transcripts.
Attachment — file/image attachment card with upload state and actions.
Marker — status / system / separator row for streaming, date breaks, tool activity.
scroll-fade — scroll-aware edge-fade utility.
shimmer — text shimmer utility for live status.
These should appear in shadcn-vue under /docs/components/message-scroller, /docs/components/message, /docs/components/bubble, /docs/components/attachment, /docs/components/marker, and the CSS utilities under /docs/utils/scroll-fade, /docs/utils/shimmer.
Cross-cutting requirements
Vue 3 Composition API only (<script setup lang="ts">).
Reka UI primitives where applicable (ScrollArea for the viewport if it helps, but the React version uses a custom scroller; behavior is the priority).
Tailwind CSS v4 canonical classes; ship styles in shadcn/tailwind.css-equivalent or per-component CSS.
TypeScript with exported types/interfaces.
No any; use unknown + guards.
Accessibility: scroll region focusable, live region for new rows, aria-busy during streaming, aria-label on icon-only actions.
Scroll container that owns the hard parts of chat UX: anchored turns, streamed replies, saved-thread restore, prepended history, jump-to-message, scroll controls, and visibility tracking. It does not own messages, AI state, transport, or model state.
Parts (mirroring React API)
Part
Vue component
Responsibility
MessageScrollerProvider
MessageScrollerProvider.vue (or composable)
Owns scroll state and behavior props.
MessageScroller
MessageScroller.vue
Styled frame; lays out viewport, content, controls.
On prepend, preserve the previously visible row's scroll position (do not jump).
MessageScrollerButton
Scrolls toward the nearest unread/latest direction.
Becomes inert and tabindex="-1" when there is nothing to scroll toward.
Exposes data-active for styling.
Key behaviors to implement
Anchoring turns: When a new scrollAnchor item is appended, move it near the top of the viewport, keeping scrollPreviousItemPeek of the previous item visible.
Follow live edge: With autoScroll, only follow when the reader is already at the bottom. Scrolling away releases follow.
Prepend preservation: Loading older messages above must not move the current view.
Jump to message:scrollToMessage(id) scrolls to the item with matching messageId; can queue if not yet mounted.
Opening position:'last-anchor' shows the last anchored turn; falls back to 'end' if no anchor or it already fits.
Performance: Keep scroll hot path outside Vue reactivity where possible; mirror scroll state to data-* attributes instead of forcing re-renders.
2. Message
Purpose
Lays out a single message row: avatar, header, content, footer, alignment, and grouping.
Actions (download, remove) are clickable without triggering the card-level action.
Accessible: icon-only action buttons need aria-label.
5. Marker
Purpose
Renders status updates, system notes, bordered rows, and labeled separators for streaming state, tool activity, date breaks, typing indicators, errors.
variant="separator" renders a centered label with horizontal lines.
variant="bordered" renders a bordered pill/card row.
Default variant renders an inline icon + text row.
Use role="status" for in-progress streaming markers so assistive tech announces updates.
6. Utilities
scroll-fade
A scroll-aware edge-fade utility applied via a utility class or CSS custom property. Should fade the top/bottom (or start/end) edges of a scroll container when content overflows.
.scroll-fade {
mask-image: linear-gradient(
to bottom,
transparent,
black 1rem,
black calc(100% - 1rem),
transparent
);
}
Support directional variants: scroll-fade-t, scroll-fade-b, scroll-fade-x.
shimmer
Text shimmer for live status text ("Thinking…", "Generating response…").
Marker supports default, separator, and bordered variants with role="status".
scroll-fade and shimmer utilities ship with the components or globally.
All components pass TypeScript strict mode and have no any.
Light and dark mode both look correct.
Docs pages published on shadcn-vue.com matching the React docs structure.
Open questions for maintainers
Should shadcn-vue implement the headless behavior package (@shadcn/react equivalent, e.g. a composable-only package) or ship behavior inline in the registry components?
Which Reka UI primitive (if any) should back the scroll viewport — a custom scroller or ScrollArea?
Should scroll-fade/shimmer be global CSS utilities or scoped to component styles?
Is there a preferred Vue animation library for entrance animations (the React docs show motion)?