Skip to content
Template Empire
Browse components

Split Panel FAQ

A free split panel FAQ that becomes real accordions on mobile, from one set of markup.

Frequently asked questions

Copy them into a commercial project, change anything you like and ship it. The source is yours once it is in your repository, so there is no runtime dependency on us and no attribution requirement.

Each component is a shadcn registry item. Running the CLI writes the source and any shared helpers straight into your app, alongside your own code rather than inside node_modules, so you can read and edit every line.

Motion is loaded lazily and only the features a component actually uses are pulled in. Transforms and opacity do the visual work, so the browser can composite them off the main thread and the section stays responsive while it animates.

Every component honours the operating system setting. Travel and springs are dropped, transitions become instant and nothing is hidden behind an animation that never plays, so the full content is still readable and operable.

Re-run the CLI for that component and diff the result against your copy. Because the code lives in your repository you decide what to take, and local changes are never overwritten without you seeing them.

Customise

360
18px
0.32s
24px

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-panel

Usage

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

PropTypeDefaultDescription
itemsReadonlyArray<{ id: string; question: string; answer: string }>requiredThree 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.
headingstring"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.
initialIdstringundefinedWhich 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.
indicatorStiffnessnumber360Spring stiffness for the active question marker, clamped to 140-560. Higher is snappier.
answerOffsetnumber18How far the answer travels as it crossfades in at wide sizes, in CSS pixels, clamped to 0-40. Zero gives a pure crossfade.
heightDurationnumber0.32Narrow-screen open and close duration in seconds, clamped to 0-0.7. Zero makes disclosures instant.
radiusnumber24Question and answer corner radius in CSS pixels, clamped to 0-44.
onQuestionChange(id: string) => voidundefinedCalled after an explicit user selection. Not called on the initial render, and not called when the already-open question is clicked again.
classNamestringundefinedAdditional 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.

Related components

Want the full page this belongs to?Template Empire builds complete Next.js templates with this motion system throughout. Browse the paid templates that pair with these free components.Explore Template Empire templates