diff --git a/e2e/test/scenarios/dashboard-filters/dashboard-filters-sql-number.cy.spec.js b/e2e/test/scenarios/dashboard-filters/dashboard-filters-sql-number.cy.spec.js
index 2b6ae6a10f13b6499dbef2525743ea76c5fd9126..5e6fec3e652c882e905eedb9d5ca9b20389397fb 100644
--- a/e2e/test/scenarios/dashboard-filters/dashboard-filters-sql-number.cy.spec.js
+++ b/e2e/test/scenarios/dashboard-filters/dashboard-filters-sql-number.cy.spec.js
@@ -5,8 +5,10 @@ import {
   filterWidget,
   editDashboard,
   saveDashboard,
+  getDashboardCard,
   setFilter,
   visitQuestion,
+  sidebar,
   visitDashboard,
 } from "e2e/support/helpers";
 
@@ -38,7 +40,7 @@ describe("scenarios > dashboard > filters > SQL > text/category", () => {
 
       setFilter("Number", filter);
 
-      cy.findByText("Select…").click();
+      clickSelect();
       popover().contains(filter).click();
     });
 
@@ -60,15 +62,13 @@ describe("scenarios > dashboard > filters > SQL > text/category", () => {
     );
   });
 
-  it(`should work when set as the default filter`, () => {
+  it("should work when set as the default filter", () => {
     setFilter("Number", "Equal to");
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.findByText("Default value").next().click();
+    sidebar().findByText("Default value").next().click();
 
     addWidgetNumberFilter("3.8");
 
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.findByText("Select…").click();
+    clickSelect();
     popover().contains("Equal to").click();
 
     saveDashboard();
@@ -90,3 +90,106 @@ describe("scenarios > dashboard > filters > SQL > text/category", () => {
     });
   });
 });
+
+describe("scenarios > dashboard > filters > SQL > number", () => {
+  const questionDetails = {
+    name: "Question 1",
+    native: {
+      query:
+        "SELECT * from products where true [[ and price > {{price}}]] [[ and rating > {{rating}} ]] limit 5;",
+      "template-tags": {
+        price: {
+          type: "number",
+          name: "price",
+          id: "b22a5ce2-fe1d-44e3-8df4-f8951f7921bc",
+          "display-name": "Price",
+        },
+        rating: {
+          type: "number",
+          name: "rating",
+          id: "68821a54-f0f3-4f09-8c32-6f7c0e5e5399",
+          "display-name": "Rating",
+        },
+      },
+    },
+  };
+
+  const filterDetails = [
+    {
+      name: "Rating",
+      slug: "rating",
+      id: "10c0d4ba",
+      type: "number/=",
+      sectionId: "number",
+    },
+    {
+      name: "Price",
+      slug: "price",
+      id: "88b1a9dd",
+      type: "number/=",
+      sectionId: "number",
+    },
+  ];
+
+  const parameterMapping = filterDetails.map(filter => ({
+    parameter_id: filter.id,
+    target: ["variable", ["template-tag", filter.slug]],
+  }));
+
+  const dashboardDetails = {
+    name: "Dashboard #31975",
+    parameters: filterDetails,
+  };
+  const dashcardDetails = {
+    row: 0,
+    col: 0,
+    size_x: 16,
+    size_y: 8,
+  };
+
+  beforeEach(() => {
+    restore();
+    cy.signInAsAdmin();
+
+    cy.createNativeQuestionAndDashboard({
+      questionDetails,
+      dashboardDetails,
+    }).then(({ body: { id, card_id, dashboard_id } }) => {
+      cy.request("PUT", `/api/dashboard/${dashboard_id}/cards`, {
+        cards: [
+          {
+            id,
+            card_id,
+            ...dashcardDetails,
+            parameter_mappings: parameterMapping.map(mapping => ({
+              ...mapping,
+              card_id,
+            })),
+          },
+        ],
+      });
+
+      visitDashboard(dashboard_id);
+    });
+  });
+
+  it("should keep filter value on blur (metabase#31975)", () => {
+    cy.findByPlaceholderText("Price").type("95").blur();
+    cy.findByPlaceholderText("Rating").type("3.8").blur();
+
+    cy.findAllByTestId("table-row")
+      .should("have.length", 2)
+      // first line price
+      .and("contain", "98.82")
+      // first line rating
+      .and("contain", "4.3")
+      // second line price
+      .and("contain", "95.93")
+      // second line rating
+      .and("contain", "4.4");
+  });
+});
+
+function clickSelect() {
+  getDashboardCard().findByText("Select…").click();
+}
diff --git a/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx b/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx
index 1be9dd131869c3259fdb112c1f780b335d4a27b0..c725110229b875d33e392f1aba92031871c35c76 100644
--- a/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx
+++ b/frontend/src/metabase/components/TextWidget/TextWidget.stories.tsx
@@ -1,6 +1,6 @@
 import type { ComponentStory } from "@storybook/react";
 import { useArgs } from "@storybook/client-api";
-import TextWidget from "./TextWidget";
+import { TextWidget } from "./TextWidget";
 
 export default {
   title: "Parameters/TextWidget",
diff --git a/frontend/src/metabase/components/TextWidget/TextWidget.tsx b/frontend/src/metabase/components/TextWidget/TextWidget.tsx
index 58af004c1139e5e569228dd1560a8596718d544e..410c7d1498b4791df431326a6f8ab2aa79f3ee6f 100644
--- a/frontend/src/metabase/components/TextWidget/TextWidget.tsx
+++ b/frontend/src/metabase/components/TextWidget/TextWidget.tsx
@@ -2,7 +2,6 @@ import { Component } from "react";
 import ReactDOM from "react-dom";
 import { t } from "ttag";
 import { forceRedraw } from "metabase/lib/dom";
-import { KEYCODE_ENTER, KEYCODE_ESCAPE } from "metabase/lib/keyboard";
 
 type Props = {
   value: string | number;
@@ -20,7 +19,7 @@ type State = {
   isFocused: boolean;
 };
 
-class TextWidget extends Component<Props, State> {
+export class TextWidget extends Component<Props, State> {
   static defaultProps = {
     isEditing: false,
     commitImmediately: false,
@@ -82,9 +81,9 @@ class TextWidget extends Component<Props, State> {
         }}
         onKeyUp={e => {
           const target = e.target as HTMLInputElement;
-          if (e.keyCode === KEYCODE_ESCAPE) {
+          if (e.key === "Escape") {
             target.blur();
-          } else if (e.keyCode === KEYCODE_ENTER) {
+          } else if (e.key === "Enter") {
             setValue(this.state.value ?? null);
             target.blur();
           }
@@ -94,7 +93,9 @@ class TextWidget extends Component<Props, State> {
         }}
         onBlur={() => {
           changeFocus(false);
-          this.setState({ value: this.props.value });
+          if (this.state.value !== this.props.value) {
+            setValue(this.state.value ?? null);
+          }
         }}
         placeholder={isEditing ? t`Enter a default value…` : defaultPlaceholder}
         disabled={disabled}
@@ -102,6 +103,3 @@ class TextWidget extends Component<Props, State> {
     );
   }
 }
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default TextWidget;
diff --git a/frontend/src/metabase/components/TextWidget/TextWidget.unit.spec.tsx b/frontend/src/metabase/components/TextWidget/TextWidget.unit.spec.tsx
index fbbbbede7fc815726c1eaea583450ec13b2743a9..76a9ac513d39931d31d9e596c58b40b073a26547 100644
--- a/frontend/src/metabase/components/TextWidget/TextWidget.unit.spec.tsx
+++ b/frontend/src/metabase/components/TextWidget/TextWidget.unit.spec.tsx
@@ -1,7 +1,7 @@
 import { useState } from "react";
 import { fireEvent, render, screen } from "@testing-library/react";
 import userEvent from "@testing-library/user-event";
-import TextWidget from "./TextWidget";
+import { TextWidget } from "./TextWidget";
 
 const TextInputWithStateWrapper = ({ value }: { value?: number | string }) => {
   const [val, setVal] = useState<number | string | null>(value ?? "");
diff --git a/frontend/src/metabase/components/TextWidget/index.ts b/frontend/src/metabase/components/TextWidget/index.ts
index 93b7d2dc3d3050c9f700538f3b95f177436c2c34..9ce758df73768fa985eca7ad6ef8c5e9d5ac134f 100644
--- a/frontend/src/metabase/components/TextWidget/index.ts
+++ b/frontend/src/metabase/components/TextWidget/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./TextWidget";
+export { TextWidget } from "./TextWidget";
diff --git a/frontend/src/metabase/dashboard/actions/parameters.js b/frontend/src/metabase/dashboard/actions/parameters.js
index fc6dda3cc9be2610eeea00a3098eefd0079043c9..72b0a30193e1198b5397be9d88ebc228ab963bf8 100644
--- a/frontend/src/metabase/dashboard/actions/parameters.js
+++ b/frontend/src/metabase/dashboard/actions/parameters.js
@@ -189,6 +189,7 @@ export const setParameterValue = createThunkAction(
   SET_PARAMETER_VALUE,
   (parameterId, value) => (_dispatch, getState) => {
     const isSettingDraftParameterValues = !getIsAutoApplyFilters(getState());
+
     return {
       id: parameterId,
       value: normalizeValue(value),
@@ -202,6 +203,10 @@ function normalizeValue(value) {
     return null;
   }
 
+  if (value === "") {
+    return null;
+  }
+
   return value;
 }
 
diff --git a/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx b/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx
index 550cd43fc7b953e7f07783941822f4307d5b7f7c..7e7914d653b6c160441cb0e9fe08938fc85c7137 100644
--- a/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx
+++ b/frontend/src/metabase/parameters/components/ParameterValueWidget.jsx
@@ -16,7 +16,7 @@ import DateRelativeWidget from "metabase/components/DateRelativeWidget";
 import DateMonthYearWidget from "metabase/components/DateMonthYearWidget";
 import DateQuarterYearWidget from "metabase/components/DateQuarterYearWidget";
 import { DateAllOptionsWidget } from "metabase/components/DateAllOptionsWidget";
-import TextWidget from "metabase/components/TextWidget";
+import { TextWidget } from "metabase/components/TextWidget";
 import WidgetStatusIcon from "metabase/parameters/components/WidgetStatusIcon";
 import FormattedParameterValue from "metabase/parameters/components/FormattedParameterValue";
 import NumberInputWidget from "metabase/parameters/components/widgets/NumberInputWidget";