Skip to main content
A progressively enhanced HTML <form> that submits data to actions via fetch, activating pending states in useNavigation which enables advanced user interfaces beyond a basic HTML form.
import { Form } from "react-router";

function NewEvent() {
  return (
    <Form action="/events" method="post">
      <input name="title" type="text" />
      <input name="description" type="text" />
      <button type="submit">Create</button>
    </Form>
  );
}

Type Declaration

export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
  method?: HTMLFormMethod;
  encType?:
    | "application/x-www-form-urlencoded"
    | "multipart/form-data"
    | "text/plain";
  action?: string;
  relative?: RelativeRoutingType;
  preventScrollReset?: boolean;
  onSubmit?: React.FormEventHandler<HTMLFormElement>;
  unstable_defaultShouldRevalidate?: boolean;
  discover?: DiscoverBehavior;
  fetcherKey?: string;
  navigate?: boolean;
  reloadDocument?: boolean;
  replace?: boolean;
  state?: any;
  viewTransition?: boolean;
}

export const Form: React.ForwardRefExoticComponent<
  FormProps & React.RefAttributes<HTMLFormElement>
>;

type HTMLFormMethod = "get" | "post" | "put" | "patch" | "delete";

Props

action
string
The URL to submit the form data to. If undefined, this defaults to the closest route in context.
<Form action="/projects/new" method="post">
  <input name="name" />
  <button type="submit">Create Project</button>
</Form>
method
HTMLFormMethod
The HTTP verb to use when the form is submitted. Supports "get", "post", "put", "patch", and "delete".Native forms only support "get" and "post", so avoid the other verbs if you’d like to support progressive enhancement.
<Form method="post">
  {/* ... */}
</Form>
encType
'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain'
The encoding type to use for the form submission.
<Form encType="application/x-www-form-urlencoded" /> {/* Default */}
<Form encType="multipart/form-data" /> {/* For file uploads */}
<Form encType="text/plain" />
replace
boolean
Replaces the current entry in the browser History stack when the form navigates. Use this if you don’t want the user to be able to click “back” to the page with the form on it.
<Form method="post" replace>
  {/* ... */}
</Form>
state
any
State object to add to the History stack entry for this navigation.
<Form method="post" state={{ from: "dashboard" }}>
  {/* ... */}
</Form>
preventScrollReset
boolean
Prevent the scroll position from resetting to the top of the viewport on completion of the navigation when using the <ScrollRestoration> component.
<Form method="post" preventScrollReset>
  {/* ... */}
</Form>
relative
RelativeRoutingType
Determines whether the form action is relative to the route hierarchy or the pathname.
<Form relative="route" />
<Form relative="path" />
reloadDocument
boolean
Forces a full document navigation instead of client side routing and data fetch.
<Form method="post" reloadDocument>
  {/* ... */}
</Form>
navigate
boolean
When false, skips the navigation and submits via a fetcher internally. This is essentially a shorthand for useFetcher + <fetcher.Form> where you don’t care about the resulting data.
<Form method="post" navigate={false}>
  {/* ... */}
</Form>
fetcherKey
string
Indicates a specific fetcherKey to use when using navigate={false} so you can pick up the fetcher’s state in a different component using useFetcher.
<Form method="post" navigate={false} fetcherKey="my-form">
  {/* ... */}
</Form>
viewTransition
boolean
Enables a View Transition for this navigation. To apply specific styles during the transition, see useViewTransitionState.
<Form method="post" viewTransition>
  {/* ... */}
</Form>
discover
DiscoverBehavior
Defines the form lazy route discovery behavior.
  • render (default) - Discover the route when the form renders
  • none - Don’t eagerly discover, only discover if the form is submitted
<Form discover="none" />

Examples

Basic Form

import { Form } from "react-router";

function CreateProject() {
  return (
    <Form method="post" action="/projects">
      <label>
        Name: <input name="name" type="text" />
      </label>
      <label>
        Description: <textarea name="description" />
      </label>
      <button type="submit">Create Project</button>
    </Form>
  );
}

// Route action
export async function action({ request }) {
  const formData = await request.formData();
  const project = await createProject({
    name: formData.get("name"),
    description: formData.get("description"),
  });
  return redirect(`/projects/${project.id}`);
}

With Pending UI

import { Form, useNavigation } from "react-router";

function CreateProject() {
  const navigation = useNavigation();
  const isSubmitting = navigation.state === "submitting";

  return (
    <Form method="post">
      <input name="name" disabled={isSubmitting} />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Creating..." : "Create Project"}
      </button>
    </Form>
  );
}

File Upload

<Form method="post" encType="multipart/form-data">
  <label>
    Upload file:
    <input type="file" name="file" />
  </label>
  <button type="submit">Upload</button>
</Form>

// Action
export async function action({ request }) {
  const formData = await request.formData();
  const file = formData.get("file");
  await uploadFile(file);
  return redirect("/files");
}

Search Form (GET)

function SearchForm() {
  return (
    <Form method="get" action="/search">
      <input name="q" placeholder="Search..." />
      <button type="submit">Search</button>
    </Form>
  );
}

// Loader
export async function loader({ request }) {
  const url = new URL(request.url);
  const query = url.searchParams.get("q");
  const results = await searchProducts(query);
  return { results };
}

Without Navigation (Fetcher)

import { Form, useFetcher } from "react-router";

function NewsletterSignup() {
  const fetcher = useFetcher();

  return (
    <fetcher.Form method="post" action="/newsletter/subscribe">
      <input name="email" type="email" />
      <button type="submit">
        {fetcher.state === "submitting" ? "Subscribing..." : "Subscribe"}
      </button>
    </fetcher.Form>
  );
}

// Or use navigate={false}
function NewsletterSignup() {
  return (
    <Form method="post" action="/newsletter/subscribe" navigate={false}>
      <input name="email" type="email" />
      <button type="submit">Subscribe</button>
    </Form>
  );
}

Progressive Enhancement

function CreateTask() {
  return (
    <Form method="post" action="/tasks">
      <input name="title" required />
      <select name="priority">
        <option value="low">Low</option>
        <option value="medium">Medium</option>
        <option value="high">High</option>
      </select>
      <button type="submit">Create Task</button>
    </Form>
  );
}

// This form works without JavaScript!
// - Browser submits the form on submit
// - After JS loads, React Router takes over for a better UX

Behavior

  • After a form’s action completes, all data on the page is automatically revalidated to keep the UI in sync with the data
  • Server rendered pages are interactive at a basic level before JavaScript loads
  • After JavaScript loads, React Router takes over enabling web application user experiences
  • Form submissions can be cancelled by the user or interrupted by other navigations
  • Multiple forms can be submitted simultaneously and React Router will track them all

Notes

  • Use <Form> for submissions that should change the URL or add an entry to the browser history stack
  • Use <fetcher.Form> or navigate={false} for forms that shouldn’t manipulate the browser History stack
  • All standard HTML form attributes are supported
  • Progressive enhancement allows forms to work before JavaScript loads

Build docs developers (and LLMs) love