Basic — docs sidebar
import { html, atom, renderApp } from 'mates';
import { CollapsibleList, Text } from 'mates-ui';
import { BoltIcon, DataObjectIcon, HomeIcon, HubIcon, LayersIcon, RocketLaunchIcon } from 'mates-icons';
const CATEGORIES = [
{
id: "getting-started", title: "Getting started",
items: [
{ id: "intro", label: "Introduction", icon: HomeIcon({ size: 16 }) },
{ id: "install", label: "Installation", icon: RocketLaunchIcon({ size: 16 }) },
{ id: "quickstart", label: "Quick start", icon: BoltIcon({ size: 16 }) },
],
},
{
id: "core", title: "Core concepts",
items: [
{ id: "atoms", label: "Atoms & state", icon: DataObjectIcon({ size: 16 }) },
{ id: "components", label: "Components", icon: LayersIcon({ size: 16 }) },
{ id: "routing", label: "Routing", icon: HubIcon({ size: 16 }) },
],
},
];
const App = () => {
const active = atom("intro");
return () => html`
<x-row gap="var(--md-space-5)" align="flex-start">
<div style="width:14rem;border:0.06rem solid var(--md-color-border);border-radius:var(--md-radius-md);overflow:hidden;">
${CollapsibleList({ title: "Docs", categories: CATEGORIES, activeId: active(), onSelect: (item) => active.set(item.id) })}
</div>
<x-col gap="var(--md-space-1)">
${Text("Selected", { type: "label-sm", color: "muted" })}
${Text(active(), { type: "body-md" })}
</x-col>
</x-row>
`;
Dot-only — lessons sidebar (no icons)
import { html, atom, renderApp } from 'mates';
import { CollapsibleList, Text } from 'mates-ui';
const CATEGORIES = [
{
id: "basics", title: "Basics",
items: [
{ id: "l1", label: "Hello world" },
{ id: "l2", label: "Reactive atoms" },
{ id: "l3", label: "Templates" },
{ id: "l4", label: "Events" },
],
},
{
id: "routing", title: "Routing",
items: [
{ id: "l8", label: "Client-side routing" },
{ id: "l9", label: "Nested routes" },
{ id: "l10", label: "Guards & redirects", disabled: true },
],
},
];
const App = () => {
const active = atom("l2");
return () => html`
<x-row gap="var(--md-space-5)" align="flex-start">
<div style="width:14rem;border:0.06rem solid var(--md-color-border);border-radius:var(--md-radius-md);overflow:hidden;">
${CollapsibleList({ title: "Lessons", categories: CATEGORIES, activeId: active(), onSelect: (item) => active.set(item.id) })}
</div>
${Text("Selected: " + active(), { type: "body-sm", color: "muted" })}
</x-row>
`;
};
renderApp(App, document.getElementById('app'));
Icons only — no dot (showDot: false)
import { html, atom, renderApp } from 'mates';
import { CollapsibleList } from 'mates-ui';
import { NotificationsIcon, PersonIcon, SettingsIcon } from 'mates-icons';
const CATEGORIES = [
{
id: "account", title: "Account",
items: [
{ id: "profile", label: "Profile", icon: PersonIcon({ size: 16 }) },
{ id: "notifications", label: "Notifications", icon: NotificationsIcon({ size: 16 }) },
],
},
{
id: "app", title: "App settings",
items: [
{ id: "general", label: "General", icon: SettingsIcon({ size: 16 }) },
{ id: "advanced", label: "Advanced", icon: SettingsIcon({ size: 16 }) },
],
},
];
const App = () => {
const active = atom("profile");
return () => html`
<div style="width:14rem;border:0.06rem solid var(--md-color-border);border-radius:var(--md-radius-md);overflow:hidden;">
${CollapsibleList({ title: "Settings", showDot: false, categories: CATEGORIES, activeId: active(), onSelect: (item) => active.set(item.id) })}
</div>
`;
};
renderApp(App, document.getElementById('app'));
Controlled open state
import { html, atom, renderApp } from 'mates';
import { CollapsibleList, OutlinedButton, Text } from 'mates-ui';
const CATEGORIES = [
{
id: "basics", title: "Basics",
items: [
{ id: "l1", label: "Hello world" },
{ id: "l2", label: "Reactive atoms" },
{ id: "l3", label: "Templates" },
],
},
{
id: "routing", title: "Routing",
items: [
{ id: "l8", label: "Client-side routing" },
{ id: "l9", label: "Nested routes" },
],
},
{
id: "advanced", title: "Advanced",
items: [
{ id: "l15", label: "SSR" },
{ id: "l16", label: "Testing" },
],
},
];
const App = () => {
const active = atom("l1");
const openIds = atom(["basics", "routing"]);
return () => html`
<x-row gap="var(--md-space-5)" align="flex-start">
<div style="width:14rem;border:0.06rem solid var(--md-color-border);border-radius:var(--md-radius-md);overflow:hidden;">
${CollapsibleList({
title: "Lessons", showCount: true, categories: CATEGORIES,
No header (showCount: false, no title)
import { html, atom, renderApp } from 'mates';
import { CollapsibleList } from 'mates-ui';
const CATEGORIES = [
{
id: "basics", title: "Basics",
items: [
{ id: "l1", label: "Hello world" },
{ id: "l2", label: "Reactive atoms" },
{ id: "l3", label: "Templates" },
],
},
{
id: "routing", title: "Routing",
items: [
{ id: "l8", label: "Client-side routing" },
{ id: "l9", label: "Nested routes" },
],
},
];
const App = () => {
const active = atom("l1");
return () => html`
<div style="width:14rem;border:0.06rem solid var(--md-color-border);border-radius:var(--md-radius-md);overflow:hidden;">
${CollapsibleList({ showCount: false, categories: CATEGORIES, activeId: active(), onSelect: (item) => active.set(item.id) })}
</div>
`;
};
renderApp(App, document.getElementById('app'));
CollapsibleList
import { CollapsibleList } from "mates-ui";
| Prop | Type | Default | Description |
|---|---|---|---|
activeId
|
string
|
— | ID of the currently selected item. |
attr
|
AttrMap
|
— | HTML attributes on the root element. |
categories*
|
CollapsibleListCategory[]
|
— | Ordered list of category groups, each containing items. |
classes
|
string | string[]
|
— | Extra root classes — joined and appended after built-in classes. |
data
|
DataAttrMap
|
— | Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`. |
id
|
string
|
— | Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged. |
on
|
OnEventMap
|
—
|
DOM event handlers on the root element. |
onSelect
|
(item: CollapsibleListItem) => void
|
— | Called when the user clicks an item. |
onToggleCategory
|
(id: string, open: boolean) => void
|
— | Called when a category is expanded or collapsed. |
openIds
|
string[]
|
— | Controlled list of open category IDs. When omitted all categories start open (uncontrolled). |
showCount
|
boolean
|
true
|
Show the total item count in the header. |
showDot
|
boolean
|
true
|
Show the dot indicator on each item. |
style
|
StyleMap
|
— | Root inline CSS (`style` prop; `style` wins if both set). |
style
|
StyleMap
|
—
|
Inline CSS on the root element. |
title
|
string
|
— | Optional header title shown above the list. |
CollapsibleListCategory
| Prop | Type | Default | Description |
|---|---|---|---|
id*
|
string
|
— | Unique identifier for the category. |
items*
|
CollapsibleListItem[]
|
— | Items rendered inside this category. |
title*
|
string
|
— | Label shown in the category header button. |
CollapsibleListItem
| Prop | Type | Default | Description |
|---|---|---|---|
data
|
unknown
|
— | Any extra data the consumer wants to carry on the item. |
disabled
|
boolean
|
— | Prevents the item from being selected. |
icon
|
TemplateResult
|
— | Optional icon rendered before the label. |
id*
|
string
|
— | Unique identifier matched against activeId. |
label*
|
string
|
— | Visible item label. |
Caveats
- All categories are open by default (uncontrolled). Pass openIds to control expansion state.
- The component renders inline — wrap in a fixed-height container with overflow:auto for sidebar use.