Accordion
An accordion component that allows users to show and hide sections of content. It uses collapse functionality internally to create collapsible panels.
#Import
use gpui_kit::component::accordion::Accordion;// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { AccordionEntry, AccordionItem, Accordion } from "accordion.slint";use std::collections::BTreeSet;#Usage
#Single
Accordion::new("my-accordion")
.item(|item| {
item.title("Section 1")
.child("Content for section 1")
})
.item(|item| {
item.title("Section 2")
.child("Content for section 2")
})
.item(|item| {
item.title("Section 3")
.child("Content for section 3")
})Accordion {
open-index: 0;
items: [
{ title: "What is GPUI Kit?", content: "A Rust desktop application framework built on GPUI, with styled components inspired by macOS and Windows controls." },
{ title: "Which platforms are supported?", content: "macOS on Apple silicon and Intel, Linux on x86_64, and Windows on x86_64." },
{ title: "Can I restyle the components?", content: "Yes. Every component reads semantic theme tokens, so switching the palette restyles the whole application." },
];
toggled(index, open) => {
root.last-toggle = open ? "Section \{index + 1} opened." : "Section \{index + 1} closed.";
}
}
Text {
text: root.last-toggle;
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
}// Opening one section closes the others: single mode is the caller's own
// rule over its open set, which is why the last of these three is not open.
let single = panel(
app,
SINGLE,
true,
ControlSize::Medium,
true,
&[
(
"What is Iced?",
"A Rust desktop application framework built on iced, with styled \
components inspired by macOS and Windows controls.",
),
(
"Which platforms are supported?",
"macOS on Apple silicon and Intel, Linux on x86_64, and Windows on \
x86_64.",
),
(
"Can I restyle the components?",
"Yes. Every component reads semantic theme tokens, so switching the \
palette restyles the whole application.",
),
],
);#Multiple
// The open set holds every open section, so an accordion the caller does not
// close on a toggle simply allows several — two are open on boot.
let multiple = panel(
app,
MULTIPLE,
false,
ControlSize::Medium,
true,
&[
(
"Shipping",
"Orders ship within two business days from our warehouse.",
),
(
"Returns",
"Return any unused item within 30 days for a full refund.",
),
(
"Warranty",
"Hardware is covered for two years against manufacturing defects.",
),
],
);By default, only one accordion item can be open at a time. Use multiple() to allow multiple items to be open:
Accordion::new("my-accordion")
.multiple(true)
.item(|item| item.title("Section 1").child("Content 1"))
.item(|item| item.title("Section 2").child("Content 2"))Accordion {
multiple: true;
open-index: 0;
items: [
{ title: "Shipping", content: "Orders ship within two business days from our warehouse." },
{ title: "Returns", content: "Return any unused item within 30 days for a full refund." },
{ title: "Warranty", content: "Hardware is covered for two years against manufacturing defects." },
];
}#Icons and custom content
Accordion::new("my-accordion")
.item(|item| {
item.title(
h_flex()
.gap_2()
.child(Icon::new(IconName::Settings))
.child("Settings")
)
.child("Settings content here")
})Accordion {
AccordionItem {
title: "Notifications";
icon: "bell";
open: true;
VerticalLayout {
spacing: 8px;
Text {
text: root.notifications == 0 ? "You're all caught up." : "\{root.notifications} unread notifications.";
color: Theme.foreground;
font-size: Theme.font-size-sm;
}
GridLayout {
spacing: 8px;
Button {
horizontal-stretch: 0;
text: "Mark all as read";
size: ControlSize.small;
enabled: root.notifications > 0;
clicked => {
root.notifications = 0;
}
}
Button {
horizontal-stretch: 0;
text: "Simulate new";
size: ControlSize.small;
variant: ButtonVariant.ghost;
icon: "plus";
clicked => {
root.notifications += 1;
}
}
}
}
}
AccordionItem {
title: "Settings";
icon: "settings";
Text {
text: "Preferences for appearance, keyboard shortcuts, and updates.";
color: Theme.foreground;
font-size: Theme.font-size-sm;
wrap: word-wrap;
}
}
AccordionItem {
title: "Nested sections";
icon: "layers";
last: true;
Accordion {
size: ControlSize.small;
items: [
{ title: "Child section 1", content: "Content of the first child section." },
{ title: "Child section 2", content: "Content of the second child section." },
];
}
}
}// The header is the caller's element, so a leading icon is just another
// child; the panel is the caller's too, so the notification controls below
// live inside a section.
let unread = if app.notifications == 0 {
"You're all caught up.".to_string()
} else {
format!("{} unread notifications.", app.notifications)
};
let icons = Accordion::new([
Section::new(labeled_header(
mark(BELL, 14.0, muted),
"Notifications",
theme::FONT_SIZE_SM,
8.0,
))
.on_toggle(toggle(app, ICONS, 0, "Notifications", false))
.open(is_open(app, ICONS, 0))
.content(column![
text(unread)
.size(theme::FONT_SIZE_SM)
.style(|theme: &Theme| text::Style {
color: Some(Tokens::of(theme).foreground),
}),
row![
Button::new("Mark all as read")
.size(ControlSize::XSmall)
.on_press(Message::MarkAllRead),
Button::new("Simulate new")
.variant(Variant::Ghost)
.size(ControlSize::XSmall)
.icon(mark(PLUS, 14.0, |theme| {
Tokens::of(theme).muted_foreground
}))
.on_press(Message::SimulateNew),
]
.spacing(8.0),
]),
Section::new(labeled_header(
mark(SETTINGS, 14.0, muted),
"Settings",
theme::FONT_SIZE_SM,
8.0,
))
.on_toggle(toggle(app, ICONS, 1, "Settings", false))
.open(is_open(app, ICONS, 1))
.content(line(
"Preferences for appearance, keyboard shortcuts, and updates.",
theme::FONT_SIZE_SM,
muted,
)),
])
.size(ControlSize::Medium);#Icons and custom content — 2 of 2
// The marker repeats the heading above because the page has no heading of
// its own for a caller-supplied disclosure: this example sits under the
// closest one the page documents. `Section::disclosure` hands the trailing
// lane the caller's element — here Lucide's arrow in place of the
// component's default chevron — so the icon set stays the caller's.
let custom_icon = Accordion::new([
arrow_section(
app,
CUSTOM_ICON,
0,
"Custom arrow",
"The disclosure in the trailing lane is the caller's arrow.",
),
arrow_section(
app,
CUSTOM_ICON,
1,
"Second section",
"Each section draws the mark its own open state wants.",
),
])
.size(ControlSize::Medium);#Sizes
use gpui_kit::component::{Sizable as _, Size};
Accordion::new("my-accordion")
.small()
.item(|item| item.title("Small Section").child("Content"))
Accordion::new("my-accordion")
.large()
.item(|item| item.title("Large Section").child("Content"))GridLayout {
spacing: 16px;
Accordion {
horizontal-stretch: 1;
size: ControlSize.xsmall;
open-index: 0;
items: [
{ title: "Extra small", content: "Compact rows for dense panels." },
{ title: "Second section", content: "Content." },
];
}
Accordion {
horizontal-stretch: 1;
size: ControlSize.small;
open-index: 0;
items: [
{ title: "Small", content: "Rows for sidebars and inspectors." },
{ title: "Second section", content: "Content." },
];
}
Accordion {
horizontal-stretch: 1;
size: ControlSize.large;
open-index: 0;
items: [
{ title: "Large", content: "Roomy rows for settings pages." },
{ title: "Second section", content: "Content." },
];
}
}// The size ladder: compact rows at xsmall, sidebar rows at small, roomy rows
// at large. iced has no wrapping grid, so the three stack.
let sizes = column![
caption("Extra small"),
panel(
app,
XSMALL,
false,
ControlSize::XSmall,
true,
&[
("Extra small", "Compact rows for dense panels."),
("Second section", "Content."),
],
),
caption("Small"),
panel(
app,
SMALL,
false,
ControlSize::Small,
true,
&[
("Small", "Rows for sidebars and inspectors."),
("Second section", "Content."),
],
),
caption("Large"),
panel(
app,
LARGE,
false,
ControlSize::Large,
true,
&[
("Large", "Roomy rows for settings pages."),
("Second section", "Content."),
],
),
]
.spacing(8.0);#Borderless and disabled
Accordion::new("my-accordion")
.disabled(true)
.item(|item| item.title("Disabled Section").child("Content"))GridLayout {
spacing: 24px;
Accordion {
horizontal-stretch: 1;
bordered: false;
open-index: 0;
items: [
{ title: "Borderless", content: "Items are still separated by a line." },
{ title: "Second section", content: "Content." },
];
}
Accordion {
horizontal-stretch: 1;
enabled: false;
items: [
{ title: "Disabled section", content: "Content." },
{ title: "Another disabled section", content: "Content." },
];
}
}// Borderless drops the card but keeps the separating rule; disabled is the
// caller not wiring a header at all, drawn in the muted ink the component's
// `ink` helper gives a disabled title.
let borderless_and_disabled = column![
panel(
app,
BORDERLESS,
false,
ControlSize::Medium,
false,
&[
("Borderless", "Items are still separated by a line."),
("Second section", "Content."),
],
),
Accordion::new([
Section::new(disabled_header(
"Disabled section",
label_size(ControlSize::Medium)
))
.open(false)
.content(line("Content.", theme::FONT_SIZE_SM, muted)),
Section::new(disabled_header(
"Another disabled section",
label_size(ControlSize::Medium),
)),
])
.size(ControlSize::Medium),
]
.spacing(16.0);#With Borders
Accordion::new("my-accordion")
.bordered(true)
.item(|item| item.title("Section 1").child("Content 1"))// `bordered(true)` is the default and draws the single rounded card the
// sections sit in, with the padding that keeps the border off the rows.
let with_borders = panel(
app,
BORDERED,
false,
ControlSize::Medium,
true,
&[("Section 1", "Content 1"), ("Section 2", "Content 2")],
);#Handle Toggle Events
Accordion::new("my-accordion")
.on_toggle_click(|open_indices, window, cx| {
println!("Open items: {:?}", open_indices);
})
.item(|item| item.title("Section 1").child("Content 1"))// Every header reports the caller's message with the section's new state, so
// the caller writes the open set and can show what it received.
let events = panel(
app,
EVENTS,
true,
ControlSize::Medium,
true,
&[
("Section 1", "The first section."),
("Section 2", "The second section."),
("Section 3", "The third section."),
],
);
let events = column![
events,
line(
app.notice
.as_deref()
.unwrap_or("Click a section title to toggle it."),
theme::FONT_SIZE_XS,
muted,
),
]
.spacing(8.0);#Nested Accordions
Accordion::new("outer")
.item(|item| {
item.title("Parent Section")
.child(
Accordion::new("inner")
.item(|item| item.title("Child 1").child("Content"))
.item(|item| item.title("Child 2").child("Content"))
)
})// A section's content is the caller's, so it can be another accordion; the
// inner one reveals inside the outer one's reveal.
let inner = Accordion::new([
Section::new(header(
"Child section 1",
label_size(ControlSize::Small),
|theme| Tokens::of(theme).foreground,
))
.on_toggle(toggle(app, INNER, 0, "Child section 1", true))
.open(is_open(app, INNER, 0))
.content(line(
"Content of the first child section.",
theme::FONT_SIZE_SM,
muted,
)),
Section::new(header(
"Child section 2",
label_size(ControlSize::Small),
|theme| Tokens::of(theme).foreground,
))
.on_toggle(toggle(app, INNER, 1, "Child section 2", true))
.open(is_open(app, INNER, 1))
.content(line(
"Content of the second child section.",
theme::FONT_SIZE_SM,
muted,
)),
])
.size(ControlSize::Small);
let nested = Accordion::new([Section::new(header(
"Parent section",
label_size(ControlSize::Medium),
|theme| Tokens::of(theme).foreground,
))
.on_toggle(toggle(app, OUTER, 0, "Parent section", false))
.open(is_open(app, OUTER, 0))
.content(inner)])
.size(ControlSize::Medium);#API Reference
#Sizing
Implements Sizable trait:
small()- Small sizemedium()- Medium size (default)large()- Large sizexsmall()- Extra small size
#AccordionEntry
One section of a data-driven `Accordion`.
| Name | Kind | Type | Default |
|---|---|---|---|
title | field | string | |
icon | field | string | |
content | field | string |
#AccordionItem
One collapsible section. Mirrors GPUI Component's `AccordionItem`: a title row with an optional leading icon that toggles a content panel, sizes, a disabled state, and an animated chevron and reveal. Put the content in `@children`, and set `last` on the final item to drop its separator.
| Name | Kind | Type | Default |
|---|---|---|---|
title | in | string | |
icon | in | string | |
size | in | ControlSize | ControlSize.medium |
enabled | in | bool | true |
last | in | bool | |
open | in-out | bool | |
toggled | callback | (open: bool) |
#Accordion
A vertical stack of collapsible sections. Mirrors GPUI Component's `Accordion`: one open section at a time, or several with `multiple`, a bordered card by default, sizes, a disabled state, and a toggle event. Pass text sections through `items`, or compose `AccordionItem` children for rich content; `open-index` is the open section in single mode.
| Name | Kind | Type | Default |
|---|---|---|---|
items | in | [AccordionEntry] | |
multiple | in | bool | |
bordered | in | bool | true |
enabled | in | bool | true |
size | in | ControlSize | ControlSize.medium |
open-index | in-out | int | -1 |
toggled | callback | (index: int, open: bool) |