Collapsible
Animated collapsible container that can be toggled open and closed.
This content can be shown or hidden.
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible";
export function MyScreen() {
return (
<Collapsible>
<CollapsibleTrigger>
<Text>Toggle content</Text>
</CollapsibleTrigger>
<CollapsibleContent>
<Text>This content can be shown or hidden.</Text>
</CollapsibleContent>
</Collapsible>
);
}Installation
npx @aniui/cli add collapsibleThis component requires react-native-reanimated for animations.
Usage
app/index.tsx
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible";
export function MyScreen() {
return (
<Collapsible>
<CollapsibleTrigger>
<Text>Toggle content</Text>
</CollapsibleTrigger>
<CollapsibleContent>
<Text>This content can be shown or hidden.</Text>
</CollapsibleContent>
</Collapsible>
);
}Controlled
Use the open and onOpenChange props for controlled behavior.
app/index.tsx
const [open, setOpen] = useState(false);
<Collapsible open={open} onOpenChange={setOpen}>
<CollapsibleTrigger>
<Text>{open ? "Hide" : "Show"} details</Text>
</CollapsibleTrigger>
<CollapsibleContent>
<Text>Controlled collapsible content.</Text>
</CollapsibleContent>
</Collapsible>Compound Components
ComponentDescription
CollapsibleRoot container managing open/closed state
CollapsibleTriggerPressable element that toggles the content
CollapsibleContentAnimated container for collapsible content
Props
Collapsible
PropTypeDefault
openbooleanfalse (uncontrolled)onOpenChange(open: boolean) => void—classNamestring—Source
components/ui/collapsible.tsx
import React, { createContext, useContext, useState } from "react";
import { View, Pressable } from "react-native";
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
import { cn } from "@/lib/utils";
const CollapsibleContext = createContext<{ isOpen: boolean; toggle: () => void }>({
isOpen: false,
toggle: () => {},
});
export interface CollapsibleProps extends React.ComponentPropsWithoutRef<typeof View> {
className?: string;
open?: boolean;
onOpenChange?: (open: boolean) => void;
children?: React.ReactNode;
}
export function Collapsible({ open: controlledOpen, onOpenChange, className, children, ...props }: CollapsibleProps) {
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
const isOpen = controlledOpen ?? uncontrolledOpen;
const toggle = () => {
const next = !isOpen;
setUncontrolledOpen(next);
onOpenChange?.(next);
};
return (
<CollapsibleContext.Provider value={{ isOpen, toggle }}>
<View className={cn("", className)} {...props}>{children}</View>
</CollapsibleContext.Provider>
);
}
export interface CollapsibleTriggerProps extends React.ComponentPropsWithoutRef<typeof Pressable> {
className?: string;
children?: React.ReactNode;
}
export function CollapsibleTrigger({ className, children, ...props }: CollapsibleTriggerProps) {
const { isOpen, toggle } = useContext(CollapsibleContext);
return (
<Pressable
className={cn("min-h-12 min-w-12", className)}
onPress={toggle}
accessible={true}
accessibilityRole="button"
accessibilityState={{ expanded: isOpen }}
{...props}
>
{children}
</Pressable>
);
}
export interface CollapsibleContentProps extends React.ComponentPropsWithoutRef<typeof View> {
className?: string;
children?: React.ReactNode;
}
export function CollapsibleContent({ className, children, ...props }: CollapsibleContentProps) {
const { isOpen } = useContext(CollapsibleContext);
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 (
<Animated.View style={animatedStyle}>
<View className={cn("", className)} {...props}>{children}</View>
</Animated.View>
);
}