Installation
Install Qwik UI with your choice of package manager below:
Meta-framework guides
Building Blocks
Each component includes a building blocks
section, which provides a barebones example of the component. This can be used as a starting point for implementing the component from scratch.
Below is an example of the Accordion
component's building block:
import { component$ } from '@builder.io/qwik';
import { Accordion } from '@qwik-ui/headless';
export default component$(() => {
return (
<Accordion.Root>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>Title</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>Content</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
);
});
We will use this example as a Qwik UI component in each guide.
Qwik City
To create a Qwik city app, run the following command:
Let's select the empty app
option in the CLI to keep it simple. And remember, choosing the dad joke is not optional - it's mandatory! We have a highly sophisticated Dad Joke Detection System (DJDS) in place, so we'll know if you didn't!
Below is the Qwik City project structure. Inside of the src
directory, let's create a new folder called accordion
. Then, a file named accordion.tsx
inside of src/components
.
Qwik City files ├─ .eslintignore ├─ .eslintrc.cjs ├─ .gitignore ├─ .prettierignore ├─ README.md ├─ package-lock.json ├─ package.json ├─ public │ ├─ favicon.svg │ ├─ manifest.json │ └─ robots.txt ├─ src │ ├─ components │ │ └─ accordion │ │ └─ accordion.tsx │ │ └─ router-head │ │ └─ router-head.tsx │ ├─ entry.dev.tsx │ ├─ entry.preview.tsx │ ├─ entry.ssr.tsx │ ├─ global.css │ ├─ root.tsx │ └─ routes │ ├─ index.tsx │ ├─ layout.tsx │ └─ service-worker.ts ├─ tsconfig.json └─ vite.config.ts
Now, to create a component, you can simply copy/paste the code from the building blocks section, or the snippet provided in the code tab below 👇
import { component$ } from '@builder.io/qwik';
import { Accordion } from '@qwik-ui/headless';
export default component$(() => {
return (
<Accordion.Root>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>Click on me!</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>Content</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
);
});
Hooray! We've now added our first Qwik UI component in Qwik City. It includes a starting point where the accordion functionality and aria behavior is added for us.
Let's add a few styles to make the component stand out more, we'll use Tailwind CSS
as a styling solution.
Admiral Turbo Meowington
Edger Allen Paw
Captain Sushi
Fernsbane The Inquisitive
import { PropsOf, component$ } from '@builder.io/qwik';
import { Accordion } from '@qwik-ui/headless';
export default component$(() => {
const cats = [
'Admiral Turbo Meowington',
'Edger Allen Paw',
'Captain Sushi',
'Fernsbane The Inquisitive',
];
return (
<Accordion.Root class="box-border w-[250px] max-w-[500px] rounded-base border border-slate-500 bg-slate-600 text-white">
{cats.map((item, index) => (
<Accordion.Item class="w-full" key={index}>
<Accordion.Header as="h3">
<Accordion.Trigger
class={`group flex min-h-[44px] w-full items-center justify-between ${
index === 0 ? 'rounded-t-sm' : ''
} ${
index === cats.length - 1
? 'rounded-b-sm border-b-[0px]'
: 'border-b-[1px]'
} border-slate-500 bg-slate-600 px-4 py-2 text-left hover:bg-slate-700`}
>
<span>favorite cat {index + 1}</span>
<span class="pl-2">
<SVG class="ease transition-transform duration-500 group-aria-expanded:rotate-180 group-aria-expanded:transform" />
</span>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content class="accordion-animation-1 overflow-hidden">
<p class="bg-slate-900 p-4 ">{item}</p>
</Accordion.Content>
</Accordion.Item>
))}
</Accordion.Root>
);
});
export function SVG(props: PropsOf<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 1024 1024"
{...props}
>
<path
fill="currentColor"
d="M831.872 340.864L512 652.672L192.128 340.864a30.592 30.592 0 0 0-42.752 0a29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728a30.592 30.592 0 0 0-42.752 0z"
></path>
</svg>
);
}
Astro
To set up an Astro application, add the following command in the terminal:
Add a relative path for your project, and select the empty
option in the CLI.
Next, let's add the @qwikdev/astro integration. This integration allows us to leverage resumability and Qwik components inside of Astro.
Run the following command in the terminal:
Then, let's make sure we use Qwik as our main jsxImportSource in tsconfig.json
. Otherwise, it will not get the proper Qwik types.
Under the src
directory, let's create a folder called components
. Similar to the Qwik City guide, we'll create a folder named accordion
, and a file named accordion.tsx
.
Below is the project structure with this in place.
Astro files ├─ .gitignore ├─ README.md ├─ astro.config.mjs ├─ package-lock.json ├─ package.json ├─ public │ └─ favicon.svg ├─ src │ ├─ env.d.ts │ └─ pages │ ├─ components │ │ └─ accordion │ │ └─ accordion.tsx │ └─ index.astro └─ tsconfig.json
Once again, let's copy our building-blocks
example from above.
import { component$ } from '@builder.io/qwik';
import { Accordion } from '@qwik-ui/headless';
export default component$(() => {
return (
<Accordion.Root>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>Title</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>Content</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
);
});
import { component$ } from '@builder.io/qwik';
import { Accordion } from '@qwik-ui/headless';
export default component$(() => {
return (
<Accordion.Root>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>Click on me!</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>Content</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
);
});
And we'll spice it up a bit using some vanilla CSS.
Tom's bookshelf
import { PropsOf, component$, useStyles$ } from '@builder.io/qwik';
import { Accordion } from '@qwik-ui/headless';
export default component$(() => {
const dogs = ['Othello', 'The Hobbit', 'Dune', 'The Giver'];
useStyles$(`
.accordion-root {
box-sizing: border-box;
border-radius: 0.275rem;
color: #ffffff;
width: 250px;
max-width: 500px;
border: 1px solid #4B5563;
background-color: #4B5563;
}
.accordion-trigger {
display: flex;
min-height: 44px;
width: 100%;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem;
text-align: left;
border-bottom: 1px solid #374151;
background-color: #4B5563;
transition: background-color 200ms ease;
}
.accordion-trigger:hover {
background-color: #374151;
}
.accordion-trigger.rounded-t-sm {
border-top-left-radius: 0.125rem;
border-top-right-radius: 0.125rem;
}
.accordion-trigger.rounded-b-sm {
border-bottom-left-radius: 0.125rem;
border-bottom-right-radius: 0.125rem;
}
.accordion-trigger.border-b-0 {
border-bottom: 0;
}
.accordion-trigger[aria-expanded="true"] {
border-radius: 0;
}
.accordion-trigger svg {
transition: transform 500ms ease;
}
.accordion-trigger[aria-expanded="true"] svg {
transform: rotateX(180deg);
}
.accordion-content {
overflow: hidden;
background-color: #1F2937;
}
.accordion-slide[data-state='open'] {
animation: 500ms cubic-bezier(0.87, 0, 0.13, 1) 0s 1 normal forwards accordion-open;
}
.accordion-slide[data-state='closed'] {
animation: 500ms cubic-bezier(0.87, 0, 0.13, 1) 0s 1 normal forwards accordion-close;
}
.accordion-content p {
background: #1F2937;
padding: 0.5rem 1rem;
}
.accordion-item:last-of-type .accordion-content {
border-bottom-left-radius: 0.275rem;
border-bottom-right-radius: 0.275rem;
}
.tom-headline {
margin-bottom: 16px;
font-weight: 600;
}
`);
return (
<>
<h2 class="tom-headline">Tom's bookshelf</h2>
<Accordion.Root class="accordion-root">
{dogs.map((item, index) => (
<Accordion.Item class="accordion-item" key={index}>
<Accordion.Header as="h3">
<Accordion.Trigger
class={`accordion-trigger group ${index === 0 ? 'rounded-t-sm' : ''} ${
index === dogs.length - 1 ? 'rounded-b-sm border-b-0' : ''
}`}
>
<span>favorite book {index + 1}</span>
<span style={{ paddingLeft: '8px' }}>
<SVG />
</span>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content class="accordion-slide accordion-content">
<p>{item}</p>
</Accordion.Content>
</Accordion.Item>
))}
</Accordion.Root>
</>
);
});
export function SVG(props: PropsOf<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 1024 1024"
{...props}
>
<path
fill="currentColor"
d="M831.872 340.864L512 652.672L192.128 340.864a30.592 30.592 0 0 0-42.752 0a29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728a30.592 30.592 0 0 0-42.752 0z"
></path>
</svg>
);
}