<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: georg.dev</title>
    <description>The latest articles on Forem by georg.dev (@georg-dev).</description>
    <link>https://forem.com/georg-dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1450891%2Fe770c049-b6f3-4bdb-8f20-4983c4616e97.jpg</url>
      <title>Forem: georg.dev</title>
      <link>https://forem.com/georg-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/georg-dev"/>
    <language>en</language>
    <item>
      <title>Better Context, Better GitHub Copilot: A Guide to copilot-instructions.md</title>
      <dc:creator>georg.dev</dc:creator>
      <pubDate>Tue, 22 Jul 2025 12:36:57 +0000</pubDate>
      <link>https://forem.com/georg-dev/better-context-better-github-copilot-a-guide-to-copilotinstructionsmd-cda</link>
      <guid>https://forem.com/georg-dev/better-context-better-github-copilot-a-guide-to-copilotinstructionsmd-cda</guid>
      <description>&lt;p&gt;GitHub Copilot is only as good as the context you give it. Without proper guidance, you'll waste time correcting off-target recommendations or explaining basic project details repeatedly.&lt;/p&gt;

&lt;p&gt;A well-crafted &lt;code&gt;copilot-instructions.md&lt;/code&gt; file provides consistent, project-specific context for every prompt. You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better first drafts&lt;/li&gt;
&lt;li&gt;Fewer corrections&lt;/li&gt;
&lt;li&gt;Faster work in Agent mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s examine how to build an effective instructions file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don’t &lt;em&gt;vibe&lt;/em&gt; your copilot-instructions.md
&lt;/h2&gt;

&lt;p&gt;I researched on GitHub and in developer communities to see how people write their &lt;code&gt;copilot-instructions.md&lt;/code&gt; files. The majority either let an LLM generate it without proper project context, or copy/paste generic instructions from others.&lt;/p&gt;

&lt;p&gt;The result is mostly generic AI slop. Don’t do that.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;copilot-instructions.md&lt;/code&gt; file becomes part of every Copilot request's context window. Since it's included with every prompt, its content directly impacts the quality and relevance of all suggestions. Research shows that &lt;a href="https://arxiv.org/pdf/2503.01781" rel="noopener noreferrer"&gt;irrelevant information can drastically reduce response quality&lt;/a&gt; or, worse, send the AI astray entirely. Therefore, avoid generic sentences that just waste tokens and instead prioritize project-specific details Copilot can't immediately infer from your code alone, e.g. architectural patterns, domain terms, and non-obvious constraints.&lt;/p&gt;

&lt;p&gt;But first, let's set it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up your copilot-instructions.md
&lt;/h2&gt;

&lt;p&gt;You have two starting options: writing it yourself by filling out the skeleton below or have Copilot Agent bootstrap it for you and then edit the file.&lt;br&gt;
Due to the reasons above, I recommend using the former option.&lt;/p&gt;
&lt;h3&gt;
  
  
  Option 1: Write the file yourself (recommended)
&lt;/h3&gt;

&lt;p&gt;In your repository root, create the folder &lt;code&gt;.github&lt;/code&gt; if it doesn't exist already.&lt;br&gt;
Then, create an empty file in that directory called &lt;code&gt;copilot-instructions.md&lt;/code&gt; and copy/paste the following content into the file:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Summary&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- brief summary of the repository --&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Terminology&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- list of domain specific terms with their explanation --&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Architecture&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- short summary of the architecture --&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Task planning and problem-solving&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- the most important problem-solving guidelines --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- e.g. "plan the task before writing any code" --&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Coding guidelines&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- the most important coding guidelines --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 2: Generate the file with the Copilot Agent
&lt;/h3&gt;

&lt;p&gt;Alternatively, you can let GitHub Copilot Agent generate the file with the latest VSCode release (&lt;a href="https://x.com/pierceboggan" rel="noopener noreferrer"&gt;@pierceboggan&lt;/a&gt; thanks for the heads-up):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjf36pbodkgl5eiovffc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjf36pbodkgl5eiovffc3.png" alt="Screenshot of VSCode showing the context menu with the option to generate the custom instructions" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will provide you with a usable starting point but some information will most likely be off or even incorrect and some will be missing. Therefore, after creating the file, go over each sentence, improve the existing content iteratively, and add your own.&lt;/p&gt;

&lt;p&gt;Next, let’s cover what to include.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to include in your instruction file
&lt;/h2&gt;

&lt;p&gt;I researched the most frequently used sections in &lt;code&gt;copilot-instructions.md&lt;/code&gt; files to identify effective practices. Use these as inspiration, but tailor them to your project and Copilot usage. If you use Copilot to only generate unit tests for a React repository, your instruction file will look very different from someone who only uses it to generate new features for Java services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Summarize your repository briefly, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;This repository contains the source code for a Pokémon card 
trading platform's web app, enabling online card trading.
It manages trade processing and card inventory tracking.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Terminology
&lt;/h3&gt;

&lt;p&gt;List domain-specific terms with explanations, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; trade - User’s trade request (card, condition, price).
&lt;span class="p"&gt;-&lt;/span&gt; stock - Inventory of Pokémon cards for tracking and listing.
&lt;span class="p"&gt;-&lt;/span&gt; queue - List of pending trades, prioritizing user offers.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;Describe the architecture, focusing on non-obvious details. This section is most effective when you explain the "why" behind your architectural decisions, not just the "what". Also, make sure to reference important files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;The React frontend interacts directly with Supabase
for database operations, user authentication, and 
real-time trade updates, while integrating with 
the PokeAPI for Pokémon data.  Supabase was chosen for 
its real-time capabilities and PokeAPI for its generous 
rate limits.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`supabaseClient.js`&lt;/span&gt; - Supabase client &amp;amp; authentication.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`realTimeTradeSubscriptions.js`&lt;/span&gt; - Manages trade updates.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`pokeApiIntegration.js`&lt;/span&gt; - Interacts with PokeAPI.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Task planning and problem-solving
&lt;/h3&gt;

&lt;p&gt;LLMs struggle with common sense and problem-solving. To improve Copilot’s task accuracy, include step-by-step instructions in this section. I noticed better results when telling the LLM to plan before coding. Some models drown problems in code until they work (Claude 👀). Instructing Copilot to reuse existing code helps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; Before each task, you must first complete the following steps:
&lt;span class="p"&gt;  1.&lt;/span&gt; Provide a full plan of your changes.
&lt;span class="p"&gt;  2.&lt;/span&gt; Provide a list of behaviors that you'll change.
&lt;span class="p"&gt;  3.&lt;/span&gt; Provide a list of test cases to add.
&lt;span class="p"&gt;-&lt;/span&gt; Before you add any code, always check if you can just re-use or 
  re-configure any existing code to achieve the result.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Model-specific instructions
&lt;/h3&gt;

&lt;p&gt;If you find yourself mostly working with the same AI model, you probably notice some recurring flaws or annoyances. Use this section to nudge the LLM in your desired direction. For example, if the AI tends to be over-comprehensive, use something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; Always focus on simplicity and precision and not 
  comprehensiveness.
&lt;span class="p"&gt;-&lt;/span&gt; When writing tests, focus on the happy path and only the most 
  important edge cases.
&lt;span class="p"&gt;-&lt;/span&gt; Before adding a new test, always make sure that a similar test 
  doesn't exist already.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Language-specific instructions
&lt;/h3&gt;

&lt;p&gt;Add language specific rules or use &lt;a href="https://code.visualstudio.com/blogs/2025/03/26/custom-instructions#_going-all-in-with-custom-instructions" rel="noopener noreferrer"&gt;code generation instructions&lt;/a&gt; (at the time of writing only available for VSCode).&lt;/p&gt;

&lt;p&gt;Try &lt;a href="https://10xrules.ai/" rel="noopener noreferrer"&gt;10xrules.ai&lt;/a&gt; to generate these quickly.&lt;br&gt;&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Coding Guidelines
&lt;/h3&gt;

&lt;p&gt;Specify coding styles not caught by linters. Include examples where needed.&lt;br&gt;&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What else?
&lt;/h3&gt;

&lt;p&gt;These are just the most commonly useful examples. Add other relevant instructions specific to your project. For instance, here are two Reddit posts that contain some additional categories which can be used as further inspiration: &lt;a href="https://www.reddit.com/r/ChatGPTCoding/comments/1jl6gll/copilotinstructionsmd_has_helped_me_so_much/" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://www.reddit.com/r/GithubCopilot/comments/1llss4p/this_is_my_generalinstructionsmd_file_to_use_with/" rel="noopener noreferrer"&gt;2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But first, let’s ensure your instructions follow an effective writing style.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to write your copilot-instructions.md
&lt;/h2&gt;

&lt;p&gt;Whatever you finally decide to include in your &lt;code&gt;copilot-instructions.md&lt;/code&gt; file, I strongly recommend following this writing tips. They ensure your instructions are clear and actionable for Copilot:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use consistent imperative voice.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Don't ❌ --&amp;gt;&lt;/span&gt;
When fixing a bug, it helps to write a failing test first.

&lt;span class="c"&gt;&amp;lt;!-- Do ✅--&amp;gt;&lt;/span&gt;
When fixing a bug, always write a failing test first.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Don’t be generic. Advice should be specific and actionable.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Don't ❌ --&amp;gt;&lt;/span&gt;
Write maintainable code.

&lt;span class="c"&gt;&amp;lt;!-- Do ✅ --&amp;gt;&lt;/span&gt;
Always follow the DRY principle and avoid code duplication.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Instead of just telling the AI what not to do, tell it what to do instead.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Don't ❌ --&amp;gt;&lt;/span&gt;
Don’t use hard-coded numbers.

&lt;span class="c"&gt;&amp;lt;!-- Do ✅--&amp;gt;&lt;/span&gt;
Avoid hard-coded numbers and use shared constants instead.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Don’t add style guides that your linter catches anyway. Less is more.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Don't ❌ --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Follow the ESLint rule &lt;span class="sb"&gt;`default-case`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; Always add a &lt;span class="sb"&gt;`default`&lt;/span&gt; case at the end of a &lt;span class="sb"&gt;`switch`&lt;/span&gt; statement.

&lt;span class="c"&gt;&amp;lt;!-- Do ✅ --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Let your linter catch these issues instead. --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Don't link to external resources in the instructions.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Don't ❌ --&amp;gt;&lt;/span&gt;
The API endpoint to retrieve data about a specific Pokémon 
is defined &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;here&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://pokeapi.co/docs/v2#pokemon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;.

&lt;span class="c"&gt;&amp;lt;!-- Do ✅--&amp;gt;&lt;/span&gt;
To retrieve data about a specific Pokémon, send a &lt;span class="sb"&gt;`GET`&lt;/span&gt; request
to &lt;span class="sb"&gt;`https://pokeapi.co/api/v2/pokemon/{id or name}/`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Include examples where necessary, especially when writing about patterns.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Don't ❌ --&amp;gt;&lt;/span&gt;
Prefix boolean variables with an appropriate verb.

&lt;span class="c"&gt;&amp;lt;!-- Do ✅--&amp;gt;&lt;/span&gt;
Prefix boolean variables with an appropriate verb, e.g.
&lt;span class="sb"&gt;`isLoading`&lt;/span&gt;, &lt;span class="sb"&gt;`hasPermissions`&lt;/span&gt;, &lt;span class="sb"&gt;`matchesFilter`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus: Boost the Agent mode
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot’s Agent mode runs tasks in the background while you focus elsewhere. Ideally, you give it an instruction, and it completes code, tests, and checks automatically. In reality, Copilot often changes code and stops, needing prompts to run linters or tests. Adding upfront instructions can automate this process.&lt;/p&gt;

&lt;p&gt;For this, add to the task planning section of your &lt;code&gt;copilot-instructions.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;If you add new code or change existing code, always verify that
everything still works by running &lt;span class="ge"&gt;*each*&lt;/span&gt; of the following checks:
&lt;span class="p"&gt;1.&lt;/span&gt; &lt;span class="sb"&gt;`npm run lint`&lt;/span&gt; to run the linter.
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="sb"&gt;`npm run test:unit`&lt;/span&gt; to run the unit tests.
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="sb"&gt;`npm run test:e2e`&lt;/span&gt; to run the e2e tests.

Complete the task only after all checks pass.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copilot adds changes, runs the commands, assesses output, and iterates until all errors are fixed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73e5a8myeczyqyhy0and.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73e5a8myeczyqyhy0and.png" alt="Screenshot from VSCode showing GitHub Copilot Agent asking for approval to run the lint command" width="518" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default, Copilot requires your approval for each tool usage, but in VSCode, you can add &lt;code&gt;"chat.tools.autoApprove": true&lt;/code&gt; to your &lt;code&gt;settings.json&lt;/code&gt; to enable &lt;a href="https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode#_autoapprove-all-tools-and-commands-experimental" rel="noopener noreferrer"&gt;auto-approval&lt;/a&gt; and go full vibe-coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;A tailored &lt;code&gt;copilot-instructions.md&lt;/code&gt; saves you time and improves Copilot’s output. Focus on specific, actionable instructions to guide its suggestions. Use the tips above to craft a file that fits your project.&lt;/p&gt;

&lt;p&gt;Try these strategies in your next project! Did I miss any tips you use for &lt;code&gt;copilot-instructions.md&lt;/code&gt;? Share them in the comments!&lt;/p&gt;

&lt;p&gt;For more tips like these, follow me on &lt;a href="https://x.com/georg_dev" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://bsky.app/profile/georg.dev" rel="noopener noreferrer"&gt;BlueSky&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>githubcopilot</category>
      <category>ai</category>
      <category>contextengineering</category>
      <category>programming</category>
    </item>
    <item>
      <title>How (not) to find the unsung heroes of JavaScript</title>
      <dc:creator>georg.dev</dc:creator>
      <pubDate>Wed, 26 Feb 2025 10:21:04 +0000</pubDate>
      <link>https://forem.com/georg-dev/how-not-to-find-the-unsung-heroes-of-javascript-2707</link>
      <guid>https://forem.com/georg-dev/how-not-to-find-the-unsung-heroes-of-javascript-2707</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6a6f560xn8dvmhcpu1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6a6f560xn8dvmhcpu1o.png" alt="xkcd-comic " width="385" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ve seen this xkcd comic — the one where the entire internet runs on a project some random developer has been thanklessly maintaining since 2003. It’s funny because it’s true. And in [insert current year here], it’s still true.&lt;/p&gt;

&lt;p&gt;I’m Georg, a data scientist turned software engineer working mostly in the JavaScript/TypeScript ecosystem. I wanted to fix the problem behind the comic — or at least find those invisible maintainers holding up the ecosystem with duct tape and caffeine. You probably depend on their work every day. &lt;em&gt;I&lt;/em&gt; definitely do.&lt;/p&gt;

&lt;p&gt;But here’s what happened instead: I found thousands of useless npm packages, a blockchain protocol gone wrong, and a lesson in why noble goals aren't enough. Let’s talk about good intentions, bad incentives, and why your code — and your career — are built on a house of cards.&lt;/p&gt;

&lt;h2&gt;
  
  
  The search for JavaScript’s invisible maintainers
&lt;/h2&gt;

&lt;p&gt;If you ask, “Who here deserves funding for their open-source work?” everyone raises their hand. That’s the problem. The loudest voices — or the savviest marketers — aren’t always the ones keeping your code from collapsing. After all, most open-source maintainers have better things to do than self-market, like fixing bugs, reviewing PRs, and keeping the lights on.&lt;/p&gt;

&lt;p&gt;So I tried something simple: ignore the noise. Instead of listening to &lt;em&gt;who&lt;/em&gt; was asking for money, I wanted to see &lt;em&gt;what&lt;/em&gt; the ecosystem actually depended on. JavaScript’s dependency graphs are public, after all. How hard could it be?&lt;/p&gt;

&lt;p&gt;I downloaded npm’s metadata dump — every package, every dependency, every maintainer — and ran the numbers. The goal: find projects with high dependents but low visibility. Think libraries like &lt;code&gt;ipaddr.js&lt;/code&gt;, a utility for manipulating IP addresses with &lt;a href="https://www.npmjs.com/package/ipaddr.js" rel="noopener noreferrer"&gt;44 million weekly downloads&lt;/a&gt;, or &lt;code&gt;long&lt;/code&gt;, a library for working with 64-bit integers with &lt;a href="https://www.npmjs.com/package/long" rel="noopener noreferrer"&gt;28 million weekly downloads&lt;/a&gt;. These tools are everywhere, but their maintainers often go unnoticed — working quietly behind the scenes to keep the ecosystem running.&lt;/p&gt;

&lt;p&gt;The logic was sound. If a package has thousands of dependents but just one maintainer and almost no contributors, that’s probably our famous Nebraskan. If it’s a transitive dependency — something your code uses indirectly, three layers deep — even more so.&lt;/p&gt;

&lt;p&gt;At first, it worked. I did not only find well-known packages but also others like &lt;code&gt;anakjalanan&lt;/code&gt;, &lt;code&gt;nitroteh&lt;/code&gt;, and &lt;code&gt;acertea&lt;/code&gt; — each with thousands and thousands of dependents, a single maintainer, and names I’d never heard of. &lt;em&gt;These&lt;/em&gt; had to be the unsung heroes.&lt;/p&gt;

&lt;p&gt;Then I checked the repositories.&lt;/p&gt;

&lt;p&gt;Some led me to completely empty codebases. Others only contained bootstrapped Next.js or Node.js applications without any additional functionality. They weren’t just unmaintained. They were literally useless.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhbzxkln4kmrhrfrkorc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhbzxkln4kmrhrfrkorc.png" alt="npm registry page of the package anakjalanan showing a bootstrapped Next.js app with 26,000 dependents" width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What’s going on here? Why would thousands of projects depend on empty repositories and abandoned forks?&lt;/p&gt;

&lt;p&gt;But then I noticed that all those repositories had one thing in common: a file called &lt;code&gt;tea.xyz&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is why we can't have nice things
&lt;/h2&gt;

&lt;p&gt;Meet the &lt;a href="https://tea.xyz" rel="noopener noreferrer"&gt;tea Protocol&lt;/a&gt; — a blockchain-based attempt to fix open-source funding. Their goal was similar to what I was trying to do, but with a cryptocurrency on top: use the dependency graph to measure a package’s impact and automatically reward maintainers with tokens. Simple.&lt;/p&gt;

&lt;p&gt;Too simple.&lt;/p&gt;

&lt;p&gt;tea’s “Proof of Contribution” model tied funding to dependency counts. The more projects that depend on you, the more you earn. Sounds fair — until you realize how easy it is to fake a dependency.&lt;/p&gt;

&lt;p&gt;Suddenly, those empty repositories made sense. &lt;a href="https://socket.dev/blog/tea-xyz-spam-plagues-npm-and-rubygems-package-registries" rel="noopener noreferrer"&gt;Spammers flooded npm&lt;/a&gt; with trivial packages, each containing a &lt;code&gt;tea.xyz&lt;/code&gt; file. By artificially inflating their dependency counts, they could trick the protocol into paying them for “impact.”&lt;/p&gt;

&lt;p&gt;Even worse, some scammers tried to sneak their own &lt;code&gt;tea.xyz&lt;/code&gt; file into widely used open-source packages, like &lt;code&gt;node-bin-gen&lt;/code&gt;, via a &lt;a href="https://github.com/aredridel/node-bin-gen/pull/241" rel="noopener noreferrer"&gt;pull request&lt;/a&gt;. The maintainers shut it down, but not before uncovering countless similar PRs targeting other established repositories. The same abuse appeared in other package registries like PyPI and RubyGems, with useless packages clogging up the ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6msdl1qhvte80tyayk9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6msdl1qhvte80tyayk9.png" alt="Comment on the PR — the creator answering to someone accusing them of being an LLM: " width="800" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To tea’s credit, they reacted quickly. By mid-2024, they added mandatory registration and guardrails against spam. npm uploads returned to normal. But the damage was already done: the NPM registry is still littered with those packages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ixcfev4edkxmlqq4hi9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ixcfev4edkxmlqq4hi9.png" alt="Graph showing the explosion of NPM package uploads from beginning to mid 2024" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Good intentions, bad incentives
&lt;/h2&gt;

&lt;p&gt;Goodhart’s Law states: &lt;em&gt;“When a measure becomes a target, it ceases to be a good measure.”&lt;/em&gt; In simpler terms, the moment you tie money to a metric — like GitHub stars, dependency counts, or downloads — you create an incentive to optimize for the metric, not the underlying value.&lt;/p&gt;

&lt;p&gt;tea Protocol learned this the hard way. And keep in mind, when this happened, tea wasn’t even a major funding allocator yet. Imagine the chaos if large organizations had poured hundreds of thousands of dollars into the system.&lt;/p&gt;

&lt;p&gt;Fix one loophole, and another emerges. This isn’t unique to tea — &lt;a href="https://support.gitcoin.co/gitcoin-knowledge-base/about-gitcoin/policy/understanding-potential-attack-vectors/sybil-attack" rel="noopener noreferrer"&gt;Gitcoin’s quadratic funding battles Sybil attacks&lt;/a&gt;, App Store rankings get manipulated constantly, and even academic citation metrics are gamed. Platforms like GitHub Sponsors and &lt;a href="https://thanks.dev/" rel="noopener noreferrer"&gt;thanks.dev&lt;/a&gt; take a different approach, enabling companies to fund projects they depend on — even transitive dependencies. While less prone to manipulation, they’re not immune; developers can still game the system by fragmenting code into shallow dependencies. &lt;strong&gt;Any funding model based on automation will eventually be exploited.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But let’s assume, for a moment, that you find the perfect metric after all. Let’s say you invent the ungameable tea Protocol 2.0. Now what?&lt;/p&gt;

&lt;p&gt;Where does the money come from?&lt;/p&gt;

&lt;p&gt;tea’s tokenomics hinge on the price of its cryptocurrency. But if the token’s only utility is to be paid out to maintainers — not to be spent — there’s no real demand. Its effective value is zero. The only way to keep the price above that is to artificially counterbalance the excess supply with &lt;a href="https://docs.tea.xyz/tea/i-want-to.../learn-about-teas-tokenomics/token-demand-drivers#mechanisms" rel="noopener noreferrer"&gt;enforced staking and built-in deflation&lt;/a&gt;, creating demand through financial speculation.&lt;/p&gt;

&lt;p&gt;But sustainable, long-term open-source funding can’t be built on speculation. At the end of the day, someone has to open their wallet and pay. Collectively, we seem to agree that it should be those who profit financially from open-source work. But that system has a major flaw.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why aren't companies funding?
&lt;/h2&gt;

&lt;p&gt;Let me rephrase the question: &lt;em&gt;Why should they?&lt;/em&gt; From a company’s perspective, the current system isn’t perfect, but it mostly works (👀 log4j), and best of all, it’s free. As a rational actor, there are only two reasons you’d fund open source:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The open-source project is core to your commercial product.&lt;/strong&gt; But in this case, why fund it when you could just hire the maintainer? That way, you gain influence over the project and ensure it aligns with your business strategy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your product targets developers, and funding open-source projects is good PR.&lt;/strong&gt; Sponsoring a trendy library or framework can win hearts in the developer community, which is great for hiring and marketing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don’t get me wrong — both are valid reasons. Funding with a personal incentive is still infinitely better than not funding at all. But neither scenario solves the Nebraska problem. &lt;strong&gt;Critical but unsexy projects stay underfunded.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So, what now?
&lt;/h2&gt;

&lt;p&gt;The xkcd comic endures because it’s not a joke — it’s a reflection of reality. We’ve tried donations (too small), corporate sponsorships (too selective), and blockchain experiments (too gameable). None scale.&lt;/p&gt;

&lt;p&gt;Maybe the answer isn’t technical. Maybe it’s a cultural shift — a recognition that open-source infrastructure is as vital as roads or electricity, and that we cannot neglect it in a time when the functioning of our entire modern world depends on it. Or maybe we’re stuck here, forever stacking yet another block on top of our Jenga tower.&lt;/p&gt;

&lt;p&gt;What’s your take? Have you seen a funding model that actually works for invisible projects?&lt;/p&gt;

&lt;p&gt;Let me know in the comments.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>What is the Shadow DOM</title>
      <dc:creator>georg.dev</dc:creator>
      <pubDate>Mon, 30 Sep 2024 06:04:19 +0000</pubDate>
      <link>https://forem.com/georg-dev/what-is-the-shadow-dom-1g92</link>
      <guid>https://forem.com/georg-dev/what-is-the-shadow-dom-1g92</guid>
      <description>&lt;h2&gt;
  
  
  Missing style encapsulation
&lt;/h2&gt;

&lt;p&gt;As a web developer, you've probably dealt with the headaches of missing style encapsulation. A single broad CSS selector can unexpectedly mess up the UI somewhere else on your site.&lt;/p&gt;

&lt;p&gt;Adding third-party UI libraries only complicates things. Do their CSS class names clash with yours? Will their buttons break yours? Maybe.&lt;/p&gt;

&lt;h3&gt;
  
  
  The partial solution
&lt;/h3&gt;

&lt;p&gt;Frameworks like Angular or Vue.js offer some form of component-based style encapsulation. Others, like React, let you choose from various style encapsulation libraries, such as styled-components.&lt;sup&gt;[1]&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;However, their solutions typically offer only &lt;em&gt;pseudo style encapsulation&lt;/em&gt;. Instead of fully isolating styles, they often just add prefixes or suffixes to CSS class names following some patterns that hopefully prevent name clashes.&lt;/p&gt;

&lt;p&gt;Doesn't this feel like a workaround? It does to me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5rq27l5rvrfa0vf8mr80.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5rq27l5rvrfa0vf8mr80.jpg" alt="Meme: Fixing broken water tank with flex tape" width="500" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Shadow DOM to the rescue
&lt;/h2&gt;

&lt;p&gt;The shadow DOM attempts to fix the issue of missing encapsulation.&lt;sup&gt;[2]&lt;/sup&gt; It allows you to attach additional subtrees to the main DOM. With this API, you take an HTML node from your DOM and you simply attach a new subtree to it. We call the root node of this new subtree the &lt;strong&gt;shadow root&lt;/strong&gt;. The neat part: styles don't cross this shadow root.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyt5y9oy2i1v2bgrw5kv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyt5y9oy2i1v2bgrw5kv.jpg" alt="Meme: Gandalf shouting " width="770" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means, the styles of your subtree don't leak out into the main DOM. Also, the styles of your main DOM don't affect the encapsulated content of the shadow DOM. Moreover, the same encapsulation also applies to browser events. An &lt;code&gt;onclick&lt;/code&gt; event originating from the shadow DOM will not bubble up to the root of the main DOM. Instead, it will end at the shadow root.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the shadow DOM works
&lt;/h2&gt;

&lt;p&gt;There are two different ways how you can set up the shadow DOM. If you want to understand the concept better, take a look at the imperative API. Otherwise, feel free to jump ahead to the declarative API since that one is usually preferred by most developers nowadays.&lt;/p&gt;

&lt;h3&gt;
  
  
  Imperative API
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; the imperative API does not work for server-side rendering (SSR). If you need SSR, use the declarative approach instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's assume, we have a very basic HTML structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&amp;lt;span&amp;gt;&lt;/span&gt;OUTSIDE the shadow DOM&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"host"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go over this. Our DOM has a &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element that contains two HTML nodes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; containing a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; with the text &lt;code&gt;OUTSIDE the shadow DOM&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with the ID &lt;code&gt;host&lt;/code&gt;. This will be our host element to which we'll attach the shadow DOM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgnhl0gt863fmupridta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgnhl0gt863fmupridta.png" alt="Diagram of the initial HTML structure" width="800" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we get the reference to this &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element and attach a shadow DOM to it. Then, for the sake of demonstration, we create another &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; and append it to the shadow DOM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// get the reference to the div&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hostElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#host&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// attach a shadow DOM&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shadowDom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hostElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// create a new HTML node (just for demonstration)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exampleElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;span&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;exampleElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSIDE the shadow DOM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// append the new HTML node to the shadow DOM&lt;/span&gt;
&lt;span class="nx"&gt;shadowDom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exampleElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the new structure looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3lyjztmh1c25wj7wp5bh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3lyjztmh1c25wj7wp5bh.png" alt="Diagram of the HTML structure with added shadow DOM" width="800" height="734"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you inspect how this is rendered in the browser, you will see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&amp;lt;span&amp;gt;&lt;/span&gt;OUTSIDE the shadow DOM&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"host"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    #shadow-root (open)
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;INSIDE the shadow DOM&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;    
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see all these steps together in this short animated diagram:&lt;/p&gt;


  
Your browser does not support the video tag.


&lt;h3&gt;
  
  
  Declarative API
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; the declarative shadow DOM has only recently been implemented in the last remaining browsers and is not fully adopted yet.&lt;sup&gt;[3]&lt;/sup&gt; If you use the declarative API, add the required polyfill to your application.&lt;sup&gt;[4]&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the declarative shadow DOM API, you can do the same that we did in the imperative example above in plain HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Outside the shadow DOM&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"host"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;shadowrootmode=&lt;/span&gt;&lt;span class="s"&gt;"open"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Inside the shadow DOM&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we used the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; element here. The reason for this is that the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; element has its own mechanism for rendering its content. If you want to learn more about it, check out my &lt;a href="https://georg.dev/blog/02-what-is-the-template-api" rel="noopener noreferrer"&gt;template API article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://styled-components.com/" rel="noopener noreferrer"&gt;Library: styled-components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM" rel="noopener noreferrer"&gt;MDN Docs: Using shadow DOM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/declarative-shadow-dom" rel="noopener noreferrer"&gt;Can I Use: Declarative shadow DOM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill" rel="noopener noreferrer"&gt;web.dev: Declarative shadow DOM polyfill&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>What is the Template API</title>
      <dc:creator>georg.dev</dc:creator>
      <pubDate>Fri, 02 Aug 2024 13:36:49 +0000</pubDate>
      <link>https://forem.com/georg-dev/what-is-the-template-api-1opb</link>
      <guid>https://forem.com/georg-dev/what-is-the-template-api-1opb</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Re-using HTML within the same page can be tricky. Picture this: you have a complex piece of markup that needs to appear in several places on your page.&lt;/p&gt;

&lt;p&gt;As a web developer, you might think, &lt;em&gt;"No problem, I'll just use a framework and create a component."&lt;/em&gt;. But does it really make sense to bring in an entire library just to re-use some HTML? Shouldn't re-using HTML be something that is supported natively?&lt;/p&gt;

&lt;p&gt;Entering the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Content inside a &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; element isn't rendered when the page loads. However, it's still part of the DOM and can be accessed via JavaScript. This lets you append this content to other DOM nodes and, thereby, making it reusable.&lt;sup&gt;[1]&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Consider the following HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;I'm visible.&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"my-template"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;I'm initially &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;not&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt; visible.&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you will see below, the content from inside the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; is not visible. However, we can get the contents reference and append it to another node where it will be displayed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// get the refence to the template&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// extract and clone the content from the template&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clonedContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// append the extracted content to another node&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clonedContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the previously hidden content becomes visible. Try it out yourself:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/what-is-the-template-api-rendering-template-content?ctl=1&amp;amp;embed=1&amp;amp;view=preview&amp;amp;file=script.js,index.html" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This approach not only lets you render content dynamically, showing one component under specific conditions and another under different conditions.&lt;/p&gt;

&lt;p&gt;It also allows you to attach the content to multiple nodes, enabling you to declare HTML once and use it multiple times across your page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using dynamic content
&lt;/h2&gt;

&lt;p&gt;There's one more thing to consider: dynamic content. &lt;/p&gt;

&lt;p&gt;So far, we've only defined static content in the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;, but what if we want to display dynamic content?&lt;/p&gt;

&lt;p&gt;In React, you use the &lt;code&gt;children&lt;/code&gt; property for that. In Angular, you use &lt;code&gt;&amp;lt;ng-content&amp;gt;&lt;/code&gt;. With the template API, HTML introduces a native element: &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt;.&lt;sup&gt;[2]&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt; element specifies the entry point for child nodes. Consider this HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Is this the real life&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Caught in a landslide&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- and later --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;bohemian-rhapsody&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Is this just fantasy&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/bohemian-rhapsody&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rendered result will be:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/what-is-the-template-api-rendering-dynamic-content?ctl=1&amp;amp;embed=1&amp;amp;view=preview&amp;amp;file=bohemian-rhapsody.js,index.html" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;As you can see, the content put into the &lt;code&gt;&amp;lt;bohemian-rhapsody&amp;gt;&lt;/code&gt; element got rendered into the &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;If you're wondering where the &lt;code&gt;&amp;lt;bohemian-rhapsody&amp;gt;&lt;/code&gt; element came from, check out my upcoming intro to custom elements.&lt;/p&gt;



&lt;blockquote&gt;
&lt;h3&gt;
  
  
  Curious to dive deeper?
&lt;/h3&gt;

&lt;p&gt;In my next articles on &lt;a href="https://georg.dev/" rel="noopener noreferrer"&gt;georg.dev&lt;/a&gt; I’ll break down the other two web component specifications as well: Shadow DOM and Custom Elements. Each article aims to equip you with the tools necessary to implement these technologies in your own projects. Keep learning!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template" rel="noopener noreferrer"&gt;MDN Docs: The Content Template element&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot" rel="noopener noreferrer"&gt;MDN Docs: The Web Component Slot element&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webcomponents</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>What are Web Components</title>
      <dc:creator>georg.dev</dc:creator>
      <pubDate>Fri, 05 Jul 2024 14:05:36 +0000</pubDate>
      <link>https://forem.com/georg-dev/what-are-web-components-5g2h</link>
      <guid>https://forem.com/georg-dev/what-are-web-components-5g2h</guid>
      <description>&lt;h2&gt;
  
  
  Why Web Components
&lt;/h2&gt;

&lt;p&gt;In 2014, the developer community was hyped: Google just released their new design system, Material Design. It looked fresh and exciting, promising a cohesive and well-thought-out experience. But what if you wanted to use it in your own web app? After all, Material Design is just the design specification.&lt;/p&gt;

&lt;p&gt;You needed a component library.&lt;/p&gt;

&lt;p&gt;If you were an Angular developer, you would install Angular Material. For React, you would use MUI, and for Vue, you would use Material Vue. Each individual framework needed its own library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feka8edd4u33w77v6jjy5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feka8edd4u33w77v6jjy5.png" alt="Diagram showing the various libraries you needed for Material Design 2" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
For Material Design 2, each framework still required its own component library since web components weren't supported by all frameworks yet.



&lt;p&gt;Think of all the effort wasted. Not only for the library creators who wrote and maintained the libraries but also for the library users who had to learn them. You could be an expert in Angular Material and still have to re-learn everything if you wanted to do Material Design in React.&lt;/p&gt;

&lt;p&gt;But what if we could just write our UI component libraries once and use them in any web framework? This is exactly what happens with Material Design 3. How? With the power of web components.&lt;sup&gt;[1]&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3nrzv9o108tamzxp5xs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3nrzv9o108tamzxp5xs.png" alt="Diagram showing that only one component library will be required for Material Design 3" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
For Material Design 3, only one component library is required, which is framework-agnostic thanks to web components.



&lt;h2&gt;
  
  
  The Growing Significance of Web Components
&lt;/h2&gt;

&lt;p&gt;Chances are, you've never used a custom web component yet, let alone created one. They are mostly popular among large companies needing framework-agnostic components, like Google, Microsoft, or Netflix. For example, GitHub has been a famous early adopter of web components.&lt;sup&gt;[2]&lt;/sup&gt; A more recent example is the Photoshop Web UI which was built by relying heavily on the web component framework Lit. In fact, Adobe built their whole Creative Cloud with the power of web components.&lt;sup&gt;[3]&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12qdff4kuk4xxx3acu64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12qdff4kuk4xxx3acu64.png" alt="Screenshot of the Photoshop Web UI" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
Screenshot of the Photoshop Web UI: Almost every element you see here is a web component.



&lt;p&gt;According to the Google Chrome Platform status report, 15-20% of page loads and around 16% of URLs world-wide contain web components nowadays.&lt;sup&gt;[4]&lt;/sup&gt; This number has been steadily growing for years and the newly added web component support in React will likely increase it even further.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9f4hgdpxp5ba5nrogn17.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9f4hgdpxp5ba5nrogn17.png" alt="Chart of the Google Chrome Platform Status report showing the rising number of web components" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;
Google Chrome Platform Status: The number of URLs containing web components has been steadily rising from &amp;lt;0% in 2018 to around 16% in 2024.



&lt;p&gt;What this means for you as a software developer is that even if you've never had any contact web components yet, you will encounter them sooner or later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining a Standard
&lt;/h2&gt;

&lt;p&gt;Even though we talk about the web component standard, it is actually the result of three different specifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTML Templates&lt;/strong&gt;: allows re-using HTML in the same document&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shadow DOM&lt;/strong&gt;: Creates encapsulated DOM structures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Elements&lt;/strong&gt;: Associates HTML elements with JavaScript classes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each specification already brings useful functionality on its own.&lt;br&gt;
Combined, they create re-usable and encapsulated UI components as a web standard: web components.&lt;/p&gt;



&lt;blockquote&gt;
&lt;h3&gt;
  
  
  Curious to dive deeper?
&lt;/h3&gt;

&lt;p&gt;Check out my upcoming articles on &lt;a href="https://georg.dev/" rel="noopener noreferrer"&gt;georg.dev&lt;/a&gt; where I’ll break down each of the three specifications: HTML Templates, Shadow DOM, and Custom Elements. Each article aims to equip you with the tools necessary to implement these technologies in your own projects. Keep learning!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/material-components/material-web" rel="noopener noreferrer"&gt;GitHub: Material Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/github/github-elements" rel="noopener noreferrer"&gt;GitHub: github-elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/articles/ps-on-the-web" rel="noopener noreferrer"&gt;web.dev: Photoshop's journey to the web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chromestatus.com/metrics/feature/timeline/popularity/1689" rel="noopener noreferrer"&gt;Chrome Platform Status: CustomElementsRegistryDefine metrics&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webcomponents</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
