diff --git a/src/internal/components/TextInput.test.tsx b/src/internal/components/TextInput.test.tsx
new file mode 100644
index 0000000000..f72f89ec68
--- /dev/null
+++ b/src/internal/components/TextInput.test.tsx
@@ -0,0 +1,81 @@
+import '@testing-library/jest-dom';
+import { fireEvent, render, screen } from '@testing-library/react';
+import { act } from 'react';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import { TextInput } from './TextInput';
+
+const DELAY_MS = 100;
+
+const RenderTest = ({
+ className = 'custom-class',
+ delayMs = DELAY_MS,
+ onChange = vi.fn(),
+ placeholder = 'Enter text',
+ setValue = vi.fn(),
+ value = 'test',
+ ...props
+}) => (
+
+);
+
+describe('TextInput', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('renders with default props', () => {
+ render();
+ expect(screen.getByTestId('ockTextInput_Input')).toBeInTheDocument();
+ });
+
+ it('handles value changes', () => {
+ const onChange = vi.fn();
+ const { getByTestId } = render();
+
+ const input = getByTestId('ockTextInput_Input');
+ act(() => {
+ fireEvent.change(input, { target: { value: '2' } });
+ });
+
+ vi.advanceTimersByTime(DELAY_MS);
+
+ expect(onChange).toHaveBeenCalledWith('2');
+ });
+
+ it('applies custom className', () => {
+ render();
+ expect(screen.getByTestId('ockTextInput_Input')).toHaveClass(
+ 'custom-class',
+ );
+ });
+
+ it('handles placeholder text', () => {
+ render();
+ expect(screen.getByPlaceholderText('Enter text')).toBeInTheDocument();
+ });
+
+ it('handles disabled state', () => {
+ render();
+ expect(screen.getByTestId('ockTextInput_Input')).toBeDisabled();
+ });
+
+ it('handles inputMode', () => {
+ const { getByTestId } = render();
+ expect(getByTestId('ockTextInput_Input')).toHaveAttribute(
+ 'inputMode',
+ 'decimal',
+ );
+ });
+});
diff --git a/src/internal/components/TextInput.tsx b/src/internal/components/TextInput.tsx
index 198b2ae30e..31f870aa58 100644
--- a/src/internal/components/TextInput.tsx
+++ b/src/internal/components/TextInput.tsx
@@ -1,5 +1,5 @@
import { useCallback } from 'react';
-import type { ChangeEvent } from 'react';
+import type { ChangeEvent, InputHTMLAttributes } from 'react';
import { useDebounce } from '../../core-react/internal/hooks/useDebounce';
type TextInputReact = {
@@ -7,6 +7,8 @@ type TextInputReact = {
className: string;
delayMs: number;
disabled?: boolean;
+ // specify 'decimal' to trigger numeric keyboards on mobile devices
+ inputMode?: InputHTMLAttributes['inputMode'];
onBlur?: () => void;
onChange: (s: string) => void;
placeholder: string;
@@ -24,6 +26,7 @@ export function TextInput({
onChange,
placeholder,
setValue,
+ inputMode,
value,
inputValidator = () => true,
}: TextInputReact) {
@@ -53,6 +56,7 @@ export function TextInput({
data-testid="ockTextInput_Input"
type="text"
className={className}
+ inputMode={inputMode}
placeholder={placeholder}
value={value}
onBlur={onBlur}