diff --git a/components/dash-core-components/src/components/Input.react.js b/components/dash-core-components/src/components/Input.react.js
index d4656fddae..779f526439 100644
--- a/components/dash-core-components/src/components/Input.react.js
+++ b/components/dash-core-components/src/components/Input.react.js
@@ -184,16 +184,23 @@ export default class Input extends PureComponent {
 
     onChange() {
         const {debounce} = this.props;
-        if (debounce) {
-            if (Number.isFinite(debounce)) {
-                this.debounceEvent(debounce);
-            }
-            if (this.props.type !== 'number') {
-                this.setState({value: this.input.current.value});
+        const input = this.input.current;
+        const cursorPosition = input.selectionStart;
+        const currentValue = input.value;
+        this.setState({value: currentValue}, () => {
+            if (debounce) {
+                if (Number.isFinite(debounce)) {
+                    this.debounceEvent(debounce);
+                }
+                if (this.props.type !== 'number') {
+                    setTimeout(() => {
+                        input.setSelectionRange(cursorPosition, cursorPosition);
+                    }, 0);
+                }
+            } else {
+                this.onEvent();
             }
-        } else {
-            this.onEvent();
-        }
+        });
     }
 }
 
diff --git a/components/dash-core-components/tests/integration/input/test_input_basics.py b/components/dash-core-components/tests/integration/input/test_input_basics.py
index 853caef319..c76e4fa481 100644
--- a/components/dash-core-components/tests/integration/input/test_input_basics.py
+++ b/components/dash-core-components/tests/integration/input/test_input_basics.py
@@ -103,3 +103,72 @@ def test_inbs003_styles_are_scoped(dash_dcc):
     dash_outline_css = dash_input.value_of_css_property("outline")
 
     assert external_outline_css != dash_outline_css
+
+@pytest.mark.parametrize(
+    "initial_text, invalid_char, cursor_position_before, expected_text, expected_cursor_position",
+    [
+        ("abcdddef", "/", 2, "ab/cdddef", 3),
+        ("abcdef", "$", 2, "ab$cdef", 3),
+        ("abcdef", "$", 3, "abc$def", 4),
+        ("abcdef", "A", 4, "abcdAef", 5),  # valid character
+    ],
+)
+def test_inbs004_cursor_position_on_invalid_input(
+    dash_dcc,
+    initial_text,
+    invalid_char,
+    cursor_position_before,
+    expected_text,
+    expected_cursor_position,
+):
+    app = Dash(__name__)
+
+    app.layout = html.Div(
+        [
+            dcc.Input(
+                id="test-input",
+                type="text",
+                placeholder="File name",
+                className="create_file_input",
+                pattern="[a-zA-Z_][a-zA-Z0-9_]*",
+            ),
+            html.Div(id="output"),
+        ]
+    )
+
+    dash_dcc.start_server(app)
+    input_elem = dash_dcc.find_element("#test-input")
+
+    input_elem.send_keys(initial_text)
+    assert (
+        input_elem.get_attribute("value") == initial_text
+    ), "Initial text should match"
+
+    dash_dcc.driver.execute_script(
+        f"""
+        var elem = arguments[0];
+        elem.setSelectionRange({cursor_position_before}, {cursor_position_before});
+        elem.focus();
+    """,
+        input_elem,
+    )
+
+    input_elem.send_keys(invalid_char)
+
+    assert (
+        input_elem.get_attribute("value") == expected_text
+    ), f"Input should be {expected_text}"
+
+    cursor_position = dash_dcc.driver.execute_script(
+        """
+        var elem = arguments[0];
+        return elem.selectionStart;
+    """,
+        input_elem,
+    )
+
+    assert (
+        cursor_position == expected_cursor_position
+    ), f"Cursor should be at position {expected_cursor_position}"
+
+    assert dash_dcc.get_logs() == []