nubpaws.dev
Back to Articles
Article

Chapter 1: Setting Up the Frontend

Build the HTML, CSS, and JavaScript skeleton for the truth table calculator - input handling, operator display, and UI wiring.

February 28, 2026 NubPaws
JavaScript HTML CSS Frontend Tutorial

Table of Contents

  1. Overview
  2. The HTML Structure
  3. Styling with CSS
  4. Wiring Up the UI
  5. Formatting the Input
  6. What’s Next

Overview

Before we dive into tokenizers and parsers, we need something the user can actually interact with. In this chapter we’ll set up the complete frontend for our truth table calculator: an input field, a generate button, a result area, and a reference for supported operators.

The entire project is vanilla HTML, CSS, and JavaScript - no frameworks, no build tools. You can follow along by creating three files: index.html, index.css, and index.js.

The full source is on GitHub, and you can try the live version at the project page.

The HTML Structure

The markup is minimal. We need:

  • A heading and attribution link
  • A list of supported operators so the user knows what to type
  • An input field and a button
  • An empty div where results will be injected
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Truth Table, CNF, and DNF Generator</title>
    <link rel="stylesheet" href="index.css" />
    <script src="index.js" defer></script>
  </head>
  <body>
    <h1>Truth Table, CNF, and DNF Generator</h1>
    <h5>by <a href="https://github.com/NubPaws/CNF-DNF-Calculator">NubPaws</a> on GitHub</h5>
    <p>Enter a logical expression using the following symbols:</p>

    <ul>
      <li><code>~</code> for NOT</li>
      <li><code>&</code> for AND</li>
      <li><code>|</code> for OR</li>
      <li><code>-></code> for IMPLIES</li>
      <li><code><-></code> for EQUIVALENCE</li>
    </ul>

    <p>Example: <code>(A & B) -> C</code></p>

    <input id="expr" type="text" placeholder="Enter logical expression here" />
    <button id="generate">Generate</button>

    <div id="result"></div>
  </body>
</html>

Styling with CSS

We use the Fira Code monospaced font for a clean code aesthetic, and keep the styling simple:

@import url('https://fonts.googleapis.com/css2?family=Fira+Code:[email protected]&display=swap');

body {
  font-family: 'Fira Code', serif;
  font-optical-sizing: auto;
  font-style: normal;
  margin: 20px;
}

input[type='text'] {
  font-size: 1em;
  padding: 5px;
  width: 400px;
  font-family: 'Fira Code', serif;
  font-optical-sizing: auto;
  font-style: normal;
}

button {
  font-size: 1em;
  padding: 5px 10px;
}

For the truth table itself we use collapsed borders and a light header background:

table {
  border-collapse: collapse;
  margin-top: 20px;
}

th,
td {
  border: 1px solid #333;
  padding: 5px 10px;
  text-align: center;
}

th {
  background-color: #ddd;
}

And a couple of utility classes for error messages and the normal form output:

.error {
  color: red;
  margin-top: 10px;
}

.normal-forms {
  margin-top: 20px;
  font-size: 1.1em;
}

Wiring Up the UI

At the bottom of index.js we attach two event listeners. The first fires our main function when the user clicks Generate:

document.getElementById('generate').addEventListener('click', generateTableAndNF);

The second listens for keypresses on the input field. It does two things:

  1. Auto-completes parentheses - typing ( inserts both ( and ), placing the cursor in between. If text is selected, it wraps the selection.
  2. Submits on Enter - pressing Enter triggers the same function as clicking the button.
document.getElementById('expr').addEventListener('keydown', (event) => {
  const input = event.target;

  if (event.key === '(') {
    event.preventDefault();

    const start = input.selectionStart;
    const end = input.selectionEnd;

    input.value =
      input.value.slice(0, start) +
      '(' +
      input.value.slice(start, end) +
      ')' +
      input.value.slice(end);
    input.selectionStart = start + 1;
    input.selectionEnd = end + 1;
    return;
  }

  if (event.key === 'Enter') {
    generateTableAndNF();
    return;
  }
});
Note:

The parenthesis auto-completion preserves any selected text. If you select A & B and press (, you get (A & B) with the selection intact inside the parens.

Formatting the Input

When we display the expression in the truth table header, we want to show proper Unicode symbols instead of the ASCII shortcuts the user typed. The formatInput function handles this conversion:

const symbols = {
  or: ' ∨ ',
  and: ' ∧ ',
  implies: ' -> ',
  equiv: ' <-> ',
  not: '¬',
};

function formatInput(input) {
  return input
    .replace(/\s+/g, '')
    .replace(/\|/g, symbols.or)
    .replace(/\&/g, symbols.and)
    .replace(/\<\-\>/g, symbols.equiv)
    .replace(/(?<!<)\-\>/g, symbols.implies)
    .replace(/\~/g, symbols.not);
}

The order of replacements matters - we replace <-> before -> to avoid the -> inside <-> being matched first. The negative lookbehind (?<!<) in the -> pattern provides an extra safety net.

After formatting, (A & B) -> C becomes (A ∧ B) -> C, which is displayed as the final column header in the truth table.

What’s Next

With the frontend in place we have a working shell - type an expression, click Generate, and… nothing happens yet. The generateTableAndNF function is where all the logic lives, and it starts by tokenizing the input string.

In Chapter 2: Tokenizing Logical Expressions, we’ll build the tokenizer that turns a raw string like (A & B) -> C into a structured array of tokens.


Series: Back to Truth Table, CNF, and DNF Generator