npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tapestry-ui/accordion

v0.3.0

Published

A nestable, WCAG 2.1 AA accordion (disclosure) for Preact — headless behavior, optional slide, skin via CSS. Forged inside a woven-tapestry game; ships with its container contract, not just its markup.

Readme

@tapestry-ui/accordion

A nestable, accessible accordion for Preact — one disclosure, or a set of them. Headless behavior, skin via CSS. Forged inside a woven-tapestry video game, where it learned its most important lesson the hard way (see The container contract).

npm i @tapestry-ui/accordion
import { Disclosure, Accordion } from '@tapestry-ui/accordion';
import '@tapestry-ui/accordion/styles.css';   // structural only — optional

<Disclosure summary="SOUND">
  <VolumeSliders />
</Disclosure>

// nesting is just children
<Disclosure summary="settings" defaultOpen>
  <Disclosure summary="advanced">…</Disclosure>
</Disclosure>

// the data-source layer; `exclusive` makes it an accordion
<Accordion
  exclusive
  items={[
    { summary: 'one', content: <A /> },
    { summary: 'two', content: <B /> },
  ]}
/>

Accessibility (WCAG 2.1 AA by design, not by audit)

  • The summary is a real <button> — Enter/Space, focus, and every assistive tech behavior come free and honest.
  • aria-expanded + aria-controls wire the WAI-ARIA disclosure pattern; ids are auto-generated and collision-free under nesting.
  • headingLevel={2..6} wraps the button in a real heading, so an accordion that titles a section never lies to the document outline.
  • The closed body is removed from the DOM, not visually hidden — screen readers never wade through collapsed forests.
  • :focus-visible outline ships in the structural CSS and is yours to restyle, never to remove.

Sliding open (keepMounted)

By default a closed body is removed, which is honest but cannot be animated — there is nothing to animate from. Pass keepMounted and the body stays in the DOM, carrying inert whenever it is closed:

<Disclosure summary="SOUND" keepMounted>…</Disclosure>

inert takes the closed content out of the focus order and out of the accessibility tree — the same promise removal makes — so the accordion can slide without lying to anyone. The shipped CSS animates it with the 0fr → 1fr grid trick (and hides it outright once shut, so browsers without inert keep the same guarantee). prefers-reduced-motion: reduce turns the transition off.

| custom property | default | what it tunes | | --- | --- | --- | | --tui-disclosure-slide | 280ms | how long the body takes to open and shut |

The children are wrapped in [data-part="body-inner"] in this mode, since the clip has to happen on an element that is not the grid row itself.

⭐ The container contract

An accordion is only half a component. The other half is the scroll contract of whatever contains it. A Accordion never owns scrolling and never constrains its container; when open accordions can exceed the available space, some ancestor you choose must be the scroll region:

.my-modal-content {           /* ONE scrollable middle… */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;           /* …owns ALL the growth */
}

Skip this and an open accordion will push its tail past your modal's bounds — this exact bug is the reason this package exists. Put the accordion INSIDE the region that scrolls, never as a sibling below it.

Skin

Structural CSS only — nothing in styles.css picks a color. Hooks:

| hook | what | |---|---| | [data-part="root"] [data-state="open"\|"closed"] | the accordion + its state | | [data-part="summary"] / "caret" / "body" / "heading" | the parts | | class / summaryClass / bodyClass props | merge your own classes | | --tui-disclosure-gap · --tui-disclosure-indent · --tui-disclosure-summary-pad | spacing | | --tui-disclosure-caret-closed / -open (content strings) · --tui-disclosure-caret-size | the caret | | --tui-disclosure-focus | the focus ring |

API

<Disclosure summary children defaultOpen? open? onOpenChange? disabled? headingLevel? class? summaryClass? bodyClass? id?> — controlled when open is passed (the component then only reports intent through onOpenChange), uncontrolled otherwise.

<Accordion items exclusive? keepMounted? headingLevel? label? onOpenChange? …classes> — the molecule: disclosures from data, exclusive for one-at-a-time, and Up/Down/Home/End across its own headers. Exported as Squeezebox too. Everything it does, composition can; it just owns the set's state and keyboard.