diff --git a/.changeset/normalize-form-method.md b/.changeset/normalize-form-method.md
new file mode 100644
index 0000000000..e781bf8a20
--- /dev/null
+++ b/.changeset/normalize-form-method.md
@@ -0,0 +1,7 @@
+---
+"react-router": minor
+"react-router-dom": minor
+"@remix-run/router": minor
+---
+
+Add `future.v7_normalizeFormMethod` flag to normalize exposed `useNavigation().formMethod` and `useFetcher().formMethod` fields as uppercase HTTP methods to align with the `fetch()` behavior
diff --git a/docs/routers/create-browser-router.md b/docs/routers/create-browser-router.md
index 433354249e..6126de6499 100644
--- a/docs/routers/create-browser-router.md
+++ b/docs/routers/create-browser-router.md
@@ -47,6 +47,8 @@ function createBrowserRouter(
   routes: RouteObject[],
   opts?: {
     basename?: string;
+    future?: FutureConfig;
+    hydrationData?: HydrationState;
     window?: Window;
   }
 ): RemixRouter;
diff --git a/docs/routers/create-memory-router.md b/docs/routers/create-memory-router.md
index 2b5c24148a..60103b2a83 100644
--- a/docs/routers/create-memory-router.md
+++ b/docs/routers/create-memory-router.md
@@ -52,9 +52,10 @@ function createMemoryRouter(
   routes: RouteObject[],
   opts?: {
     basename?: string;
+    future?: FutureConfig;
+    hydrationData?: HydrationState;
     initialEntries?: InitialEntry[];
     initialIndex?: number;
-    window?: Window;
   }
 ): RemixRouter;
 ```
diff --git a/packages/react-router-dom/dom.ts b/packages/react-router-dom/dom.ts
index a87ff65715..6a01100aff 100644
--- a/packages/react-router-dom/dom.ts
+++ b/packages/react-router-dom/dom.ts
@@ -1,8 +1,8 @@
-import type { FormEncType, FormMethod } from "@remix-run/router";
+import type { FormEncType, HTMLFormMethod } from "@remix-run/router";
 import type { RelativeRoutingType } from "react-router";
 
-export const defaultMethod = "get";
-const defaultEncType = "application/x-www-form-urlencoded";
+export const defaultMethod: HTMLFormMethod = "get";
+const defaultEncType: FormEncType = "application/x-www-form-urlencoded";
 
 export function isHtmlElement(object: any): object is HTMLElement {
   return object != null && typeof object.tagName === "string";
@@ -110,7 +110,7 @@ export interface SubmitOptions {
    * The HTTP method used to submit the form. Overrides `<form method>`.
    * Defaults to "GET".
    */
-  method?: FormMethod;
+  method?: HTMLFormMethod;
 
   /**
    * The action URL path used to submit the form. Overrides `<form action>`.
diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx
index 3bb811ca74..dcfa2b8f7d 100644
--- a/packages/react-router-dom/index.tsx
+++ b/packages/react-router-dom/index.tsx
@@ -30,11 +30,14 @@ import type {
   Fetcher,
   FormEncType,
   FormMethod,
+  FutureConfig,
   GetScrollRestorationKeyFunction,
   HashHistory,
   History,
+  HTMLFormMethod,
   HydrationState,
   Router as RemixRouter,
+  V7_FormMethod,
 } from "@remix-run/router";
 import {
   createRouter,
@@ -71,6 +74,7 @@ export type {
   ParamKeyValuePair,
   SubmitOptions,
   URLSearchParamsInit,
+  V7_FormMethod,
 };
 export { createSearchParams };
 
@@ -199,16 +203,20 @@ declare global {
 //#region Routers
 ////////////////////////////////////////////////////////////////////////////////
 
+interface DOMRouterOpts {
+  basename?: string;
+  future?: FutureConfig;
+  hydrationData?: HydrationState;
+  window?: Window;
+}
+
 export function createBrowserRouter(
   routes: RouteObject[],
-  opts?: {
-    basename?: string;
-    hydrationData?: HydrationState;
-    window?: Window;
-  }
+  opts?: DOMRouterOpts
 ): RemixRouter {
   return createRouter({
     basename: opts?.basename,
+    future: opts?.future,
     history: createBrowserHistory({ window: opts?.window }),
     hydrationData: opts?.hydrationData || parseHydrationData(),
     routes,
@@ -218,14 +226,11 @@ export function createBrowserRouter(
 
 export function createHashRouter(
   routes: RouteObject[],
-  opts?: {
-    basename?: string;
-    hydrationData?: HydrationState;
-    window?: Window;
-  }
+  opts?: DOMRouterOpts
 ): RemixRouter {
   return createRouter({
     basename: opts?.basename,
+    future: opts?.future,
     history: createHashHistory({ window: opts?.window }),
     hydrationData: opts?.hydrationData || parseHydrationData(),
     routes,
@@ -611,7 +616,7 @@ export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
    * The HTTP verb to use when the form is submit. Supports "get", "post",
    * "put", "delete", "patch".
    */
-  method?: FormMethod;
+  method?: HTMLFormMethod;
 
   /**
    * Normal `<form action>` but supports React Router's relative paths.
@@ -696,7 +701,7 @@ const FormImpl = React.forwardRef<HTMLFormElement, FormImplProps>(
     forwardedRef
   ) => {
     let submit = useSubmitImpl(fetcherKey, routeId);
-    let formMethod: FormMethod =
+    let formMethod: HTMLFormMethod =
       method.toLowerCase() === "get" ? "get" : "post";
     let formAction = useFormAction(action, { relative });
     let submitHandler: React.FormEventHandler<HTMLFormElement> = (event) => {
@@ -708,7 +713,7 @@ const FormImpl = React.forwardRef<HTMLFormElement, FormImplProps>(
         .submitter as HTMLFormSubmitter | null;
 
       let submitMethod =
-        (submitter?.getAttribute("formmethod") as FormMethod | undefined) ||
+        (submitter?.getAttribute("formmethod") as HTMLFormMethod | undefined) ||
         method;
 
       submit(submitter || event.currentTarget, {
@@ -965,7 +970,7 @@ function useSubmitImpl(fetcherKey?: string, routeId?: string): SubmitFunction {
         replace: options.replace,
         preventScrollReset: options.preventScrollReset,
         formData,
-        formMethod: method as FormMethod,
+        formMethod: method as HTMLFormMethod,
         formEncType: encType as FormEncType,
       };
       if (fetcherKey) {
diff --git a/packages/react-router/index.ts b/packages/react-router/index.ts
index a0d2ca597a..a48c0c6492 100644
--- a/packages/react-router/index.ts
+++ b/packages/react-router/index.ts
@@ -21,6 +21,7 @@ import type {
   To,
   InitialEntry,
   LazyRouteFunction,
+  FutureConfig,
 } from "@remix-run/router";
 import {
   AbortedDeferredError,
@@ -233,6 +234,7 @@ export function createMemoryRouter(
   routes: RouteObject[],
   opts?: {
     basename?: string;
+    future?: FutureConfig;
     hydrationData?: HydrationState;
     initialEntries?: InitialEntry[];
     initialIndex?: number;
@@ -240,6 +242,7 @@ export function createMemoryRouter(
 ): RemixRouter {
   return createRouter({
     basename: opts?.basename,
+    future: opts?.future,
     history: createMemoryHistory({
       initialEntries: opts?.initialEntries,
       initialIndex: opts?.initialIndex,
diff --git a/packages/router/__tests__/router-test.ts b/packages/router/__tests__/router-test.ts
index fe9ed287e4..91b5f3978f 100644
--- a/packages/router/__tests__/router-test.ts
+++ b/packages/router/__tests__/router-test.ts
@@ -10,6 +10,7 @@ import type {
   RouterNavigateOptions,
   StaticHandler,
   StaticHandlerContext,
+  FutureConfig,
 } from "../index";
 import {
   createMemoryHistory,
@@ -289,6 +290,7 @@ type SetupOpts = {
   initialEntries?: InitialEntry[];
   initialIndex?: number;
   hydrationData?: HydrationState;
+  future?: FutureConfig;
 };
 
 function setup({
@@ -297,6 +299,7 @@ function setup({
   initialEntries,
   initialIndex,
   hydrationData,
+  future,
 }: SetupOpts) {
   let guid = 0;
   // Global "active" helpers, keyed by navType:guid:loaderOrAction:routeId.
@@ -424,6 +427,7 @@ function setup({
     history,
     routes: enhanceRoutes(routes),
     hydrationData,
+    future,
   }).initialize();
 
   function getRouteHelpers(
@@ -3620,6 +3624,117 @@ describe("a router", () => {
         "childIndex",
       ]);
     });
+
+    describe("formMethod casing", () => {
+      it("normalizes to lowercase in v6", async () => {
+        let t = setup({
+          routes: [
+            {
+              id: "root",
+              path: "/",
+              children: [
+                {
+                  id: "child",
+                  path: "child",
+                  loader: true,
+                  action: true,
+                },
+              ],
+            },
+          ],
+        });
+        let A = await t.navigate("/child", {
+          formMethod: "get",
+          formData: createFormData({}),
+        });
+        expect(t.router.state.navigation.formMethod).toBe("get");
+        await A.loaders.child.resolve("LOADER");
+        expect(t.router.state.navigation.formMethod).toBeUndefined();
+        await t.router.navigate("/");
+
+        let B = await t.navigate("/child", {
+          formMethod: "POST",
+          formData: createFormData({}),
+        });
+        expect(t.router.state.navigation.formMethod).toBe("post");
+        await B.actions.child.resolve("ACTION");
+        await B.loaders.child.resolve("LOADER");
+        expect(t.router.state.navigation.formMethod).toBeUndefined();
+        await t.router.navigate("/");
+
+        let C = await t.fetch("/child", "key", {
+          formMethod: "GET",
+          formData: createFormData({}),
+        });
+        expect(t.router.state.fetchers.get("key")?.formMethod).toBe("get");
+        await C.loaders.child.resolve("LOADER FETCH");
+        expect(t.router.state.fetchers.get("key")?.formMethod).toBeUndefined();
+
+        let D = await t.fetch("/child", "key", {
+          formMethod: "post",
+          formData: createFormData({}),
+        });
+        expect(t.router.state.fetchers.get("key")?.formMethod).toBe("post");
+        await D.actions.child.resolve("ACTION FETCH");
+        expect(t.router.state.fetchers.get("key")?.formMethod).toBeUndefined();
+      });
+
+      it("normalizes to uppercase in v7 via v7_normalizeFormMethod", async () => {
+        let t = setup({
+          routes: [
+            {
+              id: "root",
+              path: "/",
+              children: [
+                {
+                  id: "child",
+                  path: "child",
+                  loader: true,
+                  action: true,
+                },
+              ],
+            },
+          ],
+          future: {
+            v7_normalizeFormMethod: true,
+          },
+        });
+        let A = await t.navigate("/child", {
+          formMethod: "get",
+          formData: createFormData({}),
+        });
+        expect(t.router.state.navigation.formMethod).toBe("GET");
+        await A.loaders.child.resolve("LOADER");
+        expect(t.router.state.navigation.formMethod).toBeUndefined();
+        await t.router.navigate("/");
+
+        let B = await t.navigate("/child", {
+          formMethod: "POST",
+          formData: createFormData({}),
+        });
+        expect(t.router.state.navigation.formMethod).toBe("POST");
+        await B.actions.child.resolve("ACTION");
+        await B.loaders.child.resolve("LOADER");
+        expect(t.router.state.navigation.formMethod).toBeUndefined();
+        await t.router.navigate("/");
+
+        let C = await t.fetch("/child", "key", {
+          formMethod: "GET",
+          formData: createFormData({}),
+        });
+        expect(t.router.state.fetchers.get("key")?.formMethod).toBe("GET");
+        await C.loaders.child.resolve("LOADER FETCH");
+        expect(t.router.state.fetchers.get("key")?.formMethod).toBeUndefined();
+
+        let D = await t.fetch("/child", "key", {
+          formMethod: "post",
+          formData: createFormData({}),
+        });
+        expect(t.router.state.fetchers.get("key")?.formMethod).toBe("POST");
+        await D.actions.child.resolve("ACTION FETCH");
+        expect(t.router.state.fetchers.get("key")?.formMethod).toBeUndefined();
+      });
+    });
   });
 
   describe("action errors", () => {
diff --git a/packages/router/index.ts b/packages/router/index.ts
index 542c0b1211..3d4fea9620 100644
--- a/packages/router/index.ts
+++ b/packages/router/index.ts
@@ -13,6 +13,7 @@ export type {
   TrackedPromise,
   FormEncType,
   FormMethod,
+  HTMLFormMethod,
   JsonFunction,
   LoaderFunction,
   LoaderFunctionArgs,
@@ -22,7 +23,7 @@ export type {
   PathPattern,
   RedirectFunction,
   ShouldRevalidateFunction,
-  Submission,
+  V7_FormMethod,
 } from "./utils";
 
 export {
diff --git a/packages/router/router.ts b/packages/router/router.ts
index bd51457eab..c4f31e5dd0 100644
--- a/packages/router/router.ts
+++ b/packages/router/router.ts
@@ -22,12 +22,15 @@ import type {
   Submission,
   SuccessResult,
   AgnosticRouteMatch,
-  MutationFormMethod,
   ShouldRevalidateFunction,
   RouteManifest,
   ImmutableRouteKey,
   ActionFunction,
   LoaderFunction,
+  V7_MutationFormMethod,
+  V7_FormMethod,
+  HTMLFormMethod,
+  MutationFormMethod,
 } from "./utils";
 import {
   DeferredData,
@@ -326,15 +329,23 @@ export type HydrationState = Partial<
   Pick<RouterState, "loaderData" | "actionData" | "errors">
 >;
 
+/**
+ * Future flags to toggle new feature behavior
+ */
+export interface FutureConfig {
+  v7_normalizeFormMethod: boolean;
+}
+
 /**
  * Initialization options for createRouter
  */
 export interface RouterInit {
-  basename?: string;
   routes: AgnosticRouteObject[];
   history: History;
-  hydrationData?: HydrationState;
+  basename?: string;
   detectErrorBoundary?: DetectErrorBoundaryFunction;
+  future?: FutureConfig;
+  hydrationData?: HydrationState;
 }
 
 /**
@@ -415,7 +426,7 @@ type SubmissionNavigateOptions = {
   replace?: boolean;
   state?: any;
   preventScrollReset?: boolean;
-  formMethod?: FormMethod;
+  formMethod?: HTMLFormMethod;
   formEncType?: FormEncType;
   formData: FormData;
 };
@@ -449,7 +460,7 @@ export type NavigationStates = {
   Loading: {
     state: "loading";
     location: Location;
-    formMethod: FormMethod | undefined;
+    formMethod: FormMethod | V7_FormMethod | undefined;
     formAction: string | undefined;
     formEncType: FormEncType | undefined;
     formData: FormData | undefined;
@@ -457,7 +468,7 @@ export type NavigationStates = {
   Submitting: {
     state: "submitting";
     location: Location;
-    formMethod: FormMethod;
+    formMethod: FormMethod | V7_FormMethod;
     formAction: string;
     formEncType: FormEncType;
     formData: FormData;
@@ -483,7 +494,7 @@ type FetcherStates<TData = any> = {
   };
   Loading: {
     state: "loading";
-    formMethod: FormMethod | undefined;
+    formMethod: FormMethod | V7_FormMethod | undefined;
     formAction: string | undefined;
     formEncType: FormEncType | undefined;
     formData: FormData | undefined;
@@ -492,7 +503,7 @@ type FetcherStates<TData = any> = {
   };
   Submitting: {
     state: "submitting";
-    formMethod: FormMethod;
+    formMethod: FormMethod | V7_FormMethod;
     formAction: string;
     formEncType: FormEncType;
     formData: FormData;
@@ -676,6 +687,11 @@ export function createRouter(init: RouterInit): Router {
     manifest
   );
   let inFlightDataRoutes: AgnosticDataRouteObject[] | undefined;
+  // Config driven behavior flags
+  let future: FutureConfig = {
+    v7_normalizeFormMethod: false,
+    ...init.future,
+  };
   // Cleanup function for history
   let unlistenHistory: (() => void) | null = null;
   // Externally-provided functions to call on all state changes
@@ -1013,7 +1029,11 @@ export function createRouter(init: RouterInit): Router {
       return;
     }
 
-    let { path, submission, error } = normalizeNavigateOptions(to, opts);
+    let { path, submission, error } = normalizeNavigateOptions(
+      to,
+      future,
+      opts
+    );
 
     let currentLocation = state.location;
     let nextLocation = createLocation(state.location, path, opts && opts.state);
@@ -1568,7 +1588,12 @@ export function createRouter(init: RouterInit): Router {
       return;
     }
 
-    let { path, submission } = normalizeNavigateOptions(href, opts, true);
+    let { path, submission } = normalizeNavigateOptions(
+      href,
+      future,
+      opts,
+      true
+    );
     let match = getTargetMatch(matches, path);
 
     pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
@@ -2441,12 +2466,12 @@ export function createStaticHandler(
     { requestContext }: { requestContext?: unknown } = {}
   ): Promise<StaticHandlerContext | Response> {
     let url = new URL(request.url);
-    let method = request.method.toLowerCase();
+    let method = request.method;
     let location = createLocation("", createPath(url), null, "default");
     let matches = matchRoutes(dataRoutes, location, basename);
 
     // SSR supports HEAD requests while SPA doesn't
-    if (!isValidMethod(method) && method !== "head") {
+    if (!isValidMethod(method) && method !== "HEAD") {
       let error = getInternalRouterError(405, { method });
       let { matches: methodNotAllowedMatches, route } =
         getShortCircuitMatches(dataRoutes);
@@ -2523,12 +2548,12 @@ export function createStaticHandler(
     }: { requestContext?: unknown; routeId?: string } = {}
   ): Promise<any> {
     let url = new URL(request.url);
-    let method = request.method.toLowerCase();
+    let method = request.method;
     let location = createLocation("", createPath(url), null, "default");
     let matches = matchRoutes(dataRoutes, location, basename);
 
     // SSR supports HEAD requests while SPA doesn't
-    if (!isValidMethod(method) && method !== "head" && method !== "options") {
+    if (!isValidMethod(method) && method !== "HEAD" && method !== "OPTIONS") {
       throw getInternalRouterError(405, { method });
     } else if (!matches) {
       throw getInternalRouterError(404, { pathname: location.pathname });
@@ -2923,6 +2948,7 @@ function isSubmissionNavigation(
 // URLSearchParams so they behave identically to links with query params
 function normalizeNavigateOptions(
   to: To,
+  future: FutureConfig,
   opts?: RouterNavigateOptions,
   isFetcher = false
 ): {
@@ -2947,8 +2973,11 @@ function normalizeNavigateOptions(
   // Create a Submission on non-GET navigations
   let submission: Submission | undefined;
   if (opts.formData) {
+    let formMethod = opts.formMethod || "get";
     submission = {
-      formMethod: opts.formMethod || "get",
+      formMethod: future.v7_normalizeFormMethod
+        ? (formMethod.toUpperCase() as V7_FormMethod)
+        : (formMethod.toLowerCase() as FormMethod),
       formAction: stripHashFromPath(path),
       formEncType:
         (opts && opts.formEncType) || "application/x-www-form-urlencoded",
@@ -3462,7 +3491,7 @@ function createClientSideRequest(
 
   if (submission && isMutationMethod(submission.formMethod)) {
     let { formMethod, formEncType, formData } = submission;
-    init.method = formMethod.toUpperCase();
+    init.method = formMethod;
     init.body =
       formEncType === "application/x-www-form-urlencoded"
         ? convertFormDataToSearchParams(formData)
@@ -3831,12 +3860,14 @@ function isQueryRouteResponse(obj: any): obj is QueryRouteResponse {
   );
 }
 
-function isValidMethod(method: string): method is FormMethod {
-  return validRequestMethods.has(method as FormMethod);
+function isValidMethod(method: string): method is FormMethod | V7_FormMethod {
+  return validRequestMethods.has(method.toLowerCase() as FormMethod);
 }
 
-function isMutationMethod(method?: string): method is MutationFormMethod {
-  return validMutationMethods.has(method as MutationFormMethod);
+function isMutationMethod(
+  method: string
+): method is MutationFormMethod | V7_MutationFormMethod {
+  return validMutationMethods.has(method.toLowerCase() as MutationFormMethod);
 }
 
 async function resolveDeferredResults(
diff --git a/packages/router/utils.ts b/packages/router/utils.ts
index fd9d1cbea5..4851168d21 100644
--- a/packages/router/utils.ts
+++ b/packages/router/utils.ts
@@ -63,8 +63,28 @@ export type DataResult =
   | RedirectResult
   | ErrorResult;
 
-export type MutationFormMethod = "post" | "put" | "patch" | "delete";
-export type FormMethod = "get" | MutationFormMethod;
+type LowerCaseFormMethod = "get" | "post" | "put" | "patch" | "delete";
+type UpperCaseFormMethod = Uppercase<LowerCaseFormMethod>;
+
+/**
+ * Users can specify either lowercase or uppercase form methods on <Form>,
+ * useSubmit(), <fetcher.Form>, etc.
+ */
+export type HTMLFormMethod = LowerCaseFormMethod | UpperCaseFormMethod;
+
+/**
+ * Active navigation/fetcher form methods are exposed in lowercase on the
+ * RouterState
+ */
+export type FormMethod = LowerCaseFormMethod;
+export type MutationFormMethod = Exclude<FormMethod, "get">;
+
+/**
+ * In v7, active navigation/fetcher form methods are exposed in uppercase on the
+ * RouterState.  This is to align with the normalization done via fetch().
+ */
+export type V7_FormMethod = UpperCaseFormMethod;
+export type V7_MutationFormMethod = Exclude<V7_FormMethod, "GET">;
 
 export type FormEncType =
   | "application/x-www-form-urlencoded"
@@ -76,7 +96,7 @@ export type FormEncType =
  * external consumption
  */
 export interface Submission {
-  formMethod: FormMethod;
+  formMethod: FormMethod | V7_FormMethod;
   formAction: string;
   formEncType: FormEncType;
   formData: FormData;