Chat
MessageList
A conversation: Messages in a scrolling, labeled region that opens at the newest one. While the reader is at the bottom it follows new messages and streaming text; once they scroll up, nothing moves their view, and a "Jump to latest" button appears. Each new message from someone else is read to screen readers once, politely: a streamed reply when it finishes, and a first reply in a list that mounted empty. History is never announced: the messages it mounts with, several messages filling an empty list at once (history fetched after mount), older messages loaded above, and a message whose key changes in place (a temporary id swapped for the server's). Older messages loaded above a scrolled-up reader leave the message they are reading where it was. Scrolling is never announced.
Install
Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):
npx shadcn@latest init --base base --preset nova --yes --no-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):
npx shadcn@latest add ourostack/ouro-ui/message-list#v1.2.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/message-listIts tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):
npx shadcn@latest add ourostack/ouro-ui/message-list#v1.2.13 ourostack/ouro-ui/message-list-tests#v1.2.13import { MessageList } from "@/components/ouro/message/message-list";Paths use shadcn's default @/ alias; the CLI rewrites them to yours.
Example
<MessageList label="Conversation with Ouro" className="h-[60dvh]">
{messages.map((message) => (
<Message key={message.id} role={message.role} name={message.author} streaming={message.streaming}>
{message.text}
</Message>
))}
</MessageList>Usage
Give it a height (className="h-96", or flex-1 in a column), since it scrolls inside it.
Use instead when
- Use a plain list instead for messages that do not arrive live, such as an inbox.
Props
Type: MessageListProps.
children(required)- Type:
ReactNode - The
Messages, oldest first. className- Type:
string - Layout only: the list's height (
h-96,flex-1), margins. It scrolls inside that height, so give it one. Appearance classes warn in development. jumpLabel- Type:
string - Default:
"Jump to latest" - The button that appears once the reader scrolls up, for other languages.
label(required)- Type:
string - What the conversation is, for screen readers: "Conversation with Ouro". Required, so the region has a name.
newMessageLabel- Type:
((author: string, text: string) => string) - Default: (author, text) =>
${author}: ${text} - What screen readers hear when a new message arrives, for other languages. It gets the author's name and the message's text. The user's own messages are not read back.
ref- Type:
Ref<HTMLDivElement> - Reaches the rendered element.
Native attributes
Also accepts the attributes in React's HTMLAttributes (id, aria-*, event handlers), passed to the rendered element, except those named on this page.
Don't use
These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.
autoScroll: Not a prop. The list follows new messages while the reader is at the bottom, and stops when they scroll up.stickToBottom: Not a prop. The list follows new messages while the reader is at the bottom, and stops when they scroll up.
className is for layout only
Pass margins, width and grid placement (mt-4, w-full, col-span-2). Your classes always win over Ouro UI's: MessageList merges className last with cn (tailwind-merge), so a class that conflicts with its own replaces it.layout-classes
Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.
Alignment classes (text-center) warn too: set alignment on a parent element.
Claim sources
- layout-classes: test:registry/ouro/components/ouro/message/message-list.test.tsx::MessageList > forwards a ref to the root and lets app layout classes win
verify:testExists(registry/ouro/components/ouro/message/message-list.test.tsx::MessageList > forwards a ref to the root and lets app layout classes win)