Want to survive 12-hour coding sessions? Always keep snacks nearby and caffeine on standby. Bonus points for comfy socks and a chair that doesn’t destroy your back.
Remember: comments are your friend. Future you will thank past you for writing clear notes.
1import {2 Accordion,3 AccordionContent,4 AccordionItem,5 AccordionTrigger,6} from '@/components/ui/accordion';78export function AccordionDemo() {9 return (10 <Accordion type="single" className="w-full" defaultValue="item-1">11 <AccordionItem value="item-1">12 <AccordionTrigger>Life Hacks for Coders</AccordionTrigger>13 <AccordionContent className="flex flex-col gap-4 text-balance">14 <p>15 Want to survive 12-hour coding sessions? Always keep snacks nearby and caffeine on16 standby. Bonus points for comfy socks and a chair that doesn’t destroy your back.17 </p>18 <p>19 Remember: comments are your friend. Future you will thank past you for writing clear20 notes.21 </p>22 </AccordionContent>23 </AccordionItem>24 <AccordionItem value="item-2">25 <AccordionTrigger>Debugging Secrets</AccordionTrigger>26 <AccordionContent className="flex flex-col gap-4 text-balance">27 <p>28 Debugging is basically detective work, but your suspects are lines of code. Breakpoints29 are your magnifying glass.30 </p>31 <p>32 Pro tip: if it compiles but doesn’t work, stare at the screen, whisper “why won’t you33 work?,” then Google like your life depends on it.34 </p>35 </AccordionContent>36 </AccordionItem>37 <AccordionItem value="item-3">38 <AccordionTrigger>Random Productivity Tips</AccordionTrigger>39 <AccordionContent className="flex flex-col gap-4 text-balance">40 <p>41 Sometimes the best way to get code done is to step away. Take a walk, pet your cat, or42 pretend to meditate.43 </p>44 <p>And remember: Ctrl+S is life. Save often, panic never.</p>45 </AccordionContent>46 </AccordionItem>47 </Accordion>48 );49}
Instalación
Copia y pega el siguiente código en tu proyecto.
'use client';import { AnimatePresence, motion } from 'motion/react';import * as React from 'react';import { cn } from '../../../lib/cn';interface AccordionProps {type?: 'single' | 'multiple';defaultValue?: string;children?: React.ReactNode;className?: string;}function Accordion({ type = 'single', defaultValue, children, className }: AccordionProps) {const [openItems, setOpenItems] = React.useState<string[]>(defaultValue ? [defaultValue] : []);const handleToggle = (value: string) => {setOpenItems((prev) =>type === 'single'? prev.includes(value)? []: [value]: prev.includes(value)? prev.filter((item) => item !== value): [...prev, value],);};return (<div className={cn('w-full space-y-2', className)}>{React.Children.map(children, (child) =>React.isValidElement(child)? React.cloneElement(child, {isOpen: openItems.includes((child as React.ReactElement<AccordionItemProps>).props.value,),onToggle: () =>handleToggle((child as React.ReactElement<AccordionItemProps>).props.value),} as Partial<AccordionItemProps>): child,)}</div>);}interface AccordionItemProps {value: string;children?: React.ReactNode;isOpen?: boolean;onToggle?: () => void;className?: string;}function AccordionItem({ children, isOpen, onToggle, className }: AccordionItemProps) {return (<div className={cn('overflow-hidden border-b border-white/5 last:border-0', className)}>{React.Children.map(children, (child) =>React.isValidElement(child)? React.cloneElement(child as React.ReactElement<AccordionItemProps>, {isOpen,onToggle,}): child,)}</div>);}interface AccordionTriggerProps {children?: React.ReactNode;isOpen?: boolean;onToggle?: () => void;className?: string;}function AccordionTrigger({ children, isOpen, onToggle, className }: AccordionTriggerProps) {return (<motion.buttononClick={onToggle}type="button"// Eliminado el whileHover para que no se mueva al pasar el mousewhileTap={{ scale: 0.98 }}className={cn('group flex w-full items-center justify-between py-4 text-left text-sm font-medium transition-all hover:underline', // Volvimos al hover clásico de underlineclassName,)}>{children}<motion.svgxmlns="http://www.w3.org/2000/svg"width="24"height="24"viewBox="0 0 24 24"fill="none"stroke="currentColor"strokeWidth="2"strokeLinecap="round"strokeLinejoin="round"className="text-muted-foreground group-hover:text-primary h-4 w-4 shrink-0 transition-colors"animate={{rotate: isOpen ? 180 : 0,}}transition={{duration: 0.3,type: 'spring',stiffness: 200,damping: 15,}}><path d="m6 9 6 6 6-6" /></motion.svg></motion.button>);}interface AccordionContentProps {children?: React.ReactNode;isOpen?: boolean;className?: string;}function AccordionContent({ children, isOpen, className }: AccordionContentProps) {return (<AnimatePresence initial={false} mode="wait">{isOpen && (<motion.divkey="content"initial={{ height: 0 }}animate={{ height: 'auto' }}exit={{ height: 0 }}transition={{height: {duration: 0.3,ease: [0.04, 0.62, 0.23, 0.98],},}}className={cn('overflow-hidden text-sm', className)}><motion.div// Mantenemos la animación de blur y opacidad al abrirseinitial={{y: -15,opacity: 0,filter: 'blur(6px)',}}animate={{y: 0,opacity: 1,filter: 'blur(0px)',}}exit={{y: -15,opacity: 0,filter: 'blur(6px)',transition: { duration: 0.2, ease: 'easeIn' },}}transition={{duration: 0.35,ease: 'easeOut',}}className="text-muted-foreground pt-0 pb-4">{children}</motion.div></motion.div>)}</AnimatePresence>);}export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
Asegúrate de actualizar las rutas de importación según la estructura de tu proyecto.
Modo de uso
import {Accordion,AccordionContent,AccordionItem,AccordionTrigger,} from '@/components/ui/accordion';
<Accordion type="single" defaultValue="item-1"><AccordionItem value="item-1"><AccordionTrigger>¿Es accesible?</AccordionTrigger><AccordionContent>Sí. Se adhiere al estándar de diseño WAI-ARIA.</AccordionContent></AccordionItem></Accordion>
Ejemplos
Single
El tipo single permite que solo un ítem esté abierto a la vez.
Want to survive 12-hour coding sessions? Always keep snacks nearby and caffeine on standby. Bonus points for comfy socks and a chair that doesn’t destroy your back.
Remember: comments are your friend. Future you will thank past you for writing clear notes.
1import {2 Accordion,3 AccordionContent,4 AccordionItem,5 AccordionTrigger,6} from '@/components/ui/accordion';78export function AccordionDemo() {9 return (10 <Accordion type="single" className="w-full" defaultValue="item-1">11 <AccordionItem value="item-1">12 <AccordionTrigger>Life Hacks for Coders</AccordionTrigger>13 <AccordionContent className="flex flex-col gap-4 text-balance">14 <p>15 Want to survive 12-hour coding sessions? Always keep snacks nearby and caffeine on16 standby. Bonus points for comfy socks and a chair that doesn’t destroy your back.17 </p>18 <p>19 Remember: comments are your friend. Future you will thank past you for writing clear20 notes.21 </p>22 </AccordionContent>23 </AccordionItem>24 <AccordionItem value="item-2">25 <AccordionTrigger>Debugging Secrets</AccordionTrigger>26 <AccordionContent className="flex flex-col gap-4 text-balance">27 <p>28 Debugging is basically detective work, but your suspects are lines of code. Breakpoints29 are your magnifying glass.30 </p>31 <p>32 Pro tip: if it compiles but doesn’t work, stare at the screen, whisper “why won’t you33 work?,” then Google like your life depends on it.34 </p>35 </AccordionContent>36 </AccordionItem>37 <AccordionItem value="item-3">38 <AccordionTrigger>Random Productivity Tips</AccordionTrigger>39 <AccordionContent className="flex flex-col gap-4 text-balance">40 <p>41 Sometimes the best way to get code done is to step away. Take a walk, pet your cat, or42 pretend to meditate.43 </p>44 <p>And remember: Ctrl+S is life. Save often, panic never.</p>45 </AccordionContent>46 </AccordionItem>47 </Accordion>48 );49}
Multiple
El tipo multiple permite que múltiples ítems estén abiertos simultáneamente.
1import {2 Accordion,3 AccordionContent,4 AccordionItem,5 AccordionTrigger,6} from '@/components/ui/accordion';78export function AccordionMultipleDemo() {9 return (10 <Accordion type="multiple" className="w-full">11 <AccordionItem value="item-1">12 <AccordionTrigger>Can I open multiple items?</AccordionTrigger>13 <AccordionContent>14 Yes! When using type="multiple", you can have multiple accordion items open at the same15 time.16 </AccordionContent>17 </AccordionItem>18 <AccordionItem value="item-2">19 <AccordionTrigger>How does it work?</AccordionTrigger>20 <AccordionContent>21 Simply set the type prop to "multiple" and users can expand as many sections as they want22 simultaneously.23 </AccordionContent>24 </AccordionItem>25 <AccordionItem value="item-3">26 <AccordionTrigger>Is this useful?</AccordionTrigger>27 <AccordionContent>28 Absolutely! It's perfect for FAQ sections where users might want to compare multiple29 answers at once.30 </AccordionContent>31 </AccordionItem>32 </Accordion>33 );34}
Sin valor por defecto
Puedes omitir defaultValue para que el acordeón inicie completamente cerrado.
1import {2 Accordion,3 AccordionContent,4 AccordionItem,5 AccordionTrigger,6} from '@/components/ui/accordion';78export function AccordionCollapsedDemo() {9 return (10 <Accordion type="single" className="w-full">11 <AccordionItem value="item-1">12 <AccordionTrigger>Will it start closed?</AccordionTrigger>13 <AccordionContent>14 Yes! When you don't provide a defaultValue prop, all items start in a collapsed state.15 </AccordionContent>16 </AccordionItem>17 <AccordionItem value="item-2">18 <AccordionTrigger>Can users still open items?</AccordionTrigger>19 <AccordionContent>20 Of course! Users can click any trigger to expand the content. It just starts fully21 collapsed.22 </AccordionContent>23 </AccordionItem>24 <AccordionItem value="item-3">25 <AccordionTrigger>When is this useful?</AccordionTrigger>26 <AccordionContent>27 This is great when you want users to actively choose what information they want to see,28 keeping the interface clean initially.29 </AccordionContent>30 </AccordionItem>31 </Accordion>32 );33}
Referencia de API
Accordion
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
type | "single" | "multiple" | "single" | Controla si se puede abrir un solo ítem o varios simultáneamente. |
defaultValue | string | undefined | Ítem inicial abierto. |
children | React.ReactNode | — | Deben ser AccordionItem. |
className | string | — | Clases adicionales para el contenedor. |
AccordionItem
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
value | string | — | ID único del ítem. Obligatorio. |
children | React.ReactNode | — | Debe incluir AccordionTrigger y AccordionContent. |
isOpen | boolean | Controlado interno | Indica si el ítem está abierto. |
onToggle | () => void | Controlado interno | Función para abrir/cerrar. |
className | string | — | Clases adicionales para el wrapper. |
AccordionTrigger
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
children | React.ReactNode | — | Título o contenido del botón. |
isOpen | boolean | Controlado interno | Estado abierto/cerrado (rota el ícono). |
onToggle | () => void | Controlado interno | Acción al hacer click. |
className | string | — | Clases adicionales del botón. |
AccordionContent
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
children | React.ReactNode | — | Contenido colapsable. |
isOpen | boolean | Controlado interno | Controla apertura y animación. |
className | string | — | Clases adicionales para el contenedor animado. |