Skip to content

Faq Search

Help-center FAQ: large glass search pill filtering a frosted Radix accordion live, with category tags, result count and highlighted matches; empty state.

glass/section/faq-search
Open ↗

Source

"use client";

import { useId, useState } from "react";
import { Accordion } from "radix-ui";
import { Plus, Search } from "lucide-react";
import { cn } from "@/lib/utils";

export interface FaqSearchProps {
  eyebrow?: string;
  title?: string;
  items?: { question: string; answer: string; tag: string }[];
  className?: string;
}

const GLASS = "da-stroke bg-da-surface backdrop-blur-da backdrop-saturate-150";

const DEFAULT_ITEMS = [
  {
    tag: "Recording",
    question: "How do I stop Halo from joining a meeting?",
    answer: "Remove it from the invite, or toggle auto-join off per calendar in Settings → Recording.",
  },
  { tag: "Recording", question: "Can participants opt out?", answer: "Yes. Anyone can type “halo stop” in chat to pause recording; the recap marks the gap." },
  { tag: "Privacy", question: "Where is my data stored?", answer: "US by default; EU (Frankfurt) on request. Encrypted with AES-256 at rest." },
  {
    tag: "Privacy",
    question: "How long are recordings kept?",
    answer: "90 days by default, configurable from 7 days to forever — transcripts can outlive videos.",
  },
  { tag: "Billing", question: "Do guests need a seat?", answer: "No. Only members who record meetings are billed; viewers and guests are free." },
  {
    tag: "Billing",
    question: "Can I switch between monthly and yearly?",
    answer: "Any time. Switching to yearly applies a 20% discount pro-rated to your next invoice.",
  },
];

/** Glass FAQ with a live search field filtering a frosted accordion (Radix) and category counts; matching text is highlighted. */
export function FaqSearch({ eyebrow = "Help center", title = "How can we help?", items = DEFAULT_ITEMS, className }: FaqSearchProps) {
  const [q, setQ] = useState("");
  const id = useId();
  const query = q.trim().toLowerCase();
  const shown = items.filter((i) => `${i.question} ${i.answer} ${i.tag}`.toLowerCase().includes(query));

  const mark = (text: string) => {
    if (!query) return text;
    const at = text.toLowerCase().indexOf(query);
    if (at < 0) return text;
    return (
      <>
        {text.slice(0, at)}
        <mark className="rounded-[4px] bg-da-primary/20 text-inherit">{text.slice(at, at + query.length)}</mark>
        {text.slice(at + query.length)}
      </>
    );
  };

  return (
    <section aria-labelledby={`${id}-t`} className={cn("da-section text-da-fg", className)}>
      <div className="da-container">
        <div className="mx-auto max-w-2xl text-center">
          <p className="text-sm font-semibold text-da-primary">{eyebrow}</p>
          <h2 id={`${id}-t`} className="mt-3 font-da-display text-[clamp(2rem,4.5vw,3.25rem)] leading-[1.08] font-semibold tracking-da-display">
            {title}
          </h2>
          <div className={cn("relative mt-8 rounded-da-pill shadow-da-md", GLASS)}>
            <Search aria-hidden className="pointer-events-none absolute top-1/2 left-5 size-5 -translate-y-1/2 text-da-muted-fg" />
            <label htmlFor={`${id}-q`} className="sr-only">
              Search questions
            </label>
            <input
              id={`${id}-q`}
              type="search"
              value={q}
              onChange={(e) => setQ(e.target.value)}
              placeholder="Search “recording”, “billing”, “privacy”…"
              className="da-focus h-14 w-full rounded-da-pill bg-transparent pr-5 pl-14 text-[15px] outline-none placeholder:text-da-muted-fg"
            />
          </div>
          <p aria-live="polite" className="mt-3 text-sm text-da-muted-fg">
            {query ? `${shown.length} result${shown.length === 1 ? "" : "s"}` : `${items.length} questions`}
          </p>
        </div>
        <Accordion.Root type="single" collapsible className="mx-auto mt-8 grid max-w-2xl gap-3">
          {shown.map((it) => (
            <Accordion.Item key={it.question} value={it.question} className={cn("rounded-da-lg shadow-da-sm", GLASS)}>
              <Accordion.Header>
                <Accordion.Trigger className="da-focus group flex w-full items-center gap-3 rounded-da-lg px-5 py-4 text-left font-medium">
                  <span className="rounded-da-pill bg-da-surface-2 px-2 py-0.5 text-[11px] font-semibold text-da-muted-fg">{it.tag}</span>
                  <span className="flex-1">{mark(it.question)}</span>
                  <Plus aria-hidden className="da-transition size-4 shrink-0 group-data-[state=open]:rotate-45" />
                </Accordion.Trigger>
              </Accordion.Header>
              <Accordion.Content className="overflow-hidden text-[15px] text-da-muted-fg data-[state=closed]:hidden">
                <p className="px-5 pb-5">{mark(it.answer)}</p>
              </Accordion.Content>
            </Accordion.Item>
          ))}
          {shown.length === 0 && (
            <p className={cn("rounded-da-lg p-6 text-center text-da-muted-fg", GLASS)}>No answer for “{q}” yet — try another word or contact support.</p>
          )}
        </Accordion.Root>
      </div>
    </section>
  );
}

export default FaqSearch;

modules/glass/section/faq-search/index.tsx

Props

PropTypeDefaultDescription
eyebrow / titlestring—Header.
items{ question, answer, tag }[]—Q&As.
classNamestring—Classes.

Other faq variants in Glass

FAQ in other art directions

FAQ Accordion

Two-column FAQ: sticky title + support CTA on the left, numbered accordion cards on the right with a yellow plus that rotates into an ink cross when open. Built on Radix Accordion.

BrutalistFAQ

FAQ Categories

Help-center style FAQ: topic buttons with counts on the left (stacked on desktop, wrapped on mobile), and a ruled accordion on the right whose open question floods yellow.

BrutalistFAQ

FAQ Grid

Always-open FAQ grid: ruled cells (1/2/3 columns) with a yellow question-mark square, uppercase question and answer, closed by a full-width ink contact row. No interaction needed.

BrutalistFAQ

FAQ Search

Searchable FAQ: a huge ruled search field that presses in on focus, live result count, and native <details> cards with tags; a pink empty state points to support.

BrutalistFAQ

Faq Grid

All answers visible at once: a three-column grid of short questions and answers under a header, followed by two support link cards (documentation, talk to us).

MinimalFAQ

Faq List

Centered single-column FAQ built on native details/summary (zero JS): hairline dividers, a plus icon that rotates into a cross when open, and a contact line underneath.

MinimalFAQ

FAQ Split

Two-column FAQ: title, description and support link on the left, hairline-divided Radix accordion on the right with a rotating chevron and a smooth height + fade expand.

MinimalFAQ

Faq Tabs

FAQ grouped by category behind an underline tab bar (Radix Tabs, with counts); the active category shows every question and answer in a two-column definition list.

MinimalFAQ

Faq Inline

Ultra-compact FAQ: short questions and one-line answers flowing as a 3-column grid of mono question labels over large answers — scannable in seconds.

Mono CleanFAQ

Faq List

Numbered accordion FAQ in a ruled list: mono index + question, a +/− glyph that swaps, answer indented under the question; left column holds the label and title.

Mono CleanFAQ

Faq Split

Always-open FAQ as a two-column definition list (question left in medium weight, answer right in muted text) on hairline rows, followed by a mono contact line.

Mono CleanFAQ

Faq Tabs

FAQ grouped by topic with text tabs (Radix Tabs) underlined in ink when active, and each topic’s answers as a ruled definition list.

Mono CleanFAQ

Faq Accordion

Two-column FAQ: heading + description + contact link on the left, bordered Radix accordion with plus icons rotating to × on the right.

Neo CorporateFAQ

Faq Security

Security & compliance FAQ: dark-tinted certification cards (SOC 2, ISO 27001, GDPR, PCI) with shield icons, then security questions as native <details> disclosure rows.

Neo CorporateFAQ

Faq Sidebar

Help-center style FAQ: a vertical category nav (left, becomes horizontal scroll chips on mobile) and the selected category’s questions as an open definition list.

Neo CorporateFAQ

Faq Support

Compact 2×3 grid of short Q&As followed by three bordered support channel cards (docs, chat, dedicated support).

Neo CorporateFAQ

Faq Columns

Editorial FAQ: serif title and intro in a narrow left column, questions as a two-column definition list on the right separated by warm hairlines.

Organic SoftFAQ

Faq Contact

FAQ beside a contact card: native disclosure list on the left, and a clay-tinted card with a sprouting-leaf illustration, email and chat buttons on the right.

Organic SoftFAQ

Faq Search

Searchable FAQ: rounded search field plus topic pills filter a list of questions (native details), with a friendly empty state and live result count.

Organic SoftFAQ

Faq Soft

Soft accordion: each question is its own rounded paper pill-card that turns sage when open, with a round plus that rotates into a cross; generous spacing.

Organic SoftFAQ

Faq Columns

All-visible FAQ: sticky intro with a peach “Chat with us” pill on the left, six Q&As in a 2-column grid with round lavender number badges on the right.

Soft FlatFAQ

Faq Help

Help-center FAQ: lavender panel with a friendly greeting, big white search pill and popular-search chips; matching answers show as white cards with a peach empty state.

Soft FlatFAQ

Faq Pills

Single-column FAQ of rounded white blocks on native details/summary; the open item turns lavender and its round chevron button flips and fills periwinkle.

Soft FlatFAQ

Faq Tinted

FAQ by topic: three big pastel topic buttons (emoji + label) switch the list of white Q&A cards below, which fades in on change.

Soft FlatFAQ