An interactive worksheet that checks answers as students go

A paper worksheet tells a student they were wrong a week later. An interactive one tells them while they still remember why they wrote it.

You hand back a worksheet and the same mistake is on half of them.

By then the class has moved on. Nobody remembers why they wrote 3/8 instead of 3/4.

An interactive worksheet catches that moment. The student types an answer, the box turns red, and they try again while the question is still in front of them.

These days you do not need a worksheet platform to make one. Teachers ask ChatGPT, Claude or Gemini for it and get a working page in one reply.

Here is what that looks like. Type an answer, or click a word on the left and then its match on the right.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The water cycle - worksheet</title>
<style>
  * { box-sizing: border-box; }
  body { margin: 0; background: linear-gradient(180deg, #e3f2ff, #f7fbff 60%); color: #12324a; font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
  .page { max-width: 640px; margin: 0 auto; padding: 24px 18px 30px; }
  header { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; }
  header svg { width: 64px; height: 64px; flex: none; }
  h1 { margin: 0; font-size: 23px; }
  header p { margin: 2px 0 0; color: #4b6b85; font-size: 14px; }
  .score { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: 12px; padding: 10px 14px; margin-bottom: 14px; border-radius: 14px; background: #12324a; color: #fff; }
  .score .track { flex: 1; height: 10px; border-radius: 5px; background: rgba(255,255,255,.2); overflow: hidden; }
  .score .track i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, #5ee0a0, #2bb673); transition: width .4s; }
  .score b { font-variant-numeric: tabular-nums; }
  .score button { border: 0; border-radius: 9px; padding: 7px 10px; background: #fff; color: #12324a; font: 600 13px system-ui; cursor: pointer; }
  section { background: #fff; border-radius: 18px; padding: 16px; margin-bottom: 14px; box-shadow: 0 6px 18px rgba(18, 50, 74, .07); }
  h2 { margin: 0 0 10px; font-size: 13px; letter-spacing: .08em; text-transform: uppercase; color: #3a7bbf; }
  .q { margin: 12px 0; font-size: 16.5px; line-height: 1.7; }
  .q input { width: 150px; padding: 5px 8px; border: 2px solid #b9d3ea; border-radius: 9px; font: inherit; font-size: 16px; }
  .q input.ok { border-color: #2bb673; background: #e8f9f0; }
  .q input.no { border-color: #e2584d; background: #fdeceb; }
  .hint { display: block; margin-top: 4px; font-size: 13.5px; color: #b2443a; min-height: 1em; }
  .pairs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .pairs button { width: 100%; padding: 12px 10px; margin-bottom: 8px; border-radius: 12px; border: 2px solid #b9d3ea; background: #f6faff; font: 600 15px system-ui; color: #12324a; cursor: pointer; text-align: left; }
  .pairs button.sel { border-color: #3a7bbf; background: #e3f0ff; }
  .pairs button.done { border-color: #2bb673; background: #e8f9f0; color: #1b7a4b; cursor: default; }
  .pairs button.shake { animation: shake .3s; border-color: #e2584d; }
  @keyframes shake { 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
  .win { display: none; text-align: center; padding: 16px; border-radius: 16px; background: #e8f9f0; color: #1b7a4b; font-weight: 700; }
</style>
</head>
<body>
<main class="page">
  <header>
    <svg viewBox="0 0 64 64" aria-hidden="true"><circle cx="32" cy="32" r="32" fill="#3a7bbf"/><path d="M32 14 C 24 26 20 32 20 38 a12 12 0 0 0 24 0 c0-6-4-12-12-24z" fill="#e3f2ff"/></svg>
    <div><h1>The water cycle</h1><p>Grade 4 practice. Fill the blanks, then match the pairs.</p></div>
  </header>

  <div class="score"><span>Score</span><div class="track"><i id="bar"></i></div><b id="pts">0 / 8</b><button id="reset">Start again</button></div>

  <section>
    <h2>Part 1 - Fill in the blank</h2>
    <div class="q">The sun heats water and it turns into vapour. This is <input data-a="evaporation" data-h="It starts with e. The water evaporates."><span class="hint"></span></div>
    <div class="q">Vapour cools high up and forms clouds. This is <input data-a="condensation" data-h="Think of a cold glass that gets wet on the outside."><span class="hint"></span></div>
    <div class="q">Water falls back as rain, snow or hail. This is <input data-a="precipitation" data-h="Weather reports call rain and snow this word."><span class="hint"></span></div>
    <div class="q">Water gathers in rivers, lakes and the sea. This is <input data-a="collection" data-h="The water is collected in one place."><span class="hint"></span></div>
  </section>

  <section>
    <h2>Part 2 - Match the pairs</h2>
    <div class="pairs">
      <div id="left"></div>
      <div id="right"></div>
    </div>
  </section>

  <div class="win" id="win">All 8 right. Nice work.</div>
</main>
<script>
  const PAIRS = [
    ['Evaporation', 'Liquid to gas'],
    ['Condensation', 'Gas to liquid'],
    ['Precipitation', 'Falls from clouds'],
    ['Collection', 'Gathers in the sea'],
  ];
  const clean = (s) => s.trim().toLowerCase().replace(/\s+/g, ' ');
  const inputs = [...document.querySelectorAll('.q input')];
  let matched = 0, picked = null;

  function update() {
    const right = inputs.filter((i) => i.classList.contains('ok')).length + matched;
    document.getElementById('pts').textContent = right + ' / 8';
    document.getElementById('bar').style.width = (right / 8 * 100) + '%';
    document.getElementById('win').style.display = right === 8 ? 'block' : 'none';
  }

  inputs.forEach((inp) => {
    inp.addEventListener('input', () => {
      const v = clean(inp.value), hint = inp.nextElementSibling;
      inp.classList.remove('ok', 'no'); hint.textContent = '';
      if (!v) { update(); return; }
      if (v === inp.dataset.a) inp.classList.add('ok');
      else if (v.length >= inp.dataset.a.length) { inp.classList.add('no'); hint.textContent = 'Not quite. ' + inp.dataset.h; }
      update();
    });
  });

  function buildPairs() {
    const L = document.getElementById('left'), R = document.getElementById('right');
    L.innerHTML = ''; R.innerHTML = ''; matched = 0; picked = null;
    const shuffled = PAIRS.map((p, i) => [p[1], i]).sort(() => Math.random() - 0.5);
    PAIRS.forEach((p, i) => { const b = document.createElement('button'); b.textContent = p[0]; b.dataset.i = i; L.append(b); });
    shuffled.forEach(([t, i]) => { const b = document.createElement('button'); b.textContent = t; b.dataset.i = i; R.append(b); });
    L.querySelectorAll('button').forEach((b) => b.onclick = () => {
      if (b.classList.contains('done')) return;
      L.querySelectorAll('button').forEach((x) => x.classList.remove('sel')); b.classList.add('sel'); picked = b;
    });
    R.querySelectorAll('button').forEach((b) => b.onclick = () => {
      if (!picked || b.classList.contains('done')) return;
      if (b.dataset.i === picked.dataset.i) { b.classList.add('done'); picked.classList.add('done'); picked.classList.remove('sel'); picked = null; matched++; update(); }
      else { b.classList.add('shake'); setTimeout(() => b.classList.remove('shake'), 320); }
    });
  }

  document.getElementById('reset').onclick = () => {
    inputs.forEach((i) => { i.value = ''; i.classList.remove('ok', 'no'); i.nextElementSibling.textContent = ''; });
    buildPairs(); update();
  };
  buildPairs(); update();
</script>
</body>
</html>
An interactive worksheet on the water cycle: four fill-in blanks that check as you type, a matching section and a score that updates. Edit the code and the example reruns.

Three things that make it work

1. One skill per sheet. A worksheet that mixes fractions, decimals and word problems gives a score that tells you nothing. Keep it to one thing, so a low score points at one gap.

2. Feedback that says more than "wrong". "Not quite. Check which number is the whole." helps. A red box alone just invites guessing. Ask for a one-line hint per question.

3. A way to start again. Students learn on the second try. A reset button, or new numbers each time, turns one sheet into five rounds of practice.

Three kinds of activity an AI can build into one page: fill in the blank, match the pairs, and practice that changes its numbers.
Three kinds of activity an AI can build into one page: fill in the blank, match the pairs, and practice that changes its numbers.

Make it with AI

Open ChatGPT, Claude or Gemini and paste this. Change the words in capitals and paste your own questions at the end.

Make one complete HTML file: an interactive worksheet.
Topic: TOPIC, for GRADE / AGE.
Part 1: fill in the blank. Check each answer as the
student types. Green when right. When wrong, show
the hint I give for that question.
Part 2: match the pairs. Click one on the left,
then its match on the right.
Show a score at the top and a "Start again" button.
Rules: large text, works on a tablet, all CSS and
JavaScript inside the file, no images from other
sites, nothing sent anywhere. Accept answers in any
capitals and with extra spaces.
Use only my questions and answers. Do not add any.
Questions, answers and hints: PASTE THEM HERE

The line about capitals and spaces matters. Without it "Evaporation" and "evaporation " can be marked wrong, and students stop trusting the page.

Before you share it, do it once as a student. Type a right answer, a wrong one, and leave one empty.

For practice that never runs out, ask for new numbers each round. This one makes fresh multiplication questions and keeps a streak.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Times tables practice</title>
<style>
  * { box-sizing: border-box; }
  body { margin: 0; min-height: 100vh; background: #fdf4e3; color: #3a2a12; font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
  .page { max-width: 460px; margin: 0 auto; padding: 22px 18px; text-align: center; }
  h1 { margin: 0 0 4px; font-size: 22px; }
  .sub { margin: 0 0 14px; color: #8a6d3b; font-size: 14px; }
  .tables { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin-bottom: 16px; }
  .tables button { width: 42px; height: 42px; border-radius: 12px; border: 2px solid #ecd3a3; background: #fff; font: 700 16px system-ui; color: #3a2a12; cursor: pointer; }
  .tables button.on { background: #f29e1f; border-color: #f29e1f; color: #fff; }
  .card { background: #fff; border-radius: 22px; padding: 22px 18px; box-shadow: 0 12px 26px rgba(138, 109, 59, .15); }
  .count { font-size: 13px; color: #8a6d3b; }
  .sum { font-size: 46px; font-weight: 800; margin: 8px 0 14px; font-variant-numeric: tabular-nums; }
  input { width: 140px; padding: 10px; text-align: center; border: 3px solid #ecd3a3; border-radius: 14px; font: 700 28px system-ui; color: #3a2a12; }
  input.no { border-color: #e2584d; animation: shake .3s; }
  @keyframes shake { 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } }
  .msg { min-height: 22px; margin-top: 10px; font-size: 15px; color: #b2443a; }
  .stats { display: flex; justify-content: center; gap: 18px; margin-top: 14px; font-size: 14px; color: #8a6d3b; }
  .stats b { display: block; font-size: 22px; color: #3a2a12; }
  .done { display: none; }
  .done .big { font-size: 40px; font-weight: 800; margin: 6px 0; }
  .done button { margin-top: 10px; padding: 12px 18px; border: 0; border-radius: 14px; background: #3a2a12; color: #fff; font: 700 15px system-ui; cursor: pointer; }
</style>
</head>
<body>
<main class="page">
  <h1>Times tables practice</h1>
  <p class="sub">Pick a table. Ten questions, new numbers every round.</p>
  <div class="tables" id="tables"></div>
  <div class="card">
    <div id="play">
      <div class="count" id="count">Question 1 of 10</div>
      <div class="sum" id="sum"></div>
      <input id="ans" inputmode="numeric" autocomplete="off" aria-label="Your answer">
      <div class="msg" id="msg"></div>
    </div>
    <div class="done" id="done">
      <div>Round finished</div>
      <div class="big" id="final"></div>
      <div id="note"></div>
      <button id="again">New round</button>
    </div>
    <div class="stats"><div><b id="streak">0</b>streak</div><div><b id="best">0</b>best streak</div></div>
  </div>
</main>
<script>
  let table = 7, qs = [], n = 0, right = 0, firstTry = true, streak = 0, best = 0;
  const $ = (id) => document.getElementById(id);

  for (let t = 2; t <= 12; t++) {
    const b = document.createElement('button'); b.textContent = t;
    b.onclick = () => { table = t; start(); };
    $('tables').append(b);
  }

  function start() {
    document.querySelectorAll('#tables button').forEach((b) => b.classList.toggle('on', +b.textContent === table));
    const pool = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sort(() => Math.random() - 0.5);
    qs = pool.slice(0, 10); n = 0; right = 0;
    $('play').style.display = 'block'; $('done').style.display = 'none';
    show();
  }

  function show() {
    firstTry = true;
    $('count').textContent = 'Question ' + (n + 1) + ' of 10';
    $('sum').textContent = table + ' × ' + qs[n] + ' = ?';
    $('ans').value = ''; $('msg').textContent = ''; $('ans').focus();
  }

  $('ans').addEventListener('keydown', (e) => {
    if (e.key !== 'Enter' || !$('ans').value.trim()) return;
    const want = table * qs[n];
    if (+$('ans').value === want) {
      if (firstTry) { right++; streak++; best = Math.max(best, streak); }
      n++;
      $('streak').textContent = streak; $('best').textContent = best;
      if (n === 10) finish(); else show();
    } else {
      firstTry = false; streak = 0; $('streak').textContent = 0;
      $('ans').classList.remove('no'); void $('ans').offsetWidth; $('ans').classList.add('no');
      $('msg').textContent = 'Try again. ' + table + ' × ' + qs[n] + ' is ' + table + ' added ' + qs[n] + ' times.';
    }
  });

  function finish() {
    $('play').style.display = 'none'; $('done').style.display = 'block';
    $('final').textContent = right + ' / 10';
    $('note').textContent = right === 10 ? 'Every one on the first try.' : 'First-try answers count. Go again for a better score.';
  }
  $('again').onclick = start;
  start();
</script>
</body>
</html>
A times-tables practice page: pick a table, answer ten questions with new numbers each round, and see your streak. Edit the code and the example reruns.

Getting it to the class

You have a worksheet. The AI gave it to you as HTML code, though, and students cannot do much with code.

The first wall is getting it open. Attach the file to a post and some students download it, some cannot open it on a school tablet, and some see the code. You need it to be a page they tap.

In NOS you paste the code into a document and it becomes the worksheet straight away. Choose Create share link, and post that link wherever you post homework. How to send an HTML file in Google Classroom covers the Classroom side.

AI reply, NOS document, share link, class post. Students tap the link and get the worksheet.
AI reply, NOS document, share link, class post. Students tap the link and get the worksheet.

The second wall is the mistake you find on Tuesday. Question 3 says "hale" where it should say "hail". The heading says Grade 3 when the class is Grade 4. Small things, but students notice.

If you ask the AI again, you wait for a new reply. It often rewrites questions you were happy with, so you check every answer key again. Then you have a new file and a new link to post, and half the class still has the old one.

In NOS you click the wrong word on the page and type the right one. The link does not change. Students who open it now get the fixed version.

Changing an answer the page checks against is different: that lives in the code, so edit it in the code view or ask the AI for that one line.

"hale" corrected to "hail" on the page in NOS. The class link stayed the same and a student's tablet shows the fixed question.
"hale" corrected to "hail" on the page in NOS. The class link stayed the same and a student's tablet shows the fixed question.

What it does not do

The checking happens in each student's browser. Nothing is sent back, so the worksheet does not record who scored what.

That suits practice. For a graded task, keep the worksheet for practice and use your form or learning platform for the score.

For a quiz with a final score screen, see HTML quiz. For more ways teachers use AI pages in class, see Claude for teachers.

Questions people ask

What makes a worksheet interactive?

The page reacts to the answer. A blank turns green or red, a matching pair locks in, a score updates. The student finds out right away instead of when the sheet comes back.

Can AI make an interactive worksheet?

Yes. Ask for one complete HTML file and give it your exact questions and answers. Check every answer key yourself before sharing, because an AI can mark a wrong answer as correct.

Does the worksheet collect students’ scores?

Not on its own. The checking happens in each student’s browser and nothing is sent back. If you need each student’s score, use a form or your learning platform for that part.

How do students open it?

With a link. A NOS share link opens in an ordinary browser on a laptop, tablet or phone, with no account and no download.

What if I find a mistake after sharing?

Fix the text on the page in NOS. Students who open the same link see the corrected version.

Keep reading