<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>georg.dev</title><description>Human-written posts on AI engineering and software development.</description><link>https://georg.dev/</link><language>en-us</language><item><title>Sandbox your GitHub Copilot CLI on Linux</title><link>https://georg.dev/blog/07-sandbox-your-github-copilot-cli-on-linux/</link><guid isPermaLink="true">https://georg.dev/blog/07-sandbox-your-github-copilot-cli-on-linux/</guid><description>Containerized AI agent isolation with Docker</description><pubDate>Mon, 09 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;img src=&quot;https://georg.dev/_astro/banner.DbZfkqbS_ZD5WKn.webp&quot; alt=&quot;&quot; /&gt;&lt;details class=&quot;tldr&quot; open&gt;
  &lt;summary&gt;TL;DR&lt;/summary&gt;
  &lt;ul&gt;
    &lt;li&gt;This article shows how to set up rootless Docker on Linux for running GitHub Copilot CLI in isolation&lt;/li&gt;
    &lt;li&gt;Build a custom Ubuntu container image with GitHub Copilot CLI pre-installed&lt;/li&gt;
    &lt;li&gt;Mount your project directory into the container so you and Copilot share the same workspace&lt;/li&gt;
    &lt;li&gt;Configure persistent authentication so you don&apos;t need to log in every time&lt;/li&gt;
    &lt;li&gt;Launch ephemeral containers for different projects with a simple script&lt;/li&gt;
    &lt;li&gt;Copilot gets full access to your project directory but can&apos;t touch the rest of your system&lt;/li&gt;
  &lt;/ul&gt;
&lt;/details&gt;
&lt;p&gt;GitHub Copilot CLI is a powerful AI coding assistant, but unlike Claude Code or Codex, it has no built-in sandboxing.
If you want to be safe, you have to constantly approve every file read/write outside the project directory, and every new command execution.
You might want to enable &lt;a href=&quot;https://docs.github.com/en/copilot/how-tos/copilot-cli/use-copilot-cli#enable-all-permissions&quot;&gt;&lt;code&gt;--yolo&lt;/code&gt; mode&lt;/a&gt; and let the agent work uninterrupted, but that’s risky as Copilot could easily go rogue.&lt;/p&gt;
&lt;p&gt;The solution: run Copilot CLI in an isolated sandbox.
Docker is perfect for this. Full filesystem access inside, zero risk to your host machine.&lt;/p&gt;
&lt;p&gt;If you’re on macOS or Windows with Docker Desktop, you’re in luck.
Just use the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;experimental &lt;code&gt;docker --sandbox&lt;/code&gt; command&lt;/a&gt; and you’re done.
However, if you’re on Linux, &lt;code&gt;docker --sandbox&lt;/code&gt; isn’t available.&lt;/p&gt;
&lt;p&gt;This guide shows you how to set up a similar isolation using rootless Docker.
You’ll get a sandboxed environment for Copilot CLI without needing Docker Desktop.&lt;/p&gt;
&lt;p class=&quot;aside&quot;&gt;Disclaimer: Unlike &lt;code&gt;docker --sandbox&lt;/code&gt;, this approach doesn’t restrict network traffic.
Your agent can communicate freely with the internet, which creates the risk of data exfiltration.&lt;/p&gt;
&lt;h2 id=&quot;sneak-peak-of-what-youll-going-to-build&quot;&gt;Sneak-peak of what you’ll going to build&lt;/h2&gt;
&lt;p&gt;With this setup, you’ll be able to launch Copilot CLI in a sandboxed container with a single command.&lt;/p&gt;
&lt;p&gt;For example, let’s say you have a project at &lt;code&gt;~/projects/my-project&lt;/code&gt;. You can just run:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;./copilot-sandbox.sh&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;~/projects/my-project&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start GitHub Copilot CLI in a sandboxed environment&lt;/li&gt;
&lt;li&gt;Give Copilot access to your project folder but nothing else on your system&lt;/li&gt;
&lt;li&gt;Destroy the sandbox when you exit again, but remember the authentication so you don’t have to log in next time&lt;/li&gt;
&lt;li&gt;Allow you to run multiple sandboxed agents for different projects in parallel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s go!&lt;/p&gt;
&lt;h2 id=&quot;1-install-rootless-docker-on-linux&quot;&gt;1. Install rootless Docker on Linux&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.docker.com/engine/security/rootless/&quot;&gt;Rootless Docker&lt;/a&gt; means the Docker daemon runs as your regular user (not root), and containers are isolated using user namespaces.
The containers think they’re running as root inside, but they’re actually mapped to your unprivileged user ID on the host.
This is much safer than regular Docker.&lt;/p&gt;
&lt;h3 id=&quot;11-install-prerequisites&quot;&gt;1.1 Install prerequisites&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;apt-get&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;update&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;apt-get&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;-y&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;uidmap&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;dbus-user-session&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What’s happening here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;uidmap&lt;/code&gt; provides the &lt;code&gt;newuidmap&lt;/code&gt; and &lt;code&gt;newgidmap&lt;/code&gt; tools that allow your user to map a range of user IDs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dbus-user-session&lt;/code&gt; ensures systemd user services work correctly&lt;/li&gt;
&lt;li&gt;These tools let Linux create “fake” root environments inside containers while keeping everything unprivileged on the host&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;12-configure-subordinate-uidsgids&quot;&gt;1.2 Configure subordinate UIDs/GIDs&lt;/h3&gt;
&lt;p&gt;Check if you already have subordinate UID/GID ranges:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;cat&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;/etc/subuid&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;cat&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;/etc/subgid&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you see lines like, you’re already set up. Go to the next step (1.3).&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span&gt;yourusername:100000:65536&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Otherwise, if these files are empty or your user isn’t listed, add ranges:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;usermod&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;--add-subuids&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;100000-165535&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;--add-subgids&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;100000-165535&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;$USER&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What this means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your user gets a range of 65,536 “fake” UIDs (100000-165535)&lt;/li&gt;
&lt;li&gt;When a container thinks it’s running as UID 0 (root), it’s actually mapped to UID 100000 on your host&lt;/li&gt;
&lt;li&gt;This means even if a container is compromised, the attacker only has access to these mapped, unprivileged UIDs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;13-install-docker-ce-if-not-already-installed&quot;&gt;1.3 Install Docker CE (if not already installed)&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;-fsSL&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;https://get.docker.com&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;-o&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;get-docker.sh&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;sh&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;get-docker.sh&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;14-install-rootless-docker-extras&quot;&gt;1.4 Install rootless Docker extras&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;apt-get&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;-y&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;docker-ce-rootless-extras&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What’s in this package:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dockerd-rootless-setuptool.sh&lt;/code&gt; - the setup script&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rootlesskit&lt;/code&gt; - creates the user namespace and handles networking&lt;/li&gt;
&lt;li&gt;&lt;code&gt;slirp4netns&lt;/code&gt; - provides user-mode networking (no root needed)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;15-disable-the-root-docker-daemon&quot;&gt;1.5 Disable the root Docker daemon&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;systemctl&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;disable&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;--now&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;docker.service&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;docker.socket&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You don’t want the root daemon running alongside rootless&lt;/li&gt;
&lt;li&gt;This prevents confusion and conflicts&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;16-initialize-rootless-docker&quot;&gt;1.6 Initialize rootless Docker&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;dockerd-rootless-setuptool.sh&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;install&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What this does:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Checks prerequisites (subuid/subgid)&lt;/li&gt;
&lt;li&gt;Creates systemd user service files in &lt;code&gt;~/.config/systemd/user/docker.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Starts the Docker daemon as your user&lt;/li&gt;
&lt;li&gt;Sets up the Docker socket at &lt;code&gt;/run/user/$(id -u)/docker.sock&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;17-configure-your-shell&quot;&gt;1.7 Configure your shell&lt;/h3&gt;
&lt;p&gt;Add to &lt;code&gt;~/.bashrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; PATH&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/home/&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;$USER&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/bin:&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;$PATH&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; DOCKER_HOST&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;unix:///run/user/&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;$(&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;-u&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/docker.sock&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then reload:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;~/.bashrc&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Docker CLI needs to know to talk to your user’s socket, not the system socket&lt;/li&gt;
&lt;li&gt;The binaries are installed in your home directory, not system paths&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;18-enable-the-service-to-start-on-boot&quot;&gt;1.8 Enable the service to start on boot&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;systemctl&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;--user&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;enable&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;docker&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;loginctl&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;enable-linger&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;$USER&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What &lt;code&gt;enable-linger&lt;/code&gt; does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Normally, user services stop when you log out&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enable-linger&lt;/code&gt; keeps your user’s systemd instance running even when you’re not logged in&lt;/li&gt;
&lt;li&gt;This means your Docker daemon persists across logins&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;19-verify-installation&quot;&gt;1.9 Verify installation&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;hello-world&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Check the output in the terminal. You should see a message confirming Docker is working.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-building-your-copilot-sandbox-image&quot;&gt;2. Building your Copilot sandbox image&lt;/h2&gt;
&lt;p&gt;Now let’s create &lt;code&gt;myorg/copilot-sandbox:latest&lt;/code&gt; with Copilot CLI pre-installed.&lt;/p&gt;
&lt;h3 id=&quot;21-create-a-dockerfile&quot;&gt;2.1 Create a Dockerfile&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;~/copilot-sandbox&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;~/copilot-sandbox&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;nano&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;Dockerfile&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Put this in the Dockerfile:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; ubuntu:22.04&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Prevent interactive prompts during installation&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; DEBIAN_FRONTEND=noninteractive&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Install basic tools and Node.js repository&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; apt-get update &amp;#x26;&amp;#x26; apt-get install -y \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    curl \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    git \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    nano \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    ripgrep \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    ca-certificates \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    gnupg \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &amp;#x26;&amp;#x26; mkdir -p /etc/apt/keyrings \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &amp;#x26;&amp;#x26; curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &amp;#x26;&amp;#x26; echo &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;&quot;deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main&quot;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; | tee /etc/apt/sources.list.d/nodesource.list&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Install Node.js 22&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; apt-get update &amp;#x26;&amp;#x26; apt-get install -y nodejs&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Install GitHub Copilot CLI&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; npm install -g @github/copilot&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# OPTIONAL: Install any other dependencies your devleopment setup&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# might require (Python, Java, etc.)&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Clean up to reduce image size&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; apt-get clean &amp;#x26;&amp;#x26; rm -rf /var/lib/apt/lists/*&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Set working directory&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;WORKDIR&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; /workspace&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Default command: bash&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;CMD&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;&quot;/bin/bash&quot;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Important: make sure you also add all other packages your developer setup might require to this file.&lt;/p&gt;
&lt;h3 id=&quot;22-build-the-image&quot;&gt;2.2 Build the image&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;build&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;-t&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;myorg/copilot-sandbox:latest&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will take a few minutes. The &lt;code&gt;-t&lt;/code&gt; flag tags it with the name you want.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-running-a-session-with-the-sandboxed-copilot-cli&quot;&gt;3. Running a session with the sandboxed Copilot CLI&lt;/h2&gt;
&lt;p&gt;You’ll use ephemeral containers (destroyed on exit with &lt;code&gt;--rm&lt;/code&gt;) but persist authentication data in named Docker volumes. This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each container is fresh and clean&lt;/li&gt;
&lt;li&gt;You can work on different projects easily&lt;/li&gt;
&lt;li&gt;You only have to authenticate once (auth data persists in volumes)&lt;/li&gt;
&lt;li&gt;No container clutter building up&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;31-create-a-persistent-volume-for-authentication&quot;&gt;3.1 Create a persistent volume for authentication&lt;/h3&gt;
&lt;p&gt;First, let’s create the volume that will store authentication data:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;volume&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;copilot-sandbox-root&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What this does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates a named volume called &lt;code&gt;copilot-sandbox-root&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;This volume persists on your system even when containers are destroyed&lt;/li&gt;
&lt;li&gt;You’ll mount this to &lt;code&gt;/root&lt;/code&gt; inside the container where Copilot CLI usually stores its auth tokens&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;32-create-the-launch-script&quot;&gt;3.2 Create the launch script&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;nano&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;~/copilot-sandbox.sh&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Put this in the file:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Get project directory from first argument, default to current directory&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;PROJECT_DIR&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;${1&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;:-&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Resolve to absolute path&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;PROJECT_PATH&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;$(&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;realpath&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;$PROJECT_DIR&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;)&quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Check if project directory exists&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-d&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;$PROJECT_PATH&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;];&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;Error: Directory &lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;$PROJECT_PATH&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt; does not exist&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;==========================================&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;Launching Copilot sandbox container&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;Project: &lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;$PROJECT_PATH&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;==========================================&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;Inside the container, run &apos;copilot&apos; to open the CLI.&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;# Run the container&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;--rm&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;-it&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;--cap-drop&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;ALL&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;--security-opt&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;no-new-privileges&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;-v&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;$PROJECT_PATH&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;:/workspace:rw&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;-v&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;copilot-sandbox-root:/root&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;myorg/copilot-sandbox:latest&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; \&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;bash&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Flag explanations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--rm&lt;/code&gt;: Automatically removes the container when you exit (keeps system clean)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-it&lt;/code&gt;: Combined flags:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-i&lt;/code&gt; (interactive): Keeps STDIN open so you can type commands&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-t&lt;/code&gt; (pseudo-TTY): Allocates a terminal for proper interactive experience&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cap-drop ALL&lt;/code&gt;: Drops all Linux capabilities (prevents privileged operations)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--security-opt no-new-privileges&lt;/code&gt;: Prevents privilege escalation via setuid/setgid&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-v &quot;$PROJECT_PATH&quot;:/workspace:rw&lt;/code&gt;: Mounts your project directory to /workspace with read-write access&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-v copilot-sandbox-root:/root&lt;/code&gt;: Mounts the persistent volume to /root (where auth data is stored)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;myorg/copilot-sandbox:latest&lt;/code&gt;: The image you built earlier&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bash&lt;/code&gt;: Starts an interactive bash shell&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;33-make-the-script-executable&quot;&gt;3.3 Make the script executable&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;chmod&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;+x&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;~/copilot-sandbox.sh&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-first-time-authentication&quot;&gt;4. First-time authentication&lt;/h2&gt;
&lt;h3 id=&quot;41-launch-your-first-session&quot;&gt;4.1 Launch your first session&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/copilot-sandbox.sh &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/projects/my-project&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You’re now inside a container with a bash prompt. Your project files are available at &lt;code&gt;/workspace&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;42-authenticate-with-github-copilot-cli&quot;&gt;4.2 Authenticate with GitHub Copilot CLI&lt;/h3&gt;
&lt;p&gt;Inside the same container, run:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;copilot&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What happens:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copilot CLI starts for the first time&lt;/li&gt;
&lt;li&gt;It prompts you to authenticate with &lt;code&gt;/login&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;/login&lt;/code&gt; and press Enter&lt;/li&gt;
&lt;li&gt;Follow the browser authentication flow&lt;/li&gt;
&lt;li&gt;Sign in with your GitHub account that has Copilot access&lt;/li&gt;
&lt;li&gt;Token is saved to the &lt;code&gt;copilot-sandbox-root&lt;/code&gt; volume&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;43-exit-the-container&quot;&gt;4.3 Exit the Container&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The container is destroyed, but your authentication data remains in the &lt;code&gt;copilot-sandbox-root&lt;/code&gt; volume and persists across container sessions.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;usage-examples&quot;&gt;Usage examples&lt;/h2&gt;
&lt;h3 id=&quot;example-1-run-from-your-project-directory&quot;&gt;Example 1: Run from your project directory&lt;/h3&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;~/projects/my-project&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/copilot-sandbox.sh&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you’re already in the project folder, you can just run the script without arguments. It will use the current directory if no argument is provided.&lt;/p&gt;
&lt;h3 id=&quot;example-2-run-multiple-projects-in-parallel&quot;&gt;Example 2: Run multiple projects in parallel&lt;/h3&gt;
&lt;p&gt;Terminal 1:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/copilot-sandbox.sh &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/projects/project-a&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Terminal 2:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/copilot-sandbox.sh &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/projects/project-b&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Terminal 3:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/copilot-sandbox.sh &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;/projects/project-c&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each container is isolated, fresh, and destroyed on exit. Your authentication persists across sessions.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;You now have a fully isolated environment for running GitHub Copilot CLI on Linux.
Fresh containers for each project, persistent authentication, and strong security boundaries.
Copilot can modify files in &lt;code&gt;/workspace&lt;/code&gt; freely but can’t touch the rest of your host system.&lt;/p&gt;
&lt;p&gt;Remember: the container has full network access. The agent can send data to external servers.
Don’t run untrusted code or work with sensitive credentials inside the container unless you’re comfortable with that risk.&lt;/p&gt;
&lt;p&gt;If you want a GitHub-native workflow on top of this, have a look at &lt;a href=&quot;https://kipppunkt.dev&quot;&gt;kipp•punk&lt;/a&gt;. It is self-hosted, works with different coding-agent harnesses, and keeps clear control boundaries: you approve the scope, review the PR, and decide what ships.&lt;/p&gt;
&lt;p&gt;If you found this useful, follow me on &lt;a href=&quot;https://x.com/georg_dev&quot;&gt;X&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/georg.dev&quot;&gt;BlueSky&lt;/a&gt;, or &lt;a href=&quot;https://www.linkedin.com/in/georg-unterholzner/&quot;&gt;LinkedIn&lt;/a&gt; for more technical deep dives like this.&lt;/p&gt;</content:encoded></item><item><title>AI-driven unit testing with GitHub Copilot</title><link>https://georg.dev/blog/06-ai-driven-unit-testing-with-github-copilot/</link><guid isPermaLink="true">https://georg.dev/blog/06-ai-driven-unit-testing-with-github-copilot/</guid><description>A better alternative than vibe-coding unit tests</description><pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;img src=&quot;https://georg.dev/_astro/banner.9oROxf46_Z1iHGdu.webp&quot; alt=&quot;&quot; /&gt;&lt;details class=&quot;tldr&quot; open&gt;
  &lt;summary&gt;TL;DR&lt;/summary&gt;
  &lt;ul&gt;
    &lt;li&gt;AI can write unit tests for you, but a prompt like &quot;write tests for this code&quot; produces garbage.&lt;/li&gt;
    &lt;li&gt;This article shows an AI workflow that actually works using GitHub Copilot&apos;s instructions and prompt files.&lt;/li&gt;
    &lt;li&gt;You decide which test cases to implement, AI writes them, you review the results.&lt;/li&gt;
    &lt;li&gt;The article includes a ready-to-use prompt file and an &lt;a href=&quot;https://github.com/georg-unterholzner/ai-driven-unit-testing-with-github-copilot&quot;&gt;example repository&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/details&gt;
&lt;p&gt;Unit tests are a pain to write. They keep your codebase maintainable, sure, but that doesn’t make writing them less tedious. After all, you’ve just implemented the feature and your code works. But now you have to revisit everything and write tests for it.&lt;/p&gt;
&lt;p&gt;The obvious idea: why not let AI handle it? It excels at this type of tedious, repetitive task anyway.&lt;/p&gt;
&lt;p&gt;But if you just prompt an LLM to “write tests for this code”, you’ll get more garbage than you’d expect, e.g., useless test cases, tests tied to implementation details, and even worse, false positives.&lt;/p&gt;
&lt;p&gt;This article shows you a better way: an AI workflow that uses GitHub Copilot to do the tedious part of the work for you while you maintain control over test cases and quality.&lt;/p&gt;
&lt;p class=&quot;aside&quot;&gt;(TDD aficionados: I see you. There’s something for you at the end as well.)&lt;/p&gt;
&lt;h2 id=&quot;the-problem-with-ai-generated-unit-tests&quot;&gt;The problem with AI-generated unit tests&lt;/h2&gt;
&lt;p&gt;Before we talk about the solution, let’s understand the problem first. Why does AI perform so badly when prompted to write unit tests?&lt;/p&gt;
&lt;p&gt;LLMs are expensive to run and context windows are limited. Every new conversation with GitHub Copilot starts fresh with no prior knowledge. Copilot also has to make smart choices about how much of your codebase to load into context. Even you might struggle to derive domain logic from a codebase you’re seeing for the first time. Imagine you could only read parts of it.&lt;/p&gt;
&lt;p&gt;Current AI is also bad at logical thinking and generalization. It has difficulty deriving intent from code alone and tends to focus on testing the code rather than the underlying requirements. This leads to shallow tests and false positives. AI also struggles with granularity and implements tests for the same requirement repeatedly. Worst of all: sometimes it gets so fixated on making tests pass that it modifies production code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;, current AIs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;don’t have a good overview of the codebase,&lt;/li&gt;
&lt;li&gt;struggle with understanding the business logic,&lt;/li&gt;
&lt;li&gt;produce shallow and redundant tests,&lt;/li&gt;
&lt;li&gt;produce false-positive tests,&lt;/li&gt;
&lt;li&gt;can modify production code instead of fixing failing tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s a lot of limitations. But there’s a way to work around them.&lt;/p&gt;
&lt;h2 id=&quot;solving-ai-assistant-shortcomings&quot;&gt;Solving AI assistant shortcomings&lt;/h2&gt;
&lt;p&gt;All you need to mitigate these limitations is good instructions for the AI, proper context engineering, and minimal human oversight at the right moments.&lt;/p&gt;
&lt;p&gt;Here’s what it looks like:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&quot;frame diagram&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/diagram.CbGaKyT5_1FVhn7.svg&quot; alt=&quot;Workflow diagram&quot; width=&quot;2456&quot; height=&quot;7782&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.1&lt;/span&gt; — the human-in-the-loop workflow&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;You start by running the workflow prompt and telling GitHub Copilot which files to test.&lt;/p&gt;
&lt;p&gt;The AI loads all relevant files and instructions into context: repository overview, business domain, architecture, conventions. It reads the files it should test and gathers information while focusing on the underlying business logic. If something is unclear, it asks.&lt;/p&gt;
&lt;p&gt;Once ready, the AI proposes test cases. You review them, add new ones, modify or remove existing ones, or approve the proposal. This lets you guide the AI with minimal intervention.&lt;/p&gt;
&lt;p&gt;After your approval, the AI gets to work. You can start a new feature, check Slack, get coffee, whatever. GitHub Copilot generates tests for all approved cases, runs them, and iterates until they all pass.&lt;/p&gt;
&lt;p&gt;When done, the AI notifies you with a summary. You review the tests, provide feedback if needed, and accept the changes.&lt;/p&gt;
&lt;p&gt;You’ll see this whole process in action in a video later. First, let’s set it up.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-the-workflow&quot;&gt;Setting up the workflow&lt;/h2&gt;
&lt;p&gt;The setup requires some configuration, but each step is straightforward. I also created an &lt;a href=&quot;https://github.com/georg-unterholzner/ai-driven-unit-testing-with-github-copilot&quot;&gt;example repository&lt;/a&gt; you can use to get started quickly. Here’s what you need to do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure Copilot instructions.&lt;/strong&gt; Copilot performs much better when it understands your project’s conventions, domain, and architecture. Create a &lt;a href=&quot;https://georg.dev/blog/05-better-context-better-github-copilot/&quot;&gt;&lt;code&gt;copilot-instructions.md&lt;/code&gt; file&lt;/a&gt; to provide such general context to the AI (see &lt;a href=&quot;https://github.com/georg-unterholzner/ai-driven-unit-testing-with-github-copilot/blob/fd872842a4f85e8dbc8e1826a76fd1de1090f737/.github/copilot-instructions.md&quot;&gt;example&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add code generation instructions for tests.&lt;/strong&gt; Code generation instruction files are provided to Copilot right when it’s about to generate code. This is the ideal place to give the AI guidance on writing tests in your codebase (think: naming conventions, testing libraries, mocking patterns, etc.). Create a &lt;a href=&quot;https://code.visualstudio.com/docs/copilot/customization/custom-instructions#_custom-instructions-examples&quot;&gt;&lt;code&gt;.instructions.md&lt;/code&gt; file&lt;/a&gt; with a file pattern that matches your test files and instructions for your specific codebase and testing framework (see &lt;a href=&quot;https://github.com/georg-unterholzner/ai-driven-unit-testing-with-github-copilot/blob/fd872842a4f85e8dbc8e1826a76fd1de1090f737/.github/instructions/test.instructions.md&quot;&gt;example&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enable the todo list tool.&lt;/strong&gt; LLMs typically struggle with multi-step workflows. That’s why GitHub Copilot’s &lt;code&gt;manage_todo_list&lt;/code&gt; tool lets the AI track and execute workflow steps. Make sure it’s enabled by following &lt;a href=&quot;https://code.visualstudio.com/docs/copilot/chat/chat-tools#_enable-tools-for-chat&quot;&gt;this guide&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure auto-approval.&lt;/strong&gt; The workflow needs to run some terminal commands autonomously without constantly asking for permission. For VS Code, set up &lt;code&gt;.vscode/settings.json&lt;/code&gt; to auto-approve certain commands (see &lt;a href=&quot;https://github.com/georg-unterholzner/ai-driven-unit-testing-with-github-copilot/blob/fd872842a4f85e8dbc8e1826a76fd1de1090f737/.vscode/settings.json#L2-L23&quot;&gt;example&lt;/a&gt;). If you’re using a JetBrains IDE, there’s a &lt;a href=&quot;https://github.com/microsoft/copilot-intellij-feedback/issues/492#issuecomment-3208864352&quot;&gt;similar setting&lt;/a&gt;. Make sure to be smart about this list and only allow commands that don’t pose a risk if the AI goes astray.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create the workflow prompt.&lt;/strong&gt; This file contains the full workflow description. In the folder &lt;code&gt;.github/prompts&lt;/code&gt;, create the file &lt;code&gt;write-unit-tests.prompt.md&lt;/code&gt; and add the following content:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;---&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;mode: &apos;agent&apos;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;description: &apos;Generate unit tests for the provided code.&apos;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;---&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Your task is to write unit tests for the provided code.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Use the &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;manage_todo_list&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; tool to keep track of the following tasks:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Analyze context files:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Read all files that have been provided as context.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; For each file, briefly summarize its purpose and identify the key business logic and edge cases that require testing.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; If anything about the business logic is unclear, ask the user to provide more information.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Propose test cases:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Based on your analysis, propose a concise list of test case titles (grouped by file/module), e.g.:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt; A. &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;MyComponent.tsx&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;   &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt; should display a greeting when showGreeting is true.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;   &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt; ...&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt; B. &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;useHelloWorld.ts&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;   &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt; should ...&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Only propose tests for business logic that is relevant and actually requires testing.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Ask the user to review and approve or edit this list.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Do not proceed to implementation until the user explicitly approves the final list.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Implement approved tests:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Implement exactly the test cases approved by the user. Do not add unapproved tests and do not omit any approved test.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;4.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Run tests and iterate until green:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Run all tests that you added.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; If any test fails, first verify whether the failure is due to the test (not the product code).&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; If the failure is due to the test, fix the test.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; If the failure appears to be a product issue, ask the user how to proceed. Do not modify production code without asking the user for consent.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Re-run until all tests pass.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;5.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Check and fix project quality gates:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Run type checks, linters, and any formatting tools configured in the project.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; If any check fails, first verify whether the failure comes from one of the test files you edited or if it comes from unrelated code.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; If it comes from one of the test files you edited, fix the error.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; If it comes from unrelated code, ignore the error.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Re-run checks until they are clean or until only unrelated files cause issues.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;6.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Final verification and report:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Finally, run the full test suite. Confirm that all newly created tests pass.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Provide a very brief summary of what you did.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Rules and constraints:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; You are only done if &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E3E6EA&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; the steps have been completed successfully. Otherwise, you must continue.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; If critical information is missing, always ask the user.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;using-the-workflow&quot;&gt;Using the workflow&lt;/h2&gt;
&lt;p&gt;Here’s how to run the workflow once everything is configured. The video below shows the workflow in action. You can also follow the written steps underneath.&lt;/p&gt;
&lt;figure&gt;&lt;div class=&quot;frame video&quot;&gt;&lt;video controls preload=&quot;metadata&quot;&gt;
  &lt;source src=&quot;https://georg.dev/videos/ai-driven-unit-testing-with-github-copilot.mp4&quot; type=&quot;video/mp4&quot;&gt;
Your browser does not support the video tag.
&lt;/video&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.2&lt;/span&gt; — the workflow running in VS Code&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start the workflow.&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;Open VS Code and start a new chat in the Copilot chat window.&lt;/li&gt;
&lt;li&gt;Select a capable model. In my experiments, GPT-4o wasn’t quite up for the task and I usually used Claude Sonnet 4.5, but it ultimately depends on your codebase.&lt;/li&gt;
&lt;li&gt;Click the &lt;em&gt;Add Context…&lt;/em&gt; button in the Copilot chat input field. Select the files you want the AI to write unit tests for.&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;/write-unit-tests&lt;/code&gt; into the Copilot chat.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review proposed test cases.&lt;/strong&gt; The AI will now start gathering context. Once it’s done, it will propose the unit test cases. Carefully review them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Approve test cases.&lt;/strong&gt; Type into the chat window which test cases you’d like to have implemented. You can reference them by number, like “A1, A5, B2”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Let the AI work.&lt;/strong&gt; The AI will get to work. If you have a good allow-list for tool calls, you shouldn’t be bothered until it’s done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Final review.&lt;/strong&gt; After a while, the AI will ask for final approval. Carefully review the written tests and provide feedback where necessary. If you’re happy with the results, accept the changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you want to try out the workflow without setting everything up yourself, feel free to clone the &lt;a href=&quot;https://github.com/georg-unterholzner/ai-driven-unit-testing-with-github-copilot&quot;&gt;example repository&lt;/a&gt; and follow the steps above.&lt;/p&gt;
&lt;h2 id=&quot;discussion--recap&quot;&gt;Discussion &amp;#x26; recap&lt;/h2&gt;
&lt;p&gt;Once everything is set up properly, this workflow can be quite powerful. You can one-shot an entire test suite and move on to other work while the AI handles the tedious parts. Getting there takes some trial and error with your instruction files, but I found the experimentation process surprisingly rewarding once results started coming together.&lt;/p&gt;
&lt;p&gt;That said, there’s a tradeoff worth considering. Normally, writing tests after code serves as a quality review step. You catch issues while thinking through edge cases. This workflow skips that. The AI can catch bugs during test generation (it happened to me several times) but I wouldn’t count on it. You still need to review the generated tests carefully, and depending on how well-tuned your setup is, this might save you significant time or barely make a dent.&lt;/p&gt;
&lt;p&gt;For TDD enthusiasts: you can invert this workflow. Write the tests first and let the AI generate the code to make them pass. Same configuration steps, same human checkpoints, just reversed. If you create a TDD version of the prompt file, I’d love to see it.&lt;/p&gt;
&lt;p&gt;If you like this kind of human-in-the-loop workflow, have a look at &lt;a href=&quot;https://kipppunkt.dev&quot;&gt;kipp•punk&lt;/a&gt;. It applies the same pattern to GitHub issues and pull requests: the agent refines the task with you, implements it, opens a PR, and reacts to review comments while you stay the quality gate.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Asking an AI to “write tests pls” won’t magically produce quality unit tests. But with the right workflow, proper context, a structured prompt, and two deliberate human checkpoints, you can hand off the tedious work while you control which tests are written and provide the common sense the AI lacks.&lt;/p&gt;
&lt;p&gt;It’s not zero-effort. You need to set up instruction files and tune the workflow to your codebase. But you can use the prompt file above and the &lt;a href=&quot;https://github.com/georg-unterholzner/ai-driven-unit-testing-with-github-copilot&quot;&gt;example repository&lt;/a&gt; to get started quickly.
If you run into issues or have questions, reach out. I’m curious to hear how this works for different codebases.&lt;/p&gt;
&lt;p&gt;I share more coding and AI development tips on &lt;a href=&quot;https://x.com/georg_dev&quot;&gt;X&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/georg.dev&quot;&gt;BlueSky&lt;/a&gt;, and &lt;a href=&quot;https://www.linkedin.com/in/georg-unterholzner&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Better Context, Better GitHub Copilot</title><link>https://georg.dev/blog/05-better-context-better-github-copilot/</link><guid isPermaLink="true">https://georg.dev/blog/05-better-context-better-github-copilot/</guid><description>A guide to copilot-instructions.md</description><pubDate>Mon, 21 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;img src=&quot;https://georg.dev/_astro/banner.B73bzs-l_RbiHj.webp&quot; alt=&quot;&quot; /&gt;&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 id=&quot;dont-vibe-your-copilot-instructionsmd&quot;&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=&quot;https://arxiv.org/pdf/2503.01781&quot;&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 id=&quot;setting-up-your-copilot-instructionsmd&quot;&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.
Due to the reasons above, I recommend using the former option.&lt;/p&gt;
&lt;h3 id=&quot;option-1-write-the-file-yourself-recommended&quot;&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.
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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;##&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt; Summary&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- brief summary of the repository --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;##&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt; Terminology&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- list of domain specific terms with their explanation --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;##&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt; Architecture&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- short summary of the architecture --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;##&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt; Task planning and problem-solving&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- the most important problem-solving guidelines --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- e.g. &quot;plan the task before writing any code&quot; --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;##&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt; Coding guidelines&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- the most important coding guidelines --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;option-2-generate-the-file-with-the-copilot-agent&quot;&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=&quot;https://x.com/pierceboggan&quot;&gt;@pierceboggan&lt;/a&gt; thanks for the heads-up):&lt;/p&gt;
&lt;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/vscode-generate-instructions.BwcU3Lwh_IJXOt.webp&quot; alt=&quot;Screenshot of VSCode showing the context menu with the option to generate the custom instructions&quot; width=&quot;860&quot; height=&quot;574&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.1&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&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 id=&quot;what-to-include-in-your-instruction-file&quot;&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 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Summarize your repository briefly, e.g.:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;This repository contains the source code for a Pokémon card &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;trading platform&apos;s web app, enabling online card trading.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;It manages trade processing and card inventory tracking.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;terminology&quot;&gt;Terminology&lt;/h3&gt;
&lt;p&gt;List domain-specific terms with explanations, e.g.:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; trade - User’s trade request (card, condition, price).&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; stock - Inventory of Pokémon cards for tracking and listing.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; queue - List of pending trades, prioritizing user offers.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;architecture&quot;&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;The React frontend interacts directly with Supabase&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;for database operations, user authentication, and &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;real-time trade updates, while integrating with &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;the PokeAPI for Pokémon data. Supabase was chosen for its&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;real-time capabilities and PokeAPI for its generous &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;rate limits.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;supabaseClient.js&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; - Supabase client &amp;#x26; authentication.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;realTimeTradeSubscriptions.js&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; - Manages trade updates.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;pokeApiIntegration.js&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; - Interacts with PokeAPI.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;task-planning-and-problem-solving&quot;&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Before each task, you must first complete the following steps:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Provide a full plan of your changes.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Provide a list of behaviors that you&apos;ll change.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Provide a list of test cases to add.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Before you add any code, always check if you can just re-use&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  or re-configure any existing code to achieve the result.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;model-specific-instructions&quot;&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Always focus on simplicity and precision and not comprehensiveness.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; When writing tests, focus on the happy path and only the most &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  important edge cases.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Before adding a new test, always make sure that a similar test &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  doesn&apos;t exist already.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;language-specific-instructions&quot;&gt;Language-specific instructions&lt;/h3&gt;
&lt;p&gt;Add language specific rules or use &lt;a href=&quot;https://code.visualstudio.com/blogs/2025/03/26/custom-instructions#_going-all-in-with-custom-instructions&quot;&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=&quot;https://10xrules.ai/&quot;&gt;10xrules.ai&lt;/a&gt; to generate these quickly.&lt;/p&gt;
&lt;h3 id=&quot;coding-guidelines&quot;&gt;Coding Guidelines&lt;/h3&gt;
&lt;p&gt;Specify coding styles not caught by linters. Include examples where needed.&lt;/p&gt;
&lt;h3 id=&quot;what-else&quot;&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=&quot;https://www.reddit.com/r/ChatGPTCoding/comments/1jl6gll/copilotinstructionsmd_has_helped_me_so_much/&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;https://www.reddit.com/r/GithubCopilot/comments/1llss4p/this_is_my_generalinstructionsmd_file_to_use_with/&quot;&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 id=&quot;how-to-write-your-copilot-instructionsmd&quot;&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 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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Don&apos;t ❌ --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;When fixing a bug, it helps to write a failing test first.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Do ✅--&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;When fixing a bug, always write a failing test first.&lt;/span&gt;
&lt;/span&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Don&apos;t ❌ --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Write maintainable code.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Do ✅ --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Always follow the DRY principle and avoid code duplication.&lt;/span&gt;
&lt;/span&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Don&apos;t ❌ --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Don’t use hard-coded numbers.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Do ✅--&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Avoid hard-coded numbers and use shared constants instead.&lt;/span&gt;
&lt;/span&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Don&apos;t ❌ --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Follow the ESLint rule &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;default-case&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; Always add a &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; case at the end of a &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;switch&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; statement.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Do ✅ --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Let your linter catch these issues instead. --&gt;&lt;/span&gt;
&lt;/span&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Don&apos;t ❌ --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;The API endpoint to retrieve data about a specific Pokémon &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;is defined &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;here&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;](&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;https://pokeapi.co/docs/v2#pokemon&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Do ✅--&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;To retrieve data about a specific Pokémon, send a &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; request&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;to &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;https://pokeapi.co/api/v2/pokemon/{id or name}/&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;.&lt;/span&gt;
&lt;/span&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Don&apos;t ❌ --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Prefix boolean variables with an appropriate verb.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- Do ✅--&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Prefix boolean variables with an appropriate verb, e.g.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;isLoading&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;hasPermissions&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;matchesFilter&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;bonus-boost-the-agent-mode&quot;&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;If you add new code or change existing code, always verify that&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;everything still works by running &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; of the following checks:&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;npm run lint&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; to run the linter.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;npm run test:unit&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; to run the unit tests.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;npm run test:e2e&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; to run the e2e tests.&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Complete the task only after all checks pass.&lt;/span&gt;
&lt;/span&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;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/tool-approval.C15fqoJy_Z1jrBNA.webp&quot; alt=&quot;Screenshot from VSCode showing GitHub Copilot Agent asking for approval to run the lint command&quot; width=&quot;518&quot; height=&quot;250&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.2&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;By default, Copilot requires your approval for each tool usage, but in VSCode, you can add &lt;code&gt;&quot;chat.tools.autoApprove&quot;: true&lt;/code&gt; to your &lt;code&gt;settings.json&lt;/code&gt; to enable &lt;a href=&quot;https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode#_autoapprove-all-tools-and-commands-experimental&quot;&gt;auto-approval&lt;/a&gt; and go full vibe-coding.&lt;/p&gt;
&lt;p&gt;If you want to take the same idea further, have a look at &lt;a href=&quot;https://kipppunkt.dev&quot;&gt;kipp•punk&lt;/a&gt;. It keeps the agent loop in GitHub: the agent refines the issue with you before implementation, opens a PR, and reacts to review comments. Same principle, just applied to the full workflow instead of a single prompt.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&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 and let me know how it goes! Did I miss any tips you use for &lt;code&gt;copilot-instructions.md&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;For more tips like these, follow me on &lt;a href=&quot;https://x.com/georg_dev&quot;&gt;X&lt;/a&gt; or &lt;a href=&quot;https://bsky.app/profile/georg.dev&quot;&gt;BlueSky&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>How (not) to find the unsung heroes of JavaScript</title><link>https://georg.dev/blog/04-how-not-to-find-the-unsung-heroes-of-javascript/</link><guid isPermaLink="true">https://georg.dev/blog/04-how-not-to-find-the-unsung-heroes-of-javascript/</guid><description>Why open source is a Jenga tower</description><pubDate>Wed, 26 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img src=&quot;https://imgs.xkcd.com/comics/dependency.png&quot; alt=&quot;xkcd-comic &amp;#x22;dependency&amp;#x22;: diagram of all modern infrastructure resting on some small project some random person in Nebraska has been thanklessly maintaining since 2003&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.1&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&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 id=&quot;the-search-for-javascripts-invisible-maintainers&quot;&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=&quot;https://www.npmjs.com/package/ipaddr.js&quot;&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=&quot;https://www.npmjs.com/package/long&quot;&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;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/anakjalanan.FD8kpeof_xADl6.webp&quot; alt=&quot;npm registry page of the package anakjalanan showing a bootstrapped Next.js app with 26,000 dependents&quot; width=&quot;1320&quot; height=&quot;1045&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.2&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&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 id=&quot;this-is-why-we-cant-have-nice-things&quot;&gt;This is why we can’t have nice things&lt;/h2&gt;
&lt;p&gt;Meet the &lt;a href=&quot;https://tea.xyz&quot;&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=&quot;https://socket.dev/blog/tea-xyz-spam-plagues-npm-and-rubygems-package-registries&quot;&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=&quot;https://github.com/aredridel/node-bin-gen/pull/241&quot;&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;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/github-comment.C_mqbCt0_1pi4g0.webp&quot; alt=&quot;Comment on the PR — the creator answering to someone accusing them of being an LLM: &amp;#34;Yep, you&amp;#38;#x27;re right. Actually, I only want to add one file, tea.yml, to your repository. Because I have a job that requires uploading the file and I also don&amp;#38;#x27;t know what it is used for. I apologize and thank you&amp;#34;&quot; width=&quot;911&quot; height=&quot;229&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.3&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&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;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/npm-uploads.KQ7PH88j_20C5ol.webp&quot; alt=&quot;Graph showing the explosion of NPM package uploads from beginning to mid 2024&quot; width=&quot;1200&quot; height=&quot;600&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.4&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;good-intentions-bad-incentives&quot;&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=&quot;https://support.gitcoin.co/gitcoin-knowledge-base/about-gitcoin/policy/understanding-potential-attack-vectors/sybil-attack&quot;&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=&quot;https://thanks.dev/&quot;&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=&quot;https://docs.tea.xyz/tea/i-want-to.../learn-about-teas-tokenomics/token-demand-drivers#mechanisms&quot;&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 id=&quot;why-arent-companies-funding&quot;&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 id=&quot;so-what-now&quot;&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 on &lt;a href=&quot;https://x.com/georg_dev&quot;&gt;X&lt;/a&gt; or &lt;a href=&quot;https://bsky.app/profile/georg.dev&quot;&gt;BlueSky&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>What is the Shadow DOM</title><link>https://georg.dev/blog/03-what-is-the-shadow-dom/</link><guid isPermaLink="true">https://georg.dev/blog/03-what-is-the-shadow-dom/</guid><description>Native style encapsulation</description><pubDate>Fri, 06 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;details class=&quot;tldr&quot; open&gt;
  &lt;summary&gt;TL;DR&lt;/summary&gt;
  &lt;ul&gt;
    &lt;li&gt;Allows you to add additional subtrees to the main DOM.&lt;/li&gt;
    &lt;li&gt;Gives you style encapsulation natively supported by the browser.&lt;/li&gt;
    &lt;li&gt;
      Has an imperative API (JavaScript):
      &lt;ul&gt;+ broad browser adoption&lt;/ul&gt;
      &lt;ul&gt;- doesn&apos;t support server-side rendering&lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
      Has a declarative API (HTML) as well:
      &lt;ul&gt;+ supports server-side rendering&lt;/ul&gt;
      &lt;ul&gt;+ more concise than the imperative API&lt;/ul&gt;
      &lt;ul&gt;+ only ~90% browser adoption (but a polyfill exists)&lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/details&gt;
&lt;h2 id=&quot;missing-style-encapsulation&quot;&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 id=&quot;the-partial-solution&quot;&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;&lt;a href=&quot;#references&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;However, their solutions typically offer only &lt;strong&gt;pseudo style encapsulation&lt;/strong&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;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/shadow-dom-meme-style-encapsulation.BFGZJypy_ZEDLb5.webp&quot; alt=&quot;Meme: Fixing broken water tank with flex tape&quot; width=&quot;500&quot; height=&quot;560&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.1&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;shadow-dom-to-the-rescue&quot;&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;&lt;a href=&quot;#references&quot;&gt;[2]&lt;/a&gt;&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;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/shadow-dom-meme-shall-not-pass.kgsW-K-a_Z26ochh.webp&quot; alt=&quot;Meme: Gandalf shouting &amp;#34;you shall not pass the shadow root&amp;#34;&quot; width=&quot;770&quot; height=&quot;324&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.2&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&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 id=&quot;how-the-shadow-dom-works&quot;&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 &lt;a href=&quot;#imperative-api&quot;&gt;imperative API&lt;/a&gt;.
Otherwise, feel free to jump ahead to the &lt;a href=&quot;#declarative-api&quot;&gt;declarative API&lt;/a&gt; since that one is usually preferred by most developers nowadays.&lt;/p&gt;
&lt;h3 id=&quot;imperative-api&quot;&gt;Imperative API&lt;/h3&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;p&gt;
    &lt;b&gt;Heads up:&lt;/b&gt; the imperative API does not work for server-side rendering (SSR).
    If you need SSR, use the &lt;a href=&quot;#declarative-api&quot;&gt;declarative approach&lt;/a&gt; instead.
  &lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Let’s assume, we have a very basic HTML structure:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;OUTSIDE the shadow DOM&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s go over this. Our DOM has a &lt;code&gt;&amp;#x3C;body&gt;&lt;/code&gt; element that contains two HTML nodes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; containing a &lt;code&gt;&amp;#x3C;span&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;#x3C;div&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;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/shadow-dom-diagram1.Xz7fskIc_Z1tcb6.webp&quot; alt=&quot;Diagram of the initial HTML structure&quot; width=&quot;1149&quot; height=&quot;892&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.3&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Next, we get the reference to this &lt;code&gt;&amp;#x3C;div&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;#x3C;span&gt;&lt;/code&gt; and append it to the shadow DOM:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;// get the reference to the div&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; hostElement &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; document&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;#host&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;// attach a shadow DOM&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; shadowDom &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; hostElement&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;attachShadow&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;mode&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;// create a new HTML node (just for demonstration)&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; exampleElement &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; document&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;createElement&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;exampleElement&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;textContent &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;INSIDE the shadow DOM&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;// append the new HTML node to the shadow DOM&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;shadowDom&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;appendChild&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;(exampleElement)&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, the new structure looks like this:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/shadow-dom-diagram2.DrZYThNQ_Ze4Bn.webp&quot; alt=&quot;Diagram of the HTML structure with added shadow DOM&quot; width=&quot;1150&quot; height=&quot;1056&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.4&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If you inspect how this is rendered in the browser, you will see something like this:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;OUTSIDE the shadow DOM&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    #shadow-root (open)&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;INSIDE the shadow DOM&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&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;
&lt;figure&gt;&lt;div class=&quot;frame video&quot;&gt;&lt;video controls preload=&quot;metadata&quot;&gt;
  &lt;source src=&quot;https://georg.dev/videos/shadow-dom-diagram.mp4&quot; type=&quot;video/mp4&quot;&gt;
Your browser does not support the video tag.
&lt;/video&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.5&lt;/span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;declarative-api&quot;&gt;Declarative API&lt;/h3&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;p&gt;
    &lt;b&gt;Heads up:&lt;/b&gt; the declarative shadow DOM has only recently been implemented in the last remaining browsers and is not fully adopted yet.&lt;sup&gt;&lt;a href=&quot;#references&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; 
    If you use the declarative API, add the required polyfill to your application.&lt;sup&gt;&lt;a href=&quot;#references&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;
  &lt;/p&gt;
&lt;/div&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Outside the shadow DOM&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;	  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;shadowrootmode&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Inside the shadow DOM&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;	  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice that we used the &lt;code&gt;&amp;#x3C;template&gt;&lt;/code&gt; element here.
The reason for this is that the &lt;code&gt;&amp;#x3C;template&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=&quot;https://georg.dev/blog/02-what-is-the-template-api&quot;&gt;template API article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://styled-components.com/&quot;&gt;Library: styled-components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM&quot;&gt;MDN Docs: Using shadow DOM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caniuse.com/declarative-shadow-dom&quot;&gt;Can I Use: Declarative shadow DOM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill&quot;&gt;web.dev: Declarative shadow DOM polyfill&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>What is the Template API</title><link>https://georg.dev/blog/02-what-is-the-template-api/</link><guid isPermaLink="true">https://georg.dev/blog/02-what-is-the-template-api/</guid><description>Write HTML once and re-use it many times</description><pubDate>Fri, 02 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;details class=&quot;tldr&quot; open&gt;
  &lt;summary&gt;TL;DR&lt;/summary&gt;
  &lt;ul&gt;
    &lt;li&gt;The template API allows you to define reusable HTML content.&lt;/li&gt;
    &lt;li&gt;Everything inside a &lt;code&gt;&amp;#x3C;template&gt;&lt;/code&gt; element is part of the document but not rendered when the page is loaded.&lt;/li&gt;
    &lt;li&gt;However, you can extract the content and attach it to one or multiple other nodes where it will be displayed.&lt;/li&gt;
    &lt;li&gt; Use the &lt;code&gt;&amp;#x3C;slot&gt;&lt;/code&gt; element to allow for dynamic children.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/details&gt;
&lt;h2 id=&quot;the-problem&quot;&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;i&gt;“No problem, I’ll just use a framework and create a component.”&lt;/i&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;#x3C;template&gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;
&lt;p&gt;Content inside a &lt;code&gt;&amp;#x3C;template&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;&lt;a href=&quot;#references&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Consider the following HTML:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;I&apos;m visible.&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;my-template&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;I&apos;m initially &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;not&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; visible.&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you will see below, the content from inside the &lt;code&gt;&amp;#x3C;template&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;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;// get the refence to the template&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; template &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; document&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E8B04B&quot;&gt;my-template&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;// extract and clone the content from the template&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; clonedContent &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;cloneNode&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#5FD48A&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;// append the extracted content to another node&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D38BD8&quot;&gt;appendChild&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;(clonedContent)&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;;&lt;/span&gt;
&lt;/span&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;figure&gt;&lt;div class=&quot;frame iframe&quot;&gt;&lt;iframe src=&quot;https://stackblitz.com/edit/what-is-the-template-api-rendering-template-content?ctl=1&amp;#x26;embed=1&amp;#x26;view=preview&amp;#x26;file=script.js,index.html&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.1&lt;/span&gt; — live demo: rendering template content&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&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 id=&quot;using-dynamic-content&quot;&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;#x3C;template&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;#x3C;ng-content&gt;&lt;/code&gt;.
With the template API, HTML introduces a native element: &lt;code&gt;&amp;#x3C;slot&gt;&lt;/code&gt;.&lt;sup&gt;&lt;a href=&quot;#references&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;&amp;#x3C;slot&gt;&lt;/code&gt; element specifies the entry point for child nodes. Consider this HTML:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Is this the real life&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;slot&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;slot&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Caught in a landslide&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#7C8591&quot;&gt;&amp;#x3C;!-- and later --&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;bohemian-rhapsody&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#C9CED6&quot;&gt;Is this just fantasy&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#2BB3D4&quot;&gt;bohemian-rhapsody&lt;/span&gt;&lt;span style=&quot;color:#4A525D&quot;&gt;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The rendered result will be:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&quot;frame iframe&quot;&gt;&lt;iframe src=&quot;https://stackblitz.com/edit/what-is-the-template-api-rendering-dynamic-content?ctl=1&amp;#x26;embed=1&amp;#x26;view=preview&amp;#x26;file=bohemian-rhapsody.js,index.html&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.2&lt;/span&gt; — live demo: rendering content into a slot&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;As you can see, the content put into the &lt;code&gt;&amp;#x3C;bohemian-rhapsody&gt;&lt;/code&gt; element got rendered into the &lt;code&gt;&amp;#x3C;slot&gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;If you’re wondering where the &lt;code&gt;&amp;#x3C;bohemian-rhapsody&gt;&lt;/code&gt; element came from, check out my upcoming intro to custom elements.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template&quot;&gt;MDN Docs: The Content Template element&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot&quot;&gt;MDN Docs: The Web Component Slot element&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>What are Web Components</title><link>https://georg.dev/blog/01-what-are-web-components/</link><guid isPermaLink="true">https://georg.dev/blog/01-what-are-web-components/</guid><description>The standard for universal UI components</description><pubDate>Sun, 16 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;details class=&quot;tldr&quot; open&gt;
  &lt;summary&gt;TL;DR&lt;/summary&gt;
  &lt;ul&gt;
    &lt;li&gt;Web components are a web standard for native UI components.&lt;/li&gt;
    &lt;li&gt;Main idea: Write UI components once, use them across frameworks or standalone.&lt;/li&gt;
    &lt;li&gt;
      The web component standard consists of three specifications:
      &lt;ul&gt;
        &lt;li&gt;HTML Templates&lt;/li&gt;
        &lt;li&gt;Shadow DOM&lt;/li&gt;
        &lt;li&gt;Custom Elements&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/details&gt;
&lt;h2 id=&quot;why-web-components&quot;&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;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/material-design-2-libraries.CCgyfxyv_Z1k8kGV.webp&quot; alt=&quot;Diagram showing the various libraries you needed for Material Design 2&quot; width=&quot;1440&quot; height=&quot;1080&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.1&lt;/span&gt; — For Material Design 2, each framework still required its own component library since web components weren’t supported by all frameworks yet.&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&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;/p&gt;
&lt;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/material-design-3-web-components.DAR3X2TA_Z2vIFOx.webp&quot; alt=&quot;Diagram showing that only one component library will be required for Material Design 3&quot; width=&quot;1440&quot; height=&quot;1080&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.2&lt;/span&gt; — For Material Design 3, only one component library is required, which is framework-agnostic thanks to web components. &lt;sup&gt;&lt;a href=&quot;#references&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;the-growing-significance-of-web-components&quot;&gt;The Growing Significance of Web Components&lt;/h2&gt;
&lt;p&gt;Chances are, you’ve never used a 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;&lt;a href=&quot;#references&quot;&gt;[2]&lt;/a&gt;&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;&lt;a href=&quot;#references&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/photoshop-ui.C_8lCCBa_1ql6V.webp&quot; alt=&quot;Screenshot of the Photoshop Web UI&quot; width=&quot;1280&quot; height=&quot;800&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.3&lt;/span&gt; — Screenshot of the Photoshop Web UI: Almost every element you see here is a web component.&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&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;&lt;a href=&quot;#references&quot;&gt;[4]&lt;/a&gt;&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;figure&gt;&lt;div class=&quot;frame&quot;&gt;&lt;img  src=&quot;https://georg.dev/_astro/custom-elements-registry-usage.9LEqRBHi_jqaE8.webp&quot; alt=&quot;Chart of the Google Chrome Platform Status report showing the rising number of web components&quot; width=&quot;1473&quot; height=&quot;687&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;span&gt;&lt;span class=&quot;n&quot;&gt;fig.4&lt;/span&gt; — Google Chrome Platform Status: The number of URLs containing web components has been steadily rising from &amp;#x3C;0% in 2018 to around 16% in 2024.&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&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 id=&quot;defining-a-standard&quot;&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.
Combined, they create re-usable and encapsulated UI components as a web standard: web components.&lt;/p&gt;
&lt;div class=&quot;note&quot;&gt;
  &lt;strong&gt;Curious to dive deeper?&lt;/strong&gt;
  &lt;p&gt;
    Check out my upcoming articles 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;/div&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/material-components/material-web&quot;&gt;GitHub: Material Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/github/github-elements&quot;&gt;GitHub: github-elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/articles/ps-on-the-web&quot;&gt;web.dev: Photoshop’s journey to the web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromestatus.com/metrics/feature/timeline/popularity/1689&quot;&gt;Chrome Platform Status: CustomElementsRegistryDefine metrics&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item></channel></rss>