@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-componentsOr, 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): email → type="email"; url → type="url"; tel/tel-national → type="tel" with a loose phone pattern; new-password/current-password → type="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/