Skip to content

Response

Shows an assistant's reply as formatted text: headings, lists, tables, code, links and source markers. It is safe for text you do not control and for text that is still arriving.

AIv0.1.0experimentalWCAG 2.2 AAView spec

Bring your own model.

These components only show what you give them, in one message shape, and report what the person does. They never call a model or a server, so they work with any backend. They are built for how assistive technology handles streaming text, tool steps and approvals.

Quarterly summary

Revenue grew 12% over last quarter, driven by new subscriptions. The three biggest changes:

  1. Annual plans rose to 38% of sign-ups
  2. Churn fell below 2%
  3. Support tickets per customer dropped
RegionRevenueChange
Europe$412K+9%
Americas$655K+14%

Numbers are preliminary until the books close.

ts
const growth = (now: number, before: number) => (now - before) / before;

See the full report (opens in a new tab) for details.

import { Response } from "@rdloom/react";

const reply = `## Quarterly summary

Revenue grew **12%** over last quarter, driven by _new subscriptions_. The three biggest changes:

1. Annual plans rose to \`38%\` of sign-ups
2. Churn fell below 2%
3. Support tickets per customer dropped

| Region | Revenue | Change |
| :----- | ------: | -----: |
| Europe | $412K | +9% |
| Americas | $655K | +14% |

> Numbers are preliminary until the books close.

\`\`\`ts
const growth = (now: number, before: number) => (now - before) / before;
\`\`\`

See the [full report](https://example.com/report) for details.`;

export default function ResponseMarkdownExample() {
  return (
    <div className="w-[34rem] max-w-full">
      <Response>{reply}</Response>
    </div>
  );
}

Installation

npx rdloom add response

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/response.json

Works, but without upgrade tracking.

Usage

import { Response } from "@rdloom/react";

<div className="w-[34rem] max-w-full">
  <Response>{reply}</Response>
</div>

Streaming

import { useEffect, useState } from "react";
import { Button, Response } from "@rdloom/react";

const reply = "Checking the numbers now. **Sales rose in March**, and the biggest gain came from the new annual plan:\n\n- Annual plans: up 18%\n- Monthly plans: flat\n\n```sql\nSELECT month, SUM(total) FROM orders GROUP BY month;\n```";

// The text arrives a few characters at a time. Pass isStreaming while it does: the caret shows, and
// a screen reader waits for the finished text instead of reading every fragment.
export default function ResponseStreamingExample() {
  const [shown, setShown] = useState(0);
  const [run, setRun] = useState(0);
  const done = shown >= reply.length;

  useEffect(() => {
    setShown(0);
    const id = setInterval(() => setShown((n) => (n >= reply.length ? n : n + 3)), 40);
    return () => clearInterval(id);
  }, [run]);

  return (
    <div className="flex w-[34rem] max-w-full flex-col items-center gap-3">
      <Response className="w-full" isStreaming={!done}>{reply.slice(0, shown)}</Response>
      <Button variant="secondary" size="sm" onPress={() => setRun((n) => n + 1)}>
        Replay
      </Button>
    </div>
  );
}

With citations

Refunds go out within 5 business days 1, and only for orders placed in the last 30 days 2.

Sources

  1. Operations handbook (opens in a new tab)example.comRefunds are issued within 5 business days.
  2. Refund policy (opens in a new tab)example.com
import { Response, Sources, type CitationPart } from "@rdloom/react";

const sources: CitationPart[] = [
  { type: "citation", id: "ops", title: "Operations handbook", url: "https://example.com/handbook", snippet: "Refunds are issued within 5 business days." },
  { type: "citation", id: "policy", title: "Refund policy", url: "https://example.com/policy" },
];

// [1] and [2] in the text become markers that jump to the matching entry in Sources.
export default function ResponseWithCitationsExample() {
  return (
    <div className="flex w-[34rem] max-w-full flex-col gap-4">
      <Response citations={sources}>
        Refunds go out within 5 business days [1], and only for orders placed in the last 30 days [2].
      </Response>
      <Sources sources={sources} />
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
childrenrequired

The reply, as markdown.

stringnone
isStreaming

True while the reply is still arriving. Shows a caret and tells assistive technology the content is changing.

booleanfalse
citations

Sources for the [1], [2] markers in the text. A marker with no matching source stays as plain text.

CitationPart[]none
headingLevel

Level of the reply's first heading (a # line), 2 to 6. Replies sit inside a page, so it starts at 3.

number3

Accessibility

Role none, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Tab: moves to code blocks (copy button), long code that scrolls, wide tables and links

Screen readers announce

  • Headings, lists and tables are announced with their real structure
  • A copy button reads as, e.g. "Copy typescript code", then "Copied to clipboard"
  • Source markers read as "Source 2: <title>"

What your code must do

  • No raw HTML is ever injected: model output can't add markup or scripts
  • Links with unsafe schemes (javascript:, data:) show as plain text; links that open a new tab say so in words
  • Bare web addresses become links without taking the punctuation after them; a backslash keeps a character literal
  • Images in the text are never loaded (an address chosen by a model could track the reader): they show as a link named "<alt text> (image)"
  • Code blocks and tables that scroll sideways can be focused and scrolled with the keyboard
  • The copy button's name includes the language, and it confirms with a status message
  • While streaming, the region has aria-busy so a screen reader doesn't read each fragment
  • Half-finished markdown (an open code block, a cut-off list) never throws and still reads sensibly

Guidelines

Use it when

  • Showing what an AI assistant wrote
  • Any markdown from a source you can't fully trust

Avoid it when

  • Authoring or editing markdown: this only displays it
  • Showing a user's own message: use plain text

Don't

  • Rendering model output with dangerouslySetInnerHTML
  • Re-mounting the component on every streamed chunk instead of updating its text

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-surface-subtle
  • --rd-color-border-default
  • --rd-color-border-strong
  • --rd-color-action-primary
  • --rd-color-focus-ring
  • --rd-radius-control