AniUI

Accordion

Expandable content sections with animated height transitions.

Standard orders arrive in 3-5 business days. Express orders arrive in 1-2 business days.

Yes — unused items can be returned within 30 days of delivery for a full refund.

We ship to over 40 countries. International orders may be subject to customs fees.

You'll get a tracking link by email as soon as your order ships.

Web preview — components render natively on iOS & Android
import { Accordion, AccordionItem } from "@/components/ui/accordion";

export function MyScreen() {
  return (
    <Accordion defaultValue="shipping-time">
      <AccordionItem value="shipping-time" trigger="How long does shipping take?">
        <Text>Standard orders arrive in 3-5 business days. Express orders arrive in 1-2 business days.</Text>
      </AccordionItem>
      <AccordionItem value="returns" trigger="Can I return an item?">
        <Text>Yes — unused items can be returned within 30 days of delivery for a full refund.</Text>
      </AccordionItem>
      <AccordionItem value="international" trigger="Do you ship internationally?">
        <Text>We ship to over 40 countries. International orders may be subject to customs fees.</Text>
      </AccordionItem>
      <AccordionItem value="tracking" trigger="How do I track my order?">
        <Text>You'll get a tracking link by email as soon as your order ships.</Text>
      </AccordionItem>
    </Accordion>
  );
}

Installation#

npx @aniui/cli add accordion

This component requires react-native-reanimated for animations.

Usage#

app/index.tsx
import { Accordion, AccordionItem } from "@/components/ui/accordion";

export function MyScreen() {
  return (
    <Accordion defaultValue="shipping-time">
      <AccordionItem value="shipping-time" trigger="How long does shipping take?">
        <Text>Standard orders arrive in 3-5 business days. Express orders arrive in 1-2 business days.</Text>
      </AccordionItem>
      <AccordionItem value="returns" trigger="Can I return an item?">
        <Text>Yes — unused items can be returned within 30 days of delivery for a full refund.</Text>
      </AccordionItem>
      <AccordionItem value="international" trigger="Do you ship internationally?">
        <Text>We ship to over 40 countries. International orders may be subject to customs fees.</Text>
      </AccordionItem>
      <AccordionItem value="tracking" trigger="How do I track my order?">
        <Text>You'll get a tracking link by email as soon as your order ships.</Text>
      </AccordionItem>
    </Accordion>
  );
}

Compound Components#

ComponentDescription
Accordion

Root container that manages expanded state

AccordionItem

Individual collapsible section with trigger text

Props#

Accordion#

PropTypeDefault
defaultValue
string
type
"single" | "multiple"
"single"
className
string

AccordionItem#

PropTypeDefault
value
string
required
trigger
string
required
className
string

Accessibility#

  • Uses @rn-primitives/accordion for keyboard navigation and ARIA compliance.
  • Supports single and multiple open modes.
  • Each trigger has accessibilityRole="button" with accessibilityState for expanded/collapsed.

Source#

components/ui/accordion.tsx
import React from "react";
import { View, Pressable, Text } from "react-native";
import * as AccordionPrimitive from "@rn-primitives/accordion";
import Animated from "react-native-reanimated";
import { ChevronDown } from "lucide-react-native";
import { entering, exiting } from "@/components/ui/animate";
import { useThemeColors } from "@/components/ui/theme-provider";
import { cn } from "@/lib/utils";

export interface AccordionProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  defaultValue?: string;
  children?: React.ReactNode;
  type?: "single" | "multiple";
}

export function Accordion({ className, defaultValue, children, type = "single", ...props }: AccordionProps) {
  return (
    <AccordionPrimitive.Root
      type={type}
      defaultValue={type === "single" ? defaultValue : defaultValue ? [defaultValue] : undefined}
      asChild
    >
      <View className={cn("", className)} {...props}>{children}</View>
    </AccordionPrimitive.Root>
  );
}

export interface AccordionItemProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  value: string;
  trigger: string;
  children?: React.ReactNode;
}

// Rendered inside the Item so useItemContext can read the open state.
function AccordionTrigger({ label }: { label: string }) {
  const { isExpanded } = AccordionPrimitive.useItemContext();
  const colors = useThemeColors();
  return (
    <AccordionPrimitive.Trigger asChild>
      <Pressable
        className="flex-row items-center justify-between px-4 py-4 min-h-12"
        accessible={true}
        accessibilityRole="button"
        accessibilityState={{ expanded: isExpanded }}
      >
        <Text className="text-base font-medium text-foreground flex-1">{label}</Text>
        <ChevronDown
          size={16}
          color={colors.mutedForeground}
          strokeWidth={2}
          style={{ transform: [{ rotate: isExpanded ? "180deg" : "0deg" }] }}
        />
      </Pressable>
    </AccordionPrimitive.Trigger>
  );
}

export function AccordionItem({ value, trigger, className, children, ...props }: AccordionItemProps) {
  return (
    <AccordionPrimitive.Item value={value} asChild>
      <View className={cn("border-b border-border", className)} {...props}>
        <AccordionTrigger label={trigger} />
        <AccordionPrimitive.Content>
          <Animated.View entering={entering.fadeInDown} exiting={exiting.fadeOutUp}>
            <View className="px-4 pb-4">{children}</View>
          </Animated.View>
        </AccordionPrimitive.Content>
      </View>
    </AccordionPrimitive.Item>
  );
}