Accordion
Stacked headings that each open a section; one at a time by default, multiple for many.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion";
export default function Demo() { return ( <Accordion defaultValue={["index"]} className="max-w-md"> <AccordionItem value="index"> <AccordionTrigger>What is a tokenized index?</AccordionTrigger> <AccordionContent> A token that tracks a basket of xStocks. One join buys every token in the basket at its weight. </AccordionContent> </AccordionItem> <AccordionItem value="rebalance"> <AccordionTrigger>How is it rebalanced?</AccordionTrigger> <AccordionContent>Weekly, by the index agent.</AccordionContent> </AccordionItem> <AccordionItem value="cost"> <AccordionTrigger>What does it cost?</AccordionTrigger> <AccordionContent>A 0.4% annual management fee.</AccordionContent> </AccordionItem> </Accordion> );}<script setup lang="ts">import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion";</script>
<template> <Accordion type="single" collapsible default-value="index" class="max-w-md"> <AccordionItem value="index"> <AccordionTrigger>What is a tokenized index?</AccordionTrigger> <AccordionContent> A token that tracks a basket of xStocks. One join buys every token in the basket at its weight. </AccordionContent> </AccordionItem> <AccordionItem value="rebalance"> <AccordionTrigger>How is it rebalanced?</AccordionTrigger> <AccordionContent>Weekly, by the index agent.</AccordionContent> </AccordionItem> <AccordionItem value="cost"> <AccordionTrigger>What does it cost?</AccordionTrigger> <AccordionContent>A 0.4% annual management fee.</AccordionContent> </AccordionItem> </Accordion></template><script lang="ts"> import * as Accordion from "$lib/components/ui/accordion";</script>
<Accordion.Root type="single" value="index" class="max-w-md"> <Accordion.Item value="index"> <Accordion.Trigger>What is a tokenized index?</Accordion.Trigger> <Accordion.Content> A token that tracks a basket of xStocks. One join buys every token in the basket at its weight. </Accordion.Content> </Accordion.Item> <Accordion.Item value="rebalance"> <Accordion.Trigger>How is it rebalanced?</Accordion.Trigger> <Accordion.Content>Weekly, by the index agent.</Accordion.Content> </Accordion.Item> <Accordion.Item value="cost"> <Accordion.Trigger>What does it cost?</Accordion.Trigger> <Accordion.Content>A 0.4% annual management fee.</Accordion.Content> </Accordion.Item></Accordion.Root>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/accordionpnpm dlx shadcn@latest add @edmi-ui/accordionyarn dlx shadcn@latest add @edmi-ui/accordionbunx shadcn@latest add @edmi-ui/accordionPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @base-ui/react class-variance-authorityTerminal window pnpm add @base-ui/react class-variance-authorityTerminal window yarn add @base-ui/react class-variance-authorityTerminal window bun add @base-ui/react class-variance-authorityCopy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/accordionpnpm dlx shadcn-vue@latest add @edmi-ui/accordionyarn dlx shadcn-vue@latest add @edmi-ui/accordionbunx shadcn-vue@latest add @edmi-ui/accordionPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install reka-ui @vueuse/core class-variance-authorityTerminal window pnpm add reka-ui @vueuse/core class-variance-authorityTerminal window yarn add reka-ui @vueuse/core class-variance-authorityTerminal window bun add reka-ui @vueuse/core class-variance-authorityCopy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/accordion.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/accordion.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/accordion.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/accordion.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Copy the code from Component source into your project and update the import paths to match your aliases.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion";
<Accordion defaultValue={["index"]} className="max-w-md"> <AccordionItem value="index"> <AccordionTrigger>What is a tokenized index?</AccordionTrigger> <AccordionContent> A token that tracks a basket of xStocks. One join buys every token in the basket at its weight. </AccordionContent> </AccordionItem> <AccordionItem value="rebalance"> <AccordionTrigger>How is it rebalanced?</AccordionTrigger> <AccordionContent>Weekly, by the index agent.</AccordionContent> </AccordionItem> <AccordionItem value="cost"> <AccordionTrigger>What does it cost?</AccordionTrigger> <AccordionContent>A 0.4% annual management fee.</AccordionContent> </AccordionItem></Accordion><script setup lang="ts">import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion";</script>
<template> <Accordion type="single" collapsible default-value="index" class="max-w-md"> <AccordionItem value="index"> <AccordionTrigger>What is a tokenized index?</AccordionTrigger> <AccordionContent> A token that tracks a basket of xStocks. One join buys every token in the basket at its weight. </AccordionContent> </AccordionItem> <AccordionItem value="rebalance"> <AccordionTrigger>How is it rebalanced?</AccordionTrigger> <AccordionContent>Weekly, by the index agent.</AccordionContent> </AccordionItem> <AccordionItem value="cost"> <AccordionTrigger>What does it cost?</AccordionTrigger> <AccordionContent>A 0.4% annual management fee.</AccordionContent> </AccordionItem> </Accordion></template><script lang="ts"> import * as Accordion from "$lib/components/ui/accordion";</script>
<Accordion.Root type="single" value="index" class="max-w-md"> <Accordion.Item value="index"> <Accordion.Trigger>What is a tokenized index?</Accordion.Trigger> <Accordion.Content> A token that tracks a basket of xStocks. One join buys every token in the basket at its weight. </Accordion.Content> </Accordion.Item> <Accordion.Item value="rebalance"> <Accordion.Trigger>How is it rebalanced?</Accordion.Trigger> <Accordion.Content>Weekly, by the index agent.</Accordion.Content> </Accordion.Item> <Accordion.Item value="cost"> <Accordion.Trigger>What does it cost?</Accordion.Trigger> <Accordion.Content>A 0.4% annual management fee.</Accordion.Content> </Accordion.Item></Accordion.Root>Component source
Section titled “Component source”import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";import { cva, type VariantProps } from "class-variance-authority";import { cn } from "cn";import { CaretDownIcon } from "@phosphor-icons/react";
// `multiple` and array `defaultValue` come straight from Base UI (DESIGN §5).// ✦ `variant="card"`: items live inside a raised card with hairline dividers.const accordionVariants = cva("flex w-full flex-col", { variants: { variant: { default: "", card: "rounded-xl border border-border bg-card px-4", }, }, defaultVariants: { variant: "default" },});
function Accordion({ className, variant = "default", ...props}: AccordionPrimitive.Root.Props & VariantProps<typeof accordionVariants>) { return ( <AccordionPrimitive.Root data-slot="accordion" data-variant={variant} className={cn(accordionVariants({ variant }), className)} {...props} /> );}
function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) { return ( <AccordionPrimitive.Item data-slot="accordion-item" className={cn("border-b border-border last:border-b-0", className)} {...props} /> );}
function AccordionTrigger({ className, children, ...props}: AccordionPrimitive.Trigger.Props) { return ( <AccordionPrimitive.Header className="flex"> <AccordionPrimitive.Trigger data-slot="accordion-trigger" className={cn( "group/accordion-trigger relative flex flex-1 items-center justify-between gap-4 py-3.5 text-left text-[14px] font-medium outline-none transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className, )} {...props} > {children} <CaretDownIcon data-slot="accordion-trigger-icon" className="pointer-events-none size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-data-[panel-open]/accordion-trigger:rotate-180" /> </AccordionPrimitive.Trigger> </AccordionPrimitive.Header> );}
function AccordionContent({ className, children, ...props}: AccordionPrimitive.Panel.Props) { return ( <AccordionPrimitive.Panel data-slot="accordion-content" className="h-(--accordion-panel-height) overflow-hidden text-[13.5px] leading-relaxed text-muted-foreground transition-[height] duration-200 ease-out data-[ending-style]:h-0 data-[starting-style]:h-0" {...props} > <div className={cn( "pb-4 [&_a]:text-foreground [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4", className, )} > {children} </div> </AccordionPrimitive.Panel> );}
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, accordionVariants,};<script setup lang="ts">import type { AccordionRootEmits, AccordionRootProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import type { AccordionVariants } from '.'import { reactiveOmit } from '@vueuse/core'import { AccordionRoot, useForwardPropsEmits } from 'reka-ui'import { cn } from '@/registry/edmi/lib/utils'import { accordionVariants } from '.'
// Reka needs `type="single" | "multiple"` (React: `multiple` prop). ✦ `variant="card"` raises the list into a card.const props = withDefaults( defineProps<AccordionRootProps & { class?: HTMLAttributes['class'], variant?: AccordionVariants['variant'] }>(), { variant: 'default' },)const emits = defineEmits<AccordionRootEmits>()
const delegatedProps = reactiveOmit(props, 'class', 'variant')const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>
<template> <AccordionRoot v-slot="slotProps" data-slot="accordion" :data-variant="variant" v-bind="forwarded" :class="cn(accordionVariants({ variant }), props.class)" > <slot v-bind="slotProps" /> </AccordionRoot></template><script setup lang="ts">import type { AccordionContentProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import { reactiveOmit } from '@vueuse/core'import { AccordionContent } from 'reka-ui'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')</script>
<template> <AccordionContent data-slot="accordion-content" v-bind="delegatedProps" class="overflow-hidden text-[13.5px] leading-relaxed text-muted-foreground data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" > <div :class="cn('pb-4 [&_a]:text-foreground [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4', props.class)" > <slot /> </div> </AccordionContent></template><script setup lang="ts">import type { AccordionItemProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import { reactiveOmit } from '@vueuse/core'import { AccordionItem, useForwardProps } from 'reka-ui'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)</script>
<template> <AccordionItem v-slot="slotProps" data-slot="accordion-item" v-bind="forwardedProps" :class="cn('border-b border-border last:border-b-0', props.class)" > <slot v-bind="slotProps" /> </AccordionItem></template><script setup lang="ts">import type { AccordionTriggerProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import { ChevronDown } from '@lucide/vue'import { reactiveOmit } from '@vueuse/core'import { AccordionHeader, AccordionTrigger } from 'reka-ui'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, 'class')</script>
<template> <AccordionHeader class="flex"> <AccordionTrigger data-slot="accordion-trigger" v-bind="delegatedProps" :class=" cn( 'group/accordion-trigger relative flex flex-1 items-center justify-between gap-4 py-3.5 text-left text-[14px] font-medium outline-none transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50', props.class, ) " > <slot /> <slot name="icon"> <ChevronDown data-slot="accordion-trigger-icon" class="pointer-events-none size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-data-[state=open]/accordion-trigger:rotate-180" /> </slot> </AccordionTrigger> </AccordionHeader></template>import type { VariantProps } from "class-variance-authority";import { cva } from "class-variance-authority";
export { default as Accordion } from "./Accordion.vue";export { default as AccordionContent } from "./AccordionContent.vue";export { default as AccordionItem } from "./AccordionItem.vue";export { default as AccordionTrigger } from "./AccordionTrigger.vue";
// ✦ `variant="card"`: items live inside a raised card with hairline dividers.export const accordionVariants = cva("flex w-full flex-col", { variants: { variant: { default: "", card: "rounded-xl border border-border bg-card px-4", }, }, defaultVariants: { variant: "default" },});
export type AccordionVariants = VariantProps<typeof accordionVariants>;<script lang="ts" module> import { tv, type VariantProps } from "tailwind-variants";
// ✦ `variant="card"`: items live inside a raised card with hairline dividers. export const accordionVariants = tv({ base: "flex w-full flex-col", variants: { variant: { default: "", card: "rounded-xl border border-border bg-card px-4", }, }, defaultVariants: { variant: "default" }, });
export type AccordionVariant = VariantProps<typeof accordionVariants>["variant"];</script>
<script lang="ts"> import { Accordion as AccordionPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), value = $bindable(), variant = "default", class: className, ...restProps }: AccordionPrimitive.RootProps & { variant?: AccordionVariant } = $props();</script>
<AccordionPrimitive.Root bind:ref bind:value={value as never} data-slot="accordion" data-variant={variant} class={cn(accordionVariants({ variant }), className)} {...restProps}/><script lang="ts"> import { Accordion as AccordionPrimitive } from "bits-ui"; import { cn, type WithoutChild } from "$lib/utils.js";
let { ref = $bindable(null), class: className, children, ...restProps }: WithoutChild<AccordionPrimitive.ContentProps> = $props();</script>
<AccordionPrimitive.Content bind:ref data-slot="accordion-content" class="overflow-hidden text-[13.5px] leading-relaxed text-muted-foreground data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" {...restProps}> <div class={cn( "pb-4 [&_a]:text-foreground [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4", className )} > {@render children?.()} </div></AccordionPrimitive.Content><script lang="ts"> import { Accordion as AccordionPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, ...restProps }: AccordionPrimitive.ItemProps = $props();</script>
<AccordionPrimitive.Item bind:ref data-slot="accordion-item" class={cn("border-b border-border last:border-b-0", className)} {...restProps}/><script lang="ts"> import { Accordion as AccordionPrimitive } from "bits-ui"; import CaretDownIcon from "phosphor-svelte/lib/CaretDown"; import { cn, type WithoutChild } from "$lib/utils.js";
let { ref = $bindable(null), class: className, level = 3, children, ...restProps }: WithoutChild<AccordionPrimitive.TriggerProps> & { level?: AccordionPrimitive.HeaderProps["level"]; } = $props();</script>
<AccordionPrimitive.Header {level} class="flex"> <AccordionPrimitive.Trigger data-slot="accordion-trigger" bind:ref class={cn( "group/accordion-trigger relative flex flex-1 items-center justify-between gap-4 py-3.5 text-left text-[14px] font-medium outline-none transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className )} {...restProps} > {@render children?.()} <CaretDownIcon data-slot="accordion-trigger-icon" class="pointer-events-none size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-data-[state=open]/accordion-trigger:rotate-180" /> </AccordionPrimitive.Trigger></AccordionPrimitive.Header>import Root, { type AccordionVariant, accordionVariants,} from "./accordion.svelte";import Content from "./accordion-content.svelte";import Item from "./accordion-item.svelte";import Trigger from "./accordion-trigger.svelte";
export { type AccordionVariant, accordionVariants, Content, Content as AccordionContent, Item, Item as AccordionItem, Root, // Root as Accordion, Trigger, Trigger as AccordionTrigger,};import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion";
export default function Demo() { return ( <Accordion multiple defaultValue={["a", "c"]} variant="card" className="max-w-md" > <AccordionItem value="a"> <AccordionTrigger>3.1 Vault incorporation</AccordionTrigger> <AccordionContent>How the vault entity is formed.</AccordionContent> </AccordionItem> <AccordionItem value="b"> <AccordionTrigger>3.2 Index analytics</AccordionTrigger> <AccordionContent>Performance and weight history.</AccordionContent> </AccordionItem> <AccordionItem value="c"> <AccordionTrigger>3.3 Holder support</AccordionTrigger> <AccordionContent> Answer holder questions with the agent, using the same mandate context. </AccordionContent> </AccordionItem> </Accordion> );}<script setup lang="ts">import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion";</script>
<template> <Accordion type="multiple" :default-value="['a', 'c']" variant="card" class="max-w-md"> <AccordionItem value="a"> <AccordionTrigger>3.1 Vault incorporation</AccordionTrigger> <AccordionContent>How the vault entity is formed.</AccordionContent> </AccordionItem> <AccordionItem value="b"> <AccordionTrigger>3.2 Index analytics</AccordionTrigger> <AccordionContent>Performance and weight history.</AccordionContent> </AccordionItem> <AccordionItem value="c"> <AccordionTrigger>3.3 Holder support</AccordionTrigger> <AccordionContent> Answer holder questions with the agent, using the same mandate context. </AccordionContent> </AccordionItem> </Accordion></template><script lang="ts"> import * as Accordion from "$lib/components/ui/accordion";</script>
<Accordion.Root type="multiple" value={["a", "c"]} variant="card" class="max-w-md"> <Accordion.Item value="a"> <Accordion.Trigger>3.1 Vault incorporation</Accordion.Trigger> <Accordion.Content>How the vault entity is formed.</Accordion.Content> </Accordion.Item> <Accordion.Item value="b"> <Accordion.Trigger>3.2 Index analytics</Accordion.Trigger> <Accordion.Content>Performance and weight history.</Accordion.Content> </Accordion.Item> <Accordion.Item value="c"> <Accordion.Trigger>3.3 Holder support</Accordion.Trigger> <Accordion.Content> Answer holder questions with the agent, using the same mandate context. </Accordion.Content> </Accordion.Item></Accordion.Root>| Prop | Type | Default |
|---|---|---|
multiple |
boolean |
false |
defaultValue / value |
string[] |
[] |
variant ✦ |
"default" | "card" |
"default" |
AccordionItem value |
string |
— |
Anatomy: Accordion, AccordionItem, AccordionTrigger, AccordionContent.
✦ Edmi additions
Section titled “✦ Edmi additions”variant="card": items inside a bordered card with hairline dividers.
Vue / Svelte differences
Section titled “Vue / Svelte differences”- Vue (Reka UI) and Svelte (Bits UI):
type="single" | "multiple"(pluscollapsible) replaces React’smultipleboolean. - Svelte: the root
valueis astringwhentype="single"and astring[]whentype="multiple".