AniUI

Accordion

Expandable content sections with animated height transitions.

Yes. It uses accessibilityRole and accessibilityState.

Yes. It uses react-native-reanimated for smooth animations.

Yes. Use className to override any styles.

import { Accordion, AccordionItem } from "@/components/ui/accordion";

export function MyScreen() {
  return (
    <Accordion defaultValue="item-1">
      <AccordionItem value="item-1" trigger="Is it accessible?">
        <Text>Yes. It uses accessibilityRole and accessibilityState.</Text>
      </AccordionItem>
      <AccordionItem value="item-2" trigger="Is it animated?">
        <Text>Yes. It uses react-native-reanimated for smooth animations.</Text>
      </AccordionItem>
      <AccordionItem value="item-3" trigger="Can I customize it?">
        <Text>Yes. Use className to override any styles.</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="item-1">
      <AccordionItem value="item-1" trigger="Is it accessible?">
        <Text>Yes. It uses accessibilityRole and accessibilityState.</Text>
      </AccordionItem>
      <AccordionItem value="item-2" trigger="Is it animated?">
        <Text>Yes. It uses react-native-reanimated for smooth animations.</Text>
      </AccordionItem>
      <AccordionItem value="item-3" trigger="Can I customize it?">
        <Text>Yes. Use className to override any styles.</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
className
string

AccordionItem

PropTypeDefault
value
string
required
trigger
string
required
className
string

Source

components/ui/accordion.tsx
import React, { createContext, useContext, useState } from "react";
import { View, Pressable, Text } from "react-native";
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
import { cn } from "@/lib/utils";

const AccordionContext = createContext<{
  expanded: string | null;
  toggle: (value: string) => void;
}>({ expanded: null, toggle: () => {} });
export interface AccordionProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  defaultValue?: string;
  children?: React.ReactNode;
}
export function Accordion({ className, defaultValue, children, ...props }: AccordionProps) {
  const [expanded, setExpanded] = useState<string | null>(defaultValue ?? null);
  const toggle = (value: string) => setExpanded((prev) => (prev === value ? null : value));
  return (
    <AccordionContext.Provider value={{ expanded, toggle }}>
      <View className={cn("", className)} {...props}>{children}</View>
    </AccordionContext.Provider>
  );
}
export interface AccordionItemProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  value: string;
  trigger: string;
  children?: React.ReactNode;
}
export function AccordionItem({ value, trigger, className, children, ...props }: AccordionItemProps) {
  const { expanded, toggle } = useContext(AccordionContext);
  const isOpen = expanded === value;
  const progress = useSharedValue(0);
  React.useEffect(() => {
    progress.value = withTiming(isOpen ? 1 : 0, { duration: 250 });
  }, [isOpen, progress]);
  const animatedStyle = useAnimatedStyle(() => ({
    height: progress.value === 0 ? 0 : undefined,
    opacity: progress.value,
    overflow: "hidden" as const,
  }));
  return (
    <View className={cn("border-b border-border", className)} {...props}>
      <Pressable
        className="flex-row items-center justify-between py-4 min-h-12"
        onPress={() => toggle(value)}
        accessible={true}
        accessibilityRole="button"
        accessibilityState={{ expanded: isOpen }}
      >
        <Text className="text-base font-medium text-foreground flex-1">{trigger}</Text>
        <Text className="text-muted-foreground text-lg">{isOpen ? "−" : "+"}</Text>
      </Pressable>
      <Animated.View style={animatedStyle}>
        <View className="pb-4">{children}</View>
      </Animated.View>
    </View>
  );
}