Skip to content
← All guides
5 steps · Docusaurus

Docusaurus

Docusaurus feedback form without a backend

Docusaurus builds your docs into static HTML with React on top, so there is no server to receive reader feedback. A small React component can post straight to Formward, which stores each response in Sweden and emails it to you. This guide adds a feedback form that records which page it was sent from, then shows how to put it under every doc without editing each file.

  1. 01Create your form in Formward

    Sign up at formward.eu, create a form and copy its endpoint URL (https://forms.formward.eu/f/your-form-id). The component below uses the field names of the documentation feedback template in the Formward library: page, helpful, comments and email.

  2. 02Add the feedback component

    Create src/components/FeedbackForm.tsx. useLocation from @docusaurus/router returns the current path during the static build as well as in the browser, so the hidden page field is filled without touching window. The class names come from Infima, the CSS framework in Docusaurus's classic theme. For a JavaScript project, save it as .jsx and drop the type annotations.

    // src/components/FeedbackForm.tsx
    import { useState, type FormEvent } from "react";
    import { useLocation } from "@docusaurus/router";
    
    const ENDPOINT = "https://forms.formward.eu/f/your-form-id";
    
    export default function FeedbackForm() {
      const { pathname } = useLocation();
      const [status, setStatus] = useState<"idle" | "sending" | "ok" | "error">("idle");
    
      async function onSubmit(e: FormEvent<HTMLFormElement>) {
        e.preventDefault();
        const form = e.currentTarget;
        setStatus("sending");
        try {
          const res = await fetch(ENDPOINT, {
            method: "POST",
            headers: { Accept: "application/json" },
            body: new FormData(form),
          });
          const data = await res.json(); // { ok: true, id } or { ok: false, error }
          if (!res.ok || !data.ok) throw new Error(data.error);
          form.reset();
          setStatus("ok");
        } catch {
          setStatus("error");
        }
      }
    
      if (status === "ok") return <p>Thanks, your feedback was sent.</p>;
    
      return (
        <form onSubmit={onSubmit} className="margin-vert--lg">
          <input type="hidden" name="page" value={pathname} />
          <fieldset>
            <legend>Was this page helpful?</legend>
            <label><input type="radio" name="helpful" value="Yes" required /> Yes</label>{" "}
            <label><input type="radio" name="helpful" value="Somewhat" /> Somewhat</label>{" "}
            <label><input type="radio" name="helpful" value="No" /> No</label>
          </fieldset>
          <textarea name="comments" rows={3} placeholder="What can we improve?" required />
          <input type="email" name="email" placeholder="Email, if you want a reply" />
          {/* Honeypot: keep hidden and empty. */}
          <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off"
            style={{ position: "absolute", left: "-5000px" }} aria-hidden />
          <button type="submit" className="button button--primary" disabled={status === "sending"}>
            {status === "sending" ? "Sending..." : "Send feedback"}
          </button>
          {status === "error" && <p role="alert">Could not send. Please try again.</p>}
        </form>
      );
    }
  3. 03Use it in a doc

    Import the component with the @site alias, which points at your project root, and render it wherever the form should appear. This works in .mdx files and, with Docusaurus 3's default settings, in .md files too.

    ---
    title: Installation
    ---
    
    import FeedbackForm from "@site/src/components/FeedbackForm";
    
    Install the package and run the setup command.
    
    <FeedbackForm />
  4. 04Optional: show it under every doc

    To avoid importing the form into each file, wrap the doc footer component. Run the swizzle command below and confirm the prompts; it creates src/theme/DocItem/Footer/index.tsx. Replace that file's contents with the wrapper below. Wrapping keeps the original footer, so theme updates still reach it.

    npm run swizzle @docusaurus/theme-classic DocItem/Footer -- --wrap --typescript
    
    // src/theme/DocItem/Footer/index.tsx
    import type { ReactNode } from "react";
    import Footer from "@theme-original/DocItem/Footer";
    import type FooterType from "@theme/DocItem/Footer";
    import type { WrapperProps } from "@docusaurus/types";
    import FeedbackForm from "@site/src/components/FeedbackForm";
    
    type Props = WrapperProps<typeof FooterType>;
    
    export default function FooterWrapper(props: Props): ReactNode {
      return (
        <>
          <Footer {...props} />
          <FeedbackForm />
        </>
      );
    }
  5. 05Build and test

    Run npm run start, send feedback from a doc page and check your Formward dashboard: each response carries the page path, so you can sort feedback by page. Then run npm run build. The component uses no browser-only API while rendering, so the static build succeeds and the form works on any static host.

Ready to collect your first submission?

Create your form
Docusaurus feedback form without a backend | Formward