Split Panel FAQ
A free split panel FAQ that becomes real accordions on mobile, from one set of markup.
Frequently asked questions
Customise
Code
<FaqSplitPanel items={faqItems} />Split Panel FAQ is a free split faq component for React and Next.js: on wide screens the questions form one scannable column beside a single persistent answer panel, and on narrow screens the very same markup reads as single-open disclosure rows. Selecting a question moves a shared-layout marker on a spring and crossfades the answer into place, while the panel footprint stays exactly where it was.
There is one set of markup and one active question for both layouts, and the breakpoint is expressed only in CSS. That is what keeps a resize honest: the component never learns how wide it is, so it cannot lose your selection when the layout flips, and no second copy of an answer is ever published to a screen reader. Mobile rows open by interpolating grid-template-rows from 0fr to 1fr rather than measuring heights, and with reduced motion enabled every swap and disclosure is instant with all content readable.
Install
Add the @te registry to your components.json once:
{
"registries": {
"@te": "https://templateempire.io/r/{name}.json"
}
}Then install the component:
npx shadcn@latest add @te/faq-split-panelUsage
import { FaqSplitPanel } from "@/components/faq-split-panel";
const items = [
{
id: "onboarding",
question: "How long does onboarding take?",
answer: "Most teams are live within a day.",
},
{
id: "refunds",
question: "Do you offer refunds?",
answer: "Every paid plan comes with a 30 day money back guarantee.",
},
{
id: "support",
question: "What support is included?",
answer: "Email support on every plan, with a shared channel above ten seats.",
},
];
export default function Page() {
return <FaqSplitPanel items={items} panelSide="right" />;
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items | ReadonlyArray<{ id: string; question: string; answer: string }> | required | Three to ten FAQ items with unique ids. Entries missing an id, question or answer are dropped, as are duplicate ids; the first occurrence of an id wins. |
| heading | string | "Frequently asked questions" | The section's visible h2, which is also its accessible name. Override it to translate the section or to match surrounding copy. |
| initialId | string | undefined | Which question is open on first render. An id that does not match a surviving item falls back to the first item. |
| panelSide | "left" | "right" | "right" | Which side the persistent answer panel sits on at wide sizes. Has no effect on the narrow layout, where every answer follows its own question. |
| indicatorStiffness | number | 360 | Spring stiffness for the active question marker, clamped to 140-560. Higher is snappier. |
| answerOffset | number | 18 | How far the answer travels as it crossfades in at wide sizes, in CSS pixels, clamped to 0-40. Zero gives a pure crossfade. |
| heightDuration | number | 0.32 | Narrow-screen open and close duration in seconds, clamped to 0-0.7. Zero makes disclosures instant. |
| radius | number | 24 | Question and answer corner radius in CSS pixels, clamped to 0-44. |
| onQuestionChange | (id: string) => void | undefined | Called after an explicit user selection. Not called on the initial render, and not called when the already-open question is clicked again. |
| className | string | undefined | Additional classes merged onto the section root with cn(). |
Customisation
Split Panel FAQ declares three tokens of its own, each falling back to a global semantic token, so it retints from an ancestor without touching the component source. Geometry and timing come from props rather than tokens, because they are clamped: --split-faq-accent colours the active marker and the focus ring, --split-faq-panel is the wide answer surface, and --split-faq-divider is the question separator.
:root {
--split-faq-accent: var(--chart-2);
--split-faq-panel: var(--muted);
--split-faq-divider: oklch(from var(--border) l c h / 0.6);
}
<FaqSplitPanel items={items} panelSide="left" radius={8} />Frequently asked questions
- Is Split Panel FAQ free for commercial use?
- Yes. Split Panel FAQ is MIT licensed, so you can use it in personal and commercial projects without attribution.
- How does it switch between the split panel and the mobile accordions?
- It does not switch trees at all. There is one set of markup, and a CSS media query changes how it is laid out: at wide sizes each question and answer pair becomes display contents so the grid can place questions in one column and stack every answer in the other, and below that breakpoint the pair is an ordinary block with the answer directly after its own question. Because the breakpoint lives only in CSS, resizing cannot lose the open question, and there is never a hidden duplicate of an answer for a screen reader to find.
- Is Split Panel FAQ accessible for keyboard and screen reader users?
- Yes. Every question is a real button inside an h3, so Enter and Space work natively and focus is always visible. Each button carries aria-expanded and aria-controls pointing at its answer, which is a region labelled by that question, and closed answers transition to visibility hidden so they leave the accessibility tree and the Tab order once they have finished closing. Selecting a question never moves focus. With reduced motion enabled the marker stops travelling and every open and close is instant, with all content readable.