Build a guided troubleshooting widget with Tailwind CSS. Use a max-w-xl bordered card with a heading reading "Let us narrow it down", a muted subline, and a breadcrumb row of answered steps rendered as small pills with the chosen answers and a reset link on the right. The body shows one question at a time: a question in medium weight and two or three full-width answer buttons as bordered rows with a chevron. The final state replaces the question with a resolution panel — a tinted box with a title, two paragraphs, a monospaced command where relevant, and two buttons for marking it solved or contacting support. Then add JavaScript holding the tree as a nested object, rendering the current node, appending each answer to the breadcrumb, allowing a click on any breadcrumb pill to rewind to that point, and resetting the whole flow from the reset link.
Paste it into Claude, Cursor, v0 or any AI coding tool. Prefer the
finished markup? .
Build a guided troubleshooting widget with Tailwind CSS. Use a max-w-xl bordered card with a heading reading "Let us narrow it down", a muted subline, and a breadcrumb row of answered steps rendered as small pills with the chosen answers and a reset link on the right. The body shows one question at a time: a question in medium weight and two or three full-width answer buttons as bordered rows with a chevron. The final state replaces the question with a resolution panel — a tinted box with a title, two paragraphs, a monospaced command where relevant, and two buttons for marking it solved or contacting support. Then add JavaScript holding the tree as a nested object, rendering the current node, appending each answer to the breadcrumb, allowing a click on any breadcrumb pill to rewind to that point, and resetting the whole flow from the reset link.
<section class="mx-auto max-w-xl px-6 py-14">
<div class="rounded-2xl border border-neutral-200 bg-white p-7">
<h2 class="text-xl font-semibold tracking-tight text-neutral-900">Let us narrow it down</h2>
<p class="mt-1.5 text-sm text-neutral-500">Three questions at most. Nothing is sent anywhere until you ask for support.</p>
<div class="mt-5 flex flex-wrap items-center gap-2">
<div id="treeCrumbs" class="flex flex-wrap gap-1.5"></div>
<button id="treeReset" class="ml-auto text-xs text-neutral-500 underline-offset-4 hover:text-neutral-900 hover:underline">Start over</button>
</div>
<div id="treeBody" class="mt-6"></div>
</div>
</section>
var TREE = {
start: {
question: "What is going wrong?",
answers: [
["A sync is failing", "sync"],
["Numbers look wrong", "numbers"],
["I cannot sign in", "signin"],
],
},
sync: {
question: "What does the sync status say?",
answers: [
["Permission denied", "perm"],
["Timed out", "timeout"],
["Succeeded, but no rows", "empty"],
],
},
numbers: {
question: "Are they wrong everywhere, or in one report?",
answers: [
["One report only", "onereport"],
["Everywhere", "everywhere"],
],
},
signin: {
result: {
title: "Try the account recovery flow",
body: "Five failed attempts locks an account for fifteen minutes. If your workspace uses SSO, the password form will always reject you — use the SSO button instead.",
code: "",
},
},
perm: {
result: {
title: "The warehouse role lost a grant",
body: "This almost always follows a schema change. Re-run the grant statement below as an admin, then retry the sync from the source page.",
code: "GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO northwind_ro;",
},
},
timeout: {
result: {
title: "The query is exceeding the window",
body: "Syncs are cancelled after 15 minutes. Add an incremental cursor on an indexed timestamp column so each run reads only what changed.",
code: "cursor: updated_at, mode: incremental",
},
},
empty: {
result: {
title: "The filter is excluding everything",
body: "A sync that succeeds with zero rows usually has a where clause referencing a column that has since been renamed. Check the source filter, then run a preview.",
code: "",
},
},
onereport: {
result: {
title: "Check the report cache and its filters",
body: "Reports cache for five minutes by default. A pinned date range is the other usual suspect — open the report settings and look at the range selector.",
code: "",
},
},
everywhere: {
result: {
title: "Timezone mismatch between source and workspace",
body: "If every figure is shifted rather than wrong, the source is reporting in UTC while the workspace renders in local time. Set the workspace timezone to match the warehouse.",
code: "",
},
},
};
var body = document.getElementById("treeBody");
var crumbs = document.getElementById("treeCrumbs");
var path = [];
function renderCrumbs() {
crumbs.innerHTML = path
.map(function (step, i) {
return (
'<button data-step="' + i + '" class="crumb rounded-full bg-neutral-100 px-2.5 py-1 text-xs text-neutral-600 transition hover:bg-neutral-200">' +
step.label +
"</button>"
);
})
.join("");
Array.prototype.slice.call(crumbs.querySelectorAll(".crumb")).forEach(function (pill) {
pill.addEventListener("click", function () {
// Rewinding drops everything after the pill that was clicked.
var index = Number(pill.dataset.step);
var node = path[index].from;
path = path.slice(0, index);
render(node);
});
});
}
function render(key) {
var node = TREE[key];
renderCrumbs();
if (node.result) {
body.innerHTML =
'<div class="rounded-xl border border-green-200 bg-green-50 p-5">' +
'<p class="text-sm font-semibold text-green-900">' + node.result.title + "</p>" +
'<p class="mt-2 text-sm leading-relaxed text-green-800">' + node.result.body + "</p>" +
(node.result.code
? '<pre class="mt-3 overflow-x-auto rounded-lg bg-neutral-900 p-3 font-mono text-xs text-neutral-200">' + node.result.code + "</pre>"
: "") +
"</div>" +
'<div class="mt-4 flex flex-wrap gap-2">' +
'<button class="rounded-xl bg-neutral-900 px-4 py-2.5 text-sm font-medium text-white">That fixed it</button>' +
'<button class="rounded-xl border border-neutral-200 px-4 py-2.5 text-sm font-medium text-neutral-700">Contact support</button>' +
"</div>";
return;
}
body.innerHTML =
'<p class="text-sm font-medium text-neutral-900">' + node.question + "</p>" +
'<div class="mt-3 space-y-2">' +
node.answers
.map(function (answer) {
return (
'<button data-next="' + answer[1] + '" data-label="' + answer[0] + '" class="answer flex w-full items-center justify-between gap-3 rounded-xl border border-neutral-200 px-4 py-3 text-left text-sm text-neutral-700 transition hover:border-neutral-400 hover:bg-neutral-50">' +
"<span>" + answer[0] + "</span><span class=\"text-neutral-300\">›</span>" +
"</button>"
);
})
.join("") +
"</div>";
Array.prototype.slice.call(body.querySelectorAll(".answer")).forEach(function (button) {
button.addEventListener("click", function () {
path.push({ label: button.dataset.label, from: key });
render(button.dataset.next);
});
});
}
document.getElementById("treeReset").addEventListener("click", function () {
path = [];
render("start");
});
render("start");