COMPONENT / classified-accordion
Classified FAQ Accordion_
Framed FAQ accordion with mono question rows, signal ids and +/− marks. One item open at a time by default. Built on Base UI. Requires @cybr/tokens.
Install
1. Map the
@cybrnamespace, once per project. Add this inside yourcomponents.json, or into its existing"registries"object. Nocomponents.jsonyet? Runnpx shadcn@latest initfirst to create one."registries": { "@cybr": "https://cybr-ui.com/r/{name}.json" }2. Add components:
$ npx shadcn@latest add @cybr/classified-accordion
If shadcn reports Unknown registry "@cybr", step 1 is missing from that project's components.json.
Preview
Run npx shadcn add @cybr/<name>. The source lands in your project, so you own and edit it.
"use client";
import { Accordion } from "@base-ui/react/accordion";
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
const headings = {
h2: <h2 />,
h3: <h3 />,
h4: <h4 />,
};
// FAQ accordion on Base UI. One item open at a time by default (pass `multiple`
// to allow several), and an open item can be closed again. Panels open
// instantly, with no height animation.
export function ClassifiedAccordion({
className,
...props
}: Omit<Accordion.Root.Props, "className"> & { className?: string }) {
return <Accordion.Root className={cn("border-(length:--border-base) border-line", className)} {...props} />;
}
export function ClassifiedAccordionItem({
id,
question,
headingAs = "h3",
children,
className,
...props
}: Omit<Accordion.Item.Props, "className" | "children" | "id"> & {
value: string;
id?: ReactNode;
question: ReactNode;
headingAs?: "h2" | "h3" | "h4";
children: ReactNode;
className?: string;
}) {
return (
<Accordion.Item
className={cn("border-b-(length:--border-hair) border-line last:border-b-0", className)}
{...props}
>
<Accordion.Header render={headings[headingAs]} className="m-0">
<Accordion.Trigger className="group flex min-h-13 w-full cursor-pointer items-center justify-between gap-4 bg-transparent px-4.5 text-left font-mono text-[0.8125rem] font-bold tracking-button text-ink hover:bg-surface focus-visible:outline-(length:--focus-width) focus-visible:outline-offset-[-3px] focus-visible:outline-signal">
<span>
{id ? (
<>
<span className="text-signal group-hover:text-ink">{id}</span>{" "}
</>
) : null}
{question}
</span>
<span aria-hidden="true" className="text-lg leading-none">
<span className="group-data-panel-open:hidden">+</span>
<span className="hidden group-data-panel-open:inline">−</span>
</span>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel className="border-t-(length:--border-hair) border-dashed border-line bg-ground-deep px-4.5 pt-0.5 pb-3.5 text-xs [line-height:1.7] text-label">
{children}
</Accordion.Panel>
</Accordion.Item>
);
}
Repo source. The shadcn CLI rewrites import aliases to match your project.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| defaultValue / value | string[] | — | Values of the open items, uncontrolled or controlled. All items start closed by default. |
| onValueChange | (value: string[]) => void | — | Called when an item opens or closes. |
| multiple | boolean | false | Let more than one item be open at a time. |
| ...props | Accordion.Root props | — | Forwarded to Base UI's Accordion.Root; className is merged last with cn(). |
| Item value | string | — | Unique value for the item. |
| Item id | ReactNode | — | Signal-coloured tag before the question, such as F-01. |
| Item question | ReactNode | — | The trigger text. |
| Item headingAs | "h2" | "h3" | "h4" | "h3" | Heading level wrapping the trigger. Match your page outline. |
| Item children | ReactNode | — | The answer, shown in the panel. |
| Item ...props | Accordion.Item props | — | Forwarded to Base UI's Accordion.Item, such as disabled; className is merged last with cn(). |
Usage
Installs @base-ui/react. Needs the stylesheet from @cybr/tokens. After the install, import it in app/globals.css:
@import "tailwindcss";
@import "../styles/cybr-tokens.css";
import { ClassifiedAccordion, ClassifiedAccordionItem } from "@/components/ui/classified-accordion";
export function Faq() {
return (
<ClassifiedAccordion>
<ClassifiedAccordionItem value="install" id="F-01" question="HOW DO I INSTALL ONE?">
Run npx shadcn add @cybr/<name>.
</ClassifiedAccordionItem>
</ClassifiedAccordion>
);
}
States
- Closed: the question row with a
+mark. - Hover: the row fills with
--surfaceand the id switches from--signalto--ink, because small--signaltext does not pass contrast on--surface. - Focus-visible: a 3px solid
--signaloutline inset inside the row. - Open: the mark becomes
−and the answer shows on--ground-deepunder a dashed rule. Opening is instant, with no height animation. - One at a time: opening an item closes the others, and clicking an open item closes it. Pass
multipleto change this. The artboard opens F-01; the component starts with all items closed, so this demo passesdefaultValue.
Accessibility
- Built on Base UI's Accordion. Each trigger is a native
<button>witharia-expandedandaria-controls, inside a heading (h3by default; setheadingAs). - Enter and Space toggle an item. Tab moves between triggers.
- The
+and−marks are decorative; the open state comes fromaria-expanded. - Question text is
--inkon--bg(14.6:1). Answers are--labelon--ground-deep, darker than--bg, so at least 7.43:1. If you override colours withclassName, keep text contrast at 4.5:1 or above. - Nothing animates.