Create a floating support chat widget with Tailwind CSS. A fixed bottom-right launcher button — a dark circle with a chat glyph and a small red unread dot — plus a chat panel above it that is hidden by default: a 22rem card with rounded-2xl corners and a shadow, a dark header with an agent avatar, the agent name, a green "Typically replies in 2 minutes" line and a close ✕, a scrollable message area seeded with one incoming bubble, a row of quick-reply chips, and a composer with a text input and a send button. Incoming messages are grey bubbles aligned left, outgoing ones are dark and aligned right. Then add JavaScript that opens and closes the panel (clearing the unread dot on first open and closing on Escape), appends the typed message as an outgoing bubble on submit, shows a three-dot typing indicator for a moment, then answers with a canned reply picked by matching keywords like "pricing", "bug" or "demo" against the message with a sensible fallback, sends a quick-reply chip as if it had been typed, and keeps the message area scrolled to the newest bubble.
Paste it into Claude, Cursor, v0 or any AI coding tool. Prefer the
finished markup? .
Create a floating support chat widget with Tailwind CSS. A fixed bottom-right launcher button — a dark circle with a chat glyph and a small red unread dot — plus a chat panel above it that is hidden by default: a 22rem card with rounded-2xl corners and a shadow, a dark header with an agent avatar, the agent name, a green "Typically replies in 2 minutes" line and a close ✕, a scrollable message area seeded with one incoming bubble, a row of quick-reply chips, and a composer with a text input and a send button. Incoming messages are grey bubbles aligned left, outgoing ones are dark and aligned right. Then add JavaScript that opens and closes the panel (clearing the unread dot on first open and closing on Escape), appends the typed message as an outgoing bubble on submit, shows a three-dot typing indicator for a moment, then answers with a canned reply picked by matching keywords like "pricing", "bug" or "demo" against the message with a sensible fallback, sends a quick-reply chip as if it had been typed, and keeps the message area scrolled to the newest bubble.
var REPLIES = [
{ match: /pric|cost|plan|billing/i, text: "Team is $49 a month for the workspace plus $9 per extra seat. Annual billing takes 20% off — want me to send a quote?" },
{ match: /bug|error|fail|broken|stuck/i, text: "Sorry about that. Paste the build ID and I will pull the logs — most stuck builds are a cache key change, which we can clear from here." },
{ match: /demo|call|meeting|sales/i, text: "Happy to set one up. Thirty minutes, screen share, no slides — what does your Thursday look like?" },
{ match: /migrat|import|move|switch/i, text: "Migrations usually take an afternoon. We can run both pipelines side by side until you are confident, then flip the default." }
];
var FALLBACK = "Good question — let me check with the team and come back to you here within the hour.";
var launcher = document.getElementById("chatLauncher");
var panel = document.getElementById("chatPanel");
var badge = document.getElementById("chatBadge");
var log = document.getElementById("chatLog");
var form = document.getElementById("chatForm");
var input = document.getElementById("chatInput");
function bubble(text, mine) {
var el = document.createElement("div");
el.className = mine
? "ml-auto max-w-[80%] rounded-2xl rounded-tr-sm bg-neutral-900 px-3.5 py-2.5 text-sm leading-relaxed text-neutral-100"
: "max-w-[80%] rounded-2xl rounded-tl-sm bg-white px-3.5 py-2.5 text-sm leading-relaxed text-neutral-700 shadow-sm";
el.textContent = text;
log.appendChild(el);
log.scrollTop = log.scrollHeight;
return el;
}
function answer(question) {
var typing = bubble("● ● ●", false);
typing.className += " animate-pulse text-neutral-400";
setTimeout(function () {
var reply = REPLIES.filter(function (r) { return r.match.test(question); })[0];
typing.remove();
bubble(reply ? reply.text : FALLBACK, false);
}, 900);
}
function send(text) {
var message = text.trim();
if (!message) return;
bubble(message, true);
input.value = "";
answer(message);
}
launcher.addEventListener("click", function () {
var open = panel.classList.contains("hidden");
panel.classList.toggle("hidden", !open);
badge.classList.add("hidden"); // Nothing is unread once it has been opened.
if (open) input.focus();
});
document.getElementById("chatClose").addEventListener("click", function () {
panel.classList.add("hidden");
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") panel.classList.add("hidden");
});
form.addEventListener("submit", function (e) {
e.preventDefault();
send(input.value);
});
[].slice.call(document.querySelectorAll("#chatChips button")).forEach(function (chip) {
chip.addEventListener("click", function () { send(chip.textContent); });
});