# Accordion

> A vertically stacked set of interactive headings that each reveal a section of content.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/components/accordion
- Markdown: https://ui.ahmedbna.com/docs/components/accordion.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/accordion.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/accordion.json
- Install: `npx bna-ui add accordion`
- npm dependencies: `expo-haptics`, `lucide-react-native`, `react-native-reanimated`, `react-native-svg`, `react-native-worklets`
- Registry dependencies: `useHaptics`, `mode-provider`, `useColorScheme`, `colors`, `useColor`, `globals`, `text`, `view`, `icon`
- Preview recording: https://demo.ahmedbna.com/0001-accordion-demo.mov

---

**Example:** A basic accordion with collapsible sections

```tsx
// components/demo/accordion/accordion-demo.tsx
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Text } from '@/components/ui/text';
import React from 'react';

export function AccordionDemo() {
  return (
    <Accordion type='single' collapsible defaultValue='item-1'>
      <AccordionItem value='item-1'>
        <AccordionTrigger>Is it accessible?</AccordionTrigger>
        <AccordionContent>
          <Text>Yes. It adheres to the WAI-ARIA design pattern.</Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='item-2'>
        <AccordionTrigger>Is it styled?</AccordionTrigger>
        <AccordionContent>
          <Text>
            Yes. It comes with default styles that matches the other components'
            aesthetic.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='item-3'>
        <AccordionTrigger>Is it animated?</AccordionTrigger>
        <AccordionContent>
          <Text>
            Yes. It's animated by default, but you can disable it if you prefer.
          </Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add accordion
```

### Manual

**1.** Install the following dependencies:

```bash
npx expo install lucide-react-native
```

**2.** Copy and paste the following code into your project.

```tsx
// components/ui/accordion.tsx
import { Icon } from '@/components/ui/icon';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useHaptics } from '@/hooks/useHaptics';
import { ChevronRight } from 'lucide-react-native';
import React, { createContext, useContext, useState } from 'react';
import { TouchableOpacity } from 'react-native';
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';

// Context for accordion state
interface AccordionContextType {
  type: 'single' | 'multiple';
  collapsible?: boolean;
  value?: string | string[];
  onValueChange?: (value: string | string[]) => void;
  haptic?: boolean;
}

const AccordionContext = createContext<AccordionContextType | null>(null);

// Main Accordion component
interface AccordionProps {
  type: 'single' | 'multiple';
  collapsible?: boolean;
  defaultValue?: string | string[];
  value?: string | string[];
  onValueChange?: (value: string | string[]) => void;
  children: React.ReactNode;
  haptic?: boolean;
}

export function Accordion({
  type,
  collapsible = false,
  defaultValue,
  value: controlledValue,
  onValueChange,
  children,
  haptic = true,
}: AccordionProps) {
  const [internalValue, setInternalValue] = useState<string | string[]>(
    defaultValue || (type === 'multiple' ? [] : '')
  );

  const value = controlledValue !== undefined ? controlledValue : internalValue;

  const handleValueChange = (newValue: string | string[]) => {
    if (controlledValue === undefined) {
      setInternalValue(newValue);
    }
    onValueChange?.(newValue);
  };

  return (
    <AccordionContext.Provider
      value={{
        type,
        collapsible,
        value,
        onValueChange: handleValueChange,
        haptic,
      }}
    >
      <View style={{ width: '100%' }}>{children}</View>
    </AccordionContext.Provider>
  );
}

// AccordionItem component
interface AccordionItemProps {
  value: string;
  children: React.ReactNode;
}

export function AccordionItem({ value, children }: AccordionItemProps) {
  const context = useContext(AccordionContext);
  // Called before the guard below so the hook order stays stable.
  const feedback = useHaptics(context?.haptic ?? true);

  if (!context) {
    throw new Error('AccordionItem must be used within an Accordion');
  }

  const isOpen = Array.isArray(context.value)
    ? context.value.includes(value)
    : context.value === value;

  const toggle = () => {
    if (!context.onValueChange) return;

    if (context.type === 'single') {
      // A non-collapsible single accordion keeps the open item open, so tapping
      // it changes nothing and must not feel like it did.
      const willClose = isOpen && !!context.collapsible;
      if (!isOpen || willClose) {
        feedback(willClose ? 'toggle-off' : 'toggle-on');
      }
      const newValue = willClose ? '' : value;
      context.onValueChange(newValue);
    } else {
      feedback(isOpen ? 'toggle-off' : 'toggle-on');
      const currentValues = Array.isArray(context.value) ? context.value : [];
      const newValue = isOpen
        ? currentValues.filter((v) => v !== value)
        : [...currentValues, value];
      context.onValueChange(newValue);
    }
  };

  return (
    <AccordionItemContext.Provider value={{ value, isOpen, toggle }}>
      <View>{children}</View>
    </AccordionItemContext.Provider>
  );
}

// Context for accordion item
interface AccordionItemContextType {
  value: string;
  isOpen: boolean;
  toggle: () => void;
}

const AccordionItemContext = createContext<AccordionItemContextType | null>(
  null
);

// AccordionTrigger component
interface AccordionTriggerProps {
  children: React.ReactNode;
}

export function AccordionTrigger({ children }: AccordionTriggerProps) {
  const context = useContext(AccordionItemContext);
  if (!context) {
    throw new Error('AccordionTrigger must be used within an AccordionItem');
  }

  return (
    <TouchableOpacity
      style={{
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
        paddingVertical: 6,
      }}
      onPress={context.toggle}
      activeOpacity={0.8}
      accessibilityRole='button'
      accessibilityState={{ expanded: context.isOpen }}
    >
      <Text variant='subtitle'>{children}</Text>
      <Icon
        name={ChevronRight}
        size={18}
        style={{
          transform: [{ rotate: context.isOpen ? '90deg' : '0deg' }],
        }}
      />
    </TouchableOpacity>
  );
}

// AccordionContent component
interface AccordionContentProps {
  children: React.ReactNode;
  style?: object;
}

export function AccordionContent({ children, style }: AccordionContentProps) {
  const context = useContext(AccordionItemContext);
  if (!context) {
    throw new Error('AccordionContent must be used within an AccordionItem');
  }

  if (!context.isOpen) {
    return null;
  }

  return (
    <Animated.View
      entering={FadeIn.duration(200)}
      exiting={FadeOut.duration(150)}
      style={[
        {
          paddingBottom: 16,
          paddingLeft: 0,
        },
        style,
      ]}
    >
      {children}
    </Animated.View>
  );
}
```

**3.** Update the import paths to match your project setup.

## Usage

```tsx
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
```

```tsx
<Accordion type='single' collapsible>
  <AccordionItem value='item-1'>
    <AccordionTrigger>Is it accessible?</AccordionTrigger>
    <AccordionContent>
      Yes. It adheres to the WAI-ARIA design pattern.
    </AccordionContent>
  </AccordionItem>
</Accordion>
```

## Examples

#### Default

**Example:** A basic accordion with single selection and collapsible behavior

```tsx
// components/demo/accordion/accordion-demo.tsx
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Text } from '@/components/ui/text';
import React from 'react';

export function AccordionDemo() {
  return (
    <Accordion type='single' collapsible defaultValue='item-1'>
      <AccordionItem value='item-1'>
        <AccordionTrigger>Is it accessible?</AccordionTrigger>
        <AccordionContent>
          <Text>Yes. It adheres to the WAI-ARIA design pattern.</Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='item-2'>
        <AccordionTrigger>Is it styled?</AccordionTrigger>
        <AccordionContent>
          <Text>
            Yes. It comes with default styles that matches the other components'
            aesthetic.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='item-3'>
        <AccordionTrigger>Is it animated?</AccordionTrigger>
        <AccordionContent>
          <Text>
            Yes. It's animated by default, but you can disable it if you prefer.
          </Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
```

#### Single Selection

**Example:** An accordion that allows only one item to be open at a time

```tsx
// components/demo/accordion/accordion-single.tsx
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Text } from '@/components/ui/text';
import React from 'react';

export function AccordionSingle() {
  return (
    <Accordion type='single' collapsible>
      <AccordionItem value='item-1'>
        <AccordionTrigger>What is React Native?</AccordionTrigger>
        <AccordionContent>
          <Text>
            React Native is a framework for building native mobile applications
            using React. It allows you to create mobile apps for iOS and Android
            using JavaScript and React components.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='item-2'>
        <AccordionTrigger>What is Expo?</AccordionTrigger>
        <AccordionContent>
          <Text>
            Expo is a platform for making universal native apps that run on
            Android, iOS, and the web. It provides a set of tools and services
            built around React Native.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='item-3'>
        <AccordionTrigger>What is TypeScript?</AccordionTrigger>
        <AccordionContent>
          <Text>
            TypeScript is a programming language developed by Microsoft. It is a
            strict syntactical superset of JavaScript and adds optional static
            type checking to the language.
          </Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
```

#### Multiple Selection

**Example:** An accordion that allows multiple items to be open simultaneously

```tsx
// components/demo/accordion/accordion-multiple.tsx
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Text } from '@/components/ui/text';
import React from 'react';

export function AccordionMultiple() {
  return (
    <Accordion type='multiple' defaultValue={['item-1', 'item-2']}>
      <AccordionItem value='item-1'>
        <AccordionTrigger>Frontend Technologies</AccordionTrigger>
        <AccordionContent>
          <Text>
            Modern frontend development includes React, Vue, Angular, and many
            other frameworks that help build interactive user interfaces.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='item-2'>
        <AccordionTrigger>Backend Technologies</AccordionTrigger>
        <AccordionContent>
          <Text>
            Backend development involves server-side technologies like Node.js,
            Python, Java, and databases to handle data and business logic.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='item-3'>
        <AccordionTrigger>Mobile Development</AccordionTrigger>
        <AccordionContent>
          <Text>
            Mobile development can be done natively with Swift/Kotlin or with
            cross-platform solutions like React Native, Flutter, or Xamarin.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='item-4'>
        <AccordionTrigger>DevOps & Cloud</AccordionTrigger>
        <AccordionContent>
          <Text>
            DevOps practices and cloud platforms like AWS, Azure, and GCP help
            deploy, scale, and maintain applications efficiently.
          </Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
```

#### Controlled

**Example:** An accordion with controlled state management

```tsx
// components/demo/accordion/accordion-controlled.tsx
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function AccordionControlled() {
  const [value, setValue] = React.useState<string | string[]>('');

  return (
    <View>
      <Text variant='caption' style={{ marginBottom: 12 }}>
        Currently open: {value || 'None'}
      </Text>
      <Accordion
        type='single'
        collapsible
        value={value}
        onValueChange={setValue}
      >
        <AccordionItem value='settings'>
          <AccordionTrigger>Settings</AccordionTrigger>
          <AccordionContent>
            <Text>
              Configure your application preferences, notifications, and account
              settings here.
            </Text>
          </AccordionContent>
        </AccordionItem>
        <AccordionItem value='privacy'>
          <AccordionTrigger>Privacy</AccordionTrigger>
          <AccordionContent>
            <Text>
              Manage your privacy settings, data sharing preferences, and
              visibility controls.
            </Text>
          </AccordionContent>
        </AccordionItem>
        <AccordionItem value='security'>
          <AccordionTrigger>Security</AccordionTrigger>
          <AccordionContent>
            <Text>
              Set up two-factor authentication, change passwords, and review
              security logs.
            </Text>
          </AccordionContent>
        </AccordionItem>
      </Accordion>
    </View>
  );
}
```

#### FAQ Style

**Example:** An accordion formatted as a frequently asked questions section

```tsx
// components/demo/accordion/accordion-faq.tsx
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Text } from '@/components/ui/text';
import React from 'react';

export function AccordionFAQ() {
  return (
    <Accordion type='single' collapsible>
      <AccordionItem value='shipping'>
        <AccordionTrigger>How long does shipping take?</AccordionTrigger>
        <AccordionContent>
          <Text>
            Standard shipping typically takes 3-5 business days. Express
            shipping is available for 1-2 business days delivery.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='returns'>
        <AccordionTrigger>What is your return policy?</AccordionTrigger>
        <AccordionContent>
          <Text>
            We offer a 30-day return policy for all items in original condition.
            Return shipping is free for defective items.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='warranty'>
        <AccordionTrigger>Do you offer warranty?</AccordionTrigger>
        <AccordionContent>
          <Text>
            Yes, all products come with a 1-year manufacturer warranty. Extended
            warranty options are available at checkout.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='support'>
        <AccordionTrigger>How can I contact support?</AccordionTrigger>
        <AccordionContent>
          <Text>
            You can reach our support team via email at support@example.com or
            through our live chat feature available 24/7.
          </Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
```

#### Non-Collapsible

**Example:** An accordion where at least one item must always remain open

```tsx
// components/demo/accordion/accordion-non-collapsible.tsx
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Text } from '@/components/ui/text';
import React from 'react';

export function AccordionNonCollapsible() {
  return (
    <Accordion type='single' defaultValue='step-1'>
      <AccordionItem value='step-1'>
        <AccordionTrigger>Step 1: Planning</AccordionTrigger>
        <AccordionContent>
          <Text>
            Start by defining your project requirements and creating a detailed
            plan. This includes wireframing and technical specifications.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='step-2'>
        <AccordionTrigger>Step 2: Development</AccordionTrigger>
        <AccordionContent>
          <Text>
            Begin the development process by setting up your environment and
            implementing the core features according to your plan.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='step-3'>
        <AccordionTrigger>Step 3: Testing</AccordionTrigger>
        <AccordionContent>
          <Text>
            Thoroughly test your application across different devices and
            scenarios to ensure it works as expected.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value='step-4'>
        <AccordionTrigger>Step 4: Deployment</AccordionTrigger>
        <AccordionContent>
          <Text>
            Deploy your application to production and monitor its performance.
            Set up analytics and error tracking.
          </Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
```

#### Custom Styled

**Example:** An accordion with custom styling and icons

```tsx
// components/demo/accordion/accordion-styled.tsx
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function AccordionStyled() {
  const card = useColor('card');

  return (
    <View
      style={{
        padding: 16,
        backgroundColor: card,
        borderRadius: BORDER_RADIUS,
      }}
    >
      <Accordion type='single' collapsible>
        <AccordionItem value='features'>
          <AccordionTrigger>🚀 Features</AccordionTrigger>
          <AccordionContent style={{ paddingLeft: 8 }}>
            <Text>
              • Cross-platform compatibility{'\n'}• TypeScript support{'\n'}•
              Theme system integration{'\n'}• Customizable animations
            </Text>
          </AccordionContent>
        </AccordionItem>
        <AccordionItem value='performance'>
          <AccordionTrigger>⚡ Performance</AccordionTrigger>
          <AccordionContent style={{ paddingLeft: 8 }}>
            <Text>
              • Optimized rendering{'\n'}• Minimal re-renders{'\n'}• Smooth
              animations{'\n'}• Memory efficient
            </Text>
          </AccordionContent>
        </AccordionItem>
        <AccordionItem value='accessibility'>
          <AccordionTrigger>♿ Accessibility</AccordionTrigger>
          <AccordionContent style={{ paddingLeft: 8 }}>
            <Text>
              • Screen reader support{'\n'}• Keyboard navigation{'\n'}• Focus
              management{'\n'}• ARIA attributes
            </Text>
          </AccordionContent>
        </AccordionItem>
      </Accordion>
    </View>
  );
}
```

## API Reference

### Accordion

Contains all the parts of a collapsible accordion.

| Prop            | Type                                  | Default | Description                                                                                                                                                                 |
| --------------- | ------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `haptic`        | `boolean`                             | `true`  | Whether to trigger haptic feedback when an item expands or collapses.                                                                                                       |
| `type`          | `'single' \| 'multiple'`              | -       | Determines whether one or multiple items can be opened at the same time.                                                                                                    |
| `value`         | `string \| string[]`                  | -       | The controlled value of the item to expand when `type` is `"single"` or the controlled values of the items to expand when `type` is `"multiple"`.                           |
| `defaultValue`  | `string \| string[]`                  | -       | The value of the item to expand when initially rendered when `type` is `"single"` or the values of the items to expand when initially rendered when `type` is `"multiple"`. |
| `onValueChange` | `(value: string \| string[]) => void` | -       | Event handler called when the expanded state of an item changes.                                                                                                            |
| `collapsible`   | `boolean`                             | `false` | When `type` is `"single"`, allows closing content when clicking trigger for an open item.                                                                                   |
| `children`      | `React.ReactNode`                     | -       | One or more `AccordionItem` elements.                                                                                                                                       |

### AccordionItem

Contains all the parts of a collapsible item.

| Prop       | Type              | Description                                        |
| ---------- | ----------------- | -------------------------------------------------- |
| `value`    | `string`          | A unique value for the item.                       |
| `children` | `React.ReactNode` | An `AccordionTrigger` and `AccordionContent` pair. |

### AccordionTrigger

Toggles the collapsed state of its associated item.

| Prop       | Type              | Description                            |
| ---------- | ----------------- | -------------------------------------- |
| `children` | `React.ReactNode` | The content to display in the trigger. |

### AccordionContent

Contains the collapsible content for an item.

| Prop       | Type              | Description                                          |
| ---------- | ----------------- | ---------------------------------------------------- |
| `children` | `React.ReactNode` | The content to display when the item is expanded.    |
| `style`    | `object`          | Additional styles to apply to the content container. |

## Accessibility

Adheres to the [Accordion WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/).
