{"id":22808919,"url":"https://github.com/morganney/web-component-best-practices","last_synced_at":"2026-05-27T03:10:02.990Z","repository":{"id":77431057,"uuid":"543308342","full_name":"morganney/web-component-best-practices","owner":"morganney","description":"Some best practices regarding web component architecture, development, building and publishing.","archived":false,"fork":false,"pushed_at":"2026-05-26T15:28:30.000Z","size":186,"stargazers_count":2,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-26T17:21:14.601Z","etag":null,"topics":["css","html","javascript","web-components"],"latest_commit_sha":null,"homepage":"https://morganney.github.io/web-component-best-practices/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/morganney.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-09-29T20:31:34.000Z","updated_at":"2026-05-26T15:31:48.000Z","dependencies_parsed_at":null,"dependency_job_id":"e63d2cd2-f708-4a95-8db7-e928f68db12f","html_url":"https://github.com/morganney/web-component-best-practices","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/morganney/web-component-best-practices","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morganney%2Fweb-component-best-practices","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morganney%2Fweb-component-best-practices/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morganney%2Fweb-component-best-practices/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morganney%2Fweb-component-best-practices/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/morganney","download_url":"https://codeload.github.com/morganney/web-component-best-practices/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morganney%2Fweb-component-best-practices/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33548490,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","html","javascript","web-components"],"created_at":"2024-12-12T11:12:51.815Z","updated_at":"2026-05-27T03:10:02.957Z","avatar_url":"https://github.com/morganney.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Web Component Best Practices\n\nA practical reference for architecting, developing, and publishing modern HTML custom elements with minimal tooling.\n\n## Constraints (self-imposed)\n\n- Use as little tooling as possible.\n- ES [modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) only.\n- Consumable directly from a [CDN](https://unpkg.com/).\n- Consumable as an npm package in bundlers like Vite, Rollup, and Webpack.\n- Keep each technology in a separate file (HTML, CSS, JS/TS).\n\n## Architecture\n\nThe core pattern is strict separation of concerns:\n\n- **HTML** in `template.html`\n- **CSS** in `styles.css`\n- **Component class/runtime** in `element.js`\n- **Registration side effect** in `defined.js`\n\nCurrent example layout:\n\n```text\nexample/\n  index.html\n  src/\n    template.html\n    styles.css\n    element.js\n    defined.js\n```\n\n### `styles.css`\n\n- Standard CSS for the component [ShadowRoot](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot).\n- Loaded by `element.js` and injected into the template as a `\u003cstyle\u003e` element.\n\n### `template.html`\n\n- One root [`\u003ctemplate\u003e`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n- Contains component markup and named/default [`\u003cslot\u003e`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) regions.\n- Fetched by `element.js` and cloned into shadow DOM.\n\n### `element.js`\n\n- Defines the custom element class (`extends HTMLElement`).\n- Handles lifecycle behavior and shadow-root setup.\n- Uses [top-level `await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#top_level_await) so dependent modules wait for template/styles setup.\n- Exposes `register(name?)` for explicit, side-effect-free registration.\n\n### `defined.js`\n\n- Encapsulates the side effect of registration (`customElements.define(...)`).\n- Supports dynamic element names through query params (for example `?name=my-element`).\n- Uses `whenDefined(...)` and a duplicate-define guard for safer repeated imports.\n\n## Example behavior\n\n`example/index.html` demonstrates four registration patterns with the same underlying component class:\n\n1. **Explicit registration (no side effect)** via `element.js` + `register(...)`\n2. **Default side-effect registration** via `defined.js`\n3. **Local dynamic name** via `defined.js?name=dynamic-name`\n4. **CDN dynamic name** via `defined.js?name=cdn-dynamic-name`\n\n## Declarative Shadow DOM (DSD) approach\n\nThis repo also includes a parser-time DSD variant in `example/dsd.html`.\n\n- `example/dsd.html` is the DSD page template.\n- `example/src/dsd/template.js` provides reusable HTML template strings for the component cards.\n- `vite.config.js` injects those templates into `\u003c!-- inject:cards --\u003e` at build time.\n\nThis gives you true parser-time shadow roots in the generated HTML while keeping the card markup DRY in source.\n\n### Why `example/src/dsd/register.js` exists\n\nParser-time DSD creates shadow roots from HTML, but custom elements still need to be defined to upgrade and run lifecycle code.\n\n`example/src/dsd/register.js` is a focused bootstrap module that registers all demo tag-name variants used on the DSD page:\n\n1. explicit registration for the default tag name via `element.js` + `register(...)`\n2. explicit registration for `dynamic-name` via `register('dynamic-name')`\n3. explicit registration for `no-side-effects` via `register('no-side-effects')`\n4. CDN dynamic-name registration via `dsd/defined.js?name=cdn-dynamic-name`\n\nWithout this bootstrap file, DSD markup would still parse, but the custom elements on that page would not upgrade.\n\n## Tradeoffs\n\nThere is a hard constraint triangle for this problem space. Today, you can reliably pick two of these three goals at once: true parser-time DSD, DRY shared markup, and no build/no server composition.\n\n| Keep           | Implementation                        | Tradeoff                       |\n| -------------- | ------------------------------------- | ------------------------------ |\n| DSD + no build | duplicate markup in each HTML file    | not DRY (drift risk)           |\n| DRY + no build | runtime JS composition/fetch          | not true parser-time DSD       |\n| DSD + DRY      | build step or server-side composition | cannot stay no-build/no-server |\n\nFor this repo, the DSD path chooses DSD + DRY via build-time composition, while the runtime path keeps separate source files with no required build for local static serving.\n\n## Related example (`youtube-vid`)\n\nFor a production-oriented implementation of these patterns, see:\n\n- https://github.com/morganney/youtube-vid\n\nThat project demonstrates the same architectural goals with a different packaging decision:\n\n- It uses [Vite asset bundling](https://vitejs.dev/guide/assets#importing-asset-as-string) to include HTML/CSS and reduce runtime requests.\n- It also includes an example [CLI copy script](https://github.com/morganney/youtube-vid/blob/main/src/cli.ts) for workflows that prefer shipping static assets separately.\n\nHistorical context:\n\n- Original non-bundled implementation: https://github.com/morganney/youtube-vid/tree/3d7b8ac817170cff8bba036c1a938042a0e0b76f\n- Example consumer usage in a Next.js app: https://github.com/morganney/morgan.neys.info/commit/9771143e1c7c7e6f82baf0a11948cba5a1304c3f#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519R12\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorganney%2Fweb-component-best-practices","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorganney%2Fweb-component-best-practices","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorganney%2Fweb-component-best-practices/lists"}