How to Build an Accessible Hangman Game with Vanilla JavaScript

Lucas Silbernagel (he/him)
Webtips
Published in
7 min readJul 22, 2020

--

In this article I will outline how I built a hangman game with Vanilla JavaScript, and more importantly, how I made it accessible for players who navigate with their keyboard or with a screen reader.

My hangman game

Concept

Hangman is a simple word guessing game that can be played by one or two people. The goal of this project was to practice building a fun app using Vanilla JavaScript only, no frameworks or libraries. Players can choose to play with two people or solo. For the one-player game, categories and words are preloaded in an array and randomly generated for the player to guess (edit: this has now been replaced by a REST API that I built). For two players, the first person creates a category/word and the second person has to guess the word. Each letter guessed correctly is displayed on the page. For each incorrect guess, a body part is added to the hangman image. If the hangman image is completed before the player can guess the word, they lose the game. To guess a letter, players type it into a text input.

Setup

After creating a namespace for the app, one of the first things I did was create an array of questions (words and categories) for the one-player game (edit: this has now been replaced by a REST API that I built). This is what I started with:

Namespace and array of questions for my hangman app

The next order of business was making sure that every time someone plays the game, one of these words is selected randomly, and displayed in the proper format. I created one variable to select a random question, and another to split the word into its individual letters:

Variable to select random question and variable to split word into individual letters

For guess input, one option would be to provide onscreen buttons as a virtual keyboard for guessing each letter, but I decided to use a text input instead so that players can use their own keyboard, touch screen, or other input method of choice. To that end, I created an array to hold each guess and a simple regular expression to make sure that players can only input letters (no numbers or special characters):

Array to hold user guess and regex to allow only letters input

Game functionality (one player)

Time to start the game! When a player clicks the One Player button, the button itself disappears, the guess form appears, the word category appears at the top of the page, and blank spaces indicate how many letters the word has. As usual with Vanilla JavaScript, this involves a lot of query selectors and class toggling:

Function to start the game

Inside the above “start game” function, a “display question” function is called. I had already created empty sections in the HTML for the category and word, and this function fills them in. The category is simply extracted from the random question variable, while the word has also been split into individual letters, mapped, and returned as blank underlined spaces on the page:

Function to display category and word

The largest function by far checks the player’s guess and acts accordingly. The first and most important part of this is error-handling: making sure that the player guesses a letter, checking if a letter has already been guessed, adding a body part to the hangman image when the player guesses incorrectly, and filling in one or more of the blank spaces when a correct guess is made.

Error handling player guesses

The next step is adding a body part for each incorrect guess, which is essentially a list of “if” statements with class toggling:

Function to add a body part for each incorrect guess

Finally, we reach the end of the game. If the player manages to guess the word before the hangman is entirely visible, i.e. hanged, they win the game. Otherwise, they lose! A button appears to Play Again, which simply refreshes the app.

Game functionality (two players)

Options for one player or two players

Adding functionality to allow two players was easier than I expected. For one thing, I didn’t need to create or use an array of pre-written questions, I just needed an empty array to hold the category and word created by the first player. When starting the game for two players, I just had to toggle classes to display a question-creation form.

Form for first player to create a category and word

Once this form is submitted, the game starts like before, without the added complexity of selecting a random word. Player two attempts to guess the word chosen by player one. The function to check player guesses is nearly identical to the version for one player, with the names of certain variables being the main difference.

Accessibility challenges and solutions

Many of the accessibility issues for this app revolved around the modal that pops up when the game ends and when there is an error such as an invalid guess.

Modal that says “please enter a valid guess”

One of the biggest challenges with this was “trapping” focus in the modal when it is visible so that players navigating with their keyboard or a screen reader do not accidentally interact with elements behind or under the modal. Trapping focus like this is extremely easy when using a library such as Focus-Trap, which is specifically designed for this purpose. It is also relatively easy when using a library like jQuery, with which you can quickly select all of the background elements and remove them from both the tab index and screen reader visibility. Vanilla JavaScript is a little trickier, however. The solution I ultimately settled on was to select each background element using querySelectorAll, and use the setAttribute method on them through a forEach loop.

Trapping focus with Vanilla JavaScript

This focus trap is removed when the modal is closed, which usually happens when the “ok” button is clicked, but would also happen if the player clicks outside the modal.

Function to close the modal

When the modal opens, I set keyboard focus on the “ok” button right away. Players also have the option of closing the modal by pressing the “esc” key on their keyboard.

Function to close modal when esc key is pressed

Visual cues

Hangman is a game composed of visual cues. The blank spaces for letters indicate how long the word is, and the hangman image indicates how close the player is to losing. Making these elements “visible” to a screen reader presented a unique challenge.

When rendering the category to the page, I included functionality to add an aria-label that reads out not only the category itself, but also how long the word is.

category.innerHTML = `<h2 aria-label="Category: ${hangman.randomQuestion[0]}, ${hangman.randomQuestion[1].length} letters">${hangman.randomQuestion[0]}</h2>`

Making the blank letter spaces accessible was even more challenging. The letters for the word are contained in spans, and I initially hid the letters by changing their colour to “transparent”. While this hid them visually, they were still “visible” to screen readers, so I set the spans to be aria-hidden. The letters were then invisible to sighted users, and the spans containing them were hidden from users with visual impairments. This created a new set of problems, however, as screen readers could no longer “see” any of the blank spaces until a correct letter was guessed, making it impossible to gauge letter placement in the word. My solution was to wrap each span in an additional span visible only to screen readers, which indicated not only the number of letters but also their sequence.

const displayedLetter = hangman.onePlayerLetters.map((letter, index) => `<span class="letterSpace">letter ${index + 1}<span aria-hidden="true" class="correct">${letter}</span></span>`).join(' ');

As for the body parts, whenever a user guesses incorrectly and a body part is made visible, I also added an aria-label which indicates how many of the total body parts are visible.

Function to add a body part for each incorrect guess

Wrapping up

This project was an excellent opportunity to practice Vanilla JavaScript and address some unique accessibility challenges. In future, I may consider adding the option of a virtual keyboard, rather than a text input. Players could click or tap each letter of the virtual keyboard, or use the physical keyboard of their device. Another idea is to add sound effects or music. I would also like to build my own REST API that would store words for the one-player game, and could be used for creating similar word-based apps (edit: this is now complete).

GitHub repository

https://github.com/LucasSilbernagel/hangman

Live link

--

--

Lucas Silbernagel (he/him)
Webtips

Front-End Developer ❯ lucassilbernagel.com ❯ I specialize in the crafting of user interfaces that truly make a difference ❯ #a11y