All components / Disclosure

disclosure-panel

Native disclosure, coordinated with attributes and events. Useful for help, installation instructions, and expandable documentation.

Version 1.0.0 · MIT · Light DOM · Zero dependencies

Element module on GitHub · Registration module on GitHub · Custom Elements Manifest · License

Live example

Try the disclosure

This content is ordinary HTML. Open it with a pointer, or focus the summary and press Enter or Space.

Use the summary to open or close the panel.

The outer controls change the attribute. The inner button sends a bubbling request; the status listens for the outgoing change event. The summary works without JavaScript.

Start with HTML

Minimal HTML · HTML
<script type="module" src="/vendor/disclosure-panel/index.js"></script>
<disclosure-panel>
	<details>
		<summary>Installation help</summary>
		<p>Use your browser’s Add to Home Screen menu.</p>
	</details>
</disclosure-panel>

Keep index.js beside disclosure-panel.js. No stylesheet, build step, or shadow root is required. Put initial open on the native details too if it should start expanded without JavaScript.

Initially expanded markup · HTML
<disclosure-panel open>
	<details open>
		<summary>Help</summary>
		<p>Visible before and after upgrade.</p>
	</details>
</disclosure-panel>

Attributes and events

Attribute contract
AttributeMeaning
openBoolean presence opens the details. Remove to close. open="false" is still present and opens it. Native changes reflect back to the host.
Event contract
EventDirection and payload
disclosure-changeEmitted up after state changes: detail: { open: boolean }. Bubbles, composed, not cancelable. No event for initialization or unchanged state.
disclosure-requestListened for from children: detail: { open: boolean }. Dispatch with bubbles: true; add composed: true to cross a shadow root. Requests must reach the nearest panel.
State down, data up · JavaScript
const panel = document.querySelector('disclosure-panel');
panel.addEventListener('disclosure-change', (event) => {
	console.log('Open:', event.detail.open);
});
panel.setAttribute('open', '');
panel.removeAttribute('open');

// From a button inside the panel; no parent selector required.
button.addEventListener('click', () => {
	button.dispatchEvent(new CustomEvent('disclosure-request', {
		detail: { open: false }, bubbles: true, composed: true,
	}));
});

Here button is a reference to your child button. Hide enhancement-only controls until their listeners are installed, as the live demo does. Do not announce content twice: the component preserves native disclosure semantics and adds no ARIA.

The manifest uses the Custom Elements Manifest schema. Incoming events use the documented x-listens extension because the standard events field describes emitted events. There are no custom public methods, properties, slots, CSS parts, or CSS custom properties.

Install from GitHub

There is no npm registry publication. Clone the GitHub repository, check out a reviewed commit, and vendor the component directory into your project. Replace COMMIT_SHA with that commit’s full SHA.

Vendor a pinned GitHub revision · Shell
git clone https://github.com/jessehattabaugh/jessehattabaugh-com.git component-source
git -C component-source checkout COMMIT_SHA
mkdir -p vendor
cp -R component-source/client/components/disclosure-panel vendor/disclosure-panel

Commit the vendored files, including the license, and record the upstream SHA when updating. GitHub raw-file URLs are source downloads; serve vendored modules from your own origin with a JavaScript MIME type.

Choose one package manager · Shell
npm install ./vendor/disclosure-panel
# Or:
pnpm add ./vendor/disclosure-panel
# Or:
yarn add ./vendor/disclosure-panel

Keep the vendor directory and package lockfile in version control. These commands install a local ES module package, without publishing to another repository. Do not install this whole GitHub repository as the component package: its root package belongs to the website.

Vite or webpack application entry · JavaScript
import '@jessehattabaugh/disclosure-panel';

Import once from your browser entry. Vite and webpack resolve the package export. The registration entry is marked as a side effect so production builds keep it.

Rollup without a package-resolution plugin · JavaScript
// In your browser entry; Rollup follows these relative ES module imports.
import './vendor/disclosure-panel/index.js';

For bare package imports in Rollup, use its node-resolve plugin. The relative import needs no plugin. All three approaches register the same element; no framework-specific wrapper is needed.

Manual registration · JavaScript
import { DisclosurePanel } from '@jessehattabaugh/disclosure-panel/element';
customElements.define('disclosure-panel', DisclosurePanel);

The /element export has no registration side effect. Choose manual registration or the default auto-registration entry. Load one version per document; with automatic registration the first definition wins. For server rendering, output the HTML on the server and import the module only in the browser: HTMLElement is a browser API.

Optional local tarball · Shell
npm pack ./vendor/disclosure-panel --ignore-scripts
npm install ./jessehattabaugh-disclosure-panel-1.0.0.tgz

A tarball is optional for local distribution or a future GitHub release asset. No GitHub release is required by these instructions. See the npm install reference.

Behavior and limits

  • Use one direct details child with a first-child summary. Additional details are left alone. Missing details is inert; direct children inserted later are discovered.
  • At upgrade or reconnection, host open wins when present; otherwise the native state is adopted. Initialization emits no change event.
  • Native toggle notifications are asynchronous and can coalesce rapid changes. Attribute requests report changes immediately; do not rely on one event per rapid native toggle.
  • Malformed requests, prevented requests, and requests without a details child are ignored. Nested panels handle their own requests; request events still bubble for other listeners.
  • No JavaScript, failed module loading, or unsupported Custom Elements leaves an ordinary keyboard-accessible details element. Only state coordination requires JavaScript.
  • Use current browsers with Custom Elements, MutationObserver, and native details support. Style disclosure-panel, details, and summary in your own CSS; the wrapper is unstyled and inline by default.

This component also wraps the site’s app-install instructions. It makes no network requests and stores no data.