Propel

Accordion

A vertically stacked set of panels that expand and collapse one at a time.

Basic

Plane is an open-source project management tool for tracking issues, sprints, and product roadmaps.

Show code
import {
  Accordion,
  AccordionHeader,
  AccordionItem,
  AccordionPanel,
  AccordionTrigger,
} from "@makeplane/propel/components/accordion";

const ITEMS = [
  {
    value: "what",
    label: "What is Plane?",
    body: "Plane is an open-source project management tool for tracking issues, sprints, and product roadmaps.",
  },
  {
    value: "pricing",
    label: "How does pricing work?",
    body: "Plane is free to self-host. Managed plans add hosting, backups, and support.",
  },
];

export default function BasicDemo() {
  return (
    <Accordion defaultValue={["what"]}>
      {ITEMS.map((item) => (
        <AccordionItem key={item.value} value={item.value}>
          <AccordionHeader>
            <AccordionTrigger label={item.label} />
          </AccordionHeader>
          <AccordionPanel>{item.body}</AccordionPanel>
        </AccordionItem>
      ))}
    </Accordion>
  );
}

With icon

Each trigger can carry an icon beside its label, matching the header icon in the design.

Plane is an open-source project management tool for tracking issues, sprints, and product roadmaps.

Show code
import {
  Accordion,
  AccordionHeader,
  AccordionItem,
  AccordionPanel,
  AccordionTrigger,
} from "@makeplane/propel/components/accordion";
import { Icon } from "@makeplane/propel/components/icon";
import { CircleHelp } from "lucide-react";

const ITEMS = [
  {
    value: "what",
    label: "What is Plane?",
    body: "Plane is an open-source project management tool for tracking issues, sprints, and product roadmaps.",
  },
  {
    value: "pricing",
    label: "How does pricing work?",
    body: "Plane is free to self-host. Managed plans add hosting, backups, and support.",
  },
  {
    value: "import",
    label: "Can I import my existing data?",
    body: "Yes — Plane can import work items from common trackers so you can migrate without losing history.",
  },
];

export default function WithIconDemo() {
  return (
    <Accordion defaultValue={["what"]}>
      {ITEMS.map((item) => (
        <AccordionItem key={item.value} value={item.value}>
          <AccordionHeader>
            <AccordionTrigger
              icon={<Icon icon={CircleHelp} tint="secondary" />}
              label={item.label}
            />
          </AccordionHeader>
          <AccordionPanel>{item.body}</AccordionPanel>
        </AccordionItem>
      ))}
    </Accordion>
  );
}

Multiple

With multiple, several panels can stay open at the same time instead of collapsing one another.

Plane is an open-source project management tool for tracking issues, sprints, and product roadmaps.

Plane is free to self-host. Managed plans add hosting, backups, and support.

Show code
import {
  Accordion,
  AccordionHeader,
  AccordionItem,
  AccordionPanel,
  AccordionTrigger,
} from "@makeplane/propel/components/accordion";

const ITEMS = [
  {
    value: "what",
    label: "What is Plane?",
    body: "Plane is an open-source project management tool for tracking issues, sprints, and product roadmaps.",
  },
  {
    value: "pricing",
    label: "How does pricing work?",
    body: "Plane is free to self-host. Managed plans add hosting, backups, and support.",
  },
  {
    value: "import",
    label: "Can I import my existing data?",
    body: "Yes — Plane can import work items from common trackers so you can migrate without losing history.",
  },
];

export default function MultipleDemo() {
  return (
    <Accordion multiple defaultValue={["what", "pricing"]}>
      {ITEMS.map((item) => (
        <AccordionItem key={item.value} value={item.value}>
          <AccordionHeader>
            <AccordionTrigger label={item.label} />
          </AccordionHeader>
          <AccordionPanel>{item.body}</AccordionPanel>
        </AccordionItem>
      ))}
    </Accordion>
  );
}

Disabled

A single item can opt out with disabled; its trigger dims and ignores pointer and keyboard toggling while siblings stay interactive.

Plane is an open-source project management tool for tracking issues, sprints, and product roadmaps.

Show code
import {
  Accordion,
  AccordionHeader,
  AccordionItem,
  AccordionPanel,
  AccordionTrigger,
} from "@makeplane/propel/components/accordion";

const ITEMS = [
  {
    value: "what",
    label: "What is Plane?",
    body: "Plane is an open-source project management tool for tracking issues, sprints, and product roadmaps.",
  },
  {
    value: "pricing",
    label: "How does pricing work?",
    body: "Plane is free to self-host. Managed plans add hosting, backups, and support.",
  },
  {
    value: "import",
    label: "Can I import my existing data?",
    body: "Yes — Plane can import work items from common trackers so you can migrate without losing history.",
  },
];

export default function DisabledDemo() {
  return (
    <Accordion defaultValue={["what"]}>
      {ITEMS.map((item) => (
        <AccordionItem key={item.value} value={item.value} disabled={item.value === "pricing"}>
          <AccordionHeader>
            <AccordionTrigger label={item.label} />
          </AccordionHeader>
          <AccordionPanel>{item.body}</AccordionPanel>
        </AccordionItem>
      ))}
    </Accordion>
  );
}

Controlled

Pass value + onValueChange to own the open set. External controls mutate that state directly, and trigger clicks flow back through onValueChange.

Plane is an open-source project management tool for tracking issues, sprints, and product roadmaps.

Show code
import {
  Accordion,
  AccordionHeader,
  AccordionItem,
  AccordionPanel,
  AccordionTrigger,
} from "@makeplane/propel/components/accordion";
import { Button } from "@makeplane/propel/components/button";
import * as React from "react";

const ITEMS = [
  {
    value: "what",
    label: "What is Plane?",
    body: "Plane is an open-source project management tool for tracking issues, sprints, and product roadmaps.",
  },
  {
    value: "pricing",
    label: "How does pricing work?",
    body: "Plane is free to self-host. Managed plans add hosting, backups, and support.",
  },
  {
    value: "import",
    label: "Can I import my existing data?",
    body: "Yes — Plane can import work items from common trackers so you can migrate without losing history.",
  },
];

export default function ControlledDemo() {
  const [value, setValue] = React.useState<string[]>(["what"]);
  return (
    <div className="flex flex-col gap-3">
      <div className="flex flex-wrap items-center gap-2">
        <Button
          prominence="secondary"
          tone="neutral"
          magnitude="sm"
          sizing="hug"
          label="Expand all"
          onClick={() => setValue(ITEMS.map((item) => item.value))}
        />
        <Button
          prominence="secondary"
          tone="neutral"
          magnitude="sm"
          sizing="hug"
          label="Collapse all"
          onClick={() => setValue([])}
        />
      </div>
      <Accordion multiple value={value} onValueChange={(next) => setValue(next as string[])}>
        {ITEMS.map((item) => (
          <AccordionItem key={item.value} value={item.value}>
            <AccordionHeader>
              <AccordionTrigger label={item.label} />
            </AccordionHeader>
            <AccordionPanel>{item.body}</AccordionPanel>
          </AccordionItem>
        ))}
      </Accordion>
    </div>
  );
}

Installation

import {
  Accordion,
  AccordionItem,
  AccordionHeader,
  AccordionTrigger,
  AccordionPanel,
} from "@makeplane/propel/components/accordion";

Props

Accordion

PropTypeRequiredDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State<Value>>NoAllows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.

AccordionTrigger

PropTypeRequiredDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, AccordionTriggerState>NoAllows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.
iconReactNodeNoElement rendered before the label (inline-start), e.g. `<Icon icon={Folder} tint="secondary" />`.
labelstringYesVisible trigger label.