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

@jamid/understory-web-components

v0.2.0

Published

Native Web Components UI library (Custom Elements + Shadow DOM)

Readme

@jamid/understory-web-components

Native Web Components UI library, built with Custom Elements and Shadow DOM. No framework required — import the bundle and use the elements as plain HTML tags. Browse every component live in Understory.

Install

npm install @jamid/understory-web-components

Or, for local development against a sibling checkout:

"@jamid/understory-web-components": "file:../simple-components"

Usage

<script type="module" src="/vendor/understory-web-components.js"></script>

<uwc-card heading="Title" subtitle="Subtitle"></uwc-card>

The package ships plain compiled JavaScript (dist/index.js for CommonJS, dist/index.esm.js for ESM), so it works in projects with or without TypeScript. TypeScript projects additionally get type declarations via dist/index.d.ts.

Theming

Every component reads colors, radius, shadows, and font from a namespaced set of --uwc-* CSS custom properties, each with a built-in default — so no setup is required. To customize, copy variables.css into your own project and override what you need (e.g. in :root). Custom properties inherit through shadow DOM boundaries, so setting them anywhere in the light DOM above a component is enough; components never load this file themselves.

:root {
  --uwc-color-primary: #7c3aed;
  --uwc-color-primary-hover: #6d28d9;
  --uwc-radius: 4px;
}

Components

<uwc-card>

Simple content card.

| Attribute | Description | | -------------- | ---------------------------------------------------------------------------- | | heading | Card heading text | | subtitle | Card subtitle text | | image | Optional image URL, shown full-bleed at the top of the card | | image-alt | Alt text for the image | | image-height | CSS height for the image band (default 10rem) | | height | Fixed CSS height for the whole card (default 24rem) | | min-width | Minimum CSS width of the card before it shrinks no further (default 10rem) |

The image, when present, spans edge-to-edge with no padding or margin and is cropped (object-fit: cover) to the given height; heading, subtitle, and slot content keep the card's normal padding below it. The card itself has a fixed height, and body content that overflows is clipped (combine with the heading/subtitle line clamps to avoid mid-word cutoffs).

<uwc-card-container>

Lays out <uwc-card> children (or any children) in a flex row that wraps: multiple cards render in a row at their min-width, but the cards grow to take up the available horizontal space. Each card keeps its own fixed height; only the width stretches. gap is used both between cards and as the container's own edge padding, so spacing is even all the way around.

| Attribute | Description | | --------- | -------------------------------------------------------------------- | | gap | Spacing between cards and around the container edge (default 20px) |

<uwc-card-container gap="1.5rem">
  <uwc-card heading="One" subtitle="First card" min-width="18rem"></uwc-card>
  <uwc-card heading="Two" subtitle="Second card" min-width="18rem"></uwc-card>
  <uwc-card heading="Three" subtitle="Third card" min-width="18rem"></uwc-card>
</uwc-card-container>

<uwc-dropdown-menu>

Button that toggles a panel of links, each with an optional description. Closes on outside click, on Escape, or after picking an item's link.

| Attribute | Description | | --------- | ------------------------------------------------- | | label | Trigger button text (default Menu) | | items | JSON array of { "href", "label", "description"? } |

<uwc-dropdown-menu
  label="Resources"
  items='[
    {"href":"/docs","label":"Docs","description":"Guides and API reference"},
    {"href":"/support","label":"Support","description":"Get help from the team"}
  ]'
></uwc-dropdown-menu>

Also exposes show(), close(), and toggle() methods, and fires show/close events.

<uwc-image>

Loads any source image into a fixed width/height box without distorting it — the image is scaled and cropped (or letterboxed) to fill the box, never stretched.

| Attribute | Description | | --------- | ---------------------------------------------------------------------- | | src | Image URL | | alt | Alt text | | width | Box width (CSS length, or a bare number for pixels; default 12rem) | | height | Box height (CSS length, or a bare number for pixels; default 12rem) | | fit | cover (default, crops to fill) or contain (letterboxes, no crop) |

<uwc-image src="/photo.jpg" alt="A photo" width="200" height="120"></uwc-image>

<uwc-input>

Text input with built-in HTML5 validation.

| Attribute | Description | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | type | Input type (text, email, password, etc.) — inferred from autocomplete if omitted | | label | Label text | | placeholder | Placeholder text | | value | Current value | | name | Form field name | | required | Marks the field as required | | autocomplete | Standard HTML autocomplete token (e.g. email, tel, new-password, cc-number); also drives default type/pattern/inputmode validation | | pattern | Regex validation pattern — overrides any autocomplete-inferred default | | inputmode | Virtual keyboard hint — overrides any autocomplete-inferred default | | minlength / maxlength | Length constraints | | min / max | Numeric/date constraints | | error-message | Custom message shown when the field is invalid | | disabled | Disables the field |

Recognized autocomplete tokens and what they infer (only applied where you haven't set type/pattern/inputmode yourself): emailtype="email"; urltype="url"; tel/tel-nationaltype="tel" with a loose phone pattern; new-password/current-passwordtype="password"; one-time-code → numeric pattern; cc-number → card-number pattern; cc-csc → 3–4 digit pattern; postal-code → alphanumeric pattern. Any other token is passed through to the native autocomplete attribute (for autofill) without adding validation.

Fires standard input and change events, and exposes checkValidity() / reportValidity() methods that delegate to the underlying <input>.

<uwc-input> is built to be subclassed: type/pattern/inputmode/autocomplete defaults, and an optional adornment button inside the field, are all overridable extension points. <uwc-password-input> and <uwc-phone-input> (below) are implemented this way rather than as conditionals inside Input itself.

<uwc-password-input>

Extends <uwc-input>: always type="password" (toggling to text when revealed), with an eyeball button inside the field to show/hide the value. Accepts all the same attributes as <uwc-input> except type.

<uwc-password-input label="Password" placeholder="At least 8 characters" required minlength="8"></uwc-password-input>

<uwc-phone-input>

Extends <uwc-input>: defaults to type="tel" and autocomplete="tel", which brings in the same loose phone-number pattern and numeric keyboard hint that <uwc-input autocomplete="tel"> gets. Accepts all the same attributes as <uwc-input>.

<uwc-phone-input label="Phone" placeholder="(555) 123-4567" required></uwc-phone-input>

<uwc-button>

Button styled to match <uwc-link-button>.

| Attribute | Description | | ---------- | --------------------------------------------------- | | variant | primary (default), secondary, or danger | | type | Native button type (button, submit, reset) | | disabled | Disables the button |

<uwc-link-button>

Renders a semantically valid <a href> styled identically to <uwc-button>, for navigation that should look like a button.

| Attribute | Description | | ---------- | --------------------------------------------------- | | variant | primary (default), secondary, or danger | | href | Link target | | target | Anchor target (e.g. _blank) | | rel | Anchor rel attribute | | disabled | Renders as a non-navigable, aria-disabled link |

<uwc-sidebar-menu>

Vertical navigation menu that highlights the currently selected link.

| Attribute | Description | | ---------- | ---------------------------------------------------- | | links | JSON array of { "href": string, "label": string } | | selected | href of the currently active link |

<uwc-sidebar-menu
  links='[{"href":"/home","label":"Home"},{"href":"/settings","label":"Settings"}]'
  selected="/home"
></uwc-sidebar-menu>

<uwc-universal-nav>

Full-width top navigation bar with 20px padding: a flex row with a branding section and a user/login section, justified space-between and centered vertically. Content is provided via named slots.

| Attribute | Description | | ----------- | ----------------------------------------------------------------- | | logged-in | When present, shows the user slot instead of the login slot |

<!-- logged in -->
<uwc-universal-nav logged-in>
  <span slot="branding">Acme</span>
  <uwc-button slot="user" variant="secondary">Sign out</uwc-button>
</uwc-universal-nav>

<!-- logged out: falls back to a default "Log in" button if no login slot is given -->
<uwc-universal-nav>
  <span slot="branding">Acme</span>
  <uwc-button slot="login" variant="primary">Log in</uwc-button>
</uwc-universal-nav>

Development

npm run dev     # rollup in watch mode
npm run build   # one-off build to dist/