Grid Layout Accordion
Grid Layout Accordion is a React and Tailwind CSS layout component from UI Layouts, licensed MIT. Copy it and paste it into your project.
UI Layouts
MIT
layout
What it is
Grid Layout Accordion is a React and Tailwind CSS layout component from UI Layouts, licensed MIT. Copy it and paste it into your project.
How to use it
Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.
Five free copies a month. No card.
No packages beyond the project baseline
Licence
This component comes from UI Layouts and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.
Grid Layout Accordion
'use client';
import {
Accordion,
AccordionContainer,
AccordionHeader,
AccordionItem,
AccordionPanel,
AccordionWrapper,
} from '@/components/ui/accordion';
import React from 'react';
function SingleLayout() {
return (
<AccordionContainer className='md:grid-cols-2 grid-cols-1'>
<AccordionWrapper>
<Accordion defaultValue={'item-2'}>
<AccordionItem value='item-1' className='dark:bg-black bg-white'>
<AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
What is a UI component?
</AccordionHeader>
<AccordionPanel className='2xl:text-base text-sm'>
A UI (User Interface) component is a modular, reusable element that serves a specific
function within a graphical user interface. Examples include buttons, input fields,
dropdown menus, sliders.
</AccordionPanel>
</AccordionItem>
<AccordionItem value='item-2' className='dark:bg-black bg-white'>
<AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
Why are components important?
</AccordionHeader>
<AccordionPanel className='2xl:text-base text-sm'>
UI components promote consistency, efficiency, and scalability in software
development. They allow developers to reuse code, maintain a consistent look and feel
across an application.
</AccordionPanel>
</AccordionItem>
<AccordionItem value='item-3' className='dark:bg-black bg-white'>
<AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
UI Component Traits
</AccordionHeader>
<AccordionPanel className='2xl:text-base text-sm'>
Well-designed UI components should be modular, customizable, and accessible. They
should have clear and intuitive functionality, be easily styled to match the overall
design language.
</AccordionPanel>
</AccordionItem>
</Accordion>
</AccordionWrapper>
<AccordionWrapper>
<Accordion defaultValue={'item-4'}>
<AccordionItem value='item-4' className='dark:bg-black bg-white'>
<AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
Does Component Improve UX?
</AccordionHeader>
<AccordionPanel className='2xl:text-base text-sm'>
UI components can improve UX by providing familiar, consistent interactions that make
it easy for users to navigate and interact with an application byy using recognizable
patterns.
</AccordionPanel>
</AccordionItem>
<AccordionItem value='item-5' className='dark:bg-black bg-white'>
<AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
component design challenges?
</AccordionHeader>
<AccordionPanel className='2xl:text-base text-sm'>
Some common challenges include maintaining consistency across different devices and
screen sizes, ensuring compatibility with various browsers and assistive technologies
with ease of use.
</AccordionPanel>
</AccordionItem>
<AccordionItem value='item-6' className='dark:bg-black bg-white'>
<AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
Ensure Responsiveness
</AccordionHeader>
<AccordionPanel className='2xl:text-base text-sm'>
Developers can ensure the responsiveness of UI components by using techniques such as
fluid layouts, flexible grids, and media queries to adapt the components to different
screen sizes.
</AccordionPanel>
</AccordionItem>
</Accordion>
</AccordionWrapper>
</AccordionContainer>
);
}
export default SingleLayout;
'use client';
import { cn } from '@/lib/utils';
import { ChevronDown } from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';
import React, { type ReactNode, useCallback } from 'react';
/**
* Interface for AccordionContext values
*/
interface AccordionContextType {
/**
* Whether the accordion item is active
*/
isActive?: boolean;
/**
* The value of the accordion item
*/
value?: string;
/**
* Function to change the active index
*/
onChangeIndex?: (value: string) => void;
}
/**
* Context for accordion components
*/
const AccordionContext = React.createContext<AccordionContextType>({});
/**
* Hook to use the accordion context
*/
const useAccordion = () => React.useContext(AccordionContext);
/**
* Container component for accordion items
*/
export function AccordionContainer({
children,
className,
}: {
children: ReactNode;
className?: string;
}) {
return <div className={cn('grid grid-cols-2 gap-1', className)}>{children}</div>;
}
/**
* Wrapper component for accordion items
*/
export function AccordionWrapper({ children }: { children: ReactNode }) {
return <div>{children}</div>;
}
/**
* Interface for Accordion props
*/
interface AccordionProps {
/**
* Children components
*/
children: ReactNode;
/**
* Whether multiple items can be active at the same time
*/
multiple?: boolean;
/**
* Default active index
*/
defaultValue?: string | string[];
}
/**
* Accordion component
*/
export function Accordion({ children, multiple, defaultValue }: AccordionProps) {
/**
* State for active index
*/
const [activeIndex, setActiveIndex] = React.useState<string | string[] | null>(
multiple ? (Array.isArray(defaultValue) ? defaultValue : []) : defaultValue || null
);
/**
* Function to change the active index
*/
const onChangeIndex = useCallback(
(value: string) => {
setActiveIndex((currentActiveIndex) => {
if (!multiple) {
return value === currentActiveIndex ? null : value;
}
if (Array.isArray(currentActiveIndex)) {
if (currentActiveIndex.includes(value)) {
return currentActiveIndex.filter((i) => i !== value);
}
return [...currentActiveIndex, value];
}
return [value];
});
},
[multiple]
);
return React.Children.map(children, (child) => {
if (!React.isValidElement(child)) return null;
const childProps = child.props as { value: string };
const value = childProps.value;
const isActive = multiple
? Array.isArray(activeIndex) && activeIndex.includes(value)
: activeIndex === value;
return (
<AccordionContext.Provider value={{ isActive, value, onChangeIndex }}>
{child}
</AccordionContext.Provider>
);
});
}
/**
* Interface for AccordionItem props
*/
interface AccordionItemProps {
/**
* Children components
*/
children: ReactNode;
/**
* Value of the accordion item
*/
value: string;
className?: string;
}
/**
* Accordion item component
*/
export function AccordionItem({ children, value, className }: AccordionItemProps) {
const { isActive } = useAccordion();
return (
<div
data-active={isActive || undefined}
className={cn(
'rounded-lg overflow-hidden mb-2 group border border-neutral-200 dark:border-neutral-800',
className
)}
>
{children}
</div>
);
}
/**
* Interface for AccordionHeader props
*/
interface AccordionHeaderProps {
/**
* Children components
*/
children: ReactNode;
/**
* Icon component
*/
customIcon?: boolean;
className?: string;
}
/**
* Accordion header component
*/
export function AccordionHeader({ children, customIcon, className }: AccordionHeaderProps) {
const { isActive, value, onChangeIndex } = useAccordion();
const handleClick = useCallback(() => {
if (value && onChangeIndex) {
onChangeIndex(value);
}
}, [onChangeIndex, value]);
return (
<motion.button
type='button'
data-active={isActive || undefined}
aria-expanded={isActive}
className={cn(
'p-4 cursor-pointer w-full transition-all font-semibold text-neutral-500 dark:data-active:text-neutral-200 data-active:text-neutral-800 dark:data-active:bg-neutral-800 data-active:bg-neutral-200 hover:bg-neutral-100 hover:text-black flex justify-between gap-2 items-center text-left',
className
)}
onClick={handleClick}
>
{children}
{!customIcon && (
<ChevronDown
className={cn(
'transition-transform shrink-0 text-neutral-500 dark:text-neutral-400',
isActive ? 'rotate-180' : 'rotate-0'
)}
aria-hidden='true'
/>
)}
</motion.button>
);
}
/**
* Interface for AccordionPanel props
*/
interface AccordionPanelProps {
/**
* Children components
*/
children: ReactNode;
/**
* className
*/
className?: string;
/**
* article className
*/
articleClassName?: string;
}
/**
* Accordion panel component
*/
export function AccordionPanel({ children, className, articleClassName }: AccordionPanelProps) {
const { isActive, value } = useAccordion();
return (
<AnimatePresence initial={true}>
{isActive && (
<motion.div
data-active={isActive || undefined}
role='region'
id={`accordion-panel-${value}`}
aria-labelledby={`accordion-header-${value}`}
initial={{ height: 0, overflow: 'hidden' }}
animate={{ height: 'auto', overflow: 'hidden' }}
exit={{ height: 0 }}
transition={{ type: 'spring', duration: 0.3, bounce: 0 }}
className={cn(
'bg-neutral-100 dark:bg-neutral-900 px-2 data-active:bg-neutral-200 dark:data-active:bg-neutral-800 text-black dark:text-white',
className
)}
>
<motion.div
initial={{ clipPath: 'polygon(0 0, 100% 0, 100% 0, 0 0)' }}
animate={{ clipPath: 'polygon(0 0, 100% 0, 100% 100%, 0% 100%)' }}
exit={{
clipPath: 'polygon(0 0, 100% 0, 100% 0, 0 0)',
}}
transition={{
type: 'spring',
duration: 0.4,
bounce: 0,
}}
className={cn('px-3 bg-transparent pb-4 space-y-2', articleClassName)}
>
{children}
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}