Skip to main content
NachUI

Response

The assistant's answer as a prose column, with a caret while tokens arrive and a skeleton before the first one.

You can keep the composer and the answer in the same column. Three things make it read well:

  • Render markdown outside the component and pass the result as children.
  • Keep code in a block with its own copy button.
  • Show the caret only while tokens are still arriving.
const answer = await model.generate({
  prompt: 'Summarise this thread',
  maxTokens: 200,
});
1'use client';
2
3import { CodeBlock } from '@/components/ui/code-block';
4import { Response } from '@/components/ui/response';
5
6const CODE = `const answer = await model.generate({
7 prompt: 'Summarise this thread',
8 maxTokens: 200,
9});`;
10
11export function Default() {
12 return (
13 <div className="w-full max-w-lg">
14 <Response>
15 <p>
16 You can keep the composer and the answer in the same column. Three things make it read
17 well:
18 </p>
19 <ul>
20 <li>
21 Render markdown outside the component and pass the result as <code>children</code>.
22 </li>
23 <li>Keep code in a block with its own copy button.</li>
24 <li>Show the caret only while tokens are still arriving.</li>
25 </ul>
26 <CodeBlock code={CODE} language="ts">
27 <CodeBlock.CopyButton />
28 <CodeBlock.Content />
29 </CodeBlock>
30 </Response>
31 </div>
32 );
33}

Installation

pnpm dlx nachui add response

Anatomy

1import { Response } from '@/components/ui/response';
1<Response isStreaming={status === 'streaming'}>
2 <ReactMarkdown>{message.content}</ReactMarkdown>
3</Response>
Response does not parse markdown. It styles whatever you render inside it: paragraphs, headings, lists, inline code and blockquotes get the spacing and type of an answer, and a CodeBlock drops in as is. Render the markdown with react-markdown or your own pipeline and pass the result as children, so the component stays dependency free and you keep control of the renderer.

Composition

Use the following composition to build a Response:
Response
├── Response.Content
└── Response.Caret
Response.Skeleton

Streaming

While isStreaming is true, a caret blinks after the last node and the root carries aria-busy. Before the first token there is nothing to show, so render Response.Skeleton instead: three shimmering lines that read as an answer being written.

Reading the question and what came before it

Pulling the parts of the answer together

Writing it down

1'use client';
2
3import { useEffect, useState } from 'react';
4import { Response } from '@/components/ui/response';
5
6const TEXT =
7 'The caret sits after the last character and blinks until the stream closes. Before the first token arrives, the skeleton takes its place so the column never sits empty.';
8
9export function Streaming() {
10 const [count, setCount] = useState(0);
11 const [running, setRunning] = useState(true);
12
13 useEffect(() => {
14 if (!running) return;
15 if (count >= TEXT.length) {
16 setRunning(false);
17 return;
18 }
19 const timer = setTimeout(() => setCount((value) => value + 2), 30);
20 return () => clearTimeout(timer);
21 }, [count, running]);
22
23 const restart = () => {
24 setCount(0);
25 setRunning(true);
26 };
27
28 return (
29 <div className="flex w-full max-w-lg flex-col gap-4">
30 <button
31 type="button"
32 onClick={restart}
33 className="border-border hover:bg-muted w-fit rounded-full border px-3 py-1.5 text-xs transition-colors"
34 >
35 Stream again
36 </button>
37 {count === 0 ? (
38 <Response.Skeleton />
39 ) : (
40 <Response isStreaming={running}>
41 <p>{TEXT.slice(0, count)}</p>
42 </Response>
43 )}
44 </div>
45 );
46}

API Reference

Response

PropTypeDefaultDescription
isStreamingbooleanfalseShows the caret and sets aria-busy
classNamestring-Additional CSS classes

Response.Content

PropTypeDefaultDescription
classNamestring-Additional CSS classes

Response.Caret

PropTypeDefaultDescription
classNamestring-Additional CSS classes

Response.Skeleton

PropTypeDefaultDescription
linesstring[]three placeholder linesText of each shimmering line
classNamestring-Additional CSS classes
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue