Section
FAQ — Bento
A bento FAQ composition: a heading cell, the accordion as the grid's focus cell, plus contact and good-to-know reference cells of varied weights.
- Section
- React
- MIT
Preview
Live section
9:41 100%
devsnips.site/library/react/sections/faq/bento/ fluid · no fixed width 100%
Install
Add to your project
npx devsnips add React/Sections/FAQ/bento React/Sections/FAQ/bento import { useId, useState } from "react";
/**
* DevSnips React FAQ — Bento composition (Bento direction).
*
* A genuine 12-column bento composition (§4.4) — not a grid of
* identical FAQ cards. Varied cell sizes carry varied jobs: a large
* heading cell (span 5, holding eyebrow, title, lede); the FAQ
* accordion cell (span 7) is the primary focus of the whole grid;
* beneath, a contact cell (span 5) with the documentation action, and a
* secondary "Good to know" cell (span 7) holding three quick reference
* facts. Cells share one radius (radius-lg), one 1px border, one
* uniform gap (16px mobile / 24px desktop),and a border-only hover
* lift. One accent: the contact link and the open accordion index.
* Collapse (§12.2): large cells span both columns at sm (header,FAQ,
* sticky facts; the two lower cells halve),everything stacks to one
* column below. The FAQ accordion reuses the same disclosure pattern as
* every other direction: native `<button>` triggers with
* `aria-expanded`/`aria-controls`,`role="region"` panels, single-open
* collapsible state,and the CSS grid-rows height trick with a discrete
* visibility transition, all reduced-motion-safe.
* Intentionally action-light: the only primary action is the contact link
* in the lower-left cell — one CTA per section (§11.1).
*/
export interface FAQItem {
question: string;
answer: string;
}
export interface QuickFact {
label: string;
value: string;
}
export interface FAQSectionProps {
eyebrow?: string;
title?: string;
description?: string;
items?: FAQItem[];
contactLabel?: string;
contactHref?: string;
quickFacts?: QuickFact[];
}
const FOCUS_RING =
"focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ds-color-focus-ring)]";
const CELL_CLASSES =
"rounded-[var(--ds-radius-lg)] border border-[var(--ds-color-border)] bg-[var(--ds-color-surface)] p-6 transition-colors duration-150 ease-out hover:border-[var(--ds-color-border-strong)] motion-reduce:transition-none lg:p-8";
const TRIGGER_CLASSES =
"flex w-full min-w-0 items-baseline gap-3 px-0 py-4 text-left transition-colors duration-150 ease-out motion-reduce:transition-none " +
FOCUS_RING;
const CONTACT_CLASSES =
"text-sm font-semibold leading-5 text-[var(--ds-color-link)] underline underline-offset-4 transition-colors duration-150 ease-out hover:text-[var(--ds-color-link-hover)] motion-reduce:transition-none " +
FOCUS_RING;
const DEFAULT_ITEMS: FAQItem[] = [
{
question: "Where do snippets fit in my stack?",
answer:
"Anywhere you can drop HTML, CSS, or a React function. Sections compose existing primitives from the shared token layer, so they inherit your design system's APIs — icons, buttons, accordions — instead of importing a parallel one.",
},
{
question: "Can I take a section and adapt it to my own content?",
answer:
"That is the intended workflow. Every section is a function with overridable props — eyebrow, heading, lede, items, actions — so you replace the defaults with your real copy and ship. The generator only owns the preview; code.tsx is yours to edit.",
},
{
question: "Do the tokens break anything if I omit them?",
answer:
"No. Every snippet carries its own values as CSS-variable fallbacks,so a bare copy-paste renders exactly as designed until you define a token layer. When you do, all snippets re-theme at once with a single block.",
},
{
question: "How are the four directions different from four themes?",
answer:
"Directions are compositions, not paint jobs — a different layout, hierarchy,and interaction budget per direction, sharing the same tokens, API,and accessibility tree. Minimal is reference;Dark Premium is an editorial split;Bento is a mixed-weight grid;Neo-Brutalist is the expressive ceiling.",
},
{
question: "What is the definition of done for a section?",
answer:
"The design-token spec lists thirteen gates — tokens, structure, responsiveness at six widths in two themes, contrast, keyboard, motion, types, direction parity, content, console, metadata,and preview fidelity. A section ships only when every gate passes.",
},
];
const DEFAULT_QUICK_FACTS: QuickFact[] = [
{ label: "License", value: "MIT — commercial use included" },
{ label: "Stack", value: "HTML, Tailwind, and React — no runtime" },
{ label: "Support", value: "Every family ships a Playwright QA suite" },
];
function Chevron({ open }: { open: boolean }) {
return (
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.75}
strokeLinecap="round"
strokeLinejoin="round"
className={
"ml-auto h-4 w-4 shrink-0 transition-transform duration-200 ease-out motion-reduce:transition-none " +
(open ? "rotate-180" : "")
}
>
<path d="m6 9 6 6 6-6" />
</svg>
);
}
export function FAQSection({
eyebrow = "Help center",
title = "The answers, organized.",
description = "Installation, licenses, tokens, and the engineering practices behind every DevSnips section — right where you are looking.",
items = DEFAULT_ITEMS,
contactLabel = "Talk to support",
contactHref = "#support",
quickFacts = DEFAULT_QUICK_FACTS,
}: FAQSectionProps) {
const baseId = useId();
const [openIndex, setOpenIndex] = useState<number | null>(0);
return (
<section
aria-labelledby={`${baseId}-heading`}
className="bg-[var(--ds-color-background)] font-[var(--ds-font-sans)] text-[var(--ds-color-foreground)]"
>
<div className="mx-auto max-w-[1280px] px-4 py-[clamp(4rem,3rem+4vw,6rem)] sm:px-6 lg:px-8">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-12 lg:gap-6">
<div className={"sm:col-span-2 lg:col-span-5 " + CELL_CLASSES}>
<p className="text-xs font-semibold uppercase leading-[1.3] tracking-[0.05em] text-[var(--ds-color-muted-foreground)]">
{eyebrow}
</p>
<h2
id={`${baseId}-heading`}
className="mt-3 text-[clamp(1.875rem,1.65rem+1vw,2.25rem)] font-semibold leading-[1.15] tracking-[-0.02em]"
>
{title}
</h2>
<p className="mt-4 text-sm leading-6 text-[var(--ds-color-muted-foreground)]">
{description}
</p>
</div>
<div className={"sm:col-span-2 lg:col-span-7 " + CELL_CLASSES}>
<p className="font-[var(--ds-font-mono)] text-xs font-medium uppercase leading-[1.4] tracking-[0.05em] text-[var(--ds-color-muted-foreground)]">
Frequently asked
</p>
<ul className="mt-2 divide-y divide-[var(--ds-color-border-subtle)]">
{items.map((item, index) => {
const open = index === openIndex;
const triggerId = `${baseId}-trigger-${index}`;
const contentId = `${baseId}-content-${index}`;
return (
<li key={triggerId}>
<h3 className="m-0">
<button
type="button"
id={triggerId}
aria-expanded={open}
aria-controls={contentId}
onClick={() => setOpenIndex(open ? null : index)}
className={TRIGGER_CLASSES}
>
<span className="break-words text-sm font-medium leading-5">
{item.question}
</span>
<Chevron open={open} />
</button>
</h3>
<div
id={contentId}
role="region"
aria-labelledby={triggerId}
className={
"grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none " +
(open ? "grid-rows-[1fr]" : "grid-rows-[0fr]")
}
>
<div
className={
"min-h-0 overflow-hidden transition-[visibility] duration-200 motion-reduce:transition-none " +
(open ? "visible" : "invisible")
}
>
<div className="break-words pb-4 text-sm leading-6 text-[var(--ds-color-muted-foreground)]">
{item.answer}
</div>
</div>
</div>
</li>
);
})}
</ul>
</div>
<div className={"sm:col-span-1 lg:col-span-5 " + CELL_CLASSES}>
<p className="text-base font-semibold leading-6">Still have questions?</p>
<p className="mt-2 text-sm leading-5 text-[var(--ds-color-muted-foreground)]">
Real humans answer, usually within a day.
</p>
<p className="mt-6">
<a href={contactHref} className={CONTACT_CLASSES}>
{contactLabel}
</a>
</p>
</div>
<div className={"sm:col-span-1 lg:col-span-7 " + CELL_CLASSES}>
<p className="font-[var(--ds-font-mono)] text-xs font-medium uppercase leading-[1.4] tracking-[0.05em] text-[var(--ds-color-muted-foreground)]">
Good to know
</p>
<dl className="mt-4 divide-y divide-[var(--ds-color-border-subtle)] border-t border-[var(--ds-color-border-subtle)]">
{quickFacts.map((fact) => (
<div
key={fact.label}
className="flex items-baseline justify-between gap-4 py-3"
>
<dt className="text-sm leading-5 text-[var(--ds-color-muted-foreground)]">
{fact.label}
</dt>
<dd className="text-right text-sm font-medium leading-5">
{fact.value}
</dd>
</div>
))}
</dl>
</div>
</div>
</div>
</section>
);
} 238 lines UTF-8 · LF · Spaces: 2
DevSnips AI ready Claude · Cursor · Windsurf
Agent skill & Model Context Protocol
Pass this section straight into your AI workflow. DevSnips exposes each resource as a structured, addressable registry entry, so your agent installs the real files instead of approximating them.
devsnips://sections/faq/bento I want to use this DevSnips resource in production: Page: https://devsnips.site/library/react/sections/faq/bento/ Install: npx devsnips add React/Sections/FAQ/bento This is the FAQ — Bento — A bento FAQ composition: a heading cell, the accordion as the grid's focus cell, plus contact and good-to-know reference cells of varied weights. Use this DevSnips resource as the starting point for my project. Adapt it to my project's stack and conventions, fix any issues, and make it production-ready while preserving its intended design and behavior.
Paste into ChatGPT, Claude or your agent — it already carries the page URL and the exact install path.
Resource URI
devsnips://sections/faq/bento