AI chatbot in HTML, CSS and JavaScript: complete working code
How do I build a working AI chatbot with only HTML, CSS and JavaScript?
Save the code below as chatbot.html, put your key in it and open it. You have a chatbot that answers with a real AI model, shows the reply as it is written and remembers the conversation. It is one file of HTML, CSS and JavaScript, 77 lines, with no framework, no build step and no server.
The part most tutorials get wrong is the API key. A page that calls OpenAI or Gemini directly carries a secret key in its JavaScript, where any visitor can copy it. This version calls OneTag AI, whose key is made to sit in page source: it can only spend its own fixed budget, which on the free plan is 1,000,000 tokens a month with no card.
Live demo on a shared demo key, with the same code as below. Open it in its own tab.
Steps
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.Save this as chatbot.html
The whole chatbot: the chat window in HTML, the bubbles in CSS, and the conversation in JavaScript.
<!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>Put in your key and open it
Replace
YOUR_ONETAG_KEYwith your key and double-click the file. It runs from your disk, and from any host once you upload it: GitHub Pages, Netlify, your own server.Make it your bot
Rewrite the
SYSTEMtext: say who the bot is, list the facts it should know (opening hours, prices, policies) and how long its answers should be. Change--accentin the CSS to your colour, and the header and greeting to your name.
How the code works
- HTML. Three parts: a
#logthat holds the messages, a form with one text field, and a button. Because it is a form, pressing Enter sends the message with no extra code. - CSS. The page is a flex column and the log takes the free height. Each message is a
.msgbox, pushed right for the visitor and left for the bot. A bot bubble with no text yet shows an ellipsis through.bot:empty::after; that is the whole typing indicator. - JavaScript. On submit the code adds the visitor's bubble and an empty bot bubble, then calls
onetag.ask.systemcarries the bot's role and facts,historysends the last 20 messages so the bot remembers the conversation, andstreamappends each piece of the answer as it arrives.
In our test on 2026-11-11 the first words appeared about 2 seconds after sending, and a follow-up question about the earlier message was answered correctly.
Why not call the ChatGPT API from the page?
The usual "ChatGPT in HTML" code sends a fetch to the model provider with the secret key written in the script. Two widely read tutorials do this (both read on 2026-11-10): CodingNepal's puts a Google Gemini key in the page's JavaScript, and GeeksforGeeks' puts an OpenAI key there. That is fine while the file stays on your computer. Once the page is public, the key is in the source for anyone to copy, and a copied key spends your account's money until you revoke it.
There are two ways out. Run a small server or serverless function that holds the key and forwards the request, or use a key that is designed to be public. This page does the second. If you need a specific OpenAI model, do the first; Can I put an AI API key in HTML? covers both.
Give the bot your own knowledge
The model knows only what you send it. Short facts go in SYSTEM. To answer from the content of a page, pass an element as the second argument and the bot reads its text:
const answer = await onetag.ask(question, document.querySelector("main"), {
system: "Answer from the page text only. If the page does not say, say so.",
history: history.slice(-20),
});One call takes up to 32,000 characters of text. The sidebar layout on Chatbot HTML templates works this way. To answer from a whole site, see Add 'ask the docs' AI search.
Keep the conversation after a reload
The page forgets the chat when it is reloaded. To keep it, load the history from localStorage, draw the saved messages, and save after each answer. Replace the const history = []; line with the first two lines, and add the third after history.push(...):
const history = JSON.parse(localStorage.getItem("chat") || "[]");
history.forEach((m) => bubble(m.role === "user" ? "user" : "bot", m.content));
localStorage.setItem("chat", JSON.stringify(history));What a conversation costs
In our test on 2026-11-10, one turn with a system line, two earlier messages and a one-sentence answer used 124 tokens. 1,000,000 tokens is about 8,000 turns of that size. The earlier messages are sent again with every turn, so long conversations cost more per message; sending only the last 20 caps that.
When a month's tokens are used up, the bot shows its "resting" line until the 1st. There is no bill on the free plan.
| Free key | |
|---|---|
| Price | $0, no credit card |
| Tokens | 1,000,000 a month per key, input and output counted together. Starts again on the 1st (UTC) |
| Rate limit | 60 requests a minute per key, 20 a minute per IP address |
| Per call | Up to 32,000 characters of text and one image up to 2 MB; answers up to 10,000 tokens (500 by default) |
| Where the key works | Any web page on any domain, and curl or server code |
| Features | Text answers, streaming, JSON output, image input, chat history |
| Model | One fast model served through OpenRouter. You do not choose it |
| Data | Prompts, inputs and answers are not stored. Only token counts per key are kept |
| When it runs out | Calls return quota_exceeded until the 1st and you get an email. Pro is $9.9 a month for 100,000,000 tokens a month; the key stays the same |
Limits to know before you rely on it
- It is not ChatGPT. The answers come from one fast open model served through OpenRouter; you do not choose the model.
- The bot will answer confidently about things you never told it. Put the facts it needs in
SYSTEMand tell it to say when it does not know. - Answers are shown as plain text. If the model writes Markdown such as
**bold**, the asterisks show as typed; ask for plain text inSYSTEMor add a Markdown renderer. - Anyone can read the key in the page source and use it elsewhere. They can only spend the key's budget, and calls are limited to 60 a minute per key and 20 a minute per IP address. Pro keys can be locked to your domain.
Questions
Can I make a chatbot with only HTML and CSS?
No. HTML and CSS draw the chat window. Sending a message and showing an answer needs JavaScript, and something has to write the answer. In the code on this page that is about 40 lines of JavaScript and one call to a hosted AI model.
Is this ChatGPT?
No. The answers come from a fast open model served through OpenRouter, and the request is not in the OpenAI format. To use an OpenAI model you need a server that holds your OpenAI key.
Do I need Node.js, React or a backend?
No. It is one static HTML file. The only thing outside the file is the script tag that loads the OneTag AI SDK.
Does it work if I open the file from my computer?
Yes. We opened it from disk (a file:// address) on 2026-11-11 and it answered. Free keys accept requests from any page.
How do I put this chatbot on an existing website?
Use the floating chat bubble instead of the full page: see How to add an AI chatbot to an HTML website.
Can I use this code in my own project?
Yes. Copy it, change it and ship it, with no attribution needed. The AI calls count against the key you put in.
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.
Does it cost anything?
A free key has 1,000,000 tokens a month, reset on the 1st, enough for a few hundred summaries a month. Pro is $9.9 a month for 100,000,000 tokens a month and the domain lock.
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.
Related guides
- Chatbot HTML templates: 3 free chat UIs with working AI
- How to add an AI chatbot to an HTML website with one script tag
- Free chatbot API for websites: chat history, streaming, no backend
- Is it safe to put an AI API key in a static HTML page?
Get a free key by email, paste one tag, call onetag.ask. 1,000,000 tokens a month, no card.
Updated 2026-11-11. Something wrong or missing? Email support@odysseyx.app.