{"id":13622508,"url":"https://github.com/kgscialdone/facet","last_synced_at":"2025-04-06T22:06:51.636Z","repository":{"id":209591767,"uuid":"724435147","full_name":"kgscialdone/facet","owner":"kgscialdone","description":"Web components made simple and declarative","archived":false,"fork":false,"pushed_at":"2024-01-06T20:44:44.000Z","size":66,"stargazers_count":369,"open_issues_count":1,"forks_count":7,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-03-30T21:06:24.056Z","etag":null,"topics":["custom-elements","facet","html","javascript","vanilla-js","web-components"],"latest_commit_sha":null,"homepage":"https://discord.gg/fmqVtPABQh","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kgscialdone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"kgscialdone","ko_fi":"kgscialdone"}},"created_at":"2023-11-28T04:14:03.000Z","updated_at":"2025-03-28T15:57:29.000Z","dependencies_parsed_at":"2023-12-17T05:21:12.414Z","dependency_job_id":"650a3abc-1c04-4423-9fe4-76f9ce92934c","html_url":"https://github.com/kgscialdone/facet","commit_stats":null,"previous_names":["katrinakitten/facet","kgscialdone/facet"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kgscialdone%2Ffacet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kgscialdone%2Ffacet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kgscialdone%2Ffacet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kgscialdone%2Ffacet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kgscialdone","download_url":"https://codeload.github.com/kgscialdone/facet/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247557767,"owners_count":20958047,"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","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","facet","html","javascript","vanilla-js","web-components"],"created_at":"2024-08-01T21:01:20.283Z","updated_at":"2025-04-06T22:06:51.614Z","avatar_url":"https://github.com/kgscialdone.png","language":"JavaScript","funding_links":["https://github.com/sponsors/kgscialdone","https://ko-fi.com/kgscialdone","https://ko-fi.com/kgscialdone)_"],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cimg src=\"https://facet.unmodernweb.com/assets/facet-light.png\" style=\"width:300px\" alt=\"Facet\"\u003e\n\nFacet is a single-file web library that allows for the easy, declarative definition of [web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components). By making use of `\u003ctemplate\u003e` elements with special attributes, Facet makes it possible to define useful and effective web components with no Javascript boilerplate, so you can focus entirely on the structure and behavior of your component.\n\n\u003e _Like what you see? [Consider buying me a coffee](https://ko-fi.com/kgscialdone)_ 💜 \n\n## Installation\nYou can download `facet.min.js` from this repository and reference it locally, or retrieve it directly from a CDN like JSDelivr. Facet will automatically detect component definitions in your page's HTML and convert them into web components.\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/gh/kgscialdone/facet@0.1.2a/facet.min.js\"\u003e\u003c/script\u003e\n```\n\n## Defining Components\nFacet components are defined with `\u003ctemplate component\u003e` elements, which will be automatically detected when the page loads. Facet uses the browser's [custom elements API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) internally, so Facet components are treated just like normal HTML elements.\n```html\n\u003ctemplate component=\"hello-world\"\u003e\n  \u003cp\u003eHello, \u003cslot\u003eworld\u003c/slot\u003e!\u003c/p\u003e\n\u003c/template\u003e\n\n\u003chello-world\u003e\u003c/hello-world\u003e\n\u003chello-world\u003eFacet\u003c/hello-world\u003e \u003c!-- replaces \"world\" with \"Facet\" --\u003e\n```\n\nBy default, Facet components use the [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) in `closed` mode. You can adjust this with the `shadow` attribute, which accepts `open`, `closed`, or `none`.\n```html\n\u003ctemplate component=\"hello-world\" shadow=\"none\"\u003e\n  \u003cp\u003eHello, world!\u003c/p\u003e\n\u003c/template\u003e\n```\n\nYou can also define a list of attributes to be observed by the component; any attributes defined here will trigger the component's `attributeChanged` event when changed, allowing your code to respond to the update. (See [Attaching Behavior](#attaching-behavior) for details on `\u003cscript on\u003e`.)\n```html\n\u003ctemplate component=\"observed-attrs\" observe=\"name title\"\u003e\n  \u003cscript on=\"attributeChanged\"\u003econsole.log(event.detail.name)\u003c/script\u003e\n\u003c/template\u003e\n```\n\n## Inherited Attributes\nIn many cases, it's beneficial to be able to define attributes where a component is used, and have those attributes change the behavior of elements inside the component. Facet achieves this through inherited attributes, which make copying attributes deeper into your components quick and easy.\n```html\n\u003ctemplate component=\"labeled-input\"\u003e\n  \u003clabel inherit=\"name\u003efor required\"\u003e\u003cslot\u003eInput\u003c/slot\u003e\u003c/label\u003e\n  \u003cinput inherit=\"name\u003eid type value required placeholder\"\u003e\n\u003c/template\u003e\n\n\u003clabeled-input name=\"email\" type=\"email\" required\u003eEmail\u003c/labeled-input\u003e\n```\n\nWhen inheriting attributes, you can use them as-is, rename them, filter them through a Javascript function, or both:\n```html\n\u003cp inherit=\"title\"\u003eLorem ipsum dolor sit amet...\u003c/p\u003e\n\u003cp inherit=\"label\u003etitle\"\u003eLorem ipsum dolor sit amet...\u003c/p\u003e\n\u003cp inherit=\"title/uppercase\"\u003eLorem ipsum dolor sit amet...\u003c/p\u003e\n\u003cp inherit=\"label\u003etitle/uppercase\"\u003eLorem ipsum dolor sit amet...\u003c/p\u003e\n\n\u003cscript\u003e\n  function uppercase(string) { return string.toUpperCase() }\n\u003c/script\u003e\n\n\u003c!-- Or define your filter functions inside your component like this! --\u003e\n\u003c!-- They'll be scoped to just that component and won't conflict with anything else. --\u003e\n\u003cscript filter=\"uppercase\"\u003e\n  return value.toUpperCase()\n\u003c/script\u003e\n```\n\nIn addition, attributes that are both observed and inherited will automatically update whenever the component's attribute is changed:\n```html\n\u003ctemplate component=\"observe-inherit\" observe=\"div-style\"\u003e\n  \u003cdiv inherit=\"div-style\u003estyle\"\u003e\u003c/div\u003e\n\u003c/template\u003e\n\n\u003c!-- Changing the div-style attribute will automatically update the inner \u003cdiv\u003e's styles! --\u003e\n\u003cobserve-inherit div-style=\"width:100%;height:100%;background:rebeccapurple;\"\u003e\u003c/observe-inherit\u003e\n```\n\n## Attaching Behavior\nSince Facet components are defined entirely in HTML, you don't have the opportunity to edit the component class directly to add your behavior. Instead, Facet searches for `\u003cscript on\u003e` elements inside of component definitions, and attaches their contents to their parent elements as event handlers.\n```html\n\u003ctemplate component=\"immediate-alert\"\u003e\n  \u003cscript on=\"connect\"\u003ealert(\"You've been alerted immediately!\")\u003c/script\u003e\n\u003c/template\u003e\n```\n\nEvent handler scripts don't have to be at the top level of the component; they'll be attached to whatever their parent element is, allowing you to easily define complex behaviors for any part of your component.\n```html\n\u003ctemplate component=\"click-alert\"\u003e\n  \u003cbutton\u003e \u003c!-- Click handler will be attached here!--\u003e\n    Alert me!\n    \u003cscript on=\"click\"\u003ealert(\"I've been clicked!\")\u003c/script\u003e\n  \u003c/button\u003e\n\u003c/template\u003e\n```\n\nFacet exposes components' lifecycle hooks as custom events, which works hand in hand with event handler scripts to allow flexible, easy definition of custom behavior.\n\n| Component Class Function   | Facet Event        | `event.detail`                            |\n| :------------------------- | :----------------- | :---------------------------------------- |\n| `connectedCallback`        | `connect`          | `{ component }`                           |\n| `disconnectedCallback`     | `disconnect`       | `{ component }`                           |\n| `adoptedCallback`          | `adopt`            | `{ component }`                           |\n| `attributeChangedCallback` | `attributeChanged` | `{ component, name, newValue, oldValue }` |\n\nIn addition, event handler scripts have access to a small handful of magic variables, in a similar way to the `onclick` event handler attribute family.\n\n| Magic Variable | Description\n| :------------- | :--\n| `this`         | The element to which the handler script is attached (parent element).\n| `host`         | The host component.\n| `root`         | The host component's shadow root (or the host component if `shadow=\"none\"`).\n| `event`        | The event that triggered the handler.\n\nYou can also adjust how event handler scripts act with the `once`, `capture`, and `passive` attributes, which correspond to their respective equivalents in [`addEventListener`'s `options` parameter](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options). If the attribute is present, the corresponding option will be set to `true`, otherwise it will follow the browser defaults.\n\n## Mixins\nFacet also provides a mixin system in order to facilitate code reuse between similar components. Like components, mixins are defined with `\u003ctemplate\u003e` elements, and their contents are appended to the shadow DOM of any component they're mixed into.\n```html\n\u003ctemplate mixin=\"alert\"\u003e\n  \u003cscript on=\"connect\"\u003ealert(\"Connected!\")\u003c/script\u003e\n\u003c/template\u003e\n\n\u003ctemplate component=\"hello-world\" mixins=\"alert\"\u003e\n  \u003cp\u003eHello, \u003cslot\u003eworld\u003c/slot\u003e!\u003c/p\u003e\n\u003c/template\u003e\n\u003ctemplate component=\"goodbye-world\" mixins=\"alert\"\u003e\n  \u003cp\u003eGoodbye, \u003cslot\u003eworld\u003c/slot\u003e...\u003c/p\u003e\n\u003c/template\u003e\n\n\u003chello-world\u003e\u003c/hello-world\u003e \u003c!-- alerts on connect --\u003e\n\u003cgoodbye-world\u003e\u003c/goodbye-world\u003e \u003c!-- alerts on connect --\u003e\n```\n\nMixins with the `global` attribute will be automatically applied to all components on a page.\n```html\n\u003c!-- Now every component will alert on connect! --\u003e\n\u003ctemplate mixin=\"alert\" global\u003e\n  \u003cscript on=\"connect\"\u003ealert(\"Connected!\")\u003c/script\u003e\n\u003c/template\u003e\n```\n\nMixins with the `prepend` attribute will prepend their contents instead of appending them.\n```html\n\u003ctemplate mixin=\"lorem\" prepend\u003eLorem ipsum dolor sit amet...\u003c/template\u003e\n\n\u003ctemplate component=\"prepended-mixins\" mixin=\"lorem\"\u003e\n  is a pretty weird choice of filler text, if you think about it.\n\u003c/template\u003e\n```\n\n## Advanced Features\nYou can define [customized built-in elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#customized_built-in_elements) with the `extends` attribute.\n```html\n\u003ctemplate component=\"lorem-ipsum\" extends=\"p\"\u003e\n  Lorem ipsum dolor sit amet... \u003cslot\u003e\u003c/slot\u003e\n\u003c/template\u003e\n\n\u003cp is=\"lorem-ipsum\"\u003eis a pretty weird choice of filler text, if you think about it.\u003c/p\u003e\n```\n\nYou can define [form-associated custom elements](https://web.dev/articles/more-capable-form-controls) with the `forminput` attribute.\n```html\n\u003ctemplate component=\"inc-dec\" forminput\u003e\n  \u003cscript on=\"connect\" once\u003ehost.innerText = host.value\u003c/script\u003e\n  \u003cbutton\u003e+ \u003cscript on=\"click\"\u003ehost.innerText = ++host.value\u003c/script\u003e\u003c/button\u003e\n  \u003cspan\u003e\u003cslot\u003e\u003c/slot\u003e\u003c/span\u003e\n  \u003cbutton\u003e- \u003cscript on=\"click\"\u003ehost.innerText = --host.value\u003c/script\u003e\u003c/button\u003e\n\u003c/template\u003e\n\n\u003cinc-dec value=\"0\"\u003e\u003c/inc-dec\u003e\n```\n\n## Configuration Options\nWhile Facet's defaults are designed to serve the majority of use cases out of the box, it does have a small handful of configuration options, which can be adjusted via attributes on the `\u003cscript\u003e` tag that imports the Facet library.\n\n### Namespacing\nRequires a prefix on the `component` and `mixin` attributes to help avoid conflicts. The prefix defaults to `facet-` if no value is supplied to the attribute.\n```html\n\u003c!-- With default prefix --\u003e\n\u003cscript src=\"facet.min.js\" namespace\u003e\u003c/script\u003e\n\n\u003ctemplate facet-component=\"hello-world\"\u003e\n  \u003cp\u003eHello, \u003cslot\u003eworld\u003c/slot\u003e!\u003c/p\u003e\n  \u003cscript on=\"connect\"\u003econsole.log(this)\u003c/script\u003e\n\u003c/template\u003e\n\n\u003c!-- With custom prefix --\u003e\n\u003cscript src=\"facet.min.js\" namespace=\"fc-\"\u003e\u003c/script\u003e\n\n\u003ctemplate fc-component=\"hello-world\"\u003e\n  \u003cp\u003eHello, \u003cslot\u003eworld\u003c/slot\u003e!\u003c/p\u003e\n  \u003cscript on=\"connect\"\u003econsole.log(this)\u003c/script\u003e\n\u003c/template\u003e\n```\n\n### Disable Automatic Discovery\nPrevents the automatic discovery of component and mixin definitions, requiring you to manually call `facet.discoverDeclarativeComponents` yourself.\n```html\n\u003cscript src=\"facet.min.js\" libonly\u003e\u003c/script\u003e\n\u003cscript defer\u003efacet.discoverDeclarativeComponents(document)\u003c/script\u003e\n```\n\n### Default Shadow Mode\nChanges the default shadow root mode for new components. Equivalent to the `shadow` attribute on individual components.\n```html\n\u003cscript src=\"facet.min.js\" shadow=\"none\"\u003e\u003c/script\u003e\n```\n\n## Javascript API\nWhile Facet strives to never require you to write Javascript for any of its core behavior, it does expose its internal workings as a basic Javascript API. Facet's API is intentionally not perfectly ergonomic, since the declarative `\u003ctemplate\u003e`-based interface described above is always the intended use case, but there are some things that can only be realistically achieved with Javascript, such as single-file importable components.\n\n| Function | Description\n| :------- | :--\n| `facet.defineComponent(tagName, template, options)` | Define a new component.\n| `facet.defineMixin(name, template, options)`        | Define a new mixin.\n| `facet.discoverDeclarativeComponents(root)`         | Find and define all components and mixins in a given parent element.\n\n| Variable | Description\n| :------- | :--\n| `facet.config.namespace`         | The required namespace prefix for the `component` and `mixin` attributes.\n| `facet.config.autoDiscover`      | If false, skip automatic discovery of components/mixins on page load.\n| `facet.config.defaultShadowMode` | The default shadow root mode for new components.\n| `facet.mixins`                   | Information about all currently defined mixins.\n\nFacet's source code is also lovingly commented with [JSDoc](https://jsdoc.app/), which keeps it lightweight and build-step free while still enabling Typescript users to rest easy about type safety when interacting with Facet's API.\n\n## Reviews\n\u003e ⭐⭐⭐⭐⭐ \"Anyone exploring web components really would do themselves a major favor giving this a solid look.\"\n\u003e \n\u003e ⭐⭐⭐⭐⭐ \"Facet is the first time I've looked at WebComponents and thought 'oh, I get this, I get what it's for'.\"\n\u003e\n\u003e ⭐⭐⭐⭐⭐ \"This is exactly what I thought web components should be.\"\n\u003e\n\u003e ⭐⭐⭐⭐⭐ \"Holy f\\*\\*\\*, this is awesome!\"\n\u003e\n\u003e ⭐⭐⭐⭐⭐ \"I never thought much of web components but this really changes my view.\"\n\n\u003csub\u003e* All reviews are real quotes from users, but are used here in jest and with permission.\u003c/sub\u003e\n\n## Sponsors\nThank you so much to these $5/mo+ sponsors for your support!\n\n- [Morgan Murrah](https://github.com/airbr) ($5/mo)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkgscialdone%2Ffacet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkgscialdone%2Ffacet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkgscialdone%2Ffacet/lists"}