{"id":30980242,"url":"https://github.com/leifriksheim/customel","last_synced_at":"2025-09-12T07:04:20.442Z","repository":{"id":40951158,"uuid":"172595541","full_name":"leifriksheim/customel","owner":"leifriksheim","description":"🔹 A tiny helper function for creating Web Components","archived":false,"fork":false,"pushed_at":"2023-01-03T17:20:55.000Z","size":1080,"stargazers_count":13,"open_issues_count":10,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-12T07:02:36.523Z","etag":null,"topics":["custom-elements-v1","tagged-template-literals","ui-library","webcomponents"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/leifriksheim.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}},"created_at":"2019-02-25T22:28:53.000Z","updated_at":"2024-05-08T18:06:35.000Z","dependencies_parsed_at":"2023-02-01T08:16:23.814Z","dependency_job_id":null,"html_url":"https://github.com/leifriksheim/customel","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/leifriksheim/customel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leifriksheim%2Fcustomel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leifriksheim%2Fcustomel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leifriksheim%2Fcustomel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leifriksheim%2Fcustomel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leifriksheim","download_url":"https://codeload.github.com/leifriksheim/customel/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leifriksheim%2Fcustomel/sbom","scorecard":{"id":583420,"data":{"date":"2025-08-11","repo":{"name":"github.com/leifriksheim/customel","commit":"53dfaedfbcd130c1106cc2120bf90ad5cded39ae"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"40 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-6chw-6frg-f759","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-pp7h-53gx-mx7r","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-ff7x-qrg7-qggm","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-fhjf-83wg-r2j9","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-4g88-fppr-53pp","Warn: Project is vulnerable to: GHSA-4jqc-8m5r-9rpr","Warn: Project is vulnerable to: GHSA-g4rg-993r-mgx7","Warn: Project is vulnerable to: GHSA-wpg7-2c88-r8xv","Warn: Project is vulnerable to: GHSA-pq67-2wwv-3xjx","Warn: Project is vulnerable to: GHSA-8cj5-5rvv-wf4v","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-7p7h-4mm5-852v","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T19:48:00.069Z","repository_id":40951158,"created_at":"2025-08-20T19:48:00.069Z","updated_at":"2025-08-20T19:48:00.069Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274770735,"owners_count":25346211,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-12T02:00:09.324Z","response_time":60,"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":["custom-elements-v1","tagged-template-literals","ui-library","webcomponents"],"created_at":"2025-09-12T07:02:26.124Z","updated_at":"2025-09-12T07:04:20.427Z","avatar_url":"https://github.com/leifriksheim.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv\u003e\n  \u003cimg style=\"margin: 0 auto;\" width=\"300px\" align=\"center\" src=\"/media/logo.png\" /\u003e\n\u003c/div\u003e\n\n\u003chr /\u003e\n\n\u003cp\u003e\n\u003ca href=\"https://www.npmjs.com/package/customel\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/customel.svg\" alt=\"Version\"\u003e\u003c/a\u003e\n\u003ca href=\"https://www.npmjs.com/package/customel\"\u003e\u003cimg src=\"https://img.shields.io/npm/l/customel.svg\" alt=\"License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003e A tiny helper function for creating Web Components\n\n\u003cdiv\u003e\n  \u003cimg width=\"600px\"  src=\"/media/screenshot.png\" /\u003e\n\u003c/div\u003e\n\n## Highlights\n\n- Zero dependencies, [only 2kb](https://bundlephobia.com/result?p=customel@latest) 🕊️\n- Template literals as templating engine 💡\n- Write standards-compliant Web Components 🌍\n- Use with Vue, React, Angular and friends 🤝\n- Small and easy API 🚴\n\n## Table of contents\n\n* [Installation](#installation)\n* [Introduction](#introduction)\n* [Getting started](#getting-started)\n* [Documentation](#documentation)\n  * [Template](#template)\n  * [Styles](#styles)\n  * [State](#state)\n  * [Props](#props)\n  * [Actions](#actions)\n\n\n## Installation\n\n\u003cdetails\u003e\n  \u003csummary\u003eCDN\u003c/summary\u003e\n  \n  Create an index.html file and include Customel with the CDN:\n\n  ```html\n  \u003cscript src=\"https://unpkg.com/customel\"\u003e\u003c/script\u003e\n  ```\n\n  If you are using native ES Modules, you can include it in the index.html like this:\n\n  ```html\n  \u003cscript type=\"module\"\u003e\n    import customel from \"//unpkg.com/customel?module\";\n  \u003c/script\u003e\n  ```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\n  \u003csummary\u003eNPM\u003c/summary\u003e\n  \n  ```\n  npm install customel\n  ```\n\n  ```js\n  import customel from \"customel\";\n  ```\n\n\u003c/details\u003e\n\n## Introduction\n\nCustom elements are awesome. They make it possible for everybody to use your components, no matter which new javascript framework they are using. As a part of the native web platform they are a great way to build maintainable and long lasting UI-libraries.\n\nStill, writing Web Components from scratch can require quite a lot of boiler plate. Customel is a small wrapper around the `HTMLElement` class that reduces a lot of the boiler plate, enforces some best practises, and makes it easier to make new custom elements.\n\nCustomel is not meant to be a way to build software or large applications. It is built as a way to create small, reusable Web Components.\n\n\n## Getting started\n\nThe easiest way to try out Customel is using the [JSFiddle Hello World example](https://jsfiddle.net/waysofperception/mL9nopdf/). You can follow the Quick Start from here.\n\nFirst create a file called `my-element.js`. Import Customel, create your component, and define it.\n\n```js\n// my-element.js\nimport customel from \"//unpkg.com/customel?module\";\n\nconst Element = {\n  template() {\n    return \"\u003cdiv\u003eHello my new element!\u003c/div\u003e\";\n  }\n};\n\ncustomElements.define(\"my-element\", customel(Element));\n```\n\nCreate a `index.html` file, and import `my-element.js`.\nYour custom element is now ready to be used!\n\n```html\n\u003c!--  index.html ---\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript type=\"module\" src=\"my-element.js\"\u003e\u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cmy-element\u003e\u003c/my-element\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n## Documentation\n\n### Template\n\nYou probably want to render some html inside your custom element.\nThe easiest way to do this is to just return a string with some HTML:\n\n```js\nconst Element = {\n  template() {\n    return \"\u003cdiv\u003eHTML as a string\u003c/div\u003e\";\n  }\n};\n```\n\nThe `template` function also provides a template literal called `html` that we can use when we want to add event handlers to the component, or we want to loop through a list.\n\n```js\nconst items = [\"Item 1\", \"Item 2\"];\n\nconst Element = {\n  template: function(html) {\n    return html`\n      \u003cbutton onclick=${() =\u003e alert(\"Clicked\")}\u003eButton\u003c/button\u003e\n      \u003cul\u003e\n        ${items.map(\n          item =\u003e\n            html`\n              \u003cli\u003e${item}\u003c/li\u003e\n            `\n        )}\n      \u003c/ul\u003e\n    `;\n  }\n};\n```\n\n### Styles\n\nTo apply styles to your custom element, you can return CSS as a string:\n\n```js\nconst Element = {\n  styles() {\n    return `\n      button {\n        background-color: red;\n        color: white;\n      }\n    `;\n  },\n  template: function(html) {\n    return html`\n      \u003cbutton\u003eMy button\u003c/button\u003e\n    `;\n  }\n};\n```\n\nYou can also use props or state to render styles conditionally.\n\n```js\nconst Element = {\n  props: {\n    active: false\n  },\n  styles() {\n    return `\n      button {\n        background-color: ${this.props.active ? \"red\" : \"blue\"};\n        color: white;\n      }\n    `;\n  },\n  template: function(html) {\n    return html`\n      \u003cbutton\u003eMy button\u003c/button\u003e\n    `;\n  }\n};\n```\n\nThis will however cause a rerender of the whole internal stylesheet of the component. So it is adviced to toggle classes based on props or state instead:\n\n```js\nconst Element = {\n  props: {\n    active: false\n  },\n  styles() {\n    return `\n      button {\n        background-color: blue;\n        color: white;\n      }\n      button.active {\n        background-color: red;\n      }\n    `;\n  },\n  template: function(html) {\n    const buttonClass = this.props.active ? \"active\" : null;\n\n    return html`\n      \u003cbutton class=${buttonClass}\u003eMy button\u003c/button\u003e\n    `;\n  }\n};\n```\n\n### State\n\nYour state is contained in a state object.\nYou can modify the state with the `this.setSate` function.\n`this.setState` will cause a rerender and you will get a fresh DOM with updated data.\n\n```js\nconst Element = {\n  state: {\n    active: false\n  },\n  template: function(html) {\n    return html`\n      \u003cbutton onclick=${() =\u003e this.setState({ active: true })}\u003e\n        Active state is ${this.state.active}\n      \u003c/button\u003e\n    `;\n  }\n};\n```\n\n### Props\n\nUse props to define data that your custom element can recieve from the outside.\nAlways provide a default value to your props.\n\n#### Simple props\n\nIf the default value is a `string`, `number` or a `boolean` then your prop will be available as an attribute on your custom element:\n\n```js\nconst Accordion = {\n  props: {\n    open: false\n  },\n  template: function(html) {\n    return html`\n      \u003cdiv\u003e\n        \u003ch1\u003eAccordion title\u003c/h1\u003e\n        ${this.props.open\n          ? html`\n              \u003cp\u003eAccordion content\u003c/p\u003e\n            `\n          : null}\n      \u003c/div\u003e\n    `;\n  }\n};\n\ncustomElement.define(\"my-accordion\", customel(Accordion));\n```\n\nThis makes attributes available on your component.\n\n```html\n\u003cmy-accordion title=\"My special list\" open\u003e\u003c/my-accordion\u003e\n\n\u003cscript\u003e\n\n  const accordion = document.querySelector('my-accordion');\n\n  accordion.addEventListener(\"click\" =\u003e {\n\n    if (accordion.hasAttribute(\"open\")) {\n      accordion.removeAttribute(\"open\");\n    } else {\n      accordion.setAttribute(\"open\", \"\");\n    }\n\n  })\n\u003c/script\u003e\n```\n\nThey will also be reflected as properties, so you could just do this as well:\n\n```html\n\u003cmy-accordion title=\"My special list\" open\u003e\u003c/my-accordion\u003e\n\n\u003cscript\u003e\n  const accordion = document.querySelector(\"my-accordion\");\n  accordion.open = !accordion.open;\n\u003c/script\u003e\n```\n\n#### Advanced props\n\nIf the default value is a `function`, `object`, `array` or any other type of data, it will be available as a property on the element instead of an attribute.\n\n```js\nconst List = {\n  props: {\n    todos: [\"Buy milk\", \"Learn stuff\"]\n  },\n  template: function(html) {\n    return html`\n      \u003cul\u003e\n        ${this.props.todos.map(\n          todo =\u003e html`\n            \u003cli\u003e${todo}\u003c/li\u003e\n          `\n        )}\n      \u003c/ul\u003e\n    `;\n  }\n};\n\ncustomElement.define(\"my-list\", customel(List));\n```\n\n```html\n\u003cmy-list\u003e\u003c/my-list\u003e\n\n\u003cscript\u003e\n  // \"todos\" is an array and can only be updated to properties on the element\n  // setting the property will triggger a rerender of the component, just like setting a new attribute\n  document.querySelector(\"my-list\").todos = [\"Other things\", \"More things\"];\n\u003c/script\u003e\n```\n\n### Actions\n\n```js\nconst Element = {\n  props: {\n    title: \"Title\"\n  },\n  actions: {\n    showMessage() {\n      alert(\"Title: is\" + this.title);\n    }\n  },\n  template: function(html) {\n    return html`\n      \u003cbutton onclick=${() =\u003e this.actions.showMessage()}\u003e\n        My button\n      \u003c/button\u003e\n    `;\n  }\n};\n```\n\n## Use in frameworks\n\n### React\n\n### Vue\n\nCustom Elements play nicely with Vue, but for rich data you will need to add a `.prop` on the attribute to bind the value to the property and not the attribute of the custom element.\nFor now you will also need to replace the whole array every time you update it's value, as Customel does not watch for nested changes. This may come in the future via use of a Proxy.\n\n```html\n\u003cdiv id=\"vue\"\u003e\n  \u003cmy-accordion :title=\"mainTitle\" :items.prop=\"items\"\u003e\u003c/my-accordion\u003e\n\u003c/div\u003e\n\u003cscript\u003e\n  new Vue({\n    el: \"#vue\",\n    data: {\n      mainTitle: \"My accordion\",\n      items: [\n        {\n          title: \"My title\",\n          content: \"Here is some content about this stuff\"\n        },\n        {\n          title: \"Another title\",\n          content: \"Here is some other content about this stuff\"\n        }\n      ]\n    }\n  });\n\u003c/script\u003e\n```\n\n### Angular\n\nComing...\n\n### React\n\nComing...\n\nCustomel is [MIT licensed](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleifriksheim%2Fcustomel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleifriksheim%2Fcustomel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleifriksheim%2Fcustomel/lists"}