Response

Presents user messages in right-aligned soft bubbles and assistant responses on the left, with speaker identity, avatars, status, actions, and a custom layout API.

You
What should we focus on first?
Assistant
Start with the review flow. Name an owner and next step for each decision.

A response as it arrives

YouComplete
What should we focus on first?
AssistantComplete

Start with the review flow. Give each decision an owner and a dated next step, then link the source behind each recommendation.

Recorded reply · Replay streams the same text locally

Use it for

  • A user or assistant message whose content and response status come from the application.
  • Pass plain text or rendered React content as children. To render markdown, supply an external markdown renderer in the children slot and configure its content and link safety in your application.
  • Place ResponseActions in actions for icon controls that copy, narrate, rate, and share the response. Omit copyText when using that action bar.
  • Use avatar for an application-owned visual and renderLayout to rearrange ResponseParts. Render the complete header or its avatar, author, and status parts once; retain content, errors, and actions.
  • useResponse reads speaker, author, status, statusLabel, and errorMessage from a descendant. It requires a surrounding Response.
  • Use actions for other application-owned controls. Provide copyText for a standalone copy button when a full action bar is not needed.

Avoid

  • Assuming this component calls a model or parses markdown. It does neither.
  • Putting changing token content in statusLabel. Reserve that label for short phase changes.
  • Injecting untrusted markup or unsafe links through an external renderer.

React package

npm install @noorddev/vlak-react

Load the stylesheet once at your app root, then import the components you use.

import "@noorddev/vlak-react/css";
import { Persona, Response, ResponseActions, useResponse } from "@noorddev/vlak-react";

Per-component imports are available at @noorddev/vlak-react/components/response.

Copy the source

npx @noorddev/vlak-cli add response

Adds the component to components/vlak/. Follow the StyleX setup to compile the source in your application.

shadcn registry

npx shadcn add https://vlak.dev/r/response.json

Installs the same source through the shadcn CLI.

"use client";
import { Persona, Response, ResponseActions, useResponse } from "@noorddev/vlak-react";

function CompletedActions({ text }: { text: string }) {
  const { status } = useResponse();
  return status === "complete" ? <ResponseActions text={text} /> : null;
}

export function LeaseExchange() {
  return <>
    <Response from="user">When does the lease renew?</Response>
    <Response from="assistant" status="complete"
      avatar={<Persona variant="orb" size={20} />}
      actions={<CompletedActions text="The lease renews on 1 October." />}
      renderLayout={({ avatar, author, status, content, error, actions }) =>
        <div style={{ display: "grid", gridTemplateColumns: "auto minmax(0, 1fr)", gap: 12 }}>
          {avatar}
          <div style={{ minWidth: 0 }}>
            <div style={{ display: "flex", justifyContent: "space-between", gap: 12 }}>{author}{status}</div>
            {content}{error}{actions}
          </div>
        </div>}
    >
      <p>The lease renews on <strong>1 October</strong>.</p>
    </Response>
  </>;
}

See the integration example to compose these components with your application's model and data.

Response

One message with application-rendered content and a separate status announcement.

PropTypeDefaultDescription
from"user" | "assistant""assistant"The speaker's identity, separate from the native ARIA role.
authorReactNode
avatarReactNodeAn application-owned avatar beside the author.
statusResponseStatus"complete"
statusLabelstringA short status phrase. Keep token-by-token updates in children.
errorMessagestring
copyTextstringExplicit text to copy. Omit to hide the copy action.
copyLabelstring"Copy response"
copiedLabelstring"Copied"
copyErrorLabelstring"Could not copy. Try again."
actionsReactNodeApplication-owned actions, such as retry or feedback buttons.
renderLayout(parts: ResponseParts, state: ResponseState) => ReactNodeRearranges the same content and controls. Render each part once; keep the status announcement.
childrenReactNodeRendered content. Supply your own markdown renderer here when needed.

Also accepts HTMLAttributes<HTMLElement>.

The ref reaches HTMLElement.

useResponse (hook)

Reads speaker and response status from a custom descendant without threading props.

KeysDoes
TabReaches the optional copy button, supplied actions, and links in the rendered content.
Enter, Space on Copy responseCopies the explicit copyText value and reports success only after the clipboard write resolves.
  • A semantic article is named by its visible speaker. from identifies user or assistant without taking over the native role attribute.
  • Custom layouts retain a fallback article name. Keep the supplied status part so phase changes remain announced, and give a non-text custom author an explicit accessible name when needed.
  • Status is a separate polite, atomic live region. Changing response tokens are ordinary readable content and do not repeat the whole message through a live region.
  • The streaming, complete, error, and stopped states are supplied by the application. An errorMessage is visible when status is error; the short response status announces the failure.
  • Copy is shown only with copyText. Clipboard denial or unavailability reports a readable failure and allows retry. Pending writes prevent duplicate activation, and stale writes cannot announce success for new content.
  • The 44px copy control has a focus outline and forced-colors styling. Native article attributes, className, style, and the article ref are forwarded.

The same styles are available as CSS. Native HTML provides the static presentation; React or application code supplies state updates and actions.

npm install @noorddev/vlak
import "@noorddev/vlak/css";
<article class="rs-response" aria-labelledby="response-author"><div class="rs-response-header"><div class="rs-response-identity"><span class="rs-response-avatar" aria-hidden="true"><svg viewBox="0 0 20 20" width="20" height="20" fill="none" stroke="currentColor"><circle cx="10" cy="10" r="7" /></svg></span><span class="rs-response-author" id="response-author">Assistant</span></div><span class="rs-response-status" role="status" aria-live="polite" aria-atomic="true">Complete</span></div><div class="rs-response-content"><p>The lease renews on 1 October.</p></div></article>

Classes

.rs-response.rs-response-user.rs-response-header.rs-response-identity.rs-response-avatar.rs-response-author.rs-response-status.rs-response-content.rs-response-error.rs-response-actions.rs-response-copy.rs-response-copy-status