Chat component. Move to composition only when you need layout control.
For headless state, see Chat hooks.
Prerequisites
- A Veryfront project with an AG-UI route, such as
/api/ag-ui(see Create agent). - A configured provider for the route’s agent (see Providers).
Add the preset UI
Create a client page:useChat() connects to /api/ag-ui by default. Chat renders the composer,
message list, loading state, and scroll behavior.
The MarkdownRendererProvider wrapper is required for readable answers.
Assistants reply in Markdown, and veryfront/markdown presents plain escaped
source until a renderer is installed, so a <Chat> without one shows
## Heading rather than a heading. The chat starters scaffold the
app/markdown-renderer.tsx this sample imports; the minimal and
agentic-workflow templates do not. Create the file first if your project does
not already have it. See
Render Markdown in chat. Wrap the other samples on
this page the same way.
Add request preprocessing
UsebeforeStream when the route needs to add context, enforce authorization,
or stop a request before the agent runs:
beforeStream
before they reach the agent. Retrieved documents are treated as reference data,
not instructions.
Customize the preset
Configure the preset’s content, theme, and agent options. The preset always includes sources, multi-step rendering, message actions, scroll-to-bottom, and attachments:Chat at that route:
true to permit a request.
false and invalid runtime results such as undefined are denied.
The preset sends same-origin cookies automatically. If your route instead
requires an explicit bearer header, compose the headless useUpload() hook
with its headers option rather than placing a secret token in Chat props.
To change the upload limits, bound the complete multipart request separately
from the file bytes:
allowUnauthenticated: true explicitly.
Compose a custom layout
Use the composition components when the preset layout is too constrained:<Chat.Empty> renders whatever it is given; it does not hide itself. The preset
<Chat> decides when to show its empty state for you, but a custom layout owns
that decision, so gate the empty state on the thread being empty. <Chat.If>
reads the nearest <Chat.Root>, where ctx.isEmpty is true only while
messages is empty. Without the gate, the empty state stays mounted below the
conversation once the first message is sent.
Use Message when individual message rendering needs custom structure:
Theming and the token scope
The chat andveryfront/ui primitives resolve their var(--token) styles
against a scoped design-token stylesheet, so the tokens never leak to the
rest of your page. The canonical scope attribute is data-vf-ui;
data-vf-chat is kept as a compatibility alias (both are set on every scope
element, and every token rule matches both), so existing selectors keep working.
<Chat> establishes the scope for itself. When you compose the primitives
around <Chat>, such as a sidebar, header, or uploads panel in your own
shell, wrap that shell in one ChatThemeScope so everything inside it is themed:
[data-vf-ui].
[data-vf-chat] remains supported and will only be removed in a future major
release.
Add conversation navigation
Wrap the chat and sidebar in aConversationsProvider.
The provider owns the conversation list and persistence; <ChatSidebar> and
<Chat> both read it from context, so neither needs wiring:
ConversationStoreError; it does not pretend
the operation succeeded or silently switch to memory. The error’s operation
is list, load, save, delete, or subscribe.
Keep conversations ephemeral
UsememoryConversationStore() for SSR, short-lived demos, or sensitive
sessions whose transcript must not be written to browser storage. Create the
store once per mounted tree:
Render Markdown in chat
veryfront/markdown presents plain escaped source until a renderer is
installed, so <Chat> shows raw Markdown on its own. Every chat starter
scaffolds a renderer in app/markdown-renderer.tsx and installs it around
<Chat>, so a new project renders assistant answers with no extra setup.
Add the same two pieces to a project without one. Install the parser, pinned to
an exact version: these packages reach the browser through the module pipeline,
where a floating ^ range resolves to whatever is latest at request time.
Present Markdown source safely
veryfront/markdown is the dependency-free Markdown boundary used by chat
surfaces. Without an installed rich renderer, it preserves the exact source in
an escaped <pre><code> element. This is useful when source visibility matters
more than semantic formatting:
Install a semantic renderer
Semantic Markdown is an explicit extension capability. Select a trusted extension or application adapter that implementsMarkdownRendererProps, then
install its component for the relevant subtree. In this example,
ProjectMarkdownRenderer comes from that adapter:
renderer prop takes precedence over the provider. Pass
renderer={null} when a nested surface must display plain source even though
an ancestor installed a renderer.
Parser-dependent options are forwarded only after a renderer has been selected.
For example, replace fenced-code rendering without changing the renderer used
for the rest of the document:
components to pass framework-neutral element overrides to the selected
renderer:
remarkPlugins
or rehypePlugins on that extension, not on core Markdown; removed or unknown
core props are rejected instead of ignored. Renderer failures propagate, so
handle them with an application error boundary when recovery is required.
Never derive plugin lists from untrusted input, and bound untrusted source size
before rendering.
Verify it worked
Runveryfront dev and open the page that renders the chat UI:
- The composer renders and accepts input.
- A submitted message streams tokens from
/api/ag-ui. - The preset renders its default controls.
- Custom layouts keep the message list and composer wired to the same AG-UI stream.
- In a custom layout, the empty state disappears after the first message is sent and does not reappear below the conversation.
- A persisted conversation remains in the sidebar after a page reload.
- A conversation persistence failure renders an alert with the failed operation.
- A chat using
memoryConversationStore()starts empty after a page reload. - Standalone Markdown source is escaped and readable in the initial server HTML; an injected renderer is used only in the subtree where it is installed.
- An assistant answer containing a list or a heading renders as formatted Markdown, not raw Markdown source, and the browser console reports no missing-Markdown-renderer warning.
Next
- Chat hooks: Use headless chat state
- Memory and streaming: Configure agent memory and streaming
Related
- veryfront/chat: Chat components and hooks
- veryfront/markdown: Markdown props and renderer extension points
- veryfront/agent: Agent route helpers