DisclosureGroup
A disclosure group coordinates a set of disclosures: by default opening one closes the last, and allows-multiple-expanded lets them stand open together. Without a group each Disclosure is independent, which is often what you want — reach for the group when the sections compete for the same space.
import { Disclosure, DisclosureGroup } from "ropav";disclosure-group-basic.vue
<script setup lang="ts">
import {
Button,
Disclosure,
DisclosureBody,
DisclosureContent,
DisclosureGroup,
DisclosureHeading,
DisclosureIndicator,
} from "ropav";
const sections = [
{
body: "Every component ships its own stylesheet, and the theme decides the colours.",
id: "styling",
label: "Styling",
},
{
body: "One tab stop per control, and the keyboard interactions the ARIA pattern asks for.",
id: "keyboard",
label: "Keyboard",
},
];
</script>
<template>
<DisclosureGroup class="panel" :default-expanded-keys="['styling']">
<Disclosure v-for="section in sections" :id="section.id" :key="section.id">
<DisclosureHeading>
<Button class="trigger" variant="tertiary">
{{ section.label }}
<DisclosureIndicator class="indicator" />
</Button>
</DisclosureHeading>
<DisclosureContent>
<DisclosureBody class="body">{{ section.body }}</DisclosureBody>
</DisclosureContent>
</Disclosure>
</DisclosureGroup>
</template>
<style scoped>
.panel {
width: 100%;
max-width: var(--rp-container-md);
}
.trigger {
width: 100%;
justify-content: space-between;
}
.indicator {
color: var(--rp-muted);
}
/* `DisclosureBody` puts the class on the element inside its root, which a scoped rule cannot
reach on its own - the panel is the nearest thing wearing the scope. */
.panel :deep(.body) {
color: var(--rp-muted);
padding: calc(var(--rp-spacing) * 3);
font-size: var(--rp-text-sm);
line-height: var(--rp-text-sm--line-height);
}
</style>Each disclosure needs an id to be keyed by, and those keys are what expanded-keys carries. default-expanded-keys starts the group somewhere; v-model:expanded-keys, or expanded-keys with an expanded-change listener, hands the state to the caller.
is-disabled on the group disables every disclosure in it, and a disclosure can still disable itself. The parts inside — DisclosureHeading, DisclosureContent, DisclosureBody, DisclosureIndicator — belong to Disclosure, which is where they are documented.
The trigger is whatever you put there
A disclosure hands its press behaviour down to the first pressable inside it, so an ordinary Button in the heading becomes the trigger — with the aria-expanded and aria-controls wiring already on it — and nothing has to be forwarded. DisclosureContent shadows that, so a button inside the panel stays an ordinary button.
Accessibility
- The heading is a real
<h3>, so the sections show up in a document outline the way a reader navigating by heading expects. - The trigger carries
aria-expandedandaria-controls, and the panel is what those point at. - With one panel open at a time, closing happens under the reader — which is a reason to prefer
allows-multiple-expandedwhere the sections are reference material rather than steps.
API
| Prop | Type | Default | Description |
|---|---|---|---|
allowsMultipleExpanded | boolean | — | Whether more than one disclosure can be expanded at a time. |
defaultExpandedKeys | Iterable<DisclosureKey> | — | Expanded disclosure keys in uncontrolled mode. |
expandedKeys | Iterable<DisclosureKey> | — | Expanded disclosure keys in controlled mode. Pairs with v-model:expanded-keys. |
isDisabled | boolean | — | Disables every disclosure in the group. |
Slots: default.
| Event | Payload |
|---|---|
expandedChange | Set<DisclosureKey> |
update:expandedKeys | Set<DisclosureKey> |