Accordion

Rust/UI component that displays an Accordion.

accordion
  • Rust/UI Icons - CopyCopy Demo

This is the Accordion description

This is the Accordion description

This is the Accordion description

use leptos::prelude::*;

use crate::components::ui::accordion::{
    Accordion, AccordionContent, AccordionDescription, AccordionItem, AccordionTitle, AccordionTrigger,
};

#[component]
pub fn DemoAccordion() -> impl IntoView {
    view! {
        <Accordion class="max-w-[400px]">
            <AccordionItem>
                <AccordionTrigger open=true>
                    <AccordionTitle>Accordion item 01</AccordionTitle>
                </AccordionTrigger>
                <AccordionContent class="pt-0">
                    <AccordionDescription>"This is the Accordion description"</AccordionDescription>
                </AccordionContent>
            </AccordionItem>
            <AccordionItem>
                <AccordionTrigger>
                    <AccordionTitle>Accordion item 02</AccordionTitle>
                </AccordionTrigger>
                <AccordionContent class="pt-0">
                    <AccordionDescription>"This is the Accordion description"</AccordionDescription>
                </AccordionContent>
            </AccordionItem>
            <AccordionItem>
                <AccordionTrigger>
                    <AccordionTitle>Accordion item 03</AccordionTitle>
                </AccordionTrigger>
                <AccordionContent class="pt-0">
                    <AccordionDescription>"This is the Accordion description"</AccordionDescription>
                </AccordionContent>
            </AccordionItem>
        </Accordion>
    }
}

Installation

You can run either of the following commands:

# cargo install ui-cli --force
ui add demo_accordion
ui add accordion

Update the imports to match your project setup.

Copy and paste the following code into your project:

components/ui/accordion.rs

use icons::ChevronDown;
use leptos::prelude::*;
use leptos_ui::clx;
use tw_merge::*;

use crate::registry::hooks::use_random::use_random_id;
mod components {
    use super::*;
    clx! {Accordion, div, "divide-y divide-input w-full"}
    clx! {AccordionItem, div, "w-full [&:has(>input:checked)>label>svg:last-child]:rotate-180"}
    clx! {AccordionTitle, h4, "text-sm font-medium"}
    clx! {AccordionHeader, div, "flex gap-2 items-center [&_svg:not([class*='size-'])]:size-4"}
    clx! {RootContent, article, "grid overflow-hidden transition-all duration-400 grid-rows-[0fr] peer-checked:grid-rows-[1fr]"}
    clx! {AccordionDescription, p, "text-muted-foreground text-sm"}
    clx! {AccordionLink, a, "grid gap-2.5 items-center p-2 grid-cols-[auto_1fr] [&_svg:not([class*='size-'])]:size-4 hover:bg-muted"}
}

pub use components::*;

/* ========================================================== */
/*                     ✨ FUNCTIONS ✨                        */
/* ========================================================== */

#[component]
pub fn AccordionContent(#[prop(into, optional)] class: String, children: Children) -> impl IntoView {
    let merged_class = tw_merge!("p-3 pt-0", class);

    view! {
        <RootContent>
            // * Used for the animation using grid CSS trick.
            <div data-name="__AccordionContentInner" class="min-h-[0]">
                <div class=merged_class>{children()}</div>
            </div>
        </RootContent>
    }
}

/* ========================================================== */
/*                     ✨ FUNCTIONS ✨                        */
/* ========================================================== */

#[derive(Default)]
pub enum AccordionTriggerIcon {
    #[default]
    ChevronDown,
    Plus,
}

#[component]
pub fn AccordionTrigger(
    #[prop(into, optional)] class: String,
    #[prop(default = false)] open: bool,
    // TODO. AccrodionTriggerIcon
    children: Children,
) -> impl IntoView {
    let accordion_id = use_random_id();
    let label_class = tw_merge!(
        "flex justify-between items-center p-3 list-none cursor-pointer [&_svg:not([class*='size-'])]:size-4 peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2",
        class
    );

    view! {
        <>
            <input
                id=accordion_id.clone()
                type="checkbox"
                class="overflow-hidden absolute p-0 -m-px w-px h-px whitespace-nowrap border-0 peer"
                style="clip: rect(0, 0, 0, 0)"
                checked=open
            />
            <label for=accordion_id class=label_class>
                {children()}
                <ChevronDown class="transition-all duration-300" />
            </label>
        </>
    }
}

Update the imports to match your project setup.

Usage

use crate::components::ui::accordion::{
    Accordion,
    AccordionContent,
    AccordionHeader,
    AccordionItem,
    AccordionTitle,
};
<Accordion class="max-w-[400px]">
    <AccordionItem>
        <AccordionHeader>
            <AccordionTitle>"Accordion Item"</AccordionTitle>
        </AccordionHeader>
        <AccordionContent>
            "Accordion content goes here"
        </AccordionContent>
    </AccordionItem>
</Accordion>

Examples

Accordion Bordered

  • Rust/UI Icons - CopyCopy Demo

This is the Accordion description

This is the Accordion description

This is the Accordion description

use leptos::prelude::*;

use crate::components::ui::accordion::{
    Accordion, AccordionContent, AccordionDescription, AccordionItem, AccordionTitle, AccordionTrigger,
};

#[component]
pub fn DemoAccordionBordered() -> impl IntoView {
    view! {
        <Accordion class="overflow-hidden rounded-lg border bg-background max-w-[400px]">
            <AccordionItem>
                <AccordionTrigger open=true class="peer-checked:bg-accent hover:bg-accent">
                    <AccordionTitle>Accordion item 01</AccordionTitle>
                </AccordionTrigger>
                <AccordionContent class="pt-2">
                    <AccordionDescription>"This is the Accordion description"</AccordionDescription>
                </AccordionContent>
            </AccordionItem>
            <AccordionItem>
                <AccordionTrigger class="peer-checked:bg-accent hover:bg-accent">
                    <AccordionTitle>Accordion item 02</AccordionTitle>
                </AccordionTrigger>
                <AccordionContent class="pt-2">
                    <AccordionDescription>"This is the Accordion description"</AccordionDescription>
                </AccordionContent>
            </AccordionItem>
            <AccordionItem>
                <AccordionTrigger class="peer-checked:bg-accent hover:bg-accent">
                    <AccordionTitle>Accordion item 03</AccordionTitle>
                </AccordionTrigger>
                <AccordionContent class="pt-2">
                    <AccordionDescription>"This is the Accordion description"</AccordionDescription>
                </AccordionContent>
            </AccordionItem>
        </Accordion>
    }
}

Accordion with Icons

  • Rust/UI Icons - CopyCopy Demo
use icons::{AlignHorizontalSpaceAround, Blocks, Compass, Expand, LogIn, PanelLeft, Search};
use leptos::prelude::*;

use crate::components::ui::accordion::{
    Accordion, AccordionContent, AccordionItem, AccordionLink, AccordionTitle, AccordionTrigger,
};

#[component]
pub fn DemoAccordionIcons() -> impl IntoView {
    view! {
        <Accordion class="overflow-hidden rounded-lg border bg-background max-w-[500px]">
            <AccordionItem>
                <AccordionTrigger open=true class="peer-checked:bg-accent hover:bg-accent">
                    <AccordionTitle>Registry</AccordionTitle>
                </AccordionTrigger>
                <AccordionContent class="p-0">
                    <ul class="text-sm">
                        <li>
                            <AccordionLink attr:href="/docs/components">
                                <Blocks />
                                <span>Components</span>
                            </AccordionLink>
                        </li>
                        <li>
                            <AccordionLink attr:href="/docs/extensions">
                                <Expand />
                                <span>Extensions</span>
                            </AccordionLink>
                        </li>
                        <li>
                            <AccordionLink attr:href="/docs/hooks">
                                <Compass />
                                <span>Hooks</span>
                            </AccordionLink>
                        </li>
                        <li>
                            <AccordionLink attr:href="/icons">
                                <Search />
                                <span>Icons</span>
                            </AccordionLink>
                        </li>
                    </ul>
                </AccordionContent>
            </AccordionItem>
            <AccordionItem>
                <AccordionTrigger class="peer-checked:bg-accent hover:bg-accent">
                    <AccordionTitle>Blocks</AccordionTitle>
                </AccordionTrigger>
                <AccordionContent class="p-0">
                    <ul class="text-sm">
                        <li>
                            <AccordionLink attr:href="/blocks/login">
                                <LogIn />
                                <span>Login</span>
                            </AccordionLink>
                        </li>
                        <li>
                            <AccordionLink attr:href="/blocks/sidenav">
                                <PanelLeft />
                                <span>Sidenav</span>
                            </AccordionLink>
                        </li>
                        <li>
                            <AccordionLink attr:href="/blocks/parallax">
                                <AlignHorizontalSpaceAround />
                                <span>Parallax</span>
                            </AccordionLink>
                        </li>
                    </ul>
                </AccordionContent>
            </AccordionItem>
            <AccordionLink class="p-3" attr:href="/themes">
                <AccordionTitle>Themes</AccordionTitle>
            </AccordionLink>
            <AccordionLink class="p-3" attr:href="/icons">
                <AccordionTitle>Icons</AccordionTitle>
            </AccordionLink>
        </Accordion>
    }
}