Dynamically Swapping Input / Keyboard InputMode On iOS
In Dig Deep Fitness, my ColdFusion fitness tracker, exercise weights and reps (repetitions) are stored as text. This provides greater real-world flexibility and allows users to enter text values like "BW" (Body Weight) and "Red Band" and "Large Sand Bag". That said, the majority of cases will almost certainly be the entering of standard, numeric weights and reps. This poses a problem. I'd like the default keyboard for my exercise sets to be the Numeric keyboard, as this has large, chonky buttons that make data entry easier. But, I still want the user to be able to switch to the Text keyboard for edge-cases. Unfortunately, iOS doesn't provide a way to switch from the numeric keyboard back to the text keyboard. As such, we have come up with hacky ways to build this functionality into the user interface (UI).
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
My iOS device will change the keyboard that it opens based on the inputmode
of the focused Input control. But, if we dynamically change the inputmode
while the keyboard is already open, iOS ignores the change, leaving the previously-rendered keyboard in view.
Over on StackOverflow, Nate Adams shared an approach (that apparently took inspiration from Nike) to temporarily focus another input while changing the inputmode
; then, to re-focus the original input with a new inputmode
wherein iOS will render the new keyboard.
This gives us a foothold into a technical workflow; but, we still have an issue in that the iOS Numeric keyboard doesn't actually have a button to get back to the Text keyboard. As such, we have to provide a trigger in the UI (User Interface) of the page.
To experiment with this approach, I wanted to create an input control that has a "built in" button for toggling the inputmode
. The Input and the Button are displayed side-by-side using display: flex
; and, attempt to create a sense of unification. Clicking on the button will toggle between decimal
and text
for the inputmode
property.
CAUTION: This is not a production-ready solution - this is just a proof-of-concept. It has several issues, not the least of which is the fact that the buttons will show on Desktop devices where they serve no purpose. A full, production-ready solution would certainly include some sort of progressive enhancement with something like CSS media-queries.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
html {
box-sizing: border-box ;
}
html *,
html *:before,
html *:after {
box-sizing: inherit ;
}
.numberish {
border-radius: 1px ;
display: flex ;
font-size: 18px ;
position: relative ;
width: 100% ;
}
.numberish__input {
border: 1px solid #cccccc ;
border-radius: 3px 0px 0px 3px ;
flex: 1 1 auto ;
font-size: inherit ;
font-weight: inherit ;
margin: 0px 0px 0px 0px ;
padding: 10px 10px 10px 13px ;
}
.numberish__switch {
border: 1px solid #cccccc ;
border-radius: 0px 3px 3px 0px ;
border-left-width: 0px ;
color: #333333 ;
flex: 0 0 auto ;
font-size: 20px ;
letter-spacing: 1px ;
margin: 0px 0px 0px 0px ;
padding: 0px 15px 0px 15px ;
}
.numberish__temp {
left: 0px ;
opacity: 0 ;
pointer-events: none ; /* No clicking allowed! */
position: absolute ;
top: 0px ;
}
/* Set button text based on state of control. */
.numberish--numeric .numberish__switch:before {
content: "Aa" ;
}
.numberish--alpha .numberish__switch:before {
content: "123" ;
}
/*
Prevent individual elements from being outlined - keep outline around the
entire control when either sub-element is focused.
*/
.numberish:focus-within {
outline: 2px solid blue ;
outline-offset: 2px ;
}
.numberish__input:focus,
.numberish__switch:focus {
outline: none ;
}
</style>
</head>
<body>
<h1>
Swapping Keyboard InputMode Dynamically on iOS
</h1>
<div class="numberish">
<input
type="text"
class="numberish__input"
/>
<button
aria-hidden="true"
type="button"
tabindex="-1"
class="numberish__switch">
<!-- Text is set via CSS content. -->
</button>
<!--
In order to trigger the keyboard change on iOS, we have to focus ANOTHER input
briefly before then returning the focus to the intended input with the updated
inputmode. This temp input will not be visible or tabbable by the user, but
will be focused programmatically when the user wants to switch keyboards.
-->
<input
aria-hidden="true"
type="text"
tabindex="-1"
class="numberish__temp"
/>
</div>
<script type="text/javascript">
// Centralize the input-mode settings.
var numericInputmode = "decimal";
var numericPattern = "[0-9.]*";
var alphaInputmode = "text";
var alphaPattern = ".*";
// Cache DOM element references.
var numberish = document.querySelector( ".numberish" );
var input = numberish.querySelector( ".numberish__input" );
var button = numberish.querySelector( ".numberish__switch" );
var temp = numberish.querySelector( ".numberish__temp" );
// Initialize the input setup (default to numeric keyboard).
numberish.classList.add( "numberish--numeric" );
input.inputMode = numericInputmode;
input.pattern = numericPattern;
button.addEventListener( "click", toggleInputType );
/**
* I switch the control from one type of keyboard to the other.
*/
function toggleInputType() {
// Switching to alpha keyboard.
if ( numberish.classList.contains( "numberish--numeric" ) ) {
numberish.classList.remove( "numberish--numeric" );
numberish.classList.add( "numberish--alpha" );
input.inputMode = temp.inputMode = alphaInputmode;
input.pattern = temp.pattern = alphaPattern;
// Switching to numeric keyboard.
} else {
numberish.classList.remove( "numberish--alpha" );
numberish.classList.add( "numberish--numeric" );
input.inputMode = temp.inputMode = numericInputmode;
input.pattern = temp.pattern = numericPattern;
}
// When the input is focused, the keyboard won't update (based on the
// inputmode). As such, we have to briefly focus the temp input and then
// re-focus the main input.
// --
// Concept From StackOverflow: https://stackoverflow.com/a/55425845
temp.focus();
window.requestAnimationFrame(
() => {
input.focus();
// Move caret to end of input value.
input.setSelectionRange( input.value.length, input.value.length );
}
);
}
</script>
</body>
</html>
As you can see, when the user clicks the button in the UI, we:
Change the
inputmode
andpattern
settings of the Input.Focus the hidden temp input briefly.
Re-Focus the main visible input.
And, when we do so, we get the following experience on Chrome (and Safari) for iOS:
As you can see, when I hit the Aa
/123
button in the control, it (seems to from a User Experience perspective) leave the input focused and immediately changes from one keyboard to another.
I don't have an Android device, so I wasn't able to see if this approach works on Android. And, actually, I think I remember seeing in a screenshot that Android allows the user to switch between Numeric and Text keyboards, so it probably isn't even necessary on Android - yet another reason why this isn't a production-ready approach.
If nothing else, this gives me something to think about in terms of possible user experiences.
Want to use code from this post? Check out the license.
Reader Comments
One thing to be careful of with
inputmode
; or, more specifically withpattern
is that it can trigger the browser's native form validation. This may or may not be what you want.In my case, I ran into a situation where all my inputs were truly "text", but I was using the
inputmode
/pattern
changes to swap the keyboard as a convenience, not as a validation. However, when a non-numeric value was in my numeric keyboard field, the form was preventing submission.To fix this, I had to add
novalidate
to the form element:<form method="post" novalidate>
Then, it doesn't matter what is in the field.
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →