How I Built a Pure CSS Crossword Puzzle

Share this article

CSS Crossword Puzzle

CSS Crossword Puzzle

Recently I created a pure CSS crossword puzzle implemented using CSS grid that does not need JavaScript in order to work. It gained heavy interest pretty quickly on CodePen. As of this writing, it has more than 350 hearts and 24,000+ page views!

The great CSS Grid Garden tutorial inspired me to build something with Grid Layout features. I wondered if these features could be put to good use in building a crossword puzzle — then I thought, let’s try to create the whole thing without using JavaScript.

Building the Board/Grid

So, first thing’s first, let’s create the board itself!

I ended up with the following basic structure, with HTML comments included to show what the different sections will accomplish:

<div class="crossword-board-container">

  <div class="crossword-board">

    <!-- input elements go here. Uses CSS Grid as its layout -->

    <div class="crossword-board crossword-board--highlight crossword-board--highlight--across">
      <!-- highlights for valid 'across' answers go here. Uses CSS Grid as its layout -->
    </div>

    <div class="crossword-board crossword-board--highlight crossword-board--highlight-down">
      <!-- highlights for valid 'down' answers go here. Uses CSS Grid as its layout -->
    </div>

    <div class="crossword-board crossword-board--labels">
      <!-- row and column number labels go here. Uses CSS Grid as its layout -->
    </div>

    <div class="crossword-clues">

      <dl class="crossword-clues__list crossword-clues__list--across">
        <!-- clues for all the 'across' words go here -->
      </dl>

      <dl class="crossword-clues__list crossword-clues__list--down">
        <!-- clues for all the 'down' words go here -->
      </dl>

    </div>

  </div>

</div>

That puts our basic skeleton in place so we can add more elements and start styling things.

Using Form Elements for the Squares

The crossword puzzle I’m creating is a 13×13 grid with 44 blank spaces so I need to create 125 input elements each with its own ID in the format item{row number}-{column number}, i.e. item4-12. Here’s what the grid will look like:

Empty crossword

Each of the inputs will get have a minlength and maxlength of “1” to emulate the behaviour of a crossword puzzle (i.e. one letter per square). Each input will also have the required attribute so that HTML5 form validation will be used. I take advantage of all of these HTML5 attributes using CSS.

Using the General Sibling Selector

The input elements are visually laid out in groups (exactly how a crossword puzzle is). Each group of input elements represents a word in the crossword. If each of the elements in that group is valid (which can be verified using the :valid pseudo selector), then we can use CSS to style an element that appears later in the DOM (using an advanced CSS selector called the general sibling selector) that will indicate that the word is correct.

Due to how sibling selectors work, and how CSS works in general, this element has to appear later in the DOM. CSS can only style elements that are after the currently selected element. It cannot look backwards in the DOM (or up the DOM tree) and style something before the current element (at the moment at least anyway).

This means I can use the :valid pseudo-class to style valid elements:

.input:valid {
  border: 2px solid green;
}
.input:invalid {
  border: 2px solid red;
}

See the Pen Valid Pseudo Selector Example by SitePoint (@SitePoint) on CodePen.

To style an element later on in the DOM that is a sibling of another element, I can use the ~ (tilde/general sibling) selector, e.g. A ~ B. This selector will select all elements that match B, that are a sibling of A and appear after A in the DOM. For example:

#input1:valid ~ #input2:valid ~ #input3:valid ~ #input4:valid ~ #input5:valid ~ .valid-message {
  display: block;
}

With this code, if all these input elements are valid, the valid-message element will be displayed.

See the Pen Using Sibling Selector to Display a Message by SitePoint (@SitePoint) on CodePen.

The general sibling selector is extremely useful here. To make the crossword work, I needed to make sure that everything was laid out in a way that allowed me to take advantage of the general sibling selector.

The finished crossword example is using the above technique, starting at line 285. I’ve separated it out in the code block below:

#item1-1:valid ~ #item1-2:valid ~ #item1-3:valid ~ 
#item1-4:valid ~ #item1-5:valid ~ #item1-6:valid ~ 
.crossword-board--highlight .crossword-board__item-highlight--across-1 {
  opacity: 1;
}

This part of the CSS ensures that if all these input elements are valid, then the opacity of the .crossword-board__item-highlight--across-1 element will be changed. .crossword-board--highlight is a sibling of all the input elements, and .crossword-board__item-highlight--across-1 is a child of .crossword-board--highlight so it’s selectable with CSS!

Indicating Correct Answers

Each crossword answer (i.e. group of input elements) has a corresponding “correct answer indicator” (.crossword-board__item-highlight--across-{{clue number}}) grid item. These grid items are placed behind the input elements on the z-axis, and are hidden using opacity: 0. When a correct word is entered, then the correct answer indicator grid item is displayed by changing the opacity to 1, as the pseudo-class selector snippet above demonstrates.

Crossword with incomplete word

Crossword with completed word

This technique is repeated for each “word” group of input elements. So this means manually creating each CSS rule for each of the input elements in the word group and then selecting the corresponding correct answer indicator grid item. As you can imagine, this makes the CSS get big fast!

So the logical approach would be to create all the CSS rules that show/hide the correct answer indicator grid items for all the horizontal (across) clue answers. Then you would do the same for the vertical clue answers.

Challenges of the Grid System

If, like me, you are trying to use as little CSS as possible, you will quickly realise that you cannot have overlapping grid areas within the same grid system without having to explicitely declare it. They can only sit next to each other (1 across, and 1 down share a square at the top right of the board and this is not possible when using one CSS grid to layout all the correct answer indicator items).

The solution is to wrap each horizontal (across) correct answer indicator grid item in its own grid system, and each vertical (down) correct answer indicator grid item in another. This way I can still use CSS to select them (using the general sibling selector), and they will not interfere with each other and ruin the layout of the grids.

CSS Grid Layout items act similarly to inline-block elements. This basically means that if you specify two grid items to occupy the same space, then the second item will flow around the first item and appear after it in the grid.

See the Pen Grid Layout Module Example by SitePoint (@SitePoint) on CodePen.

In the above example, the first grid item is seven columns wide and spans from the first column to the seventh column. The second grid item is meant to start at the 4th column and span to the 9th column. CSS grid doesn’t like this so it wraps it to the next row. Even if you specify grid-row: 1/1 in the second item, that will take priority and then move the first grid item to the second row.

As explained above, I avoided this by having multiple grids for horizontal and vertical items. This situation can be avoided by specifying the row and column span for each element, but I used the above method to reduce the amount of CSS, and also to have a more maintainable HTML structure.

Checking for Valid Letter Input

Each input element has a pattern attribute with a regular expression as its value. The regular expression matches an uppercase or lowercase letter for that square:

<input id="item1-1" class="crossword-board__item"
       type="text" minlength="1" maxlength="1"
       pattern="^[sS]{1}$" required value="">

This was not ideal because the answers are in the HTML. I wanted to hide the answers in the CSS, but I could not find a way of doing this successfully. I attempted the following technique:

.input#item1-1[value="s"],
.input#item1-1[value="S"] {
  /* do something... */
}

But this won’t work. The attribute selector will select the element based on what is actually inside the HTML, and doesn’t consider live changes. So I had to resort to the :valid pseudo-class approach detailed above, and consequently (and annoyingly) exposing the answers in the HTML itself.

Highlighting the Clues on Hover

All horizontal (across) clues are wrapped in a div, as are the vertical (down) clues. These wrapping div elements are siblings of the input elements in the crossword grid. This is demonstrated in the HTML structure listed above in a previous code block. This makes it easy to select the correct clue(s) depending on which input element is being focused/hovered.

Crossword hover

For this, each input element needs :active, :focus, and :hover styles to highlight the relevant clue by applying a background color when the user interacts with an input element.

#item1-1:active ~ .crossword-clues .crossword-clues__list-item--across-1,
#item1-1:focus ~ .crossword-clues .crossword-clues__list-item--across-1,
#item1-1:hover ~ .crossword-clues .crossword-clues__list-item--across-1 {
  background: #ffff74;
}

Numbering the Clues

The numbers for the clues are positioned using a CSS Grid pattern. Here’s an example of the HTML, abbreviated:

<div class="crossword-board crossword-board--labels">
  <span id="label-1" class="crossword-board__item-label crossword-board__item-label--1">
  <span class="crossword-board__item-label-text">1</span></span>
  <span id="label-2" class="crossword-board__item-label crossword-board__item-label--2">
  <span class="crossword-board__item-label-text">2</span></span>

  <!-- rest of the items here..... -->

</div>

Then the CSS looks something like this:

.crossword-board__item-label--1 {
  grid-column: 1/1;
}

.crossword-board__item-label--2 {
  grid-column: 4/4;
}

/* etc... more items here... */

Each number is placed at the start position of its related group of input elements (or word). The number is then made to be the width and height of 1 grid square so that it takes up as little space as possible within the grid. It could take up even less room by implementing CSS Grid differently here, but I opted to do it this way.

The “Check for Valid Squares” Checkbox

At the top of the crossword, you’ll notice a checkbox labelled “Check for valid squares”. This allows the user to check if certain letters are correct, even if a given word is wrong.

Crossword valid squares highlighted

Creating this is rather beautiful as it’s one CSS rule that makes all the valid squares get highlighted. It’s using the checkbox hack to select all valid input elements that are after the checked checkbox in the DOM.

Here is the code:

#checkvaliditems:checked ~ .crossword-board-container .crossword-board__item:valid {
  background: #9aff67;
}

Conclusion

That covers all the main techniques used in the demo. As an exercise, this shows you how far CSS has come in recent years. There are plenty of features we can get creative with. I for one can’t wait to try and push other new features to the limits!

If you want to mess around with the CSS from this article, I’ve put all the code examples into a CodePen collection. The full working CSS crossword Puzzle can be found here.

Frequently Asked Questions (FAQs) about Building a Pure CSS Crossword Puzzle

How can I make my crossword puzzle responsive?

Making your crossword puzzle responsive involves using CSS media queries to adjust the layout and design of your puzzle based on the screen size. You can set different styles for different screen sizes. For instance, you can reduce the size of the crossword grid or change its layout for smaller screens. You can also adjust the font size and the spacing between the cells to ensure that the puzzle remains readable and usable on all devices.

Can I add hints or clues to my crossword puzzle?

Yes, you can add hints or clues to your crossword puzzle. This can be done by adding a ‘data-clue’ attribute to your ‘td’ elements in your HTML. The value of this attribute would be the clue for that particular cell. You can then use CSS to style and position these clues in a way that suits your design.

How can I add interactivity to my crossword puzzle?

Adding interactivity to your crossword puzzle can be achieved using JavaScript. For instance, you can use JavaScript to check if the words entered by the user are correct, to provide feedback, or to reveal the solution. However, this tutorial focuses on creating a crossword puzzle using pure CSS, without any JavaScript.

Can I use this method to create other types of puzzles?

Yes, the method described in this tutorial can be adapted to create other types of puzzles. The key is to structure your HTML in a way that represents the structure of the puzzle, and then use CSS to style it. For instance, you could create a word search puzzle, a Sudoku puzzle, or a maze.

How can I make my crossword puzzle accessible?

Making your crossword puzzle accessible involves ensuring that it can be used by people with various disabilities. This can be achieved by using semantic HTML, providing alternative text for images, ensuring sufficient color contrast, and making the puzzle navigable using the keyboard. You can also use ARIA attributes to provide additional information to assistive technologies.

Can I add a timer to my crossword puzzle?

Adding a timer to your crossword puzzle would require using JavaScript. You can create a timer that starts when the user starts the puzzle, and stops when they finish. You can also provide options to pause and reset the timer.

How can I create a crossword puzzle with more complex shapes?

Creating a crossword puzzle with more complex shapes involves designing a grid that represents the shape of the puzzle, and then filling in the cells with words. This can be a bit more challenging, as you need to ensure that the words fit correctly and that the puzzle is solvable.

Can I add animations to my crossword puzzle?

Yes, you can add animations to your crossword puzzle using CSS. For instance, you can animate the cells when the user hovers over them, or when they enter a correct word. You can use the ‘transition’ property to create smooth animations.

How can I share my crossword puzzle with others?

You can share your crossword puzzle by hosting it on a website. You can also share the HTML, CSS, and JavaScript files so that others can download and use your puzzle. If you want to share your puzzle in a printable format, you can create a PDF version using a tool like PrintFriendly.

Can I create a crossword puzzle without any coding knowledge?

Creating a crossword puzzle requires some basic knowledge of HTML and CSS. However, there are also online tools and software that allow you to create crossword puzzles without any coding. These tools provide a user-friendly interface where you can design your puzzle and fill in the words and clues. They then generate the HTML and CSS code for you.

Adrian RoworthAdrian Roworth
View Author

Adrian is a front-end web developer working in Scotland. In his professional life, Adrian enjoys using CSS and JavaScript in novel and innovative ways. When he is not coding, he spends time playing the guitar and drums.

AdvancedCSScheckbox hackcss grid layoutLouisLpseudo-classes
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week