Accordion
A vertically stacked list of headers that reveal or hide associated sections of content.
This component is still in Beta status. This means that it is ready for production, but the API might change.
Yes, if you wrap the AccordionHeader component around the trigger, screen readers will announce it properly
Yup! You can even use animations or CSS transitions using the animated prop on the accordion root!
You can do that by setting the behavior prop to "multi" on the Accordion
"Qwik UI's Accordion implementation follows the WAI-Aria design pattern, along with some additional API's that enhance the flexibility, types, and performance."
Features
Provided by Qwik UI.
- ✨ Full keyboard navigation
- ✨ Single or Multi Accordion
- ✨ Controlled or uncontrolled
- ✨ Animatable, dynamic, and resumable
Building blocks
accordion.tsx
import { component$ } from "@builder.io/qwik";
import { Accordion } from 'senzui'
const Component = component$(() => {
return (
<Accordion>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger></Accordion.Trigger>
</Accordion.Header>
<Accordion.Content></Accordion.Content>
</Accordion.Item>
</Accordion>
)
})
Anatomy
| Component | Description |
|---|---|
Accordion | The root container for the accordion. |
Accordion.Item | An item for accordion, inside the root. It contains the item's heading, trigger, and content. |
Accordion.Header | An HTML heading element. The default is set to h3, can be changed with the as prop. |
Accordion.Trigger | Toggles the corresponding content when clicked or activated. It should
be placed within the |
Accordion.Content | Contains the content associated with an item when clicking on its respective trigger. |
API
You can read more about this component's API on Qwik UI's documentation.
Manual install
If you only want this styled component to be installed, follow these steps:
Install dependencies:
pnpm add clsx tailwind-merge @qwik-ui/headless
Add the following code to your components/accordion.tsx:
import {
Slot,
type QwikIntrinsicElements,
component$,
useStylesScoped$,
} from "@builder.io/qwik";
import {
AccordionItem,
AccordionRoot,
AccordionTrigger,
AccordionContent,
AccordionHeader,
} from "@qwik-ui/headless";
import type {
AccordionHeaderProps,
AccordionTriggerProps,
AccordionRootProps,
AccordionItemProps,
} from "@qwik-ui/headless";
import twMerge from "tailwind-merge";
import clsx from "clsx";
function sz(...classes) {
return twMerge(clsx(...classes));
}
type StyledAccordionRootProps = {
defaultAnimation?: boolean;
} & AccordionRootProps;
const StyledAccordionRoot = ({ ...props }: StyledAccordionRootProps) => {
const {
defaultAnimation = false,
class: className,
animated,
...rest
} = props;
return (
<AccordionRoot
animated={animated}
class={sz(
animated &&
defaultAnimation &&
"[&>div>div[role=region][data-state=closed]]:[animation:.5s_cubic-bezier(.87,0,.13,1)_0s_1_normal_forwards_accordion-close] [&>div>div[role=region][data-state=open]]:[animation:.5s_cubic-bezier(.87,0,.13,1)_0s_1_normal_forwards_accordion-open]",
className
)}
{...rest}
/>
);
};
const StyledAccordionItem = ({ ...props }: AccordionItemProps) => {
const { class: classNames, ...rest } = props;
return (
<AccordionItem
class={sz(
"border-b border-senzui-150 pb-1 pt-3 last:border-b-0 dark:border-senzui-750 dark:text-senzui-200",
classNames
)}
{...rest}
/>
);
};
const StyledAccordionTrigger = ({ ...props }: AccordionTriggerProps) => {
const { class: classNames, ...rest } = props;
return (
<AccordionTrigger
class={sz(
"group mb-2 flex w-full items-center justify-between overflow-hidden text-senzui-650 outline-none outline hover:text-senzui-900 focus:font-medium focus:text-senzui-900 focus-visible:outline-theme dark:text-senzui-200 dark:hover:text-senzui-50 dark:focus:text-senzui-50 dark:focus-visible:outline-theme",
classNames
)}
{...rest}
/>
);
};
const StyledAccordionContent = component$(
({ ...props }: QwikIntrinsicElements["div"]) => {
const { class: classNames, ...rest } = props;
useStylesScoped$(`
[hidden] {
display: none
}
`);
return (
<AccordionContent
class={sz(
"rounded-md-md overflow-hidden text-sm text-senzui-500 dark:text-senzui-450",
classNames
)}
{...rest}
>
<div class="pb-4 pt-1">
<Slot />
</div>
</AccordionContent>
);
}
);
const StyledAccordionHeader = ({ ...props }: AccordionHeaderProps) => {
return <AccordionHeader {...props} />;
};
const Accordion = StyledAccordionRoot as typeof StyledAccordionRoot & {
Item: typeof StyledAccordionItem;
Trigger: typeof StyledAccordionTrigger;
Content: typeof StyledAccordionContent;
Header: typeof StyledAccordionHeader;
};
Accordion.Item = StyledAccordionItem;
Accordion.Trigger = StyledAccordionTrigger;
Accordion.Content = StyledAccordionContent;
Accordion.Header = StyledAccordionHeader;
export default Accordion;