Disclosure
Progressively enhances a button/content pair into an accessible disclosure.
Default closed
With open omitted, Disclosure initially shows only its trigger.
Disclosure content.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-disclosure/ui-disclosure.html"
>
<ui-disclosure
summary="Details"
>Disclosure content.</ui-disclosure><script setup>
import { UiDisclosure } from "@threadlabs/looma";
</script>
<template>
<UiDisclosure
summary="Details"
>Disclosure content.</UiDisclosure>
</template>import { UiDisclosure } from "@threadlabs/looma";
export default function Example() {
return <UiDisclosure
summary="Details"
>Disclosure content.</UiDisclosure>;
}/** @jsxImportSource preact */
import { UiDisclosure } from "@threadlabs/looma";
export default function Example() {
return <UiDisclosure
summary="Details"
>Disclosure content.</UiDisclosure>;
}<script>
import { UiDisclosure } from "@threadlabs/looma";
</script>
<UiDisclosure
summary="Details"
>Disclosure content.</UiDisclosure>import { Component } from "@angular/core";
import { UiDisclosure } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiDisclosure],
template: `
<div
ui-disclosure
[summary]="'Details'"
>Disclosure content.</div>
`,
})
export default class Example {}open and disabled
Open starts the panel expanded; disabled prevents the generated trigger from toggling its content.
Visible content.
Unavailable content.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-disclosure/ui-disclosure.html"
>
<div>
<ui-disclosure
summary="Open disclosure"
open
>Visible content.</ui-disclosure>
<ui-disclosure
summary="Disabled disclosure"
disabled
>Unavailable content.</ui-disclosure>
</div><script setup>
import { UiDisclosure } from "@threadlabs/looma";
</script>
<template>
<div>
<UiDisclosure
summary="Open disclosure"
:open="true"
>Visible content.</UiDisclosure>
<UiDisclosure
summary="Disabled disclosure"
:disabled="true"
>Unavailable content.</UiDisclosure>
</div>
</template>import { UiDisclosure } from "@threadlabs/looma";
export default function Example() {
return <div>
<UiDisclosure
summary="Open disclosure"
open
>Visible content.</UiDisclosure>
<UiDisclosure
summary="Disabled disclosure"
disabled
>Unavailable content.</UiDisclosure>
</div>;
}/** @jsxImportSource preact */
import { UiDisclosure } from "@threadlabs/looma";
export default function Example() {
return <div>
<UiDisclosure
summary="Open disclosure"
open
>Visible content.</UiDisclosure>
<UiDisclosure
summary="Disabled disclosure"
disabled
>Unavailable content.</UiDisclosure>
</div>;
}<script>
import { UiDisclosure } from "@threadlabs/looma";
</script>
<div>
<UiDisclosure
summary="Open disclosure"
open
>Visible content.</UiDisclosure>
<UiDisclosure
summary="Disabled disclosure"
disabled
>Unavailable content.</UiDisclosure>
</div>import { Component } from "@angular/core";
import { UiDisclosure } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiDisclosure],
template: `
<div>
<div
ui-disclosure
[summary]="'Open disclosure'"
[open]="true"
>Visible content.</div>
<div
ui-disclosure
[summary]="'Disabled disclosure'"
[disabled]="true"
>Unavailable content.</div>
</div>
`,
})
export default class Example {}Named group and rich summary
Disclosures with the same name allow one open item; a summary slot can include emphasis or a count.
Account settings and access.
Billing methods and invoices.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-disclosure/ui-disclosure.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<ui-stack
gap="none"
>
<ui-disclosure
name="questions"
summary="Account"
heading-level="3"
>
<strong
slot="summary"
>Account <small>2 topics</small></strong>
<ui-text>Account settings and access.</ui-text>
</ui-disclosure>
<ui-disclosure
name="questions"
summary="Billing"
heading-level="3"
>
<ui-text>Billing methods and invoices.</ui-text>
</ui-disclosure>
</ui-stack><script setup>
import { UiDisclosure, UiStack, UiText } from "@threadlabs/looma";
</script>
<template>
<UiStack
gap="none"
>
<UiDisclosure
name="questions"
summary="Account"
:heading-level="3"
>
<template #summary>
<strong>Account <small>2 topics</small></strong>
</template>
<UiText>Account settings and access.</UiText>
</UiDisclosure>
<UiDisclosure
name="questions"
summary="Billing"
:heading-level="3"
>
<UiText>Billing methods and invoices.</UiText>
</UiDisclosure>
</UiStack>
</template>import { UiDisclosure, UiStack, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="none"
>
<UiDisclosure
name="questions"
summary="Account"
headingLevel={3}
slots={{
summary: <strong>Account <small>2 topics</small></strong>,
}}
>
<UiText>Account settings and access.</UiText>
</UiDisclosure>
<UiDisclosure
name="questions"
summary="Billing"
headingLevel={3}
>
<UiText>Billing methods and invoices.</UiText>
</UiDisclosure>
</UiStack>;
}/** @jsxImportSource preact */
import { UiDisclosure, UiStack, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="none"
>
<UiDisclosure
name="questions"
summary="Account"
headingLevel={3}
slots={{
summary: <strong>Account <small>2 topics</small></strong>,
}}
>
<UiText>Account settings and access.</UiText>
</UiDisclosure>
<UiDisclosure
name="questions"
summary="Billing"
headingLevel={3}
>
<UiText>Billing methods and invoices.</UiText>
</UiDisclosure>
</UiStack>;
}<script>
import { UiDisclosure, UiStack, UiText } from "@threadlabs/looma";
</script>
{#snippet summary()}
<strong>Account <small>2 topics</small></strong>
{/snippet}
<UiStack
gap="none"
>
<UiDisclosure
name="questions"
summary="Account"
headingLevel={3}
slots={{ summary }}
>
<UiText>Account settings and access.</UiText>
</UiDisclosure>
<UiDisclosure
name="questions"
summary="Billing"
headingLevel={3}
>
<UiText>Billing methods and invoices.</UiText>
</UiDisclosure>
</UiStack>import { Component } from "@angular/core";
import { UiDisclosure, UiStack, UiText } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiStack, UiDisclosure, UiText],
template: `
<div
ui-stack
[gap]="'none'"
>
<div
ui-disclosure
[name]="'questions'"
[summary]="'Account'"
[headingLevel]="3"
>
<ng-container ngProjectAs="[slot='summary']">
<strong>Account <small>2 topics</small></strong>
</ng-container>
<ng-template
ui-text
>Account settings and access.</ng-template>
</div>
<div
ui-disclosure
[name]="'questions'"
[summary]="'Billing'"
[headingLevel]="3"
>
<ng-template
ui-text
>Billing methods and invoices.</ng-template>
</div>
</div>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: div
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
density | Compact trades padding and type size for rows that fit more on screen. | density | keyword | comfortable | comfortable, compact |
disabled | Disables the trigger and mutes its text, so the disclosure keeps its current state and the user cannot toggle it. | disabled | boolean | false | — |
fill | In a column with a bounded height, an open disclosure fills the remaining space and keeps its trigger visible. The content slot can contain a Scroll Area. Closed items keep only their trigger height. | fill | boolean | false | — |
heading-level | Adds heading semantics around the trigger at level 2 through 6. Zero leaves it a plain disclosure; choose a level that matches the page outline. | headingLevel | integer | 0 | — |
name | Disclosures with the same nonempty name form an exclusive group in one document: opening one closes the others. An empty name allows multiple open items. | name | string | | — |
open | Whether the disclosure is open initially or when set externally. The trigger toggles it from there, reporting each change as an open or close event. | open | boolean | false | — |
required-open | In a named exclusive group, keeps at least one member open. Clicking the last open trigger does not close it; opening another member transfers the open state. | requiredOpen | boolean | false | — |
summary | The text of the trigger row that opens and closes the disclosure. It is the trigger button's accessible name, so name what the content is. | summary | string | Details | — |
variant | Navigation shares icon columns, spacing, type, hover, and corners with Nav Item, while keeping expanded sections neutral: opening content is not route selection. Plain retains the standard disclosure treatment. | variant | keyword | plain | plain, navigation |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
density | "comfortable" | "compact" | comfortable | comfortable, compact |
disabled | boolean | false | — |
fill | boolean | false | — |
headingLevel | number | 0 | — |
name | string | | — |
open | boolean | false | — |
requiredOpen | boolean | false | — |
summary | string | Details | — |
variant | "plain" | "navigation" | plain | plain, navigation |
Events
| Name | Detail | Description |
|---|---|---|
close | object
| Emitted when the component transitions to a closed state. |
open | object
| Emitted when the component transitions to an open state. |
Slots
| Name | Purpose |
|---|---|
leading | Named content for the leading region. |
summary | Named content for the summary region. |
indicator | Named content for the indicator region. |
default | Default child content. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
| Token | Declared values | Fallback |
|---|---|---|
--_ui-disclosure-heading-size | var(--ui-control-size) | var(--ui-control-size-sm) | — |
--ui-disclosure-trigger-min-block-size | var(--ui-control-size) | var(--ui-control-size-sm) | — |
--ui-disclosure-trigger-padding-block | var(--ui-space-2) | var(--ui-space-1) | — |
--ui-disclosure-trigger-padding-inline | var(--ui-space-3) | var(--ui-space-2) | — |
Shared tokens
| Token | Fallback |
|---|---|
--ui-border-divider | — |
--ui-border-width | — |
--ui-control-min-block-size | — |
--ui-control-size | — |
--ui-control-size-sm | — |
--ui-disabled-text | — |
--ui-focus-ring | — |
--ui-focus-width | — |
--ui-font-normal | — |
--ui-font-semibold | — |
--ui-font-size-md | — |
--ui-font-size-sm | — |
--ui-motion-ease | — |
--ui-motion-fast | — |
--ui-motion-feedback | — |
--ui-motion-feedback-ease | — |
--ui-motion-layout | — |
--ui-motion-layout-ease | — |
--ui-motion-reveal | — |
--ui-motion-reveal-ease | — |
--ui-nav-compact-min-block-size | — |
--ui-nav-compact-padding-block | — |
--ui-nav-compact-padding-inline | — |
--ui-nav-gap | — |
--ui-nav-hover-surface | — |
--ui-nav-icon-size | — |
--ui-nav-min-block-size | — |
--ui-nav-padding-block | — |
--ui-nav-padding-inline | — |
--ui-radius-sm | — |
--ui-selection-radius | — |
--ui-space-1 | — |
--ui-space-2 | — |
--ui-space-3 | — |
--ui-space-4 | — |
--ui-text-primary | — |
--ui-text-secondary | — |