Collapsible

Interactive component to show and hide content.

Prompt engineering is the practice of creating clear and effective instructions so an AI model understands exactly what you want. Common techniques include:

  • Few-shot: Providing examples to guide the model.
  • Chain-of-thought: Asking the model to reason step by step.
  • System prompts: Defining the assistant’s role and behavior.
  • Temperature: Adjusting creativity vs. precision.
You are an expert assistant specialized in...
collapsible-demo.tsx
1import {
2 Collapsible,
3 CollapsibleContent,
4 CollapsibleTrigger,
5} from '@/components/ui/collapsible';
6
7export function CollapsibleDemo() {
8 return (
9 <div className="space-y-3">
10 <Collapsible variant="bordered" defaultOpen>
11 <CollapsibleTrigger>
12 <span className="text-lg font-semibold">Prompt Engineering</span>
13 </CollapsibleTrigger>
14 <CollapsibleContent>
15 <div className="space-y-3 pt-3">
16 <p className="text-muted-foreground text-sm">
17 Prompt engineering is the practice of creating clear and effective instructions so an
18 AI model understands exactly what you want. Common techniques include:
19 </p>
20 <ul className="text-muted-foreground list-disc space-y-1 pl-5 text-sm">
21 <li>
22 <strong>Few-shot:</strong> Providing examples to guide the model.
23 </li>
24 <li>
25 <strong>Chain-of-thought:</strong> Asking the model to reason step by step.
26 </li>
27 <li>
28 <strong>System prompts:</strong> Defining the assistant’s role and behavior.
29 </li>
30 <li>
31 <strong>Temperature:</strong> Adjusting creativity vs. precision.
32 </li>
33 </ul>
34 <div className="pt-2">
35 <code className="bg-secondary rounded px-2 py-1 text-xs">
36 You are an expert assistant specialized in...
37 </code>
38 </div>
39 </div>
40 </CollapsibleContent>
41 </Collapsible>
42 <Collapsible variant="bordered">
43 <CollapsibleTrigger>
44 <span className="text-lg font-semibold">RAG (Retrieval-Augmented Generation)</span>
45 </CollapsibleTrigger>
46 <CollapsibleContent>
47 <div className="space-y-3 pt-3">
48 <p className="text-muted-foreground text-sm">
49 RAG combines a language model with an external knowledge base. The AI retrieves
50 information from your documents and then generates an answer based on those sources.
51 </p>
52 <ul className="text-muted-foreground list-disc space-y-1 pl-5 text-sm">
53 <li>Reduces hallucinations by grounding answers in real data.</li>
54 <li>Allows up-to-date information without retraining models.</li>
55 <li>Perfect for documentation, support assistants, and intelligent search.</li>
56 </ul>
57 </div>
58 </CollapsibleContent>
59 </Collapsible>
60 <Collapsible variant="bordered">
61 <CollapsibleTrigger>
62 <span className="text-lg font-semibold">AI SDKs & Model Providers</span>
63 </CollapsibleTrigger>
64 <CollapsibleContent>
65 <div className="space-y-3 pt-3">
66 <p className="text-muted-foreground text-sm">
67 AI SDKs make it easy to connect your application with large language models. They
68 simplify authentication, requests, streaming, and model selection.
69 </p>
70 <ul className="text-muted-foreground list-disc space-y-1 pl-5 text-sm">
71 <li>
72 <strong>Groq:</strong> Extremely fast inference, ideal for real-time apps.
73 </li>
74 <li>
75 <strong>OpenAI:</strong> Access to GPT models, embeddings, and multimodal features.
76 </li>
77 <li>
78 <strong>Anthropic:</strong> Known for Claude models with strong reasoning and
79 safety.
80 </li>
81 <li>
82 <strong>Vercel AI SDK:</strong> A friendly layer for building chat and AI features
83 in React and Next.js.
84 </li>
85 </ul>
86 <p className="text-muted-foreground pt-2 text-xs">
87 *Quick note:* These SDKs help developers focus on the product experience instead of
88 handling low-level API details.
89 </p>
90 </div>
91 </CollapsibleContent>
92 </Collapsible>
93 </div>
94 );
95}

Installation

Copy and paste the following code into your project.

collapsible-source.tsx
'use client';
import { cva } from 'class-variance-authority';
import { ChevronDown } from 'lucide-react';
import { AnimatePresence, HTMLMotionProps, motion } from 'motion/react';
import * as React from 'react';
import { cn } from '../../../lib/cn';
const collapsibleVariants = cva('', {
variants: {
variant: {
default: '',
bordered: 'rounded-lg border border-border',
card: 'rounded-lg border border-border bg-card shadow-sm',
},
},
defaultVariants: {
variant: 'default',
},
});
const collapsibleTriggerVariants = cva(
[
'flex w-full items-center justify-between',
'transition-all duration-200',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
'disabled:pointer-events-none disabled:opacity-50',
].join(' '),
{
variants: {
variant: {
default: 'hover:opacity-80',
bordered: 'p-4',
card: 'p-4',
},
},
defaultVariants: {
variant: 'default',
},
},
);
const collapsibleContentVariants = cva('overflow-hidden', {
variants: {
variant: {
default: '',
bordered: 'px-4 pb-4',
card: 'px-4 pb-4',
},
},
defaultVariants: {
variant: 'default',
},
});
interface CollapsibleContextValue {
isOpen: boolean;
setIsOpen: (open: boolean) => void;
disabled?: boolean;
variant?: 'default' | 'bordered' | 'card';
}
interface CollapsibleProps extends React.HTMLAttributes<HTMLDivElement> {
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
disabled?: boolean;
variant?: 'default' | 'bordered' | 'card';
}
interface CollapsibleTriggerProps extends Omit<HTMLMotionProps<'button'>, 'children'> {
showChevron?: boolean;
chevronIcon?: React.ReactNode;
chevronPosition?: 'left' | 'right';
asChild?: boolean;
children?: React.ReactNode;
}
interface CollapsibleContentProps extends Omit<HTMLMotionProps<'div'>, 'children'> {
forceMount?: boolean;
children?: React.ReactNode;
}
const CollapsibleContext = React.createContext<CollapsibleContextValue | null>(null);
const useCollapsibleContext = (): CollapsibleContextValue => {
const context = React.useContext(CollapsibleContext);
if (!context) {
throw new Error('Collapsible components must be used within Collapsible');
}
return context;
};
const Collapsible = React.forwardRef<HTMLDivElement, CollapsibleProps>(
(
{
className,
defaultOpen = false,
open: controlledOpen,
onOpenChange,
disabled = false,
variant = 'default',
children,
...props
},
ref,
) => {
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
const isControlled = controlledOpen !== undefined;
const isOpen = isControlled ? controlledOpen : internalOpen;
const setIsOpen = React.useCallback(
(open: boolean) => {
if (disabled) return;
if (!isControlled) {
setInternalOpen(open);
}
onOpenChange?.(open);
},
[disabled, isControlled, onOpenChange],
);
const contextValue = React.useMemo<CollapsibleContextValue>(
() => ({ isOpen, setIsOpen, disabled, variant }),
[isOpen, setIsOpen, disabled, variant],
);
return (
<CollapsibleContext.Provider value={contextValue}>
<div
ref={ref}
data-state={isOpen ? 'open' : 'closed'}
data-disabled={disabled ? '' : undefined}
className={cn(collapsibleVariants({ variant }), className)}
{...props}
>
{children}
</div>
</CollapsibleContext.Provider>
);
},
);
Collapsible.displayName = 'Collapsible';
const CollapsibleTrigger = React.forwardRef<HTMLButtonElement, CollapsibleTriggerProps>(
(
{
className,
showChevron = true,
chevronIcon,
chevronPosition = 'right',
asChild = false,
children,
onClick,
...props
},
ref,
) => {
const { isOpen, setIsOpen, disabled, variant } = useCollapsibleContext();
const handleClick = React.useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
if (!disabled) {
setIsOpen(!isOpen);
onClick?.(e);
}
},
[disabled, isOpen, setIsOpen, onClick],
);
const chevron = chevronIcon ?? <ChevronDown className="h-4 w-4 shrink-0" />;
const iconTransition = { type: 'spring', stiffness: 300, damping: 20 } as const;
if (asChild && React.isValidElement(children)) {
return React.cloneElement(children, {
onClick: handleClick,
'data-state': isOpen ? 'open' : 'closed',
'aria-expanded': isOpen,
disabled,
} as React.HTMLAttributes<HTMLElement>);
}
return (
<motion.button
ref={ref}
type="button"
onClick={handleClick}
disabled={disabled}
data-state={isOpen ? 'open' : 'closed'}
aria-expanded={isOpen}
whileHover={{ scale: 1.005, backgroundColor: 'rgba(0,0,0,0.02)' }}
whileTap={{ scale: 0.99 }}
className={cn(collapsibleTriggerVariants({ variant }), className)}
{...props}
>
{showChevron && chevronPosition === 'left' && (
<motion.span
aria-hidden="true"
animate={{ rotate: isOpen ? 90 : 0 }}
transition={iconTransition}
className="mr-2"
>
{chevron}
</motion.span>
)}
<span className="flex-1 text-left">{children}</span>
{showChevron && chevronPosition === 'right' && (
<motion.span
aria-hidden="true"
animate={{ rotate: isOpen ? 180 : 0 }}
transition={iconTransition}
className="ml-2"
>
{chevron}
</motion.span>
)}
</motion.button>
);
},
);
CollapsibleTrigger.displayName = 'CollapsibleTrigger';
const CollapsibleContent = React.forwardRef<HTMLDivElement, CollapsibleContentProps>(
({ className, forceMount = false, children, ...props }, ref) => {
const { isOpen, variant } = useCollapsibleContext();
return (
<AnimatePresence initial={false} mode="sync">
{(isOpen || forceMount) && (
<motion.div
ref={ref}
initial={{ height: 0, opacity: 0, filter: 'blur(10px)' }}
animate={{
height: 'auto',
opacity: 1,
filter: 'blur(0px)',
transition: {
height: { duration: 0.3, ease: [0.04, 0.62, 0.23, 0.98] },
opacity: { duration: 0.25, delay: 0.05 },
filter: { duration: 0.3 },
},
}}
exit={{
height: 0,
opacity: 0,
filter: 'blur(10px)',
transition: {
height: { duration: 0.25, ease: [0.04, 0.62, 0.23, 0.98] },
opacity: { duration: 0.15 }, // Desaparece rápido para no estorbar
filter: { duration: 0.2 },
},
}}
className={cn(collapsibleContentVariants({ variant }), className)}
{...props}
>
<motion.div
initial={{ y: -8, scale: 0.98 }}
animate={{
y: 0,
scale: 1,
transition: { duration: 0.3, ease: 'easeOut' },
}}
exit={{
y: -8,
scale: 0.98,
transition: { duration: 0.2 },
}}
>
{children}
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
},
);
CollapsibleContent.displayName = 'CollapsibleContent';
export {
Collapsible,
CollapsibleContent,
collapsibleContentVariants,
CollapsibleTrigger,
collapsibleTriggerVariants,
collapsibleVariants,
};
export type { CollapsibleContentProps, CollapsibleProps, CollapsibleTriggerProps };

Make sure to update the import paths to match your project structure.

Usage

import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
<Collapsible>
<CollapsibleTrigger>Can I use this in my project?</CollapsibleTrigger>
<CollapsibleContent>Yes. Free to use for personal and commercial projects.</CollapsibleContent>
</Collapsible>

Variants

Bordered

Collapsible with border and rounded corners.

A bordered collapsible adds a visual container around the content with a border and rounded corners, making it stand out from the surrounding content.

collapsible-bordered-demo.tsx
1import {
2 Collapsible,
3 CollapsibleContent,
4 CollapsibleTrigger,
5} from '@/components/ui/collapsible';
6
7export function CollapsibleBorderedDemo() {
8 return (
9 <div className="w-full max-w-md space-y-4">
10 <Collapsible variant="bordered" defaultOpen>
11 <CollapsibleTrigger>What is a bordered collapsible?</CollapsibleTrigger>
12 <CollapsibleContent>
13 <p>
14 A bordered collapsible adds a visual container around the content with a border and
15 rounded corners, making it stand out from the surrounding content.
16 </p>
17 </CollapsibleContent>
18 </Collapsible>
19
20 <Collapsible variant="bordered">
21 <CollapsibleTrigger>When should I use it?</CollapsibleTrigger>
22 <CollapsibleContent>
23 <p>
24 Use bordered collapsibles when you want to group related content visually or when you
25 need clear separation between different collapsible sections.
26 </p>
27 </CollapsibleContent>
28 </Collapsible>
29 </div>
30 );
31}

Card

Collapsible with card styling including border, background, and shadow.

The card variant provides the most visual prominence with a background color, border, and subtle shadow. It's perfect for important content that needs to stand out.

collapsible-card-demo.tsx
1import {
2 Collapsible,
3 CollapsibleContent,
4 CollapsibleTrigger,
5} from '@/components/ui/collapsible';
6
7export function CollapsibleCardDemo() {
8 return (
9 <div className="w-full max-w-md space-y-4">
10 <Collapsible variant="card" defaultOpen>
11 <CollapsibleTrigger>What makes the card variant special?</CollapsibleTrigger>
12 <CollapsibleContent>
13 <p>
14 The card variant provides the most visual prominence with a background color, border,
15 and subtle shadow. It's perfect for important content that needs to stand out.
16 </p>
17 </CollapsibleContent>
18 </Collapsible>
19
20 <Collapsible variant="card">
21 <CollapsibleTrigger>Best practices</CollapsibleTrigger>
22 <CollapsibleContent>
23 <p>
24 Use card variant sparingly for emphasis. Too many card-styled elements can make your
25 interface feel cluttered. Reserve it for primary information or key features.
26 </p>
27 </CollapsibleContent>
28 </Collapsible>
29 </div>
30 );
31}

Disabled

Collapsible in disabled state.

This collapsible started open but is now disabled. The content is visible but cannot be collapsed.

collapsible-disabled-demo.tsx
1import {
2 Collapsible,
3 CollapsibleContent,
4 CollapsibleTrigger,
5} from '@/components/ui/collapsible';
6
7export function CollapsibleDisabledDemo() {
8 return (
9 <div className="w-full max-w-md space-y-4">
10 <Collapsible variant="bordered" disabled>
11 <CollapsibleTrigger>Disabled Collapsible (Closed)</CollapsibleTrigger>
12 <CollapsibleContent>
13 <p>This content cannot be revealed because the collapsible is disabled.</p>
14 </CollapsibleContent>
15 </Collapsible>
16
17 <Collapsible variant="bordered" disabled defaultOpen>
18 <CollapsibleTrigger>Disabled Collapsible (Open)</CollapsibleTrigger>
19 <CollapsibleContent>
20 <p>
21 This collapsible started open but is now disabled. The content is visible but cannot be
22 collapsed.
23 </p>
24 </CollapsibleContent>
25 </Collapsible>
26 </div>
27 );
28}

No Chevron

Collapsible without indicator icon.

collapsible-no-chevron-demo.tsx
1import {
2 Collapsible,
3 CollapsibleContent,
4 CollapsibleTrigger,
5} from '@/components/ui/collapsible';
6
7export function CollapsibleNoChevronDemo() {
8 return (
9 <div className="w-full max-w-md space-y-4">
10 <Collapsible variant="bordered">
11 <CollapsibleTrigger showChevron={false}>Click me (No Chevron)</CollapsibleTrigger>
12 <CollapsibleContent>
13 <p>
14 This collapsible doesn't show a chevron icon. Useful when you want a cleaner look or
15 when the expandable nature is obvious from context.
16 </p>
17 </CollapsibleContent>
18 </Collapsible>
19
20 <Collapsible variant="card">
21 <CollapsibleTrigger showChevron={false} className="font-semibold">
22 Another Example
23 </CollapsibleTrigger>
24 <CollapsibleContent>
25 <p>
26 Without the chevron, the trigger can look like a simple heading or button, making it
27 more subtle in your design.
28 </p>
29 </CollapsibleContent>
30 </Collapsible>
31 </div>
32 );
33}

Custom Icon

Use a custom icon instead of the default chevron.

collapsible-custom-icon-demo.tsx
1import { Minus, Plus } from 'lucide-react';
2import {
3 Collapsible,
4 CollapsibleContent,
5 CollapsibleTrigger,
6} from '@/components/ui/collapsible';
7
8export function CollapsibleCustomIconDemo() {
9 return (
10 <div className="w-full max-w-md space-y-4">
11 <Collapsible variant="bordered">
12 <CollapsibleTrigger chevronIcon={<Plus className="h-4 w-4" />}>
13 Custom Plus Icon
14 </CollapsibleTrigger>
15 <CollapsibleContent>
16 <p>
17 This collapsible uses a plus icon instead of the default chevron. The icon rotates when
18 opened, creating a nice visual transition.
19 </p>
20 </CollapsibleContent>
21 </Collapsible>
22
23 <Collapsible variant="card">
24 <CollapsibleTrigger chevronIcon={<Minus className="h-4 w-4" />}>
25 Custom Minus Icon
26 </CollapsibleTrigger>
27 <CollapsibleContent>
28 <p>
29 You can use any icon you want! This example uses a minus icon to indicate collapsible
30 content.
31 </p>
32 </CollapsibleContent>
33 </Collapsible>
34 </div>
35 );
36}

API Reference

Collapsible

PropTypeDefaultDescription
defaultOpenbooleanfalseInitial open state (uncontrolled mode).
openbooleanControlled open state.
onOpenChange(open: boolean) => voidCallback fired when the open state changes.
disabledbooleanfalseDisables interaction.
variant'default' | 'bordered' | 'card''default'Visual style of the collapsible container.
classNamestringAdditional classes.
childrenReact.ReactNodeContent inside the collapsible.

CollapsibleTrigger

PropTypeDefaultDescription
showChevronbooleantrueShows the chevron icon.
chevronIconReact.ReactNodeCustom icon for the chevron.
chevronPosition'left' | 'right''right'Position of the chevron icon.
asChildbooleanfalseAllows passing a custom child element as the trigger.
childrenReact.ReactNodeTrigger label or content.
onClick(event: React.MouseEvent) => voidClick handler (merged with open/close logic).
classNamestringAdditional classes.

CollapsibleContent

PropTypeDefaultDescription
forceMountbooleanfalseRenders content even when closed (useful for SSR).
childrenReact.ReactNodeContent to be revealed/collapsed.
classNamestringAdditional classes.