Accordion

Material Design 3 accordion component for expandable content panels

Accordion

Accordions allow users to show and hide sections of related content on a page. @duskmoon-dev/core provides a complete set of Material Design 3 accordion variants.

Basic Usage

Basic Accordion

Material Design is a design system created by Google to help teams build high-quality digital experiences.
Install using npm: npm install @duskmoon-dev/core

Variants

Default Accordion

The default accordion has a clean, minimal appearance with bottom borders separating items.

Default Accordion

Content for section 1
Content for section 2

Filled Accordion

Filled accordions have a background color and are separated by spacing.

Filled Accordion

Content with filled background
Content with filled background

Outlined Accordion

Outlined accordions have a border around the entire container.

Outlined Accordion

Content within outlined container
Content within outlined container

Separated Accordion

Separated accordions display each item as an individual card with spacing between them.

Separated Accordion

Each item is a separate card
Each item is a separate card

Density

Compact

Compact accordions have reduced padding for space-efficient layouts.

Compact Accordion

Compact content with less padding

Comfortable

Comfortable accordions have increased padding for more spacious layouts.

Comfortable Accordion

Comfortable content with more padding

Color Variants

Color variants change the text color of expanded items.

Primary

Primary Accordion

The header text turns primary color when expanded

Secondary

Secondary Accordion

The header text turns secondary color when expanded

Tertiary

Tertiary Accordion

The header text turns tertiary color when expanded

Icons

Expansion Icon

The accordion icon rotates 180 degrees when the item is expanded.

Accordion with Expansion Icon

The chevron icon rotates when expanded

Leading Icons

Add custom icons at the start of accordion headers.

Accordion with Leading Icon

Content with a leading icon

States

Open State

Add the accordion-item-open class to expand an item by default.

Open and Closed States

This section is expanded by default
This section is collapsed by default

Disabled State

Disable an accordion item to prevent interaction.

Disabled Accordion

This content cannot be accessed

Without Animation

Remove animations for reduced motion preferences or performance.

Accordion Without Animation

Expands instantly without animation

Single vs Multiple Expansion

Single Expansion

Only one item can be open at a time (requires JavaScript).

Single Expansion Accordion

Opening this closes other sections
Opening this closes other sections

Multiple Expansion

Multiple items can be open simultaneously (requires JavaScript).

Multiple Expansion Accordion

Multiple sections can be open
Multiple sections can be open

Combining Variants

You can combine different modifiers for custom layouts.

Combined Variants

This accordion is filled, comfortable, and uses primary color
This accordion is separated, compact, and uses secondary color

Best Practices

Content Organization

Use accordions to organize related content sections:

Organized Content with Icons

Manage your account settings and preferences
Configure your privacy and security settings

Accessibility

  • Use semantic HTML with <button> elements for headers
  • Implement proper ARIA attributes for screen readers
  • Ensure keyboard navigation support
  • Provide clear labels for all sections

Accessible Accordion

Content with proper ARIA attributes

Avoid Overuse

Don’t use accordions when:

  • Content should be immediately visible
  • There are only 1-2 items
  • Users need to compare information across sections

Progressive Disclosure

Use accordions to progressively reveal complex information:

Progressive Disclosure

Most commonly used settings
Advanced options for power users

Framework Examples

React

import { useState } from 'react';

interface AccordionItemProps {
  title: string;
  children: React.ReactNode;
  icon?: React.ReactNode;
}

function AccordionItem({ title, children, icon }: AccordionItemProps) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className={`accordion-item ${isOpen ? 'accordion-item-open' : ''}`}>
      <button
        className="accordion-header"
        onClick={() => setIsOpen(!isOpen)}
        aria-expanded={isOpen}
      >
        {icon && <span className="accordion-header-icon-leading">{icon}</span>}
        <span className="accordion-title">{title}</span>
        <span className="accordion-icon">▼</span>
      </button>
      <div className="accordion-content">
        <div className="accordion-body">
          {children}
        </div>
      </div>
    </div>
  );
}

interface AccordionProps {
  variant?: 'default' | 'filled' | 'outlined' | 'separated';
  density?: 'default' | 'compact' | 'comfortable';
  color?: 'primary' | 'secondary' | 'tertiary';
  children: React.ReactNode;
}

export function Accordion({
  variant = 'default',
  density = 'default',
  color,
  children
}: AccordionProps) {
  const classes = [
    'accordion',
    variant !== 'default' && `accordion-${variant}`,
    density !== 'default' && `accordion-${density}`,
    color && `accordion-${color}`
  ].filter(Boolean).join(' ');

  return (
    <div className={classes}>
      {children}
    </div>
  );
}

// Usage
<Accordion variant="filled" color="primary">
  <AccordionItem title="Section 1">
    Content for section 1
  </AccordionItem>
  <AccordionItem title="Section 2">
    Content for section 2
  </AccordionItem>
</Accordion>

Vue

<template>
  <div :class="accordionClasses">
    <slot />
  </div>
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({
  variant: {
    type: String,
    default: 'default',
    validator: (value) => ['default', 'filled', 'outlined', 'separated'].includes(value)
  },
  density: {
    type: String,
    default: 'default',
    validator: (value) => ['default', 'compact', 'comfortable'].includes(value)
  },
  color: {
    type: String,
    validator: (value) => ['primary', 'secondary', 'tertiary'].includes(value)
  }
});

const accordionClasses = computed(() => {
  const classes = ['accordion'];
  if (props.variant !== 'default') classes.push(`accordion-${props.variant}`);
  if (props.density !== 'default') classes.push(`accordion-${props.density}`);
  if (props.color) classes.push(`accordion-${props.color}`);
  return classes.join(' ');
});
</script>

<!-- AccordionItem.vue -->
<template>
  <div :class="itemClasses">
    <button
      class="accordion-header"
      @click="toggle"
      :aria-expanded="isOpen"
    >
      <span v-if="$slots.icon" class="accordion-header-icon-leading">
        <slot name="icon" />
      </span>
      <span class="accordion-title">{{ title }}</span>
      <span class="accordion-icon">▼</span>
    </button>
    <div class="accordion-content">
      <div class="accordion-body">
        <slot />
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const props = defineProps({
  title: {
    type: String,
    required: true
  },
  defaultOpen: {
    type: Boolean,
    default: false
  }
});

const isOpen = ref(props.defaultOpen);

const itemClasses = computed(() => {
  return isOpen.value ? 'accordion-item accordion-item-open' : 'accordion-item';
});

function toggle() {
  isOpen.value = !isOpen.value;
}
</script>

<!-- Usage -->
<Accordion variant="filled" color="primary">
  <AccordionItem title="Section 1">
    Content for section 1
  </AccordionItem>
  <AccordionItem title="Section 2">
    Content for section 2
  </AccordionItem>
</Accordion>

API Reference

Container Classes

ClassDescription
.accordionBase accordion container (required)
.accordion-filledFilled variant with background color
.accordion-outlinedOutlined variant with border
.accordion-separatedCard-style separated items
.accordion-compactReduced padding
.accordion-comfortableIncreased padding
.accordion-primaryPrimary color for expanded items
.accordion-secondarySecondary color for expanded items
.accordion-tertiaryTertiary color for expanded items
.accordion-no-animationDisable animations

Item Classes

ClassDescription
.accordion-itemIndividual accordion item (required)
.accordion-item-openExpanded state
.accordion-item-disabledDisabled state

Header Classes

ClassDescription
.accordion-headerClickable header/trigger (required)
.accordion-header-iconContainer for header with leading icon
.accordion-header-icon-leadingLeading icon in header
.accordion-titleTitle text container
.accordion-iconExpansion indicator icon

Content Classes

ClassDescription
.accordion-contentCollapsible content wrapper (required)
.accordion-bodyContent body with padding (required)

Class Combinations

Class Combinations

  • Card - Container component
  • List - List display component
  • Tabs - Tabbed navigation

See Also