Collapsible

Componente interactivo para mostrar y ocultar contenido.

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

Instalación

Copia y pega el siguiente código en tu proyecto.
'use client';
import { ArrowDown01Icon } from '@hugeicons/core-free-icons';
import { HugeiconsIcon } from '@hugeicons/react';
import { cva } from 'class-variance-authority';
import { AnimatePresence, HTMLMotionProps, motion, useReducedMotion } from 'motion/react';
import * as React from 'react';
import { cn } from '../lib/cn';
// --- Animation constants (module level) ---
const COLLAPSIBLE_ICON_TRANSITION = { type: 'spring', stiffness: 300, damping: 20 } as const;
const COLLAPSIBLE_HEIGHT_VARIANTS = {
open: {
height: 'auto',
opacity: 1,
filter: 'blur(0px)',
transition: {
height: { duration: 0.3, ease: [0.04, 0.62, 0.23, 0.98] as [number, number, number, number] },
opacity: { duration: 0.25, delay: 0.05 },
filter: { duration: 0.3 },
},
},
closed: {
height: 0,
opacity: 0,
filter: 'blur(10px)',
transition: {
height: {
duration: 0.25,
ease: [0.04, 0.62, 0.23, 0.98] as [number, number, number, number],
},
opacity: { duration: 0.15 },
filter: { duration: 0.2 },
},
},
} as const;
const COLLAPSIBLE_INNER_VARIANTS = {
open: { y: 0, scale: 1, transition: { duration: 0.3, ease: 'easeOut' } },
closed: { y: -8, scale: 0.98, transition: { duration: 0.2 } },
} as const;
const COLLAPSIBLE_CONTENT_STYLE = { willChange: 'height, opacity, filter' } as const;
// --- CVA ---
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',
},
});
// --- Context ---
interface CollapsibleContextValue {
isOpen: boolean;
setIsOpen: (open: boolean) => void;
disabled?: boolean;
variant?: 'default' | 'bordered' | 'card';
id: string;
}
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.use(CollapsibleContext);
if (!context) {
throw new Error('Collapsible components must be used within Collapsible');
}
return context;
};
// --- Components ---
const CollapsibleRoot = ({
className,
defaultOpen = false,
open: controlledOpen,
onOpenChange,
disabled = false,
variant = 'default',
children,
ref,
...props
}: CollapsibleProps & { ref?: React.Ref<HTMLDivElement> }) => {
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 id = React.useId();
const contextValue = React.useMemo<CollapsibleContextValue>(
() => ({ isOpen, setIsOpen, disabled, variant, id }),
[isOpen, setIsOpen, disabled, variant, id],
);
return (
<CollapsibleContext value={contextValue}>
<div
ref={ref}
data-state={isOpen ? 'open' : 'closed'}
data-disabled={disabled ? '' : undefined}
className={cn(collapsibleVariants({ variant }), className)}
{...props}
>
{children}
</div>
</CollapsibleContext>
);
};
CollapsibleRoot.displayName = 'Collapsible';
const CollapsibleTrigger = ({
className,
showChevron = true,
chevronIcon,
chevronPosition = 'right',
asChild = false,
children,
onClick,
ref,
...props
}: CollapsibleTriggerProps & { ref?: React.Ref<HTMLButtonElement> }) => {
const { isOpen, setIsOpen, disabled, variant, id } = useCollapsibleContext();
const shouldReduceMotion = useReducedMotion();
const handleClick = React.useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
if (!disabled) {
setIsOpen(!isOpen);
onClick?.(e);
}
},
[disabled, isOpen, setIsOpen, onClick],
);
const chevron = chevronIcon ?? (
<HugeiconsIcon icon={ArrowDown01Icon} className="h-4 w-4 shrink-0" size={16} />
);
if (asChild && React.isValidElement(children)) {
return React.cloneElement(children, {
onClick: handleClick,
'data-state': isOpen ? 'open' : 'closed',
'aria-expanded': isOpen,
'aria-controls': `${id}-content`,
disabled,
} as React.HTMLAttributes<HTMLElement>);
}
return (
<motion.button
ref={ref}
type="button"
onClick={handleClick}
disabled={disabled}
data-state={isOpen ? 'open' : 'closed'}
aria-expanded={isOpen}
aria-controls={`${id}-content`}
whileHover={
!disabled && !shouldReduceMotion
? { scale: 1.005, backgroundColor: 'rgba(0,0,0,0.02)' }
: {}
}
whileTap={!disabled && !shouldReduceMotion ? { scale: 0.99 } : {}}
className={cn(collapsibleTriggerVariants({ variant }), className)}
{...props}
>
{showChevron && chevronPosition === 'left' && (
<motion.span
aria-hidden="true"
animate={shouldReduceMotion ? undefined : { rotate: isOpen ? 90 : 0 }}
transition={COLLAPSIBLE_ICON_TRANSITION}
className="mr-2"
>
{chevron}
</motion.span>
)}
<span className="flex-1 text-left">{children}</span>
{showChevron && chevronPosition === 'right' && (
<motion.span
aria-hidden="true"
animate={shouldReduceMotion ? undefined : { rotate: isOpen ? 180 : 0 }}
transition={COLLAPSIBLE_ICON_TRANSITION}
className="ml-2"
>
{chevron}
</motion.span>
)}
</motion.button>
);
};
CollapsibleTrigger.displayName = 'CollapsibleTrigger';
const CollapsibleContent = ({
className,
forceMount = false,
children,
ref,
...props
}: CollapsibleContentProps & { ref?: React.Ref<HTMLDivElement> }) => {
const { isOpen, variant, id } = useCollapsibleContext();
return (
<AnimatePresence initial={false} mode="sync">
{(isOpen || forceMount) && (
<motion.div
ref={ref}
id={`${id}-content`}
variants={COLLAPSIBLE_HEIGHT_VARIANTS}
initial="closed"
animate="open"
exit="closed"
style={COLLAPSIBLE_CONTENT_STYLE}
className={cn(collapsibleContentVariants({ variant }), className)}
{...props}
>
<motion.div
variants={COLLAPSIBLE_INNER_VARIANTS}
initial="closed"
animate="open"
exit="closed"
>
{children}
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
};
CollapsibleContent.displayName = 'CollapsibleContent';
const Collapsible = Object.assign(CollapsibleRoot, {
Trigger: CollapsibleTrigger,
Content: CollapsibleContent,
});
export { Collapsible, collapsibleContentVariants, collapsibleTriggerVariants, collapsibleVariants };
export type { CollapsibleContentProps, CollapsibleProps, CollapsibleTriggerProps };
Asegúrate de actualizar las rutas de importación según la estructura de tu proyecto.

Anatomía

import { Collapsible } from '@/components/ui/collapsible';
<Collapsible>
<Collapsible.Trigger>¿Puedo usarlo en mi proyecto?</Collapsible.Trigger>
<Collapsible.Content>Sí. Es gratuito y de código abierto.</Collapsible.Content>
</Collapsible>

Ejemplos

Bordered

Collapsible con borde y esquinas redondeadas.

React is a JavaScript library for building user interfaces. It lets you create reusable components that manage their own state.

1'use client';
2
3import { Collapsible } from '@/components/ui/collapsible';
4
5export function Bordered() {
6 return (
7 <div className="space-y-3">
8 <Collapsible variant="bordered" defaultOpen>
9 <Collapsible.Trigger>
10 <span className="text-lg font-semibold">What is React?</span>
11 </Collapsible.Trigger>
12 <Collapsible.Content>
13 <div className="space-y-2 pt-3">
14 <p className="text-muted-foreground text-sm">
15 React is a JavaScript library for building user interfaces. It lets you create
16 reusable components that manage their own state.
17 </p>
18 </div>
19 </Collapsible.Content>
20 </Collapsible>
21
22 <Collapsible variant="bordered">
23 <Collapsible.Trigger>
24 <span className="text-lg font-semibold">What are React Hooks?</span>
25 </Collapsible.Trigger>
26 <Collapsible.Content>
27 <div className="space-y-2 pt-3">
28 <p className="text-muted-foreground text-sm">
29 Hooks are functions that let you use state and other React features in functional
30 components. Common hooks include useState, useEffect, and useContext.
31 </p>
32 </div>
33 </Collapsible.Content>
34 </Collapsible>
35
36 <Collapsible variant="bordered">
37 <Collapsible.Trigger>
38 <span className="text-lg font-semibold">What is JSX?</span>
39 </Collapsible.Trigger>
40 <Collapsible.Content>
41 <div className="space-y-2 pt-3">
42 <p className="text-muted-foreground text-sm">
43 JSX is a syntax extension for JavaScript that looks similar to HTML. It allows you to
44 write UI components in a more declarative way.
45 </p>
46 </div>
47 </Collapsible.Content>
48 </Collapsible>
49 </div>
50 );
51}

Card

Collapsible con estilo de tarjeta incluyendo borde, fondo y sombra.

Welcome to our platform! Here's everything you need to know to get started with your first project.

  • Create your account
  • Set up your workspace
  • Invite team members
  • Start building!
1'use client';
2
3import { Collapsible } from '@/components/ui/collapsible';
4
5export function Card() {
6 return (
7 <div className="space-y-3">
8 <Collapsible variant="card" defaultOpen>
9 <Collapsible.Trigger>
10 <span className="text-lg font-semibold">🚀 Getting Started</span>
11 </Collapsible.Trigger>
12 <Collapsible.Content>
13 <div className="space-y-2 pt-3">
14 <p className="text-muted-foreground text-sm">
15 Welcome to our platform! Here's everything you need to know to get started with your
16 first project.
17 </p>
18 <ul className="text-muted-foreground list-disc space-y-1 pl-5 text-sm">
19 <li>Create your account</li>
20 <li>Set up your workspace</li>
21 <li>Invite team members</li>
22 <li>Start building!</li>
23 </ul>
24 </div>
25 </Collapsible.Content>
26 </Collapsible>
27
28 <Collapsible variant="card">
29 <Collapsible.Trigger>
30 <span className="text-lg font-semibold">📚 Documentation</span>
31 </Collapsible.Trigger>
32 <Collapsible.Content>
33 <div className="space-y-2 pt-3">
34 <p className="text-muted-foreground text-sm">
35 Explore our comprehensive documentation to learn about all features and capabilities.
36 </p>
37 <div className="flex flex-wrap gap-2 pt-2">
38 <code className="bg-secondary rounded px-2 py-1 text-xs">API Reference</code>
39 <code className="bg-secondary rounded px-2 py-1 text-xs">Guides</code>
40 <code className="bg-secondary rounded px-2 py-1 text-xs">Examples</code>
41 </div>
42 </div>
43 </Collapsible.Content>
44 </Collapsible>
45
46 <Collapsible variant="card">
47 <Collapsible.Trigger>
48 <span className="text-lg font-semibold">⚙️ Settings</span>
49 </Collapsible.Trigger>
50 <Collapsible.Content>
51 <div className="space-y-2 pt-3">
52 <p className="text-muted-foreground text-sm">
53 Customize your experience with our flexible settings and preferences.
54 </p>
55 </div>
56 </Collapsible.Content>
57 </Collapsible>
58 </div>
59 );
60}

Referencia de API

Collapsible

PropTipoDefaultDescripción
defaultOpenbooleanfalseEstado inicial abierto (modo no controlado).
openbooleanEstado abierto controlado.
onOpenChange(open: boolean) => voidCallback que se ejecuta cuando cambia el estado abierto.
disabledbooleanfalseDesactiva la interacción.
variant'default' | 'bordered' | 'card''default'Estilo visual del contenedor collapsible.
classNamestringClases adicionales.
childrenReact.ReactNodeContenido dentro del collapsible.

Collapsible.Trigger

PropTipoDefaultDescripción
showChevronbooleantrueMuestra el ícono de chevron.
chevronIconReact.ReactNodeÍcono personalizado para el chevron.
chevronPosition'left' | 'right''right'Posición del ícono.
asChildbooleanfalsePermite usar un elemento personalizado como trigger.
childrenReact.ReactNodeTexto o contenido del trigger.
onClick(event: React.MouseEvent) => voidManejador de click (combinado con la lógica de abrir/cerrar).
classNamestringClases adicionales.

Collapsible.Content

PropTipoDefaultDescripción
forceMountbooleanfalseFuerza que se renderice incluso cuando está cerrado (útil para SSR).
childrenReact.ReactNodeContenido a mostrar u ocultar.
classNamestringClases adicionales.