{"id":15099276,"url":"https://github.com/ngasull/classic-element","last_synced_at":"2025-10-08T05:30:38.164Z","repository":{"id":246506029,"uuid":"821315796","full_name":"ngasull/classic-element","owner":"ngasull","description":"Practical Web Components","archived":true,"fork":false,"pushed_at":"2024-07-13T09:12:16.000Z","size":75,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-10-02T06:42:36.152Z","etag":null,"topics":["custom-elements","deno","javascript","nodejs","typescript","web-components"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ngasull.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2024-06-28T09:12:01.000Z","updated_at":"2024-07-20T11:05:01.000Z","dependencies_parsed_at":"2024-06-28T11:15:30.864Z","dependency_job_id":"6ed2b2bb-a29a-4774-aa0a-d0e0c5e820b5","html_url":"https://github.com/ngasull/classic-element","commit_stats":{"total_commits":25,"total_committers":1,"mean_commits":25.0,"dds":0.0,"last_synced_commit":"ff4f9fae5c549688af5cbe958cce8363edf5da23"},"previous_names":["ngasull/classic-element"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngasull%2Fclassic-element","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngasull%2Fclassic-element/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngasull%2Fclassic-element/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngasull%2Fclassic-element/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ngasull","download_url":"https://codeload.github.com/ngasull/classic-element/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235683861,"owners_count":19029079,"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","deno","javascript","nodejs","typescript","web-components"],"created_at":"2024-09-25T17:09:12.073Z","updated_at":"2025-10-08T05:30:32.874Z","avatar_url":"https://github.com/ngasull.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @classic/element - Practical Web Components\n\n[![JSR](https://jsr.io/badges/@classic/element)](https://jsr.io/@classic/element)\n\nAims to be the thinnest practical layer over custom elements / web components.\n\n## Background\n\n### Why custom elements?\n\nJS and CSS can natively be associated to custom element tags. This way, SSR is solved by default:\n\n- Custom elements are rendered in real time. No [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content)!\n- The page contains all SEO information without executing JS\n\n\u003e [!NOTE]\n\u003e **This implies much simpler development stacks, only requiring backends to produce classic HTML**.\n\u003e \n\u003e In most cases, custom elements are ideally bundled together and inlined into the first download of a page. Subsequent navigation may be further sped up by dynamically fetching content.\n\nThanks to [CSS parts](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_shadow_parts) and [variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties), custom elements solve known issues with nesting and precedence.\n\nLikewise, custom elements rely on [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) to sandbox and control their rendering.\n\n### Why Classic?\nWe loved component frameworks like react to solve dynamic web application development. Today, we've got native standards instead to build upon: custom elements. However, while their native API allows as much flexibility as possible, it's not systematized enough to be practical.\n\nClassic provides:\n- Simplicity\n- Small bundle size\n- Low memory footprint\n- Signal-based reactivity\n- JSX = reactive DOM\n- CSS in JS, convenient and optimized\n- TypeScript-first attribute to property synchronization\n- Creation of form-accessible elements through [ElementInternals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)\n- Compressible event helpers in JSX and aside\n- SSR-ready API using [declarative shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#declaratively_with_html) (for complex components)\n- Cross-framework reusability\n\nThe native API is very verbose, which has an impact on the size of the JS bundle sent over the network. This size is critical to good user experience. Not only Classic compiles down below 2KB gzipped and is designed to let developers write concise and highly compressible code (avoiding tokens that minifiers can't mangle).\n\nClassic aims to be the thinnest layer to efficiently guide developers in their custom elements journey. Note that Classic elements are native custom elements.\n\n### When should I use custom elements?\nFor any web **page**.\n\n### When should I use a component library? (react, vue...)\nFor complex **components**: rich text editors, interactive graphs, games... They can be embedded and loaded into a web page that use custom elements or use custom elements themselves.\n\n## Learn by example\n\n### Self-incrementing counter\n\n```tsx\nimport { define, signal } from \"classic/element\"\n\ndefine(\"x-counter\", {\n  css: {\n    \"\": { // Same as \":host\"\n      color: \"red\",\n    },\n  },\n  js(dom) {\n    const [count, setCount] = signal(0);\n    \n    const root = dom(\u003c\u003eCounting {count}\u003c/\u003e);\n\n    const t = setInterval(() =\u003e setCount(count() + 1), 1000);\n    onDisconnect(root, () =\u003e clearInterval(t));\n  },\n});\n```\n\n### Custom button with SVG icon\n```tsx\ndefine(\"x-button\", {\n  css: {\n    \"\": {\n      color: \"red\",\n    },\n    svg: {\n      width: 30,\n      height: 30,\n    },\n  },\n  props: { circle: Boolean },\n  js(dom, { circle, type }) {\n\n    const root = dom(\n      \u003cbutton type={type} onClick={}\u003e\n        {svgns(() =\u003e (\n          \u003csvg\u003e\n            {circle()\n            ? \u003ccircle r=\"15\" fill=\"blue\" cx={15} cy={15} /\u003e\n            : \u003crect cx={15} cy={15} /\u003e}\n          \u003c/svg\u003e\n        ))}\n        \u003cslot /\u003e\n      \u003c/button\u003e,\n    );\n  },\n});\n```\n\n## Signals\n\nClassic signals are mutable values that can be tracked over time. Native functions are read-only signals that can use other signals.\n\nClassic JSX accepts signals:\n\n```tsx\nimport { signal } from \"classic/element\"\n\nconst [hover, setHover] = signal(false); // Initial value: false\n\nconst button = (\n  \u003cspan\n    onMouseOver={() =\u003e setHover(true)}\n    onMouseOut={() =\u003e setHover(false)}\n    data-hover={hover}\n  \u003e\n     {() =\u003e hover() ? \"Use CSS for hovers!\" : \"Hover me\"}\n  \u003c/span\u003e\n);\n```\n\n\u003e [!IMPORTANT]\n\u003e Classic JSX requires explicit signals:\n\u003e \n\u003e ```tsx\n\u003e const [value, setValue] = signal(false);\n\u003e \n\u003e // 🛑 Not reactive\n\u003e \u003cinput disabled={value()} /\u003e\n\u003e \n\u003e // ✅ Reactive\n\u003e \u003cinput disabled={value} /\u003e\n\u003e \u003cinput disabled={() =\u003e !value()} /\u003e\n\u003e ```\n\u003e \n\u003e This allows Classic to work as a regular library. No need for bundler plugins, like SolidJS does for example.\n\nSignal values can be manually tracked:\n\n```ts\nimport { on, signal } from \"classic/element\"\n\nconst [clicks, setClicks] = signal(0);\nconst [overs, setOvers] = signal(0);\n\non(clicks, (n) =\u003e alert(`${n} clicks`));\n\non(\n  // Functions are read-only signals\n  () =\u003e clicks() + overs(),\n  (sum, prev) =\u003e alert(`${sum} interactions, previously ${prev}`)\n);\n\nsetClicks(1); // Calls alert(1);\n```\n\nSignals are lazy when initialized with a function. This avoids unneeded computations and allows lazy use.\n\n```ts\nconst [sig, setSig] = signal(() =\u003e throw \"Never called!\");\nsetSig(42);\nassertEquals(sig(), 42); // 👍\n```\n\n\u003e [!IMPORTANT]\n\u003e **About laziness**: `on` eagerly evaluates the signals it depends on. Otherwise, we couldn't know what to watch.\n\u003e \n\u003e ```ts\n\u003e const [sig, setSig] = signal(() =\u003e throw \"Oh noes\");\n\u003e on(sig, (v) =\u003e alert(v)) // 💥\n\u003e ```\n\n## About SSR\n\nAt first, Classic aimed SSR as a prime priority goal. Then came the realization that no backend pre-processing is needed for an optimal experience if custom elements are loaded synchronously, just like regular native elements. This way, **SSR is solved by default**.\n\nSEO also works well with custom elements: they semantically contain all the required information and are even executed by SEO engines. Google themselves promote the use of custom elements (Lit, Polymer, [web.dev](https://web.dev/articles/web-components)...)\n\nClassic kept the design of SSR-ready components, which will allow complex and heavy components to be server-rendered as declarative shadow DOM and hydrated asynchronously. This however **shouldn't be the default**.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngasull%2Fclassic-element","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fngasull%2Fclassic-element","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngasull%2Fclassic-element/lists"}