Skip to content
Frontend
Skill

/message-list

Build chat, messaging, and AI conversation UIs with @virtuoso.dev/message-list. Use this skill when (1) building a chat or messaging interface, (2) streaming AI assistant responses into a conversation, (3) loading older messages when scrolling up, (4) adding scroll-to-bottom

BOOST
From plugin
react-virtuoso
6.5k4 skills
Install
$ npx -y skills add petyosi/react-virtuoso --skill message-list --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/message-list

Context preview

The summary Claude sees to decide when to auto-load this skill.

Build chat, messaging, and AI conversation UIs with @virtuoso.dev/message-list. Use this skill when (1) building a chat or messaging interface, (2) streaming AI assistant responses into a conversation, (3) loading older messages when scrolling up, (4) adding scroll-to-bottom

SKILL.md

message-list.SKILL.md
name: message-list
description: >-
  Build chat, messaging, and AI conversation UIs with @virtuoso.dev/message-list. Use this skill when (1) building a chat or
  messaging interface, (2) streaming AI assistant responses into a conversation, (3) loading older messages when scrolling up,
  (4) adding scroll-to-bottom buttons or unseen-message indicators, (5) switching between channels/conversations, or (6) any task
  involving VirtuosoMessageList, VirtuosoMessageListLicense, useVirtuosoMethods, useVirtuosoLocation, scrollModifier,
  notifyItemsChanged, scrollToBottomIfAtBottom, scrollToBottomAlways, smooth-scroll retargeting, replacement anchors, or
  data.append/prepend/map/replace. The package is commercial and requires a license key.

@virtuoso.dev/message-list

`VirtuosoMessageList` is a virtualized list purpose-built for human and AI chat: stick-to-bottom behavior, streaming responses that grow without scroll jumps, history prepending that preserves the visual position, and scroll position tracking. Use it instead of plain `Virtuoso` when building conversation UIs — these behaviors are built in rather than hand-assembled.

Licensing

The package is commercial (annual, per-developer). Every instance must be wrapped in `VirtuosoMessageListLicense`:

<VirtuosoMessageListLicense licenseKey={licenseKey}>
  <VirtuosoMessageList ... />
</VirtuosoMessageListLicense>

An empty `licenseKey=""` is licensed for 30-day non-production evaluation: it keeps rendering in development with a console reminder, while production without a valid key renders an error. Validation is local — no network requests. Surface this to the user when introducing the package into a project; keys come from <https://virtuoso.dev/pricing/>. Prefer an app-level wrapper that reads from the normal public client-side environment configuration.

Core mental model: data updates carry scroll instructions

Instead of separate imperative scroll calls, each data update includes a `scrollModifier` describing how the viewport should react:

const [data, setData] = useState<VirtuosoMessageListProps<Message, null>['data']>(() => ({
  data: initialMessages,
  scrollModifier: { type: 'item-location', location: { index: 'LAST', align: 'end' } },
}))

<VirtuosoMessageList<Message, null> style={{ height: '100%' }} data={data} computeItemKey={({ data }) => data.key} ItemContent={ItemContent} />

`ItemContent` is a component receiving `{ data, index, context }` props (not positional arguments like react-virtuoso). Extract it outside the parent render function unless it needs to close over local state.

External row state is different from data updates. If `context`, side maps, expansion state, reactions, approvals, or loading flags can change row height without changing the data array, call `notifyItemsChanged({ scrollToBottom })` on the message-list ref after updating that state.

Scroll modifier reference

| Modifier | When to use | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | | `{ type: 'item-location', location, purgeItemSizes? }` | Initial load or channel switch; `purgeItemSizes: true` clears cached sizes when the items are different | | `{ type: 'auto-scroll-to-bottom', autoScroll }` | New messages are appended; callback can return `false`, a behavior, `true`, or an item location | | `'prepend'` | Older messages added to the top; keeps the current messages visually in place | | `{ type: 'items-change', behavior }` | Existing data items changed size, such as streaming text or reactions stored in the data item | | `'remove-from-start'` / `'remove-from-end'` | Trimming data while preserving the visual position | | `null` / `undefined` | Leave the scroll position alone |

In callback form, returning a behavior or item location scrolls even when `atBottom` is false. Return `false` to preserve the viewport for a scrolled-up user.

Common patterns

Receiving messages (auto-scroll when at bottom)

import { scrollToBottomIfAtBottom } from '@virtuoso.dev/message-list'

setData((current) => ({
  data: [...current.data, incoming],
  scrollModifier: {
    type: 'auto-scroll-to-bottom',
    autoScroll: scrollToBottomIfAtBottom,
  },
}))

Use `scrollToBottomIfAtBottom` for remote messages. It returns `'smooth'` when the list is already at the bottom or already scrolling there, otherwise `false`. If the app needs an unseen-message counter, use the inline callback form and increment the counter before returning `false`.

Sending local messages (force bottom)

import { scrollToBottomAlways } from '@virtuoso.dev/message-list'

setData((current) => ({
  data: [...current.data, optimisticLocalMessage],
  scrollModifier: {
    type: 'auto-scroll-to-bottom',
    autoScroll: scrollToBottomAlways,
  },
}))

Use `scrollToBottomAlways` for local current-user actions where the new last item should become visible even if the user was scrolled up. If a realtime socket echoes the same optimistic message back, reconcile it by `localId` or another client-generated identity and treat it as confirmation, not a new incoming remote message.

Streaming an AI response

Append an empty assistant message, then grow it as tokens arrive:

setData((current) => ({
  data: current.data.map((msg) => (msg.key === botKey ? { ...msg, text: msg.text + chunk } : msg)),
  scrollModifier: { type: 'items-change', behavior: 'smooth' },
}))

`items-change` keeps the view pinn

Read more
Ships withreact-virtuoso

The most complete React virtualization rendering family of components. Variable sized items out of the box; no manual measurements or hard-coding item heights is necessary; Chat message list UI; Grouped mode with sticky headers; Responsive grid layout;

Get the whole plugin
Stats
6,464
Stars
357
Forks
Active
Maintenance
TypeScript
Language
6d ago
Last commit
7y ago
Created
16h ago
Added

Repo: petyosi/react-virtuoso

Other skills on react-virtuoso.