DP
Making Flashcards with HTML, CSS, JavaScript

How to make simple flashcards with HTML, CSS, JavaScript


After figuring out different approaches to making flashcards, I have decided to create a simplified version with just HTML, CSS and JavaScript. Here is the final code with a few adjustments.

Data example (JSON)

[
  {
    "id": "set1",
    "title": "Basic HTML",
    "description": "Core HTML concepts.",
    "cards": [
      {
        "front": "What does HTML stand for?",
        "back": "HyperText Markup Language"
      },
      {
        "front": "What is the purpose of the head element?",
        "back": "Contains meta information, links, and scripts for the document."
      }
    ]
  },
  {
    "id": "set2",
    "title": "CSS Basics",
    "description": "Styling fundamentals.",
    "cards": [
      {
        "front": "What is the purpose of CSS?",
        "back": "To style and layout HTML documents."
      },
      {
        "front": "What does the selector .class do?",
        "back": "Applies styles to elements with the given class attribute."
      }
    ]
  }
]

Structure (HTML)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./styles/style.css" />
    <title>Flashcards</title>
  </head>
  <body>
    <header>
      <h1>Flashcards</h1>
      <p>Browse sets and flip cards to reveal details.</p>
    </header>
    <main id="app"></main>
    <script src="./scripts/script.js"></script>
  </body>
</html>

Functionality (JavaScript)

async function loadData() {
  const response = await fetch("./data/data.json");

  if (!response.ok) {
    throw new Error("Could not load data.");
  }

  return response.json();
}

function createSetCard(set) {
  const card = document.createElement("article");

  card.className = "set-card";
  card.innerHTML = `<h3>${set.title}</h3><p>${set.description}</p>`;

  card.addEventListener("click", () => {
    renderSetCards(set);
  });

  return card;
}

let flashcardData = null;

function renderSetCards(set) {
  const cardsSection = document.createElement("section");

  const title = document.createElement("h2");
  title.className = "section-title";
  title.textContent = "Set: " + set.title;
  cardsSection.appendChild(title);

  const cardsContainer = document.createElement("div");
  cardsContainer.className = "cards-container";

  set.cards.forEach((card) => {
    const cardContainer = document.createElement("article");
    cardContainer.className = "card";

    const inner = document.createElement("div");
    inner.className = "card-inner";
    inner.innerHTML = `<p class="card-face card-front">${card.front}</p><p class="card-face card-back">${card.back}</p>`;

    inner.addEventListener("click", (e) => {
      e.stopPropagation();
      cardContainer.classList.toggle("flipped");
    });

    cardContainer.appendChild(inner);
    cardsContainer.appendChild(cardContainer);
  });

  cardsSection.appendChild(cardsContainer);

  const backBtn = document.createElement("button");
  backBtn.className = "back-btn";
  backBtn.textContent = "Back to sets";
  backBtn.addEventListener("click", () => {
    renderAllSets();
  });
  cardsSection.appendChild(backBtn);

  const app = document.getElementById("app");
  app.innerHTML = "";
  app.appendChild(cardsSection);
}

function renderAllSets() {
  const app = document.getElementById("app");
  app.innerHTML = "";

  const setsSection = document.createElement("section");
  setsSection.className = "sets-section";

  flashcardData.forEach((set) => {
    setsSection.appendChild(createSetCard(set));
  });

  app.appendChild(setsSection);
}

async function renderContent() {
  try {
    flashcardData = await loadData();

    renderAllSets();
  } catch (err) {
    console.error(err);

    document.getElementById("app").textContent =
      "Could not load flashcards data.";
  }
}

renderContent();

Basic styling (CSS)

:root {
  --primary: #f2f4f3;
  --secondary: #7aa8e5;
  --card-front: #90e0ef;
  --card-back: #caf0f8;
  --accent: #023e8a;
  --text: #03045e;
  --hover: #0077b6;
  --shadow: 0 5px 10px rgba(0, 0, 0, 0.15);
}

* {
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
  margin: 0;
  color: var(--text);
  background: var(--primary);
}

header {
  padding: 1rem;
  text-align: center;
  background: linear-gradient(135deg, var(--accent), var(--secondary));
  color: var(--primary);
}

main {
  padding: 1.5rem;
}

.sets-section {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1rem;
}

.set-card {
  background: var(--primary);
  border-radius: 10px;
  padding: 1rem;
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: transform 0.2s;
}

.set-card:hover {
  transform: translateY(-1px);
}

.set-card h3 {
  margin: 0 0 0.5rem;
}

.set-card p {
  margin: 0;
  color: var(--text);
}

.cards-container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1rem;
}

.card {
  width: 100%;
  height: 250px;
  perspective: 1000px;
}

.card-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.5s;
  cursor: pointer;
}

.card.flipped .card-inner {
  transform: rotateY(180deg);
}

.card-face {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  border-radius: 10px;
  text-align: center;
  border: 1px solid var(--secondary);
}

.card-front {
  background: var(--card-front);
}

.card-back {
  background: var(--card-back);
  transform: rotateY(180deg);
}

.section-title {
  margin-top: 1rem;
  margin-bottom: 0.5rem;
  color: var(--text);
}

.back-btn {
  margin-top: 2.5rem;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 5px;
  background: var(--accent);
  color: var(--primary);
  cursor: pointer;
  font-size: 1rem;
}

.back-btn:hover {
  background: var(--hover);
}

@media (max-width: 700px) {
  .card {
    height: 200px;
  }

  .sets-section {
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  }
}

Thank you for reading.