OneTag AI

Chatbot HTML templates: 3 free chat UIs with working AI

Where can I get a chatbot HTML template that actually answers?

Here are three chatbot templates you can copy as they are. Each is plain HTML, CSS and JavaScript in one file, under 90 lines, with no framework and no build step. Each is wired to a real AI model through OneTag AI, so it answers questions instead of showing canned replies, and it needs no backend: the key in the file is public by design and is free for 1,000,000 tokens a month.

Steps

  1. Get a free key

    Enter your email at onetagai.com. The key (it starts with ot_) arrives by email. One key per email, no card.

  2. Copy a template

    Pick one of the three below and press Copy on its code. Save the full-page chat and the sidebar as an .html file; paste the bubble before </body> of your page.

  3. Put in your key

    Replace YOUR_ONETAG_KEY in the script tag.

  4. Make it yours

    Rewrite the SYSTEM text with who the bot is and what it knows, then change the title, greeting and colours.

1. Full-page chat

A chat window that fills the page: message bubbles, a typing indicator, answers that appear as they are written, and memory of the conversation. AI chatbot in HTML, CSS and JavaScript explains this code part by part.

Live demo on a shared demo key, with the same code as below. Open it in its own tab.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AI chatbot</title>
<style>
  :root { --accent: #0b5fff; --ink: #0b1020; --soft: #f5f7fb; }
  * { box-sizing: border-box; }
  body { max-width: 760px; margin: 0 auto; height: 100dvh; display: flex; flex-direction: column; font: 16px/1.5 system-ui, sans-serif; color: var(--ink); }
  header { padding: 12px 16px; border-bottom: 1px solid var(--ink); font-weight: 600; }
  #log { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
  .msg { max-width: 80%; padding: 8px 12px; border: 1px solid var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
  .user { align-self: flex-end; background: var(--accent); border-color: var(--accent); color: #fff; }
  .bot { align-self: flex-start; background: var(--soft); }
  .bot:empty::after { content: "…"; }
  form { display: flex; border-top: 1px solid var(--ink); }
  input { flex: 1; min-width: 0; padding: 14px 16px; border: 0; font: inherit; outline: none; }
  button { padding: 0 24px; border: 0; background: var(--accent); color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
  button:disabled { opacity: .5; cursor: default; }
</style>
</head>
<body>
<header>Acme Bikes assistant</header>
<div id="log" aria-live="polite"><div class="msg bot">Hi! Ask me anything about Acme Bikes.</div></div>
<form id="chat">
  <input id="q" placeholder="Type a message" autocomplete="off" required>
  <button id="send">Send</button>
</form>

<script src="https://onetagai.com/v1.js" data-key="YOUR_ONETAG_KEY"></script>
<script>
  // What the bot is and what it knows. Replace with your own facts.
  const SYSTEM = `You are the assistant of Acme Bikes, a bike shop in Portland.
Open Monday to Saturday, 9am to 6pm. Repairs take two working days.
A tune-up costs $60 and a full service costs $140.
Answer in under 60 words. If you do not know, say so.`;

  const log = document.getElementById("log");
  const q = document.getElementById("q");
  const send = document.getElementById("send");
  const history = [];

  function bubble(who, text) {
    const el = document.createElement("div");
    el.className = "msg " + who;
    el.textContent = text;
    log.appendChild(el);
    log.scrollTop = log.scrollHeight;
    return el;
  }

  document.getElementById("chat").addEventListener("submit", async (event) => {
    event.preventDefault();
    const question = q.value.trim();
    if (!question) return;
    q.value = "";
    send.disabled = true;
    bubble("user", question);
    const bot = bubble("bot", "");
    try {
      const answer = await onetag.ask(question, null, {
        system: SYSTEM,
        history: history.slice(-20),
        stream: (delta) => { bot.textContent += delta; log.scrollTop = log.scrollHeight; },
      });
      bot.textContent = answer;
      history.push({ role: "user", content: question }, { role: "assistant", content: answer });
    } catch (error) {
      bot.textContent = error.code === "quota_exceeded" ? "The chat is resting for now. Please come back later." : "Something went wrong: " + error.message;
    }
    send.disabled = false;
    q.focus();
  });
</script>
</body>
</html>

2. Chat bubble

A Chat button fixed to the bottom-right corner that opens a panel. It is a snippet, not a page: paste it before </body>. Its class names start with cb- and its script is wrapped in a function, so it does not collide with the page's own CSS and JavaScript.

Live demo on a shared demo key, with the same code as below. Open it in its own tab.

<!-- Chat bubble. Paste before </body> on any page. -->
<style>
  .cb-button, .cb-panel { position: fixed; right: 20px; z-index: 9999; font: 15px/1.5 system-ui, sans-serif; color: #0b1020; }
  .cb-button { bottom: 20px; height: 48px; padding: 0 20px; border: 0; background: #0b5fff; color: #fff; font-weight: 600; cursor: pointer; }
  .cb-panel { bottom: 80px; width: min(360px, calc(100vw - 40px)); height: min(480px, calc(100dvh - 100px)); display: flex; flex-direction: column; background: #fff; border: 1px solid #0b1020; box-shadow: 0 12px 32px rgba(11, 16, 32, .18); }
  .cb-panel[hidden] { display: none; }
  .cb-head { padding: 10px 14px; border-bottom: 1px solid #0b1020; font-weight: 600; }
  .cb-log { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px; }
  .cb-msg { max-width: 85%; padding: 6px 10px; border: 1px solid #0b1020; white-space: pre-wrap; overflow-wrap: anywhere; }
  .cb-user { align-self: flex-end; background: #0b5fff; border-color: #0b5fff; color: #fff; }
  .cb-bot { align-self: flex-start; background: #f5f7fb; }
  .cb-bot:empty::after { content: "…"; }
  .cb-form { display: flex; border-top: 1px solid #0b1020; }
  .cb-form input { flex: 1; min-width: 0; padding: 12px 14px; border: 0; font: inherit; outline: none; }
  .cb-form button { padding: 0 16px; border: 0; background: #0b5fff; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
</style>

<button class="cb-button" id="cb-button" aria-expanded="false">Chat</button>
<div class="cb-panel" id="cb-panel" hidden>
  <div class="cb-head">Ask Acme Bikes</div>
  <div class="cb-log" id="cb-log" aria-live="polite"><div class="cb-msg cb-bot">Hi! How can I help?</div></div>
  <form class="cb-form" id="cb-form">
    <input id="cb-q" placeholder="Type a message" autocomplete="off" required>
    <button>Send</button>
  </form>
</div>

<script src="https://onetagai.com/v1.js" data-key="YOUR_ONETAG_KEY"></script>
<script>
(() => {
  // What the bot is and what it knows. Replace with your own facts.
  const SYSTEM = `You are the assistant of Acme Bikes, a bike shop in Portland.
Open Monday to Saturday, 9am to 6pm. Repairs take two working days.
A tune-up costs $60 and a full service costs $140.
Answer in under 60 words. If you do not know, say so.`;

  const button = document.getElementById("cb-button");
  const panel = document.getElementById("cb-panel");
  const log = document.getElementById("cb-log");
  const q = document.getElementById("cb-q");
  const history = [];

  button.addEventListener("click", () => {
    panel.hidden = !panel.hidden;
    button.textContent = panel.hidden ? "Chat" : "Close";
    button.setAttribute("aria-expanded", String(!panel.hidden));
    if (!panel.hidden) q.focus();
  });

  function bubble(who, text) {
    const el = document.createElement("div");
    el.className = "cb-msg cb-" + who;
    el.textContent = text;
    log.appendChild(el);
    log.scrollTop = log.scrollHeight;
    return el;
  }

  document.getElementById("cb-form").addEventListener("submit", async (event) => {
    event.preventDefault();
    const question = q.value.trim();
    if (!question) return;
    q.value = "";
    bubble("user", question);
    const bot = bubble("bot", "");
    try {
      const answer = await onetag.ask(question, null, {
        system: SYSTEM,
        history: history.slice(-20),
        stream: (delta) => { bot.textContent += delta; log.scrollTop = log.scrollHeight; },
      });
      bot.textContent = answer;
      history.push({ role: "user", content: question }, { role: "assistant", content: answer });
    } catch (error) {
      bot.textContent = error.code === "quota_exceeded" ? "The chat is resting for now. Please come back later." : "Something went wrong: " + error.message;
    }
  });
})();
</script>

A page with the content on the left and a chat column on the right. The bot reads the <article> and answers from it, and says so when the page does not cover a question. On a narrow screen the chat moves under the content.

Live demo on a shared demo key, with the same code as below. Open it in its own tab.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page with an AI sidebar</title>
<style>
  :root { --accent: #0b5fff; --ink: #0b1020; --soft: #f5f7fb; }
  * { box-sizing: border-box; }
  body { margin: 0; display: grid; grid-template-columns: 1fr 360px; height: 100dvh; font: 16px/1.6 system-ui, sans-serif; color: var(--ink); }
  article { overflow-y: auto; padding: 32px 40px; }
  article > * { max-width: 680px; }
  aside { display: flex; flex-direction: column; min-height: 0; border-left: 1px solid var(--ink); }
  aside h2 { margin: 0; padding: 12px 16px; border-bottom: 1px solid var(--ink); font-size: 16px; }
  #log { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
  .msg { max-width: 90%; padding: 8px 12px; border: 1px solid var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
  .user { align-self: flex-end; background: var(--accent); border-color: var(--accent); color: #fff; }
  .bot { align-self: flex-start; background: var(--soft); }
  .bot:empty::after { content: "…"; }
  form { display: flex; border-top: 1px solid var(--ink); }
  input { flex: 1; min-width: 0; padding: 14px 16px; border: 0; font: inherit; outline: none; }
  button { padding: 0 20px; border: 0; background: var(--accent); color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
  @media (max-width: 760px) {
    body { grid-template-columns: 1fr; grid-template-rows: 1fr 45dvh; }
    aside { border-left: 0; border-top: 1px solid var(--ink); }
    article { padding: 20px; }
  }
</style>
</head>
<body>
<article id="content">
  <h1>Returns and repairs at Acme Bikes</h1>
  <p>You can return an unused bike within 30 days of delivery for a full refund. Bring the receipt and the original packaging. Used bikes can be exchanged within 14 days.</p>
  <p>Repairs take two working days. A basic tune-up costs $60 and a full service costs $140. Parts are charged separately, and we call you before fitting anything that costs more than $40.</p>
  <p>The workshop is open Monday to Saturday, 9am to 6pm. No appointment is needed for a puncture; book ahead for a full service.</p>
</article>

<aside>
  <h2>Ask about this page</h2>
  <div id="log" aria-live="polite"><div class="msg bot">Ask me anything about this page.</div></div>
  <form id="chat">
    <input id="q" placeholder="Type a question" autocomplete="off" required>
    <button>Ask</button>
  </form>
</aside>

<script src="https://onetagai.com/v1.js" data-key="YOUR_ONETAG_KEY"></script>
<script>
  const SYSTEM = "Answer from the page text only, in under 80 words. If the page does not say, say that it does not.";
  const content = document.getElementById("content");
  const log = document.getElementById("log");
  const q = document.getElementById("q");
  const history = [];

  function bubble(who, text) {
    const el = document.createElement("div");
    el.className = "msg " + who;
    el.textContent = text;
    log.appendChild(el);
    log.scrollTop = log.scrollHeight;
    return el;
  }

  document.getElementById("chat").addEventListener("submit", async (event) => {
    event.preventDefault();
    const question = q.value.trim();
    if (!question) return;
    q.value = "";
    bubble("user", question);
    const bot = bubble("bot", "");
    try {
      // The second argument is what the bot reads: here, the text of the article.
      const answer = await onetag.ask(question, content, {
        system: SYSTEM,
        history: history.slice(-10),
        stream: (delta) => { bot.textContent += delta; log.scrollTop = log.scrollHeight; },
      });
      bot.textContent = answer;
      history.push({ role: "user", content: question }, { role: "assistant", content: answer });
    } catch (error) {
      bot.textContent = error.code === "quota_exceeded" ? "The chat is resting for now. Please come back later." : "Something went wrong: " + error.message;
    }
  });
</script>
</body>
</html>

Change the look

The full-page chat and the sidebar take their colours from three CSS variables at the top: --accent for the visitor's bubbles and the button, --ink for text and lines, --soft for the bot's bubbles. The corners are square; add border-radius to .msg for round bubbles. The bubble snippet uses plain colour values so that it cannot be changed by variables your page already defines.

Change what the bot knows

Two of the templates read a SYSTEM text: write the bot's role and the facts it may use there. The sidebar passes the page's own text with each question instead: the second argument of onetag.ask is what the bot reads, up to 32,000 characters per call. You can do the same in the other two.

What the templates leave out

Questions

Are the templates free?

Yes. Copy, change and ship them, with no attribution needed. The AI answers use your OneTag AI key, which is free for 1,000,000 tokens a month with no card.

Do the templates work without a backend?

Yes. Each is a static file that calls the model from the browser. We ran all three on 2026-11-11, one of them opened straight from disk.

Can I use them with Bootstrap or Tailwind?

Yes. They use no framework. The bubble's classes are prefixed with cb- so they do not clash; in the other two you can swap the CSS for your framework's classes.

Can I use a template in React, Vue or Next.js?

The markup and CSS carry over as they are. Move the submit handler into your component, and load the SDK with the script tag or with npm install onetagai.

Can I connect a template to OpenAI or my own server instead?

Yes. Replace the onetag.ask call with a fetch to your own endpoint that holds the key. Do not put an OpenAI key in the page itself: anyone can copy it from the source.

How do I make the bot answer from my own content?

Put short facts in the SYSTEM text. For longer content pass an element or a string as the second argument of onetag.ask, as the sidebar template does.

Is it safe to put the OneTag AI key in my page?

Yes, it is designed for that. The key can only spend its own token budget and cannot read or change anything on your account. On the Pro plan you can lock it to your domain so other sites cannot use it; free keys work from any page.

What happens to the text my visitors send?

It goes to the model and comes back. OneTag AI stores token counts per key for the quota and keeps no prompts, inputs or answers. The access log carries no IP addresses.

Try it on your sitefree

Get a free key by email, paste one tag, call onetag.ask. 1,000,000 tokens a month, no card.

Get a free key

Updated 2026-11-11. Something wrong or missing? Email support@odysseyx.app.