FAQ
Accordion with helpful votes
Answers that collect feedback as they close
Answers that collect feedback as they close
Build an FAQ accordion with Tailwind CSS where each answer asks whether it helped. Use native details/summary elements inside a bordered, divided card: the summary row is a flex line with the question in medium weight and a plus glyph that rotates when open, and the answer body is relaxed grey prose. Under every answer add a feedback row — a muted "Was this helpful?" label, a Yes and a No button as small bordered pills, and a right-aligned tally line reading something like "18 of 21 found this helpful". Give the details elements a shared name-like data attribute and each feedback row a data-votes pair of numbers. Then add JavaScript that keeps only one answer open at a time, and on a vote replaces the two buttons with a quiet "Thanks — this helps us rewrite it" note while incrementing the tally and recomputing the ratio, ignoring further clicks in that row. <section class="mx-auto max-w-3xl px-6 py-16">
<div class="mb-10 text-center">
<h2 class="text-3xl font-semibold text-neutral-900">Questions we get every week</h2>
<p class="mt-2 text-neutral-500">Still stuck? Support replies in under an hour on weekdays.</p>
</div>
<div id="faq" class="divide-y divide-neutral-100 overflow-hidden rounded-2xl border border-neutral-200 bg-white">
<details data-faq class="group p-6">
<summary class="flex cursor-pointer list-none items-center justify-between gap-4">
<span class="font-medium text-neutral-900">Can I move an existing project across without downtime?</span>
<span class="shrink-0 text-neutral-400 transition group-open:rotate-45">+</span>
</summary>
<p class="mt-3 text-sm leading-relaxed text-neutral-500">
Yes. Run both pipelines in parallel and point a share of traffic at the new one. Most teams cut over inside a week, and rolling back is a config change rather than a redeploy.
</p>
<div class="mt-5 flex flex-wrap items-center gap-3 border-t border-neutral-100 pt-4 text-xs" data-votes="18,21">
<span class="text-neutral-400">Was this helpful?</span>
<button data-vote="yes" class="rounded-lg border border-neutral-200 px-2.5 py-1 font-medium text-neutral-700 transition hover:bg-neutral-50">Yes</button>
<button data-vote="no" class="rounded-lg border border-neutral-200 px-2.5 py-1 font-medium text-neutral-700 transition hover:bg-neutral-50">No</button>
<span data-tally class="ml-auto text-neutral-400">18 of 21 found this helpful</span>
</div>
</details>
<details data-faq class="group p-6">
<summary class="flex cursor-pointer list-none items-center justify-between gap-4">
<span class="font-medium text-neutral-900">What happens to my data if we cancel?</span>
<span class="shrink-0 text-neutral-400 transition group-open:rotate-45">+</span>
</summary>
<p class="mt-3 text-sm leading-relaxed text-neutral-500">
Exports stay available for 30 days after the last invoice, in the same JSON shape the API returns. After that the workspace is deleted and the deletion is logged.
</p>
<div class="mt-5 flex flex-wrap items-center gap-3 border-t border-neutral-100 pt-4 text-xs" data-votes="41,44">
<span class="text-neutral-400">Was this helpful?</span>
<button data-vote="yes" class="rounded-lg border border-neutral-200 px-2.5 py-1 font-medium text-neutral-700 transition hover:bg-neutral-50">Yes</button>
<button data-vote="no" class="rounded-lg border border-neutral-200 px-2.5 py-1 font-medium text-neutral-700 transition hover:bg-neutral-50">No</button>
<span data-tally class="ml-auto text-neutral-400">41 of 44 found this helpful</span>
</div>
</details>
<details data-faq class="group p-6">
<summary class="flex cursor-pointer list-none items-center justify-between gap-4">
<span class="font-medium text-neutral-900">Do you count preview builds against my plan?</span>
<span class="shrink-0 text-neutral-400 transition group-open:rotate-45">+</span>
</summary>
<p class="mt-3 text-sm leading-relaxed text-neutral-500">
Preview builds run on a separate allowance and never touch production minutes. You will see them itemised, but they only bill once the allowance runs out.
</p>
<div class="mt-5 flex flex-wrap items-center gap-3 border-t border-neutral-100 pt-4 text-xs" data-votes="9,14">
<span class="text-neutral-400">Was this helpful?</span>
<button data-vote="yes" class="rounded-lg border border-neutral-200 px-2.5 py-1 font-medium text-neutral-700 transition hover:bg-neutral-50">Yes</button>
<button data-vote="no" class="rounded-lg border border-neutral-200 px-2.5 py-1 font-medium text-neutral-700 transition hover:bg-neutral-50">No</button>
<span data-tally class="ml-auto text-neutral-400">9 of 14 found this helpful</span>
</div>
</details>
<details data-faq class="group p-6">
<summary class="flex cursor-pointer list-none items-center justify-between gap-4">
<span class="font-medium text-neutral-900">Is there a self-hosted option?</span>
<span class="shrink-0 text-neutral-400 transition group-open:rotate-45">+</span>
</summary>
<p class="mt-3 text-sm leading-relaxed text-neutral-500">
On the Business plan, yes — the runner ships as a container you place inside your own VPC, while scheduling stays with us. Air-gapped installs are handled case by case.
</p>
<div class="mt-5 flex flex-wrap items-center gap-3 border-t border-neutral-100 pt-4 text-xs" data-votes="27,29">
<span class="text-neutral-400">Was this helpful?</span>
<button data-vote="yes" class="rounded-lg border border-neutral-200 px-2.5 py-1 font-medium text-neutral-700 transition hover:bg-neutral-50">Yes</button>
<button data-vote="no" class="rounded-lg border border-neutral-200 px-2.5 py-1 font-medium text-neutral-700 transition hover:bg-neutral-50">No</button>
<span data-tally class="ml-auto text-neutral-400">27 of 29 found this helpful</span>
</div>
</details>
</div>
</section> var items = [].slice.call(document.querySelectorAll("[data-faq]"));
// One answer at a time — the markup works without this, it just reads better with it.
items.forEach(function (item) {
item.addEventListener("toggle", function () {
if (!item.open) return;
items.forEach(function (other) { if (other !== item) other.open = false; });
});
});
[].slice.call(document.querySelectorAll("[data-votes]")).forEach(function (row) {
var tally = row.querySelector("[data-tally]");
var buttons = [].slice.call(row.querySelectorAll("[data-vote]"));
var parts = row.getAttribute("data-votes").split(",");
var helpful = Number(parts[0]);
var total = Number(parts[1]);
buttons.forEach(function (button) {
button.addEventListener("click", function () {
total += 1;
if (button.getAttribute("data-vote") === "yes") helpful += 1;
buttons.forEach(function (b) { b.remove(); });
var thanks = document.createElement("span");
thanks.className = "font-medium text-neutral-700";
thanks.textContent = "Thanks — this helps us rewrite it";
tally.before(thanks);
tally.textContent = helpful + " of " + total + " found this helpful";
});
});
});