Design system
GitHub

Component

Accordion

Expandable questions or sections with keyboard navigation.

npx -y github:anwarlemu/agentcard-brand-cli#release add accordion
accordion.preview

It lets AI agents pay at real checkouts. You fund a card, set what it can buy, and your agent uses it like a person would.

Preview
accordion.in-window
faq.help / Agentcard

Questions, answered

It lets AI agents pay at real checkouts. You fund a card, set what it can buy, and your agent uses it like a person would.

In window

Usage

use-it-for.md

Use for FAQs or details that can stay collapsed. The InWindow example adds topic tabs and a containing window.

never.md
  • Chevrons or typed plus signs
  • Bold questions
  • Cards around each question
  • More than one list open by default

Dependencies: @radix-ui/react-accordion.

Example

examples/accordion.tsx
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Window } from "@/components/ui/window";

const GENERAL = [
  { q: "What does Agentcard do?", a: "It lets AI agents pay at real checkouts. You fund a card, set what it can buy, and your agent uses it like a person would." },
  { q: "Do I need to write code?", a: "No. Connect your agent with the command line tool or the API, or share a card with an agent you already use." },
  { q: "Is my card number shared with the agent?", a: "Never. The agent gets a token that only works within the limits you set, and you can pause it at any time." },
  { q: "What does it cost?", a: "Starting is free. You pay per card and per purchase as your agents buy more; see the pricing page for details." },
];

function Faq({ items }: { items: { q: string; a: string }[] }) {
  return (
    <Accordion type="single" collapsible defaultValue="item-0" className="w-full max-w-170">
      {items.map((item, i) => (
        <AccordionItem key={item.q} value={`item-${i}`}>
          <AccordionTrigger>{item.q}</AccordionTrigger>
          <AccordionContent>{item.a}</AccordionContent>
        </AccordionItem>
      ))}
    </Accordion>
  );
}

export function Site() {
  return <Faq items={GENERAL} />;
}

export function InWindow() {
  return (
    <Window name="faq.help / Agentcard" className="mx-auto max-w-198" bodyClassName="flex flex-col items-center px-4 pt-10 pb-12 md:px-14">
      <h2 className="text-h2 leading-heading tracking-tighter">Questions, answered</h2>
      <Tabs defaultValue="general" className="mt-9 w-full max-w-170 items-center gap-9">
        <TabsList className="w-full">
          <TabsTrigger value="general">General</TabsTrigger>
          <TabsTrigger value="vault">Vault</TabsTrigger>
          <TabsTrigger value="issuing">Issuing</TabsTrigger>
        </TabsList>
        <TabsContent value="general" className="w-full">
          <Faq items={GENERAL} />
        </TabsContent>
        <TabsContent value="vault" className="w-full">
          <Faq items={GENERAL.slice(2)} />
        </TabsContent>
        <TabsContent value="issuing" className="w-full">
          <Faq items={GENERAL.slice(1, 3)} />
        </TabsContent>
      </Tabs>
    </Window>
  );
}

Source

components/ui/accordion.tsx
"use client";

import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";

import { cn } from "@/lib/utils";

// The brand's FAQ list (agentcard.sh /, .faq-list under .v7-page, measured as rendered): a 14 percent ink rule above
// the first question and a 20 percent one under each; the question in the 14px display face at 500 (tracked -0.01em,
// 1.2 leading, 20px by 14px of padding, 6px corners), blue and on a faint wash under the pointer (the wash eases in
// 160ms, the color does not); a drawn + (a bar across and a bar down in a 30px box, not a glyph) that turns an eighth
// into an x when open (220ms ease-out). Open, the question and its mark stay blue. The answer is 14px Inter at 1.8
// leading, 68 percent ink at 80 percent opacity, 540px wide; it slides open in 200ms ease-out (and shut in 200ms) and
// fades down 4px into place (240ms, 40ms late). Reduced motion opens it at once.
function Accordion(props: React.ComponentProps<typeof AccordionPrimitive.Root>) {
  return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
}

function AccordionItem({ className, ...props }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
  return <AccordionPrimitive.Item data-slot="accordion-item" className={cn("border-b border-b-line-mid first:border-t first:border-t-line", className)} {...props} />;
}

/** The FAQ's mark: a 12px plus drawn in two 1.5px bars, in a 30px box so a column of them lines up. */
function AccordionMark({ className }: { className?: string }) {
  return (
    <span aria-hidden data-slot="accordion-mark" className={cn("grid h-3.5 w-7.5 shrink-0 place-items-center self-center text-fg-faint transition-[rotate,color] duration-220 ease-out motion-reduce:transition-none", className)}>
      <svg viewBox="0 0 12 12" className="size-3 fill-none stroke-current [stroke-width:1.5]">
        <path d="M0 6h12M6 0v12" />
      </svg>
    </span>
  );
}

function AccordionTrigger({ className, children, icon, ...props }: React.ComponentProps<typeof AccordionPrimitive.Trigger> & { icon?: React.ReactNode | null }) {
  return (
    <AccordionPrimitive.Header className="flex">
      <AccordionPrimitive.Trigger
        data-slot="accordion-trigger"
        className={cn(
          "group flex flex-1 cursor-pointer items-center justify-between gap-3.75 rounded-sm px-3.5 py-5 text-left font-display text-body-sm font-medium [letter-spacing:-0.01em] [line-height:1.2] text-fg transition-[background-color] duration-160 ease-out outline-none hover:bg-wash-hover hover:text-fg-brand focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus-ring disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-fg-brand motion-reduce:transition-none",
          className,
        )}
        {...props}
      >
        {children}
        {icon === undefined ? <AccordionMark className="group-hover:text-fg-brand group-data-[state=open]:rotate-45 group-data-[state=open]:text-fg-brand" /> : icon}
      </AccordionPrimitive.Trigger>
    </AccordionPrimitive.Header>
  );
}

function AccordionContent({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
  return (
    <AccordionPrimitive.Content
      data-slot="accordion-content"
      className="overflow-hidden text-body-sm [line-height:1.8] text-fg-muted data-[state=closed]:animate-accordion-up data-[state=closed]:[animation-duration:var(--ds-duration-base)] data-[state=open]:animate-accordion-down motion-reduce:animate-none"
      {...props}
    >
      <div className={cn("max-w-135 px-3.5 pb-5.5 opacity-80 transition-[opacity,translate] delay-40 duration-240 ease-out starting:-translate-y-1 starting:opacity-0 motion-reduce:transition-none", className)}>{children}</div>
    </AccordionPrimitive.Content>
  );
}

export { Accordion, AccordionContent, AccordionItem, AccordionMark, AccordionTrigger };

Reference: agentcard-website src/app/globals.css (.faq-list, .v7-page summary, .v7-page details > p); live https://www.agentcard.sh/ (#faq .faq-list, computed).