diff --git a/.changeset/stale-poets-clap.md b/.changeset/stale-poets-clap.md
new file mode 100644
index 000000000000..d3430c0337d4
--- /dev/null
+++ b/.changeset/stale-poets-clap.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: handles form target attribute in remote form redirects
diff --git a/packages/kit/src/runtime/client/remote-functions/form.svelte.js b/packages/kit/src/runtime/client/remote-functions/form.svelte.js
index cc192c00f546..090fd273f971 100644
--- a/packages/kit/src/runtime/client/remote-functions/form.svelte.js
+++ b/packages/kit/src/runtime/client/remote-functions/form.svelte.js
@@ -298,6 +298,14 @@ export function form(id) {
return;
}
+ const target = event.submitter?.hasAttribute('formtarget')
+ ? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formTarget
+ : clone(form).target;
+
+ if (target === '_blank') {
+ return;
+ }
+
event.preventDefault();
const form_data = new FormData(form, event.submitter);
diff --git a/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/+page.svelte
new file mode 100644
index 000000000000..95de0b093475
--- /dev/null
+++ b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/+page.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/destination/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/destination/+page.svelte
new file mode 100644
index 000000000000..3c832468812a
--- /dev/null
+++ b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/destination/+page.svelte
@@ -0,0 +1 @@
+destination
diff --git a/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/form.remote.ts b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/form.remote.ts
new file mode 100644
index 000000000000..1977f00fd86c
--- /dev/null
+++ b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/form.remote.ts
@@ -0,0 +1,12 @@
+import { form } from '$app/server';
+import { redirect } from '@sveltejs/kit';
+import { object, optional, string } from 'valibot';
+
+export const redirectForm = form(
+ object({
+ id: optional(string())
+ }),
+ () => {
+ redirect(303, '/remote/form/redirect-target/destination');
+ }
+);
diff --git a/packages/kit/test/apps/async/test/test.js b/packages/kit/test/apps/async/test/test.js
index f9715f83842a..c5af41c86d31 100644
--- a/packages/kit/test/apps/async/test/test.js
+++ b/packages/kit/test/apps/async/test/test.js
@@ -154,6 +154,52 @@ test.describe('remote functions', () => {
await page.waitForURL('/remote');
});
+ test('remote form redirect opens in new tab when target=_blank', async ({ page }) => {
+ await page.goto('/remote/form/redirect-target');
+
+ const popup_promise = page.waitForEvent('popup', { timeout: 5000 });
+
+ await page.locator('[data-testid="form-blank"] button').click();
+
+ const popup = await popup_promise;
+ await popup.waitForLoadState();
+
+ expect(popup.url()).toContain('/remote/form/redirect-target/destination');
+
+ expect(page.url()).toContain('/remote/form/redirect-target');
+ expect(page.url()).not.toContain('/destination');
+ });
+
+ test('remote form redirect navigates same tab without target=_blank', async ({ page }) => {
+ await page.goto('/remote/form/redirect-target');
+
+ let popup_opened = false;
+ page.on('popup', () => {
+ popup_opened = true;
+ });
+
+ await page.locator('form:not([target]) button').click();
+ await page.waitForURL('**/remote/form/redirect-target/destination');
+
+ expect(popup_opened).toBe(false);
+ expect(page.url()).toContain('/remote/form/redirect-target/destination');
+ });
+
+ test('remote form redirect opens in new tab when formtarget=_blank on input', async ({
+ page
+ }) => {
+ await page.goto('/remote/form/redirect-target');
+
+ const popup_promise = page.waitForEvent('popup', { timeout: 5000 });
+ await page.locator('[data-testid="form-input-blank"] input').click();
+ const popup = await popup_promise;
+ await popup.waitForLoadState();
+
+ expect(popup.url()).toContain('/remote/form/redirect-target/destination');
+ expect(page.url()).toContain('/remote/form/redirect-target');
+ expect(page.url()).not.toContain('/destination');
+ });
+
test('form multiple submit buttons work', async ({ page, javaScriptEnabled }) => {
await page.goto('/remote/form/multiple-submit');