Server-first web components.
A collection of Custom Elements designed to work with server-rendered HTML and progressive enhancement.
- Server-rendered HTML is the baseline
- Custom Elements without Shadow DOM by default
- Small, composable components
- Minimal JavaScript
- No framework lock-in
- Optional framework-specific variants (e.g. Bootstrap 5)
| Component | Description |
|---|---|
<pwc-auto-submit> |
Auto-submits a form on change events, with optional local reload via fetch + transclusion |
<pwc-shown-if> / <pwc-hidden-if> / <pwc-enabled-if> / <pwc-disabled-if> |
Conditionally show/hide or enable/disable DOM sections based on a form input |
<pwc-dialog-opener> |
Enhances links to open their targets in a modal dialog |
<pwc-filter> |
Adds a search input to filter arbitrary markup based on free-text input |
<pwc-include> |
Client-side HTML transclusion — fetches HTML from a URL and inserts it |
<pwc-modal-dialog> |
Low-level building block for modal dialogs from JavaScript |
<pwc-multiselect-dual-list> |
Dual-list multiselect UI that enhances a native <select> element |
<pwc-validity> |
Maps data-validity attributes to setCustomValidity() with optional auto-clearing |
<pwc-zone-transfer> |
Zone-based drag & drop and keyboard sorting for moving elements between containers |
npm install @tillsc/progressive-web-components
Import individual components:
import "@tillsc/progressive-web-components/filter";
import "@tillsc/progressive-web-components/filter-bs5";Or import all components at once:
import "@tillsc/progressive-web-components/all";
import "@tillsc/progressive-web-components/all-bs5";For more control you can import the unbundled source files directly
(e.g. src/filter/filter.js). In that case you need to:
- Call the exported
define()function yourself to register the Custom Element. - Bundle the accompanying CSS file (
src/<component>/<component>.css) into your stylesheet where one exists.
dist/ contains one JavaScript file per component (and variant). No build step is required for consumers.
auto-submit.js(no Bootstrap variant needed)conditional-display.js(no Bootstrap variant needed)dialog-opener.js/dialog-opener-bs5.jsfilter.js/filter-bs5.jsinclude.js(no Bootstrap variant needed)modal-dialog.js/modal-dialog-bs5.jsmultiselect-dual-list.js/multiselect-dual-list-bs5.jsvalidity.js/validity-bs5.jszone-transfer.js(no Bootstrap variant needed)all.js/all-bs5.js(all components bundled)
Components that replace HTML (<pwc-include>, <pwc-dialog-opener> and
<pwc-auto-submit> with local-reload) support an optional morph hook. When
registered, the morph library is used instead of
replacing the DOM wholesale, which preserves focus, scroll position, and input state during
updates.
Register the Idiomorph namespace
object directly. The components configure morphStyle: "innerHTML",
restoreFocus: true, and a beforeAttributeUpdated callback that protects
value/checked on connected, editable form elements automatically (readonly
and disabled fields are always updated from the server). Without a registered
morph library the components fall back to innerHTML / replaceChildren.
Register via the Context Protocol:
import { Idiomorph } from "idiomorph";
document.addEventListener("context-request", (e) => {
if (e.context === "idiomorph") e.callback(Idiomorph);
});Alternatively, set it as a global:
window.PWC = { idiomorph: Idiomorph };To disable morphing for a specific element, add the nomorph attribute:
<pwc-include src="/partial" nomorph></pwc-include>Readonly and disabled fields are never protected (the user cannot have changed them), so they always receive the server-rendered value automatically.
For editable fields that should still accept the server value, add data-pwc-force-value
in the server response:
<input name="total" value="42" data-pwc-force-value>For checkboxes and radio buttons, the checked attribute is forced instead of value.