Conversation
freeConversation keeps a growing entry stream anchored to its latest item until a reader deliberately scrolls away.
Preview
Dark
import { useState } from 'react';
import Conversation from '@/components/composites/Conversation';
import Button from '@/components/ui/Button';
const initialEntries = [
{ id: '1', text: 'Project brief saved.' },
{ id: '2', text: 'Jordan added a new research link.' },
{ id: '3', text: 'Review notes are ready for the next pass.' },
{ id: '4', text: 'Two open tasks were assigned to the design team.' },
{ id: '5', text: 'The staging deployment completed successfully.' },
{ id: '6', text: 'The activity feed now includes file changes.' },
{ id: '7', text: 'A new comment was added to the timeline.' },
{ id: '8', text: 'The weekly report is available for review.' },
];
export default function UsageDemo() {
const [entries, setEntries] = useState(initialEntries);
return (
<div className="space-y-2">
<Button
size="sm"
onClick={() => {
setEntries((current) => [
...current,
{
id: String(current.length + 1),
text: `New entry ${current.length + 1} was added.`,
},
]);
}}
>
Add entry
</Button>
<div className="h-72 overflow-hidden rounded-card border border-border bg-card">
<Conversation className="h-full">
{entries.map((entry, index) => (
<div
key={entry.id}
className="border-b border-border pb-3 last:border-b-0"
>
<p className="text-xs text-muted-foreground">
Entry {index + 1}
</p>
<p className="mt-1 text-sm text-foreground">{entry.text}</p>
</div>
))}
<Conversation.ScrollButton />
</Conversation>
</div>
</div>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add ConversationExamples
Without Scroll Button
The scroll button is optional when a feed does not need a return-to-latest control.
Preview
Dark
import Conversation from '@/components/composites/Conversation';
const entries = [
'Configuration updated.',
'A member joined the workspace.',
'The latest sync completed.',
'The status changed to ready.',
];
export default function WithoutScrollButtonDemo() {
return (
<div className="h-64 overflow-hidden rounded-card border border-border bg-card">
<Conversation className="h-full">
{entries.map((entry) => (
<div
key={entry}
className="border-b border-border pb-3 last:border-b-0"
>
<p className="text-sm text-foreground">{entry}</p>
</div>
))}
</Conversation>
</div>
);
}API
Conversation
| Prop | Description | Type | Default |
|---|---|---|---|
children | Entries to render, plus an optional direct Conversation.ScrollButton child. | ReactNode | — |
className | Classes merged with the full-height conversation root. | string | — |
...divProps | Standard <div> attributes forwarded to the conversation root. | ComponentProps<'div'> | — |
Conversation.ScrollButton
| Prop | Description | Type | Default |
|---|---|---|---|
aria-label | Accessible name for the return-to-latest control. | string | 'Scroll to bottom' |
className | Classes merged with the positioned circular button. | string | — |
disabled | Prevents the control from scrolling. | boolean | false |
...buttonProps | Standard <button> attributes forwarded to the control. | ComponentProps<'button'> | — |