contentbit
componentastro-faqsource: ok · hit
FAQ Block (Astro)
Accordion of question/answer pairs.
npx shadcn add "https://contentbit.dev/r/astro-faq.json"free
Where it goes
faq
Interaction
accordion
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · light@contentbit/core@contentbit/blocks@contentbit/astro
Files1 · source cached 10/15/2026, 3:42:06 AM
src/astro/faq.astro
---
import type { FaqData, FaqItemData } from '@contentbit/blocks'
import type { ValidatedBlockNode } from '@contentbit/core'
import type { AstroBlockContext } from '@contentbit/astro'
import { isValidatedBlock } from '@contentbit/core'
interface Props {
node: ValidatedBlockNode<unknown>
ctx: AstroBlockContext
}
const { node, ctx } = Astro.props
const data = node.data as FaqData
const items = await Promise.all(
data.blocks.map(async (item) => ({
question: String(item.props.question),
html: await ctx.renderMarkdown(isValidatedBlock(item) ? (item.data as FaqItemData).markdown : item.body),
})),
)
// Inline lucide path: chevron-down.
const CHEVRON = '<path d="m6 9 6 6 6-6"/>'
// Native <details> means items open independently (the React pack's accordion
// is single-open). Add name="faq" to the <details> elements for exclusivity.
---
<div data-cb-styled class="bg-card my-6 rounded-lg border px-4">
{
items.map((item, i) => (
<details class:list={['group', i < items.length - 1 && 'border-b']}>
<summary class="flex cursor-pointer list-none items-center justify-between gap-4 py-4 text-left text-sm font-medium [&::-webkit-details-marker]:hidden">
{item.question}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-muted-foreground size-4 shrink-0 transition-transform group-open:rotate-180"
aria-hidden="true"
set:html={CHEVRON}
/>
</summary>
<div class="text-muted-foreground pb-4 text-sm leading-relaxed" set:html={item.html} />
</details>
))
}
</div>