iOS Keyboard Recreated in HTML, CSS, and jQuery

Over a long period of time, Apple has gradually changed the appearance of the iOS keyboard. It has evolved from using gradients and engraved key labels to a much flatter look while the layouts have been basically the same.

iOS keyboard evolution

In this post, I will show you how to create working replica of the latest iOS keyboard for fun in HTML, CSS, and jQuery. It is the one on the iPhone and iPod Touch. You won’t see special buttons like the language (globe) button because the typing functionality and the main keyboard layouts will be the focus.


The HTML

First, here is the textarea for typing. Since only the virtual keyboard should add text, we add the readonly attribute. If we were to add and remove text manually, then the keyboard would no longer work properly.

<textarea cols="45" rows="5" readonly></textarea>

Since the rest of the HTML code is ridiculously long and repetitive, here is the code pattern for four different keyboard layouts given these IDs: lowercase (a-z), uppercase (A-Z), numbers (1-0/symbols/punctuation), and symbols (additional symbols/punctuation). There is div with the row class for each row of the keyboard. The white class is for each white key, and the gray class is for the gray keys such as Shift (⇧) and Backspace (⌫). Inside the key * divs, the popout divs contain the key pop-out and the neck (trapezoid) that connects it and the key.

<div id="keyboard">
    <div id="lowercase">
        <div class="row">
            <div class="key white">
                <span>q</span>
                <div class="popout">
                    <div class="head"></div>
                    <div class="neck"></div>
                </div>
            </div>
            <div class="key white">
                <span>w</span>
                <div class="popout">
                    <div class="head"></div>
                    <div class="neck"></div>
                </div>
            </div>
            . . .
        </div>
   </div>
</div>

The CSS

For the font, we’ll use first target Helvetica Neue to give the most native feel on Apple computers or iDevices since they use that font. If on any device not from Apple, we will target the regular Helvetica instead if it’s available.

body, textarea {
    font-family: "Helvetica Neue", Helvetica, sans-serif;
}
textarea {
    font-size: 12pt;
    padding: 5px;
}

Keyboard

The next part of the CSS gives the keyboard its appearance and aligns the rows of keys. Since we want the lowercase keyboard mode to appear by default, the other modes shall be hidden with display: none;. The reason they are absolutely positioned is that when styling the key pop-outs later, they are going to appear directly above their keys.

/* Main styles */
#keyboard {
    background: #d1d5db;
    font-size: 16pt;
    width: 380px;
    height: 216px;
}
#lowercase, #uppercase, #numbers, #symbols {
    position: absolute;
    z-index: 0;
}
#lowercase {
    display: block;
}
#uppercase, #numbers, #symbols {
    display: none;
}
.row {
    margin: 10px 3px;
    text-align: center;
}

These styles affect the Shift and Backspace keys. As we see on the lowercase keyboard mode, they add the space between the Shift and z keys and the space between the m and Backspace keys. Notice that Backspace has an :active state of a white background for when this key is pressed.

/* Third row */
.row:nth-of-type(3) .gray:first-of-type {
    margin-right: 10px;
}
.row:nth-of-type(3) .gray:last-of-type {
    margin-left: 10px;
}
.row:nth-of-type(3) .gray:last-of-type:active {
    background: #fff;
}

The next portion of styles gives the 123, spacebar, and Return keys their proper widths. Just like the Backspace key, the spacebar will turn white during its :active state, and the Return key will turn gray.

/* Last row */
.row:last-of-type .key {
    font-size: 12pt;
    line-height: 2.7em;
}
.row:last-of-type .gray {
    width: 88px;
}
.row:last-of-type .white {
    width: 184px;
}
.row:last-of-type .white:active {
    background: #aaa;
}
.row:last-of-type .gray:last-of-type:active {
    background: #fff;
}

Keys

Each of the keys on the keyboard will use a border radius of 5 pixels, box shadow, and the other properties below. This is also where we define styles for the white and gray keys.

/* Keys */
.key {
    border-radius: 5px;
    box-shadow: 0 1px 0 #888;
    display: inline-block;
    line-height: 1.9em;
    height: 42px;
    position: relative;
    -webkit-user-select: none;
}
.white {
    background: #fff;
    width: 32px;
}
.gray {
    background: #acb3bd;
    width: 42px;
}

Finally, this is where we style the key pop-outs. A pop-out will not be displayed unless the user interacts with its associated key.

/* Key pop-out */
.popout {
    display: none;
    font-size: 24pt;
    position: absolute;
    left: -9px;
    bottom: 38px;
    z-index: 1;
}

These next two rules will adjust the leftmost and rightmost white keys so that they are not off on the edges of the screen.

/* align pop-outs for q Q 1 - [ _*/
#lowercase .row:first-of-type .key:first-of-type .popout, 
#uppercase .row:first-of-type .key:first-of-type .popout, 
#numbers .row:nth-child(-n+2) .key:first-of-type .popout, 
#symbols .row:nth-child(-n+2) .key:first-of-type .popout {
    left: 0;
}
/* align pop-outs for p P 0 " = • */
#lowercase .row:first-of-type .key:last-of-type .popout, 
#uppercase .row:first-of-type .key:last-of-type .popout, 
#numbers .row:nth-child(-n+2) .key:last-of-type .popout, 
#symbols .row:nth-child(-n+2) .key:last-of-type .popout {
    left: -18px;
}

Then, we specify the appearance for the heads and necks of the pop-outs. We will use trapezoid clip paths for the necks connecting the pop-outs and keys.

.head {
    background: #fff;
    border-top-left-radius: 5px;
    border-top-right-radius: 5px;
    box-shadow: 0 -1px 1px #888;
    line-height: 1.5em;
    width: 50px;
    height: 50px;
}
.neck {
    background: #fff;
    width: 50px;
    height: 10px;
    /* Note: The clip path may not be rendered properly in some browsers. */
    -webkit-clip-path: polygon(0% 0%, 100% 0%, 80% 100%, 20% 100%);
    clip-path: polygon(0% 0%, 100% 0%, 80% 100%, 20% 100%);
}

Since the period, comma, question mark, exclamation point, and foot mark keys are wider than other white keys, we have to adjust the widths of their pop-out heads and necks.

/* adjust width of . , ? ! ' */
#numbers .row:nth-child(3) .key:nth-child(n+2):nth-child(-n+6) .head, 
#symbols .row:nth-child(3) .key:nth-child(n+2):nth-child(-n+6) .head, 
#numbers .row:nth-child(3) .key:nth-child(n+2):nth-child(-n+6) .neck, 
#symbols .row:nth-child(3) .key:nth-child(n+2):nth-child(-n+6) .neck {
    width: 64px;
}

Lastly, we will adjust the neck clip paths of the leftmost and rightmost white keys to properly connect them to their heads.

/* alter necks for q Q 1 - [ _ */
#lowercase .row:first-of-type .key:first-of-type .neck, 
#uppercase .row:first-of-type .key:first-of-type .neck, 
#numbers .row:nth-child(-n+2) .key:first-of-type .neck, 
#symbols .row:nth-child(-n+2) .key:first-of-type .neck {
    -webkit-clip-path: polygon(0% 0%, 100% 0%, 67% 100%, 0% 100%);
    clip-path: polygon(0% 0%, 100% 0%, 67% 100%, 0% 100%);
}
/* alter necks for p P 0 " = • */
#lowercase .row:first-of-type .key:last-of-type .neck, 
#uppercase .row:first-of-type .key:last-of-type .neck, 
#numbers .row:nth-child(-n+2) .key:last-of-type .neck, 
#symbols .row:nth-child(-n+2) .key:last-of-type .neck {
    -webkit-clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 33% 100%);
    clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 33% 100%);
}

The jQuery

This part is where all the magic happens. The script begins with on/off (or true/false) variables for three of the keyboard modes and caps lock. We’ll see how the kbdMode array will be used in a moment.

var isShift = false,
    capsLock = false,
    isNumSymbols = false,
    isMoreSymbols = false,

    kbdMode = ["lowercase", "uppercase", "numbers", "symbols"];

This for loop will add a mousedown event to all white keys in each keyboard mode. During the interaction, the keys will print their contained letters in the textarea. This is also where the key pop-ups come to life. During the mouseup event, the pop-up will disappear after its letter has been typed. If the Shift key was pressed and the caps lock was off, then the keyboard mode will revert to lowercase form uppercase.

/* typing */
for (var i = 0; i < kbdMode.length; ++i) {
    $("#" + kbdMode[i] + " .row .white:not(:last)").mousedown(function(){
	$("textarea").append($(this).find("span").html().substring(0,1));
    	$(this).find(".popout").css("display", "block");
    	$(this).find(".popout .head").html($(this).find("span").html().substring(0,1));
    	$(this).find("span").css("color", "transparent");
    });
    $("#" + kbdMode[i] + " .row .white:last").mousedown(function(){
        // &#32; is the unicode decimal code for a space
        $("textarea").append(" ");
    });
}
$(".white").mouseup(function(){
    $(this).find(".popout").css("display", "none");
    $(this).find(".popout .head").html("");
    $(this).find("span").css("color", "#000");
    if (isShift == true && capsLock == false) {
    	$("#lowercase").css("display", "block");
    	$("#uppercase").css("display", "none");
        isShift = false;
    }
});

The next chunk of code applies to the shift and caps lock functions. You can see that we’ll use .dblclick() function for the caps lock, as you have to double-tap Shift to enable caps lock on an actual iDevice.

/* toggle shift */
// lowercase to uppercase
$("#lowercase .row:eq(2) .gray:eq(0)").click(function(){
    if (isShift == false) {
    	$("#lowercase").css("display", "none");
    	$("#uppercase").css("display", "block");
        isShift = true;
    }
});
// uppercase to lowercase
$("#uppercase .row:eq(2) .gray:eq(0)").click(function(){
    if (isShift == true) {
    	$("#lowercase").css("display", "block");
    	$("#uppercase").css("display", "none");
        isShift = false;
    }
});
// caps lock on
$("#uppercase .row:eq(2) .gray:eq(0)").dblclick(function(){
    if (capsLock == false) {
    	$("#lowercase").css("display", "none");
    	$("#uppercase").css("display", "block");
        // &#8682; is the unicode decimal code for the caps lock arrow
        $(this).children("span").html("&#8682;");
        capsLock = true;
    }
});
// caps lock off
$("#uppercase .row:eq(2) .gray:eq(0)").click(function(){
    if (capsLock == true) {
        $("#lowercase").css("display", "block");
    	$("#uppercase").css("display", "none");
        // &#11014; is the unicode decimal code for the black shift arrow
        $(this).children("span").html("&#11014;");
        capsLock = false;
    }
});

This function allows the Backspace key in all keyboard modes to delete a character in the textarea. To emulate backspacing, the function changes the current textarea text to what it is without the last character. In other words, it will look like a character was deleted every time you press the Backspace key.

/* backspace */
var backspace = function(){
    $("textarea").html($("textarea").html().substring(0,$("textarea").html().length-1));
};
for (var j = 0; j < kbdMode.length; ++j) {
	$("#" + kbdMode[j] + " .row:eq(2) .key:last").click(backspace);
};

The next few functions will allow the transition from the letter keyboard modes to the number one and the number one back to the lowercase one. Note that caps lock will be disabled when switching to the number mode.

/* toggle numbers */
// lowercase/uppercase to numbers
for (var k = 0; k < kbdMode.length-2; ++k) {
    $("#" + kbdMode[k] + " .row:eq(3) .gray:eq(0)").click(function(){
    	if (isNumSymbols == false) {
    		$("#numbers").css("display", "inherit");
    		$("#lowercase").css("display", "none");
                $("#uppercase").css("display", "none");
                $("#uppercase .row:eq(2) .gray:eq(0)").children("span").html("&#11014;");
        	isNumSymbols = true;
        	isShift = false;
                capsLock = false;
    	}
    });
}
// numbers to lowercase
$("#numbers .row:eq(3) .gray:eq(0)").click(function(){
    if (isNumSymbols == true) {
    	$("#numbers").css("display", "none");
    	$("#lowercase").css("display", "block");
        isNumSymbols = false;
    }
});

The last group of functions enables toggling between the number mode and additional symbols mode. It also allows the transition from additional symbols to lowercase letters.

/* toggle symbols */
// numbers to symbols
$("#numbers .row:eq(2) .gray:eq(0)").click(function(){
    if (isMoreSymbols == false) {
    	$("#numbers").css("display", "none");
        $("#symbols").css("display", "block");
        isMoreSymbols = true;
    }
});
// symbols to numbers
$("#symbols .row:eq(2) .gray:eq(0)").click(function(){
    if (isMoreSymbols == true) {
    	$("#numbers").css("display", "block");
        $("#symbols").css("display", "none");
        isMoreSymbols = false;
    }
});
// symbols to lowercase
$("#symbols .row:eq(3) .gray:eq(0)").click(function(){
    if (isMoreSymbols == true) {
    	$("#lowercase").css("display", "block");
        $("#symbols").css("display", "none");
        isMoreSymbols = false;
    }
});

Finally, this last for loop will make the Return key on each keyboard mode insert line breaks in the textarea.

/* return (line break) */
for (var l = 0; l < kbdMode.length; ++l) {
	$("#" + kbdMode[l] + " .row:eq(3) .gray:eq(1)").click(function(){
        // &#10; is the unicode decimal code for a line break
    	$("textarea").append("&#10;");
	});
}

Conclusion

There may be some parts missing, but using front-end languages to recreate a program originally made in a non-Web language like the iOS keyboard can be a challenge to do well.

View fullscreen result


Posted in: CSS, HTML, JavaScript