Skip to main content

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.

  1. What Gets Replaced​

    renderFooter is a full replacement, not an additive slot:

    • Built-in textarea with 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 footerEndActions slot

    All of the above stop rendering — your renderer owns the entire footer area.

  2. 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 job

    The 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 on isStopping (not just isConnecting), and only show a stop control when canStop — isConnecting is true for four unrelated things and only the user's own turn is stoppable.

    See Stop Generation for the full picture.

  3. useAsgardContext Key Fields​

    FieldTypeDescription
    sendMessage(params) => voidSubmit a message; undefined in preview / disconnected state
    isConnectingbooleanChannel 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.setInputValue
    setPendingInputValue(v: string | null) => voidClear the pending value back to null once consumed
    inputPlaceholderstringPlaceholder passed through Chatbot props
    messagesMap<string, ConversationMessage>All messages (if you need token counts, etc.)
  4. Wiring renderMenu + ChatbotRef.setInputValue​

    The demo above also uses renderMenu to list quick prompts. Clicking a prompt calls chatbotRef.current?.setInputValue(prompt) to push the text into the SDK's internal pendingInputValue. Your custom footer consumes it, producing a "click → text appears in textarea → user decides whether to send" flow.

  5. 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.

Ready to get started?

Want to see how Asgard fits your team? Talk to us.

Contact us