REFERENCE
SDK Demos
How to use @asgard-js/core and @asgard-js/react, with demos you can try on the page.
Custom Footer
Use renderFooter to fully replace the default footer. Reach for it when
you need to redesign the entire input area (single-line input, custom voice
button, completely different layout). If you only want to add a button
next to send, use footerEndActions instead.
What Gets Replaced
renderFooteris a full replacement, not an additive slot:- Built-in
textareawith auto-resize - Send button and microphone button
- Image upload, document upload, export conversation
- Drag-and-drop upload integration
- IME composition guard (prevents Enter from submitting mid-composition)
- The
footerEndActionsslot
All of the above stop rendering — your renderer owns the entire footer area.
- Built-in
Code Example
import {
Chatbot,
useAsgardContext,
type ChatbotRef,
} from "@asgard-js/react";
import { useEffect, useRef, useState } from "react";
function MyFooter() {
const {
sendMessage,
isConnecting,
isStopping,
pendingInputValue,
setPendingInputValue,
inputPlaceholder,
} = useAsgardContext();
const [value, setValue] = useState("");
const textareaRef = useRef<HTMLTextAreaElement>(null);
// Receive text pushed in via ChatbotRef.setInputValue
useEffect(() => {
if (pendingInputValue == null) return;
setValue(pendingInputValue);
setPendingInputValue(null);
textareaRef.current?.focus();
}, [pendingInputValue, setPendingInputValue]);
const submit = () => {
const text = value.trim();
// Gate on isStopping too: the old run has not finished, and sending now
// leaves two runs writing one transcript. Keep the draft — do not clear it.
if (!text || isConnecting || isStopping) return;
sendMessage?.({ text });
setValue("");
};
return (
<div className="footer">
<textarea
ref={textareaRef}
placeholder={inputPlaceholder}
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={(e) => {
// Skip submit while IME is composing
if (
e.key === "Enter" &&
!e.shiftKey &&
!e.nativeEvent.isComposing
) {
e.preventDefault();
submit();
}
}}
rows={1}
/>
<button
onClick={submit}
disabled={!value.trim() || isConnecting || isStopping}
>
Send
</button>
</div>
);
}
function Demo() {
const chatbotRef = useRef<ChatbotRef>(null);
return (
<Chatbot
ref={chatbotRef}
renderFooter={() => <MyFooter />}
{...rest}
/>
);
}Replacing the footer makes stop-generation your jobThe built-in footer handles the whole stop lifecycle; the moment you take over with
renderFooter, that becomes your responsibility. Two rules: gate every send entrance onisStopping(not justisConnecting), and only show a stop control whencanStop—isConnectingis true for four unrelated things and only the user's own turn is stoppable.See Stop Generation for the full picture.
useAsgardContextKey FieldsField Type Description sendMessage(params) => voidSubmit a message; undefinedin preview / disconnected stateisConnectingbooleanChannel is busy — use this to disable the send button isStoppingbooleanA stop was requested and the terminal event has not arrived; gate sends on this too canStopbooleanWhether the in-flight run is stoppable — only show a stop control when true canForceStopbooleanThe stop timed out; escalate to a force stop (implies isStopping)stopGeneration(opts?) => Promise<void>Ask the backend to suspend the current run pendingInputValuestring | nullText pushed in via ChatbotRef.setInputValuesetPendingInputValue(v: string | null) => voidClear the pending value back to nullonce consumedinputPlaceholderstringPlaceholder passed through Chatbot props messagesMap<string, ConversationMessage>All messages (if you need token counts, etc.) Wiring
renderMenu+ChatbotRef.setInputValueThe demo above also uses
renderMenuto list quick prompts. Clicking a prompt callschatbotRef.current?.setInputValue(prompt)to push the text into the SDK's internalpendingInputValue. Your custom footer consumes it, producing a "click → text appears in textarea → user decides whether to send" flow.When Not to Use
renderFooter- Just want a button next to send: use
footerEndActions— built-in logic stays intact. - Skip the Chatbot UI entirely: use Headless mode and assemble the whole interface yourself.
- Just want a button next to send: use