<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: mk668a</title>
    <description>The latest articles on DEV Community by mk668a (@mk668a).</description>
    <link>https://dev.to/mk668a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F964852%2Fdb07707f-ad16-4c29-947f-586faafb673c.jpeg</url>
      <title>DEV Community: mk668a</title>
      <link>https://dev.to/mk668a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mk668a"/>
    <language>en</language>
    <item>
      <title>Run Claude across all your repos from one session — without merging anyone's .claude/</title>
      <dc:creator>mk668a</dc:creator>
      <pubDate>Mon, 22 Jun 2026 18:15:13 +0000</pubDate>
      <link>https://dev.to/mk668a/run-claude-across-all-your-repos-from-one-session-without-merging-anyones-claude-36j2</link>
      <guid>https://dev.to/mk668a/run-claude-across-all-your-repos-from-one-session-without-merging-anyones-claude-36j2</guid>
      <description>&lt;h1&gt;
  
  
  Run Claude across all your repos from one session
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;&lt;code&gt;claude-muster&lt;/code&gt; is an orchestrator for Claude Code: it leaves every repo's &lt;code&gt;.claude/&lt;/code&gt; exactly where it is and runs that repo's own Claude inside it, while the Claude at your root decides who gets the work.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Here's the whole thing in one session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/work
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;claude-muster repos
&lt;span class="go"&gt;
  3 repos you can dispatch to:

  webapp
  api
  mobile

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;claude-muster dispatch api &lt;span class="s2"&gt;"fix the failing test in handler.ts"&lt;/span&gt;
&lt;span class="go"&gt;
  [api] ok

  Found it: handler.ts called the old two-arg `parse()`.
  Updated the call and the test passes.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That child ran &lt;code&gt;claude&lt;/code&gt; &lt;strong&gt;inside &lt;code&gt;api/&lt;/code&gt;&lt;/strong&gt; — with api's real working directory, environment, skills, agents, hooks, and permissions, exactly as if you'd opened Claude there yourself. Nothing was copied. Nothing was merged. There's nothing to drift or clean up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap it fills
&lt;/h2&gt;

&lt;p&gt;Say your work lives in one folder full of separate git repos, each carrying its own &lt;code&gt;.claude/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/work/
├── webapp/    → .claude/skills/deploy, .claude/commands/release
├── api/       → .claude/skills/lint, .claude/agents/db-reviewer, .claude/hooks/pre-commit
└── mobile/    → .claude/commands/build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open Claude Code &lt;strong&gt;inside &lt;code&gt;api/&lt;/code&gt;&lt;/strong&gt; and you get all of api's tooling. Good. But open it &lt;strong&gt;in &lt;code&gt;~/work/&lt;/code&gt;&lt;/strong&gt; to work across all three at once and that tooling vanishes — because Claude Code reads &lt;code&gt;.claude/&lt;/code&gt; from the current folder and the folders &lt;em&gt;above&lt;/em&gt; it, never the folders below.&lt;/p&gt;

&lt;p&gt;The obvious fix is to drag everything up: copy or symlink each repo's &lt;code&gt;.claude/&lt;/code&gt; into &lt;code&gt;~/work/.claude/&lt;/code&gt;. That works for skills, and then quietly breaks the rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A hook written to run inside &lt;code&gt;api/&lt;/code&gt; now fires from &lt;code&gt;~/work/&lt;/code&gt; with the wrong working directory.&lt;/li&gt;
&lt;li&gt;One repo's &lt;code&gt;deny&lt;/code&gt; permission silently blocks every other repo.&lt;/li&gt;
&lt;li&gt;Two repos that both set &lt;code&gt;API_URL&lt;/code&gt; collide into one value.&lt;/li&gt;
&lt;li&gt;Agents have to be copied, so they drift out of date the moment the source changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You end up babysitting a merged &lt;code&gt;.claude/&lt;/code&gt; instead of working.&lt;/p&gt;

&lt;p&gt;claude-muster takes the opposite approach. Instead of pulling every repo's tooling &lt;em&gt;up&lt;/em&gt; into one session, it leaves each &lt;code&gt;.claude/&lt;/code&gt; in place and runs &lt;strong&gt;that repo's own Claude inside that repo&lt;/strong&gt;. The Claude at your root becomes a dispatcher: it decides which repo a task belongs to, hands it off, and reads back the result.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;merge it all up&lt;/th&gt;
&lt;th&gt;claude-muster&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hook working directory&lt;/td&gt;
&lt;td&gt;wrong (runs from root)&lt;/td&gt;
&lt;td&gt;correct (the repo's own folder)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permissions / &lt;code&gt;deny&lt;/code&gt; rules&lt;/td&gt;
&lt;td&gt;cross-fire across repos&lt;/td&gt;
&lt;td&gt;scoped to each repo's session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agents&lt;/td&gt;
&lt;td&gt;copied, drift out of date&lt;/td&gt;
&lt;td&gt;read live from the repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;collides on shared keys&lt;/td&gt;
&lt;td&gt;isolated per child process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cleanup&lt;/td&gt;
&lt;td&gt;merged settings, symlinks, manifest&lt;/td&gt;
&lt;td&gt;one skill; &lt;code&gt;uninstall&lt;/code&gt; removes it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Teach your root Claude to do it on its own
&lt;/h2&gt;

&lt;p&gt;Calling &lt;code&gt;dispatch&lt;/code&gt; by hand is the manual gear. Install the routing skill and your root Claude learns to delegate without being told which repo to hit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;claude-muster &lt;span class="nb"&gt;install&lt;/span&gt;     &lt;span class="c"&gt;# adds a small skill to ~/work/.claude/&lt;/span&gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;claude
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fix the failing &lt;span class="nb"&gt;test &lt;/span&gt;&lt;span class="k"&gt;in &lt;/span&gt;api and tell me what web&lt;span class="s1"&gt;'s build command is
&lt;/span&gt;&lt;span class="go"&gt;
  (Claude dispatches to api, dispatches to web, and reports both back)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want your root Claude to know its repos the instant a session starts, instead of waiting for the skill to kick in? Add &lt;code&gt;--hook&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;claude-muster &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--hook&lt;/span&gt;    &lt;span class="c"&gt;# also registers a SessionStart hook&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every session here opens with a one-line briefing of which repos it can dispatch to. That hook is the &lt;em&gt;only&lt;/em&gt; thing claude-muster writes to your &lt;code&gt;settings.json&lt;/code&gt;, and &lt;code&gt;uninstall&lt;/code&gt; takes it back out exactly — deleting the file if nothing else is left in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  One repo, or fan out to all
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dispatch &amp;lt;repo&amp;gt; "&amp;lt;task&amp;gt;"&lt;/code&gt; sends one self-contained task to one repo. Write it as you would to a fresh Claude opened in that repo, because that's exactly what it is — the child starts with no memory of your root conversation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dispatch --all "&amp;lt;task&amp;gt;"&lt;/code&gt; sends the same task to every repo in parallel and collects the results. It's built for surveys and sweeps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;claude-muster dispatch &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="s2"&gt;"what's your test command?"&lt;/span&gt;
&lt;span class="go"&gt;[
  { "repo": "webapp", "ok": true,  "text": "npm test", "code": 0 },
  { "repo": "api",    "ok": true,  "text": "pytest -q", "code": 0 },
  { "repo": "mobile", "ok": true,  "text": "flutter test", "code": 0 }
]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;"is there a TODO about auth anywhere?"&lt;/em&gt;, &lt;em&gt;"bump the version to 2.0"&lt;/em&gt; — same shape. Pair it with &lt;code&gt;--json&lt;/code&gt; when you want to aggregate the answers yourself.&lt;/p&gt;

&lt;p&gt;By default every sibling repo with a &lt;code&gt;.claude/&lt;/code&gt; is included. Drop a &lt;code&gt;claude-muster.json&lt;/code&gt; in the root to narrow it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"webapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"services/**"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// globs, relative to root&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"legacy-*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"depth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                                       &lt;/span&gt;&lt;span class="c1"&gt;// how deep to look (default: 1)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"../shared-tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/abs/path/to/repo"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// extra repos anywhere on this machine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Claude Code reads each repo's &lt;code&gt;.claude/&lt;/code&gt; from the repo's own folder and the folders above it. claude-muster never fights that — it just starts &lt;code&gt;claude&lt;/code&gt; with the child repo as the working directory:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;discover&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Walk the root for sibling directories that contain a &lt;code&gt;.claude/&lt;/code&gt; (respecting &lt;code&gt;claude-muster.json&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;decide&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The Claude at your root (or you on the command line) picks which repo a task belongs to.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;dispatch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;claude -p "&amp;lt;task&amp;gt;" --output-format json&lt;/code&gt; with &lt;code&gt;cwd&lt;/code&gt; set to that repo.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;collect&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Parse the child's final result and hand it back to the orchestrator.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because the child is a real &lt;code&gt;claude&lt;/code&gt; process rooted in its own repo, every problem the merge-it-all approach creates simply doesn't arise: the working directory is right, hooks and permissions stay scoped, agents are never stale, environments don't collide, and there's nothing to clean up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's safe to rely on
&lt;/h2&gt;

&lt;p&gt;The most important line in the README is this one: &lt;strong&gt;claude-muster never calls an LLM itself.&lt;/strong&gt; &lt;code&gt;dispatch&lt;/code&gt; launches your local &lt;code&gt;claude&lt;/code&gt; CLI, which runs on your own Anthropic auth and your own wallet, in Claude Code's documented headless mode (&lt;code&gt;claude -p&lt;/code&gt;). No API keys of its own, no network, no telemetry. claude-muster only decides &lt;em&gt;where&lt;/em&gt; to send work and collects what comes back.&lt;/p&gt;

&lt;p&gt;It also barely touches disk. &lt;code&gt;dispatch&lt;/code&gt; and &lt;code&gt;repos&lt;/code&gt; only &lt;em&gt;read&lt;/em&gt; your folders to discover repos. The one thing it ever writes is the routing skill from &lt;code&gt;install&lt;/code&gt; (plus the optional SessionStart hook), and &lt;code&gt;uninstall&lt;/code&gt; removes exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do yet
&lt;/h2&gt;

&lt;p&gt;I'd rather you hit these knowingly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Persistent child sessions.&lt;/strong&gt; Each &lt;code&gt;dispatch&lt;/code&gt; is a fresh, single-shot &lt;code&gt;claude -p&lt;/code&gt; run, so a child doesn't remember the previous task you sent it. Warm, long-lived sessions per repo are a planned follow-up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repos on other machines.&lt;/strong&gt; Anywhere on your local filesystem works (see &lt;code&gt;paths&lt;/code&gt;), but remote or networked repos don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guessing when it's ambiguous.&lt;/strong&gt; If a task could belong to several repos, the routing skill is written to &lt;em&gt;ask&lt;/em&gt; rather than guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One practical note: &lt;code&gt;dispatch --all&lt;/code&gt; starts several &lt;code&gt;claude&lt;/code&gt; processes at once, which can hit Anthropic's rate limits if you fan out widely. Keep concurrency reasonable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;Not on npm yet — for now, clone and build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mk668a/claude-muster
&lt;span class="nb"&gt;cd &lt;/span&gt;claude-muster
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build
npm &lt;span class="nb"&gt;link&lt;/span&gt;            &lt;span class="c"&gt;# makes `claude-muster` available everywhere&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; ~/work
claude-muster repos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Node 18+. You also need the &lt;code&gt;claude&lt;/code&gt; CLI on your &lt;code&gt;PATH&lt;/code&gt; — that's what &lt;code&gt;dispatch&lt;/code&gt; runs. Prefer not to &lt;code&gt;npm link&lt;/code&gt;? Call the built file directly: &lt;code&gt;node /path/to/claude-muster/dist/cli.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Repo: &lt;strong&gt;&lt;a href="https://github.com/mk668a/claude-muster" rel="noopener noreferrer"&gt;https://github.com/mk668a/claude-muster&lt;/a&gt;&lt;/strong&gt; · MIT.&lt;/p&gt;

&lt;p&gt;The pitch fits in a sentence: stop merging everyone's &lt;code&gt;.claude/&lt;/code&gt; into one session and let each repo's Claude do its own work, in its own folder, with its own tooling intact. If you run it across a workspace where the merge approach was biting you, I'd love to hear which of the five collisions it quietly fixed first.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ai</category>
      <category>claude</category>
      <category>cli</category>
    </item>
    <item>
      <title>Grammarly costs $12/mo — a local LLM does it for free (Chrome + Ollama)</title>
      <dc:creator>mk668a</dc:creator>
      <pubDate>Mon, 15 Jun 2026 13:15:18 +0000</pubDate>
      <link>https://dev.to/mk668a/grammarly-costs-12mo-a-local-llm-does-it-for-free-chrome-ollama-128a</link>
      <guid>https://dev.to/mk668a/grammarly-costs-12mo-a-local-llm-does-it-for-free-chrome-ollama-128a</guid>
      <description>&lt;p&gt;I write a lot in the browser — email, GitHub comments, contact forms — and I wanted proofreading without uploading every keystroke to a company's cloud. My workplace bans Grammarly for exactly that reason.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/mk668a/inline-scribe" rel="noopener noreferrer"&gt;inline-scribe&lt;/a&gt;: a Chrome extension that proofreads your text with an AI that runs &lt;strong&gt;on your own machine&lt;/strong&gt; (Ollama). Nothing leaves your computer. And the fixes show up like Word's Track Changes — &lt;strong&gt;accept or reject each one individually&lt;/strong&gt; with ✓ / ✕.&lt;/p&gt;

&lt;p&gt;This post is about the two design decisions I found most interesting while building it. Both generalize to anyone wiring a local LLM into a product.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The LLM never produces the diff.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silencing Ollama's 403 with &lt;code&gt;declarativeNetRequest&lt;/code&gt;&lt;/strong&gt; — zero-config, no &lt;code&gt;OLLAMA_ORIGINS&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The missing ingredient was never the AI
&lt;/h2&gt;

&lt;p&gt;If you write in a browser today, you pick one of three bad options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Grammarly&lt;/strong&gt; — great UX, but every keystroke goes to their cloud, the good features are behind a subscription, and many workplaces ban it (legal docs, unreleased code, patient data).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paste into ChatGPT&lt;/strong&gt; — you get one big rewritten blob back. Which words changed? Did it alter your meaning? You re-read everything, every time, and your text still went to someone else's server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing&lt;/strong&gt; — and ship the typos.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The thing is, the AI isn't the hard part anymore. Anyone can run a capable model locally with &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; in two commands, for free. What's missing is the &lt;strong&gt;interface&lt;/strong&gt;. The reason Grammarly was worth paying for was never the grammar engine — it was the &lt;em&gt;friendly diff&lt;/em&gt; that lets you see and control each change.&lt;/p&gt;

&lt;p&gt;That interface, on top of a model you own, is the whole product.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;corrections&lt;/th&gt;
&lt;th&gt;your text goes to&lt;/th&gt;
&lt;th&gt;inline diff, per-fix accept/reject&lt;/th&gt;
&lt;th&gt;price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grammarly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;cloud AI&lt;/td&gt;
&lt;td&gt;their servers&lt;/td&gt;
&lt;td&gt;✅ (the reason people pay)&lt;/td&gt;
&lt;td&gt;$12+/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Harper&lt;/strong&gt; (10k★)&lt;/td&gt;
&lt;td&gt;local, rule-based&lt;/td&gt;
&lt;td&gt;nowhere ✅&lt;/td&gt;
&lt;td&gt;❌ underlines typos only — can't rewrite a clumsy sentence&lt;/td&gt;
&lt;td&gt;free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;scramble / Typollama&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;local LLM ✅&lt;/td&gt;
&lt;td&gt;nowhere ✅&lt;/td&gt;
&lt;td&gt;❌ whole-text replacement or popup&lt;/td&gt;
&lt;td&gt;free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;inline-scribe&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;local LLM ✅&lt;/td&gt;
&lt;td&gt;nowhere ✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Design decision #1: the LLM never produces the diff
&lt;/h2&gt;

&lt;p&gt;This is the one I most want to share.&lt;/p&gt;

&lt;p&gt;The intuitive move is to ask the model for structured output: "return the changes as JSON," something like &lt;code&gt;[{ "delete": "...", "insert": "..." }, ...]&lt;/code&gt;, and pipe it straight into the UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But small local models break when you do this.&lt;/strong&gt; A model like llama3.2 (3B) is surprisingly good at &lt;em&gt;fixing prose&lt;/em&gt; and terrible at &lt;em&gt;structured output&lt;/em&gt;: it breaks the JSON, adds explanations, wraps everything in a code fence, renames your keys. A chatty 3B model means a broken UI.&lt;/p&gt;

&lt;p&gt;So I split the responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The model's job:&lt;/strong&gt; return corrected prose — just text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The extension's job:&lt;/strong&gt; compute the changes (hunks) from &lt;code&gt;(original, corrected)&lt;/code&gt; with a &lt;strong&gt;deterministic algorithm&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;you press Alt+G in a text field
   │
   ▼
the extension sends your text to YOUR endpoint     ← default: Ollama on 127.0.0.1
(an OpenAI-compatible /chat/completions API)          model: llama3.2 (~2GB, free)
   │
   ▼
the model returns corrected prose — just text
   │
   ▼
inline-scribe computes a word-level diff           ← deterministic algorithm,
between your text and the correction                  NOT the LLM's opinion
   │
   ▼
review panel: accept ✓ / reject ✕ each change → Apply writes back only what you approved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diff tokenizes into words + whitespace + punctuation runs, then does an &lt;strong&gt;LCS (longest common subsequence)&lt;/strong&gt; walk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Tokenize into words/whitespace/punctuation, preserving everything&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+|&lt;/span&gt;&lt;span class="se"&gt;[^\s\w]&lt;/span&gt;&lt;span class="sr"&gt;+|&lt;/span&gt;&lt;span class="se"&gt;\w&lt;/span&gt;&lt;span class="sr"&gt;+/gu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;diffText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;corrected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Hunk&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;corrected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// DP table of LCS lengths over a × b (Uint32Array rows)&lt;/span&gt;
  &lt;span class="c1"&gt;// Walk the table emitting equal / delete / insert, merging adjacent ops.&lt;/span&gt;
  &lt;span class="c1"&gt;// Collapse delete+insert neighbours into one `replace` so a phrase rewrite&lt;/span&gt;
  &lt;span class="c1"&gt;// reads as a single reviewable hunk instead of three.&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This split has a lot of happy side effects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model-agnostic.&lt;/strong&gt; Any OpenAI-compatible endpoint works (llama.cpp, LM Studio, vLLM, or your own key). Since nothing depends on structured-output quality, the UI behaves the same whether you run 3B or 70B.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic, so reproducible.&lt;/strong&gt; Same input/output → same hunks. Easy to unit-test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accept/reject is trivial.&lt;/strong&gt; A hunk is &lt;code&gt;{ kind, original, corrected }&lt;/code&gt;. Accepted hunks take &lt;code&gt;corrected&lt;/code&gt;, rejected take &lt;code&gt;original&lt;/code&gt;, concatenate — done.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyDecisions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Hunk&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;hunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;corrected&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even with "return only text," small models still wrap output in fences or quotes. I gave up on prompting that away and instead strip the obvious wrappers in post — without touching real content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;stripWrapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*```&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\n([\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?)\n&lt;/span&gt;&lt;span class="sr"&gt;```&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;                       &lt;span class="c1"&gt;// strip ```...```&lt;/span&gt;
  &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+|&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^".*"$/&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^"/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// strip whole-reply quotes&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// preserve trailing-newline convention&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway: let small local models do what they're good at (return natural language) and keep the structure — diffs, JSON, state — in deterministic code.&lt;/strong&gt; This isn't specific to proofreading; it's a general principle for putting a local LLM into a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design decision #2: silencing Ollama's 403 with declarativeNetRequest
&lt;/h2&gt;

&lt;p&gt;This is the pothole every Chrome-extension × Ollama project hits.&lt;/p&gt;

&lt;p&gt;Stock Ollama rejects requests carrying a &lt;code&gt;chrome-extension://...&lt;/code&gt; Origin with a &lt;strong&gt;403&lt;/strong&gt; — a guard against cross-origin access from extensions. The official workaround is to have the user set the &lt;code&gt;OLLAMA_ORIGINS&lt;/code&gt; env var. But asking for that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exporting &lt;code&gt;OLLAMA_ORIGINS=chrome-extension://&amp;lt;id-that-changes&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;different steps depending on shell config and how Ollama is launched&lt;/li&gt;
&lt;li&gt;the single biggest "I installed it and it doesn't work" drop-off point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, &lt;strong&gt;the moment your README documents an env var, you've lost.&lt;/strong&gt; It should just work with a stock &lt;code&gt;ollama serve&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix: use MV3's &lt;strong&gt;declarativeNetRequest (DNR)&lt;/strong&gt; to &lt;strong&gt;strip the &lt;code&gt;Origin&lt;/code&gt; header&lt;/strong&gt; from requests to the configured endpoint with a dynamic rule. No Origin, no 403.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncOriginRule&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// e.g. 127.0.0.1:11434&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declarativeNetRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateDynamicRules&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;removeRuleIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;addRules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;urlFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`||&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resourceTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xmlhttprequest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modifyHeaders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;requestHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scope the rule to the user's configured host only&lt;/strong&gt; (&lt;code&gt;urlFilter&lt;/code&gt;). It's not a dangerous "strip Origin everywhere" rule.&lt;/li&gt;
&lt;li&gt;The endpoint is configurable, so watch &lt;code&gt;chrome.storage.onChanged&lt;/code&gt; and &lt;strong&gt;re-apply the rule whenever config changes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The only permissions needed are &lt;code&gt;declarativeNetRequest&lt;/code&gt; plus localhost &lt;code&gt;host_permissions&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// manifest.json (excerpt)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"activeTab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"declarativeNetRequest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contextMenus"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"host_permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result: the user's steps are "install Ollama, &lt;code&gt;ollama serve&lt;/code&gt;, install the extension." Zero env vars.&lt;/p&gt;

&lt;h2&gt;
  
  
  MV3 architecture: do the fetch in the service worker
&lt;/h2&gt;

&lt;p&gt;One more thing. The actual &lt;code&gt;fetch&lt;/code&gt; (the request to 127.0.0.1) happens &lt;strong&gt;in the service worker, not the content script&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// content → background message; background runs the check and replies&lt;/span&gt;
&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inline-scribe:check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;DEFAULT_CONFIG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;corrected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OllamaChecker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;corrected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="cm"&gt;/* CheckerError message */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// keep the channel open for the async response&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It isn't bound by the page's CSP.&lt;/strong&gt; A &lt;code&gt;fetch&lt;/code&gt; from a content script gets blocked when the page's Content-Security-Policy restricts &lt;code&gt;connect-src&lt;/code&gt;. The service worker runs in the extension's context and is unaffected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It plays well with the DNR Origin strip.&lt;/strong&gt; The request now originates from the extension's service worker as an &lt;code&gt;xmlhttprequest&lt;/code&gt;, so the rule above applies cleanly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI (review panel, the ✎ selection icon, in-place replacement) is rendered in a shadow DOM from the content script so it doesn't collide with the page's CSS.&lt;/p&gt;

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

&lt;p&gt;inline-scribe is, at its core, "Grammarly's diff UX on top of a local LLM." The design decisions that made it work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't let the LLM build the diff — use a deterministic algorithm.&lt;/strong&gt; The UI never breaks on small models, it's model-agnostic, and it's easy to test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strip the Origin with DNR.&lt;/strong&gt; No &lt;code&gt;OLLAMA_ORIGINS&lt;/code&gt;, zero config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetch in the service worker.&lt;/strong&gt; Not bound by page CSP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Swap the system prompt and the same diff UI becomes a translator or a tone-shifter. Source is MIT.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/mk668a/inline-scribe" rel="noopener noreferrer"&gt;https://github.com/mk668a/inline-scribe&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're putting a local LLM into a product, the leverage is in deciding what the model does — and what it doesn't.&lt;/p&gt;

</description>
      <category>node</category>
      <category>typescript</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>Position tooltips and popovers with native CSS — zero runtime JS</title>
      <dc:creator>mk668a</dc:creator>
      <pubDate>Thu, 04 Jun 2026 19:51:39 +0000</pubDate>
      <link>https://dev.to/mk668a/position-tooltips-and-popovers-with-native-css-zero-runtime-js-31oo</link>
      <guid>https://dev.to/mk668a/position-tooltips-and-popovers-with-native-css-zero-runtime-js-31oo</guid>
      <description>&lt;p&gt;Repo: &lt;strong&gt;&lt;a href="https://github.com/mk668a/css-anchor-kit" rel="noopener noreferrer"&gt;https://github.com/mk668a/css-anchor-kit&lt;/a&gt;&lt;/strong&gt; · MIT.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;code&gt;css-anchor-kit&lt;/code&gt; is floating-ui's React API with the JavaScript runtime deleted — positioning happens in the browser's layout engine, not in a &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's a tooltip. The whole thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAnchor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css-anchor-kit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Tooltip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;anchorProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;floatingProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAnchor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;anchorProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hover me&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;floatingProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"tooltip"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Type less. Think more.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;ref&lt;/code&gt;s to wire. No effect to keep position in sync on scroll. &lt;code&gt;anchorProps&lt;/code&gt; and &lt;code&gt;floatingProps&lt;/code&gt; are just inline styles that compile to &lt;code&gt;anchor-name&lt;/code&gt;, &lt;code&gt;position-anchor&lt;/code&gt;, &lt;code&gt;anchor()&lt;/code&gt;, and &lt;code&gt;position-try-fallbacks&lt;/code&gt;. When the page scrolls or the anchor moves, the browser repositions the box — the same way it already keeps &lt;code&gt;position: sticky&lt;/code&gt; glued in place. Your JavaScript never runs.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;That badge under the button is the snippet above, live. The arrow points at the trigger and the box sits 10px below it — placement the browser computed from inline &lt;code&gt;anchor-name&lt;/code&gt; / &lt;code&gt;anchor()&lt;/code&gt;, not a measurement loop.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap it fills
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://floating-ui.com" rel="noopener noreferrer"&gt;floating-ui&lt;/a&gt; is one of the most-downloaded packages on npm — ~28M installs a week — and its core job is &lt;em&gt;keep this box next to that box&lt;/em&gt;. To do that in 2020 you had to: measure the anchor's rect, measure the floating box, compute a position, write it to &lt;code&gt;style&lt;/code&gt;, and then re-run that whole pipeline on every scroll, resize, and layout shift via an &lt;code&gt;autoUpdate&lt;/code&gt; loop. That's the runtime cost. That's why there's a library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://caniuse.com/css-anchor-positioning" rel="noopener noreferrer"&gt;Browsers now do this natively.&lt;/a&gt; CSS Anchor Positioning reached Baseline in 2026 (Chrome/Edge 125+, Safari 26+; Firefox behind a flag with a solid polyfill). You name an anchor, point a floating element at it, and the layout engine keeps them together — no measurement, no loop, no reflow in the scroll path.&lt;/p&gt;

&lt;p&gt;The catch: the raw CSS API is verbose and unfamiliar. &lt;code&gt;anchor-name: --foo&lt;/code&gt;, &lt;code&gt;position-anchor: --foo&lt;/code&gt;, &lt;code&gt;top: anchor(bottom)&lt;/code&gt;, &lt;code&gt;position-try-fallbacks: flip-block&lt;/code&gt;… nobody has muscle memory for that yet, and mapping "I want &lt;code&gt;bottom-start&lt;/code&gt; with an 8px gap that flips on overflow" onto it by hand is fiddly.&lt;/p&gt;

&lt;p&gt;So there's a hole: the platform can do the work, but the ergonomics everyone learned belong to floating-ui. &lt;code&gt;css-anchor-kit&lt;/code&gt; is the thin headless layer that bridges it — floating-ui's &lt;code&gt;placement&lt;/code&gt; / &lt;code&gt;offset&lt;/code&gt; / &lt;code&gt;flip&lt;/code&gt; / &lt;code&gt;arrow&lt;/code&gt; vocabulary on top, native CSS underneath, nothing in between at runtime.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;floating-ui&lt;/th&gt;
&lt;th&gt;css-anchor-kit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Position computed by&lt;/td&gt;
&lt;td&gt;JS, on every scroll/resize&lt;/td&gt;
&lt;td&gt;the browser's layout engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime cost&lt;/td&gt;
&lt;td&gt;measure → place → &lt;code&gt;autoUpdate&lt;/code&gt; loop&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;none&lt;/strong&gt; — it's CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle (min+gzip)&lt;/td&gt;
&lt;td&gt;~6–10 KB core + React&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&amp;lt; 1 KB&lt;/strong&gt;, React optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arrow stays on anchor when edge-aligned&lt;/td&gt;
&lt;td&gt;needs JS middleware&lt;/td&gt;
&lt;td&gt;a sibling anchored to the same element&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works without React&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes — &lt;code&gt;buildAnchorStyles&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The API is the one you already know
&lt;/h2&gt;

&lt;p&gt;If you've written floating-ui, you can read this without docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;anchorProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;floatingProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arrowProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;supported&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAnchor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// the 12 floating-ui placements&lt;/span&gt;
  &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// gap in px&lt;/span&gt;
  &lt;span class="na"&gt;flip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// flip to the opposite side on overflow (default)&lt;/span&gt;
  &lt;span class="na"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// hide when the anchor scrolls out of view&lt;/span&gt;
  &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// match the anchor's size: 'width' | 'height' | true&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repo ships a docs app that turns every one of those options into a live control. Change &lt;code&gt;placement&lt;/code&gt; to &lt;code&gt;bottom&lt;/code&gt;, drag &lt;code&gt;offset&lt;/code&gt; to 10, and the canvas re-positions while the generated code on the right rewrites itself to match — there's no measurement step in between, the engine just resolves the new &lt;code&gt;anchor()&lt;/code&gt; insets:&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;useAnchor&lt;/code&gt; &lt;strong&gt;only computes position&lt;/strong&gt;. It deliberately doesn't own visibility or interaction — pair it with the native &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Popover_API" rel="noopener noreferrer"&gt;&lt;code&gt;popover&lt;/code&gt;&lt;/a&gt; attribute, a hover/focus state, or your own &lt;code&gt;useState&lt;/code&gt;. That separation is the whole reason it stays under a kilobyte.&lt;/p&gt;

&lt;p&gt;A few things worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Arrows are a sibling, not middleware.&lt;/strong&gt; The arrow element is anchored to the &lt;em&gt;same&lt;/em&gt; anchor as the floating box, so it stays centered on the trigger even when the popover is edge-aligned or flips. floating-ui needs an &lt;code&gt;arrow()&lt;/code&gt; middleware and a ref for this; here it's just &lt;code&gt;{...arrowProps}&lt;/code&gt; on a sibling &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;size&lt;/code&gt; matches the trigger.&lt;/strong&gt; &lt;code&gt;size: 'width'&lt;/code&gt; emits &lt;code&gt;anchor-size(width)&lt;/code&gt; — exactly what you want for a combobox/select popover that should be as wide as its button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RTL is free.&lt;/strong&gt; &lt;code&gt;-start&lt;/code&gt;/&lt;code&gt;-end&lt;/code&gt; placements pin &lt;em&gt;logical&lt;/em&gt; edges (&lt;code&gt;inset-inline-*&lt;/code&gt;), so &lt;code&gt;bottom-start&lt;/code&gt; aligns to the right edge in RTL automatically — matching floating-ui, with zero JS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prefer composition? There's optional headless sugar that tree-shakes away if unused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Anchored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Anchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Floating&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Arrow&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css-anchor-kit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Anchored&lt;/span&gt; &lt;span class="nx"&gt;placement&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Anchor&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hover me&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Anchor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Floating&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"tooltip"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Type less. Think more.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Floating&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Arrow&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"arrow"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;Anchored&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly how you build a real dropdown. The menu below pairs &lt;code&gt;&amp;lt;Anchored size="width"&amp;gt;&lt;/code&gt; with the native &lt;code&gt;popover&lt;/code&gt; attribute — so the list renders in the top layer (no &lt;code&gt;z-index&lt;/code&gt; wars) &lt;em&gt;and&lt;/em&gt; matches its trigger's width via &lt;code&gt;anchor-size(width)&lt;/code&gt;, which floating-ui needs a JS &lt;code&gt;size&lt;/code&gt; middleware to do:&lt;/p&gt;

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

&lt;p&gt;And it isn't even React-locked — the framework-agnostic core has zero dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildAnchorStyles&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css-anchor-kit/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;floating&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildAnchorStyles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--my-tooltip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;anchorEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;floatingEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;floating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  You don't have to migrate by hand
&lt;/h2&gt;

&lt;p&gt;If you have an existing floating-ui codebase, there's a &lt;a href="https://github.com/facebook/jscodeshift" rel="noopener noreferrer"&gt;jscodeshift&lt;/a&gt; codemod that does the mechanical 80%:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx css-anchor-kit migrate &lt;span class="s2"&gt;"src/**/*.{ts,tsx}"&lt;/span&gt;        &lt;span class="c"&gt;# rewrite in place&lt;/span&gt;
npx css-anchor-kit migrate &lt;span class="s2"&gt;"src/**/*.tsx"&lt;/span&gt; &lt;span class="nt"&gt;--dry&lt;/span&gt; &lt;span class="nt"&gt;--print&lt;/span&gt; &lt;span class="c"&gt;# preview only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It rewrites &lt;code&gt;useFloating(...)&lt;/code&gt; → &lt;code&gt;useAnchor(...)&lt;/code&gt;, maps &lt;code&gt;offset&lt;/code&gt;/&lt;code&gt;flip&lt;/code&gt;/&lt;code&gt;hide&lt;/code&gt; middleware to options, drops the now-pointless &lt;code&gt;autoUpdate&lt;/code&gt; loop, rewires &lt;code&gt;ref={refs.setReference}&lt;/code&gt; / &lt;code&gt;setFloating&lt;/code&gt; to &lt;code&gt;{...anchorProps}&lt;/code&gt; / &lt;code&gt;{...floatingProps}&lt;/code&gt;, and fixes the imports.&lt;/p&gt;

&lt;p&gt;Crucially, it does &lt;strong&gt;not&lt;/strong&gt; silently drop what has no native equivalent. &lt;code&gt;shift&lt;/code&gt;, &lt;code&gt;autoPlacement&lt;/code&gt;, &lt;code&gt;inline&lt;/code&gt;, and &lt;code&gt;arrow&lt;/code&gt; ref-wiring get a &lt;code&gt;// TODO(css-anchor-kit)&lt;/code&gt; comment left in place, so you can finish those by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"TODO(css-anchor-kit)"&lt;/span&gt; src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The codemod is a dev-time CLI only — it's never imported by the library, so it has zero effect on your runtime bundle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;CSS Anchor Positioning is &lt;strong&gt;discrete&lt;/strong&gt;, not continuous. The platform's fallback model is "try position A, then B, then C" — not "slide pixel by pixel." That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;shift&lt;/code&gt;&lt;/strong&gt; — continuously sliding a popover to keep it in view — has no native equivalent. &lt;code&gt;flip&lt;/code&gt; covers the common overflow case; if you genuinely need continuous shifting, floating-ui is still the right tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;autoPlacement&lt;/code&gt;&lt;/strong&gt; isn't mapped; pick a &lt;code&gt;placement&lt;/code&gt; and let &lt;code&gt;flip&lt;/code&gt; handle overflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else the 90% tooltip/popover/menu case actually uses — placement, offset, flip, hide, size, arrows, RTL/logical alignment — is covered natively, with no JS in the scroll path.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Verified in Chromium 148: all 12 placements position correctly (right side, ~8px gap, logical &lt;code&gt;-start&lt;/code&gt;/&lt;code&gt;-end&lt;/code&gt; alignment), &lt;code&gt;size&lt;/code&gt; matches the anchor, and &lt;code&gt;flip&lt;/code&gt; kicks in on overflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i css-anchor-kit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React 18+ is an &lt;strong&gt;optional&lt;/strong&gt; peer dependency — you only need it for the hook. For older browsers, detect support with the &lt;code&gt;supported&lt;/code&gt; flag and lazy-load the &lt;a href="https://github.com/oddbird/css-anchor-positioning" rel="noopener noreferrer"&gt;&lt;code&gt;@oddbird/css-anchor-positioning&lt;/code&gt;&lt;/a&gt; polyfill yourself — it's BYO and not bundled, so supporting browsers ship nothing extra.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;supported&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAnchor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;supported&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@oddbird/css-anchor-positioning/fn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;supported&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;strong&gt;&lt;a href="https://github.com/mk668a/css-anchor-kit" rel="noopener noreferrer"&gt;https://github.com/mk668a/css-anchor-kit&lt;/a&gt;&lt;/strong&gt; · MIT.&lt;/p&gt;

&lt;p&gt;The pitch is small enough to fit in a sentence: the thing you were shipping 10 KB of JavaScript for is a CSS feature now. Keep the API, drop the runtime. If you try the codemod on a real floating-ui app and it leaves a &lt;code&gt;TODO&lt;/code&gt; you think &lt;em&gt;should&lt;/em&gt; have a native mapping, open an issue — those edges are exactly where the discrete-vs-continuous line gets interesting.&lt;/p&gt;

</description>
      <category>react</category>
      <category>css</category>
      <category>typescript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Which package is bloating your Docker image?</title>
      <dc:creator>mk668a</dc:creator>
      <pubDate>Mon, 25 May 2026 11:24:56 +0000</pubDate>
      <link>https://dev.to/mk668a/which-package-is-bloating-your-docker-image-21j6</link>
      <guid>https://dev.to/mk668a/which-package-is-bloating-your-docker-image-21j6</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;code&gt;layer-blame&lt;/code&gt; is &lt;code&gt;git blame&lt;/code&gt; for image layers — it names the package responsible for every byte.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's what it prints when you point it at a stock Alpine image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker save alpine:3.20 &lt;span class="nt"&gt;-o&lt;/span&gt; alpine.tar
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;layer-blame alpine.tar
&lt;span class="go"&gt;
Image total: 8.4 MB across 1 layers  ·  package attribution: 100%

Layer 0  8.4 MB  ADD alpine-minirootfs-3.20.10-aarch64.tar.gz /
      4.8 MB  libcrypto3                      pkg   ← largest line highlighted
    911.7 KB  libssl3                         pkg
    906.0 KB  busybox                         pkg
    706.5 KB  musl                            pkg
    327.1 KB  apk-tools                       pkg
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every byte in that layer now has an owner. &lt;code&gt;libcrypto3&lt;/code&gt; is 4.8 of the 8.4 MB. That's the whole pitch: it's &lt;code&gt;git blame&lt;/code&gt;, but the blamed thing is the &lt;strong&gt;package&lt;/strong&gt; responsible for a layer's size.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap it fills
&lt;/h2&gt;

&lt;p&gt;You already have two tools for image size, and between them there's a hole.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker history&lt;/code&gt; tells you &lt;em&gt;that&lt;/em&gt; a layer is 95 MB. It will not tell you what's in it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wagoodman/dive" rel="noopener noreferrer"&gt;&lt;code&gt;dive&lt;/code&gt;&lt;/a&gt; (~54k⭐) lets you &lt;em&gt;browse&lt;/em&gt; what's in it — file by file, interactively. It answers &lt;strong&gt;what is in&lt;/strong&gt; a layer.&lt;/p&gt;

&lt;p&gt;Neither answers the question you actually walked up with: &lt;strong&gt;"which package put those bytes here?"&lt;/strong&gt; So the standard ritual is: read &lt;code&gt;docker history&lt;/code&gt; for the fat layer, open &lt;code&gt;dive&lt;/code&gt;, eyeball the tree, and guess. People have asked dive for package-level breakdown directly — see &lt;a href="https://github.com/wagoodman/dive/issues/291" rel="noopener noreferrer"&gt;dive#291&lt;/a&gt;, open for years.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;layer-blame&lt;/code&gt; JOINs each layer's added files against the image's own package databases — &lt;code&gt;apk&lt;/code&gt; for Alpine, &lt;code&gt;dpkg&lt;/code&gt; for Debian/Ubuntu — and attributes every byte to a package. It's not a browser. It's a non-interactive report you can paste into a PR or wire into CI.&lt;/p&gt;

&lt;p&gt;dive for browsing, layer-blame for attribution. They're complementary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do Python's 137 MB actually go?
&lt;/h2&gt;

&lt;p&gt;This is the example that sold me on building it. &lt;code&gt;python:3.12-slim&lt;/code&gt; is famously chunky. Here's the breakdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker save python:3.12-slim &lt;span class="nt"&gt;-o&lt;/span&gt; py.tar
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;layer-blame &lt;span class="nt"&gt;--top&lt;/span&gt; 5 py.tar
&lt;span class="go"&gt;
Image total: 137.7 MB across 4 layers  ·  package attribution: 69%

&lt;/span&gt;&lt;span class="gp"&gt;Layer 0  95.8 MB  #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;debian.sh &lt;span class="nt"&gt;--arch&lt;/span&gt; &lt;span class="s1"&gt;'arm64'&lt;/span&gt; out/ &lt;span class="s1"&gt;'trixie'&lt;/span&gt; ...
&lt;span class="go"&gt;     22.5 MB  libc6                           pkg
      9.1 MB  coreutils                       pkg
      7.4 MB  perl-base                       pkg
      7.3 MB  libssl3t64                      pkg
      6.6 MB  util-linux                      pkg

&lt;/span&gt;&lt;span class="gp"&gt;Layer 2  38.2 MB  RUN /bin/sh -c set -eux;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;savedAptMark&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;apt-mark showmanual&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; …
&lt;span class="go"&gt;      6.3 MB  /usr/local/lib/libpython3.12.so.1.0                          file
      1.8 MB  /usr/local/lib/python3.12/ensurepip/_bundled/pip-...whl       file
      1.1 MB  /usr/local/lib/python3.12/lib-dynload/unicodedata...so        file
&lt;/span&gt;&lt;span class="c"&gt;      ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at Layer 2. The big contributors come back as &lt;strong&gt;files&lt;/strong&gt;, not packages. That isn't a bug — it's the finding. This image &lt;em&gt;compiles Python from source&lt;/em&gt;, so those bytes belong to no dpkg package. The layer's weight is build artifacts, not an installed package, and the tool says so by falling back to file-level attribution instead of pretending. The &lt;code&gt;package attribution: 69%&lt;/code&gt; header is telling you exactly that: 69% of the image mapped cleanly to packages, the rest is unowned bytes worth a second look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker save &amp;lt;image&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; image.tar
layer-blame &lt;span class="o"&gt;[&lt;/span&gt;flags] image.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Docker daemon needed at read time — it parses the &lt;code&gt;docker save&lt;/code&gt; tarball (or a plain OCI layout) directly. That's what makes it CI-friendly: save the artifact in your build job, run &lt;code&gt;layer-blame&lt;/code&gt; against the file.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--top N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Top N contributors per layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--no-color&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Disable ANSI color (also honors &lt;code&gt;NO_COLOR&lt;/code&gt; and non-TTY output)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Print version, commit, build date&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;Five steps, all deterministic — no network, no daemon, no model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load the tarball / OCI layout via &lt;a href="https://github.com/google/go-containerregistry" rel="noopener noreferrer"&gt;&lt;code&gt;go-containerregistry&lt;/code&gt;&lt;/a&gt;, which normalizes Docker-save, BuildKit, and containerd/OCI formats for you.&lt;/li&gt;
&lt;li&gt;Walk each layer's filesystem diff, recording every added file and its size. Whiteout (deletion) markers are skipped — you can't blame a layer for bytes it removed.&lt;/li&gt;
&lt;li&gt;Build a file→package index from the image's own package DBs: &lt;code&gt;/lib/apk/db/installed&lt;/code&gt; (Alpine) and &lt;code&gt;/var/lib/dpkg/info/*.list&lt;/code&gt; (Debian/Ubuntu).&lt;/li&gt;
&lt;li&gt;For each layer, group added bytes by owning package. Files with no owner are reported individually, so a large unattributed artifact still surfaces by name.&lt;/li&gt;
&lt;li&gt;Map each layer back to the Dockerfile instruction that created it (&lt;code&gt;created_by&lt;/code&gt; from the image config) and print the table.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The novel part is step 4 — the JOIN between added bytes and the package database. Everything else is plumbing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known boundaries
&lt;/h2&gt;

&lt;p&gt;I'd rather you hit these knowingly than be surprised:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;scratch / distroless&lt;/strong&gt; have no package database, so attribution falls back to file level. Still useful, no package names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-stage &lt;code&gt;COPY --from&lt;/code&gt;&lt;/strong&gt; files are disconnected from their origin package, so they show as unattributed in the destination layer.&lt;/li&gt;
&lt;li&gt;v1 handles &lt;strong&gt;apk (Alpine)&lt;/strong&gt; and &lt;strong&gt;dpkg (Debian/Ubuntu)&lt;/strong&gt; only. rpm and language package managers (npm, pip) aren't in yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;It's a single Go binary, MIT-licensed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Prebuilt binary (Linux/macOS/Windows, amd64+arm64) — from the latest release&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSfL&lt;/span&gt; https://github.com/mk668a/layer-blame/releases/latest/download/layer-blame_&amp;lt;version&amp;gt;_linux_amd64.tar.gz &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt; layer-blame

&lt;span class="c"&gt;# or with Go&lt;/span&gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/mk668a/layer-blame@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;strong&gt;&lt;a href="https://github.com/mk668a/layer-blame" rel="noopener noreferrer"&gt;https://github.com/mk668a/layer-blame&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next time a PR balloons your image and the size-budget review asks "why is this 800 MB?", you'll have a one-command answer with a package name attached — instead of an afternoon in &lt;code&gt;dive&lt;/code&gt;. If you try it on a weird image and the attribution surprises you, open an issue; the edge cases (rpm, cross-stage &lt;code&gt;COPY&lt;/code&gt;) are exactly where it gets interesting.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>go</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Your polyfills are dead. Here's a robot that buries them.</title>
      <dc:creator>mk668a</dc:creator>
      <pubDate>Mon, 25 May 2026 03:56:01 +0000</pubDate>
      <link>https://dev.to/mk668a/your-polyfills-are-dead-heres-a-robot-that-buries-them-4e5a</link>
      <guid>https://dev.to/mk668a/your-polyfills-are-dead-heres-a-robot-that-buries-them-4e5a</guid>
      <description>&lt;h2&gt;
  
  
  The problem: polyfills never leave
&lt;/h2&gt;

&lt;p&gt;A polyfill is a deal you make with the past. A browser doesn't support some&lt;br&gt;
feature yet, so you &lt;code&gt;npm install&lt;/code&gt; a shim, ship it, and move on. The deal is&lt;br&gt;
supposed to be temporary — but nobody ever collects the other half.&lt;/p&gt;

&lt;p&gt;Years pass. The feature ships in every browser. The polyfill is now pure dead&lt;br&gt;
weight: it bloats your bundle, slows your installs, and adds one more link to&lt;br&gt;
your supply-chain attack surface. And &lt;strong&gt;nothing tells you to delete it.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Renovate / Dependabot&lt;/strong&gt; dutifully &lt;em&gt;bump&lt;/em&gt; the polyfill's version forever.
They keep the corpse fresh; they never tell you it's a corpse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linters&lt;/strong&gt; flag &lt;em&gt;some&lt;/em&gt; redundant dependencies, but they don't act, and they
don't know whether the underlying feature is actually safe to rely on yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;@oddbird/css-anchor-positioning&lt;/code&gt;, &lt;code&gt;@ungap/structured-clone&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;array.prototype.flat&lt;/code&gt;, and a dozen friends quietly live in your &lt;code&gt;package.json&lt;/code&gt;&lt;br&gt;
for years after they stopped doing anything.&lt;/p&gt;
&lt;h2&gt;
  
  
  The signal: Web Platform Baseline
&lt;/h2&gt;

&lt;p&gt;The missing piece is a trustworthy, &lt;em&gt;dated&lt;/em&gt; answer to "is this feature safe to&lt;br&gt;
use natively yet?" That signal now exists: &lt;strong&gt;&lt;a href="https://web.dev/baseline" rel="noopener noreferrer"&gt;Baseline&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/web-platform-dx/web-features" rel="noopener noreferrer"&gt;&lt;code&gt;web-features&lt;/code&gt;&lt;/a&gt; dataset&lt;br&gt;
tags each web feature with a Baseline status. The one that matters here is&lt;br&gt;
&lt;strong&gt;Widely available&lt;/strong&gt; (&lt;code&gt;status.baseline === "high"&lt;/code&gt;) — roughly 30 months after&lt;br&gt;
the feature reached every major browser engine. Once a feature is Widely&lt;br&gt;
available, the polyfill that shimmed it is, by definition, redundant.&lt;/p&gt;

&lt;p&gt;That's a &lt;em&gt;calendar-driven&lt;/em&gt; trigger: a polyfill becomes removable on a specific&lt;br&gt;
day, whether or not anyone is paying attention.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tool: &lt;code&gt;baseline-polyfill-pruner&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;baseline-polyfill-pruner&lt;/code&gt; watches that signal for you and removes the dead&lt;br&gt;
polyfills it finds. It runs as a CLI, or — better — as a &lt;strong&gt;GitHub Action that&lt;br&gt;
opens a pull request the moment a polyfill becomes removable.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How it decides
&lt;/h3&gt;

&lt;p&gt;A dependency becomes a &lt;em&gt;removal candidate&lt;/em&gt; only when &lt;strong&gt;both&lt;/strong&gt; of these are true:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;baseline-high(feature)  AND  browserslist-targets-all-support(feature)
        →  polyfill is dead weight  →  removal candidate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Baseline says so.&lt;/strong&gt; The feature the polyfill maps to is Baseline &lt;em&gt;Widely
available&lt;/em&gt; in the &lt;code&gt;web-features&lt;/code&gt; dataset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your targets agree.&lt;/strong&gt; Your project's own
&lt;a href="https://browsersl.ist" rel="noopener noreferrer"&gt;&lt;code&gt;browserslist&lt;/code&gt;&lt;/a&gt; targets &lt;em&gt;all&lt;/em&gt; support that feature
natively.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 2 is the important one. "Widely available" is a &lt;em&gt;global&lt;/em&gt; statement about&lt;br&gt;
the web. But if &lt;em&gt;your&lt;/em&gt; &lt;code&gt;browserslist&lt;/code&gt; still targets &lt;code&gt;ie 11&lt;/code&gt; or an ancient&lt;br&gt;
Safari, the polyfill is still doing real work — so the tool keeps it.&lt;/p&gt;

&lt;p&gt;The guiding principle is &lt;strong&gt;"when in doubt, don't remove."&lt;/strong&gt; A missed removal is&lt;br&gt;
harmless (you keep a dependency a little longer). A wrong removal breaks&lt;br&gt;
someone's build. The whole design is biased toward the safe mistake.&lt;/p&gt;
&lt;h3&gt;
  
  
  How it maps polyfills to features
&lt;/h3&gt;

&lt;p&gt;A hand-curated registry maps ~30 well-known polyfills (the &lt;code&gt;es-shims&lt;/code&gt;&lt;br&gt;
ECMAScript ponyfills, plus DOM/CSS shims like &lt;code&gt;intersection-observer&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;dialog-polyfill&lt;/code&gt;, &lt;code&gt;whatwg-fetch&lt;/code&gt;, and &lt;code&gt;@ungap/structured-clone&lt;/code&gt;) to their&lt;br&gt;
&lt;code&gt;web-features&lt;/code&gt; ids.&lt;/p&gt;

&lt;p&gt;Crucially, &lt;strong&gt;every id in the registry is checked against the live dataset in&lt;br&gt;
CI.&lt;/strong&gt; If a feature gets renamed or someone fat-fingers an id, the build fails —&lt;br&gt;
instead of silently mis-flagging your dependency. It's quality over quantity by&lt;br&gt;
design.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;
&lt;h3&gt;
  
  
  As a CLI
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;baseline-prune &lt;span class="nt"&gt;--diff&lt;/span&gt;           &lt;span class="c"&gt;# report removable polyfills (default, no writes)&lt;/span&gt;
baseline-prune &lt;span class="nt"&gt;--fix&lt;/span&gt;            &lt;span class="c"&gt;# remove them from package.json + list import sites&lt;/span&gt;
baseline-prune &lt;span class="nt"&gt;--json&lt;/span&gt;           &lt;span class="c"&gt;# machine-readable report&lt;/span&gt;
baseline-prune &lt;span class="nt"&gt;--cwd&lt;/span&gt; ./app      &lt;span class="c"&gt;# target a specific project root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A dry run looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;would remove  array.prototype.flat  — array-flat is Baseline Widely available (since 2022-07-15)

✓ 1 removable polyfill(s) found.
   Run with --fix to apply.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--fix&lt;/code&gt; removes the dependency from &lt;code&gt;package.json&lt;/code&gt; — preserving your&lt;br&gt;
indentation, key order, and trailing newline — and then hands you a checklist of&lt;br&gt;
every import site that still references it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;removed  array.prototype.flat  — array-flat is Baseline Widely available (since 2022-07-15)

⚠  2 import site(s) still reference the removed package(s).
   Remove these manually, then run your build/tests:
   - src/list.js:2  (array.prototype.flat)
   - src/list.js:8  (array.prototype.flat)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note what &lt;code&gt;--fix&lt;/code&gt; &lt;em&gt;doesn't&lt;/em&gt; do: it never rewrites your source code. Automatic&lt;br&gt;
import-site rewriting (an AST codemod) is deliberately out of scope. A wrong&lt;br&gt;
edit to your source breaks builds and burns trust — so the tool edits the&lt;br&gt;
manifest and lets &lt;em&gt;you&lt;/em&gt; handle the import lines.&lt;/p&gt;
&lt;h3&gt;
  
  
  As a GitHub Action (the point)
&lt;/h3&gt;

&lt;p&gt;The real value is automation. A polyfill becomes removable on the day Baseline&lt;br&gt;
flips, and no human remembers to re-check. So put it on a schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/baseline-prune.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Baseline polyfill prune&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;9&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt; &lt;span class="c1"&gt;# Mondays&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mk668a/baseline-polyfill-pruner@v1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every Monday, the Action runs the engine, applies &lt;code&gt;--fix&lt;/code&gt;, and — if it finds&lt;br&gt;
anything — opens a PR titled &lt;em&gt;"Drop N polyfills Baseline reports as widely&lt;br&gt;
available"&lt;/em&gt; with a rationale table and the import-site checklist in the body.&lt;br&gt;
You review it and merge with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this didn't already exist
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;The gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;eslint-plugin-depend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lints for redundant deps&lt;/td&gt;
&lt;td&gt;Lint-only, no autofix, no Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;module-replacements-codemods&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Codemods deps → native&lt;/td&gt;
&lt;td&gt;Doesn't key off Baseline status&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Renovate / Dependabot&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;Bump&lt;/em&gt; versions&lt;/td&gt;
&lt;td&gt;Never say "this dep is removable"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each piece existed. The combination — &lt;strong&gt;a live Baseline trigger plus an&lt;br&gt;
automatic removal PR&lt;/strong&gt; — is the thing none of them do. That combination is the&lt;br&gt;
whole idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  One design note worth stealing
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;baseline-polyfill-pruner&lt;/code&gt; uses &lt;strong&gt;no LLM at runtime.&lt;/strong&gt; &lt;code&gt;web-features&lt;/code&gt; is a&lt;br&gt;
static, versioned dataset, and the removal decision is deterministic date&lt;br&gt;
arithmetic plus a &lt;code&gt;browserslist&lt;/code&gt; check. AI helped &lt;em&gt;build&lt;/em&gt; the tool, but the&lt;br&gt;
shipped binary is boring, predictable, and offline — exactly what you want from&lt;br&gt;
something that edits your &lt;code&gt;package.json&lt;/code&gt; on a cron schedule.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;code&gt;baseline-polyfill-pruner&lt;/code&gt; is MIT-licensed. Source:&lt;br&gt;
&lt;a href="https://github.com/mk668a/baseline-polyfill-pruner" rel="noopener noreferrer"&gt;github.com/mk668a/baseline-polyfill-pruner&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>react</category>
      <category>typescript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>I built GhostType: inline AI text completion for every app on macOS</title>
      <dc:creator>mk668a</dc:creator>
      <pubDate>Fri, 15 May 2026 04:37:21 +0000</pubDate>
      <link>https://dev.to/mk668a/i-built-ghosttype-copilot-style-ghost-text-completion-for-every-app-on-macos-1o16</link>
      <guid>https://dev.to/mk668a/i-built-ghosttype-copilot-style-ghost-text-completion-for-every-app-on-macos-1o16</guid>
      <description>&lt;p&gt;Writing the same kind of sentences over and over — meeting replies, status updates, polite refusals — eats a surprising amount of the day. LLMs can finish those sentences for me, but only if I copy-paste into a chat window. By the time I've done that, I could have just typed the thing.&lt;/p&gt;

&lt;p&gt;I wanted the LLM to be &lt;strong&gt;right where I'm already typing&lt;/strong&gt; — in Gmail, in Notes, in the X compose box — and I wanted it to run on my own machine. So I built &lt;a href="https://github.com/mk668a/GhostType" rel="noopener noreferrer"&gt;&lt;strong&gt;GhostType&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faunjgz0iaqc2h92nccjc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faunjgz0iaqc2h92nccjc.png" alt="GhostType in Gmail"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;GhostType is a menu bar app for macOS 14+ that watches the text field you're typing in, sends the surrounding context to a &lt;strong&gt;local LLM server&lt;/strong&gt; (LM Studio, Ollama, llama.cpp, vLLM — anything OpenAI-compatible), and shows the completion as translucent ghost text right at the cursor. &lt;strong&gt;Tab&lt;/strong&gt; to accept, &lt;strong&gt;Esc&lt;/strong&gt; to dismiss.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You type:  "The meeting covered "
           (pause)
GhostType: "The meeting covered quarterly sales targets and the product roadmap"
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                 Ghost text — press Tab to accept
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works in Safari, Notes, Pages, Mail, and most native macOS apps via the Accessibility API. For apps where AX doesn't expose text (Slack, Discord, some Electron apps), there's a manual trigger (&lt;code&gt;Option + \&lt;/code&gt;) that uses an internal keystroke buffer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local-only
&lt;/h2&gt;

&lt;p&gt;Three reasons I didn't want this to be a cloud product:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; Every email draft, every DM, every half-written idea would otherwise hit someone else's server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency.&lt;/strong&gt; A round trip to the cloud is fine for chat, not great for completion-as-you-type.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; Local inference on Apple Silicon is free. I run a 3B model on an M2 and it's snappy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Recommended models that work well at this size:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen2.5-Coder-3B&lt;/td&gt;
&lt;td&gt;~2 GB&lt;/td&gt;
&lt;td&gt;Best all-rounder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek-Coder-V2-Lite&lt;/td&gt;
&lt;td&gt;~2 GB&lt;/td&gt;
&lt;td&gt;FIM-tuned, high quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeGemma-2B&lt;/td&gt;
&lt;td&gt;~1.5 GB&lt;/td&gt;
&lt;td&gt;Lowest latency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NSEvent.addGlobalMonitorForEvents&lt;/code&gt;&lt;/strong&gt; — watches keystrokes system-wide (requires Input Monitoring permission)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility API (&lt;code&gt;AXUIElement&lt;/code&gt;)&lt;/strong&gt; — reads the surrounding text from the focused field and writes back the accepted completion (requires Accessibility permission)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overlay &lt;code&gt;NSWindow&lt;/code&gt;&lt;/strong&gt; — a borderless, click-through window draws the ghost text at the caret position&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI-compatible client&lt;/strong&gt; — talks to whatever local server you're running on &lt;code&gt;127.0.0.1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is Swift, about 6 source files of core logic. No background daemons, no kernel extensions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fezft2djm2ipqg9f9ci7j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fezft2djm2ipqg9f9ci7j.png" alt="GhostType on X"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://lmstudio.ai" rel="noopener noreferrer"&gt;LM Studio&lt;/a&gt; or &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; and pull a model&lt;/li&gt;
&lt;li&gt;Download &lt;code&gt;GhostType-0.1.0.dmg&lt;/code&gt; from the &lt;a href="https://github.com/mk668a/GhostType/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Grant Input Monitoring + Accessibility permissions&lt;/li&gt;
&lt;li&gt;Start typing in any text field&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Requirements: macOS 14+, Apple Silicon recommended, 8 GB RAM minimum.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;This is v0.1.0. Stuff I'd like to tackle next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better context extraction for web inputs (currently falls back to keystroke buffer)&lt;/li&gt;
&lt;li&gt;Per-app prompt presets (email tone vs. code comments vs. casual chat)&lt;/li&gt;
&lt;li&gt;Multi-suggestion popup (cycle through alternatives)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Source is on GitHub under PolyForm Noncommercial: &lt;strong&gt;&lt;a href="https://github.com/mk668a/GhostType" rel="noopener noreferrer"&gt;github.com/mk668a/GhostType&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy to hear feedback, especially from people running it with non-default models or unusual local server setups. What would you want it to do that it doesn't?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>swift</category>
      <category>software</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Code Map: Visualize Code Dependencies with LLM</title>
      <dc:creator>mk668a</dc:creator>
      <pubDate>Mon, 24 Nov 2025 12:40:57 +0000</pubDate>
      <link>https://dev.to/mk668a/llm-code-map-visualize-typescriptjavascript-dependencies-empower-ai-agents-4bng</link>
      <guid>https://dev.to/mk668a/llm-code-map-visualize-typescriptjavascript-dependencies-empower-ai-agents-4bng</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm469c0aztmng3o5c76x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm469c0aztmng3o5c76x.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repository URL: &lt;a href="https://github.com/mk668a/llm-codemap" rel="noopener noreferrer"&gt;https://github.com/mk668a/llm-codemap&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you ever jumped into a new codebase and felt completely lost in a web of imports and function calls? Or maybe you are working with AI agents and want them to have a better spatial understanding of your project structure?&lt;/p&gt;

&lt;p&gt;I'm excited to share a project I've been working on called &lt;strong&gt;LLM Code Map&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is a VSCode extension designed to graphically visualize code dependencies and structure for TypeScript and JavaScript projects. But it goes a step further than just being a visualizer—it is built to work seamlessly with &lt;strong&gt;AI Agents&lt;/strong&gt; via the Language Model Tool API.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 What is LLM Code Map?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;LLM Code Map&lt;/strong&gt; analyzes your source code and renders an interactive graph in the VSCode sidebar. It helps you see not just file dependencies, but granular connections between functions, classes, and methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Granular Analysis:&lt;/strong&gt; It doesn't just look at files; it analyzes dependencies at the function, class, and method levels.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Interactive Visualization:&lt;/strong&gt; Powered by &lt;strong&gt;D3.js&lt;/strong&gt;, the graph is interactive, zoomable, and fixed conveniently in your sidebar.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AI Agent Integration:&lt;/strong&gt; This is the game-changer. It supports the &lt;strong&gt;Language Model Tool API&lt;/strong&gt;, meaning it can be automatically invoked by AI agents within VSCode.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🤖 The "Killer Feature": AI Integration
&lt;/h2&gt;

&lt;p&gt;The most exciting part of this extension is its ability to communicate with AI. Because it implements the Language Model Tool API, you can simply chat with a supported AI agent in VSCode and say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Visualize the dependencies of this project"&lt;/em&gt;&lt;br&gt;
&lt;em&gt;"Display the code structure using #codemap"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI will recognize the tool, trigger the analysis, and present the structure to you instantly. This bridges the gap between raw code text and high-level architectural understanding for AI agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ How to Try It Out
&lt;/h2&gt;

&lt;p&gt;The project is open source and currently in the development phase. You can try it out locally right now!&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Start (Development Mode)
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. &lt;strong&gt;Clone the Repository&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mk668a/llm-codemap.git
&lt;span class="nb"&gt;cd &lt;/span&gt;llm-codemap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. &lt;strong&gt;Install Dependencies&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. &lt;strong&gt;Compile&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. &lt;strong&gt;Launch in VSCode&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Open the project folder in VSCode.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;F5&lt;/strong&gt; (or go to the "Run and Debug" panel and select "Run Extension").&lt;/li&gt;
&lt;li&gt;A new VSCode window ("Extension Development Host") will open.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📖 How to Use
&lt;/h2&gt;

&lt;p&gt;Once the extension is running, you have two ways to use it:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Manual Usage (Sidebar)
&lt;/h3&gt;

&lt;p&gt;This is great for exploring the code yourself.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Open the &lt;strong&gt;"LLM Code Map"&lt;/strong&gt; view in the Explorer sidebar.&lt;/li&gt;
&lt;li&gt; Click the &lt;strong&gt;Refresh Button (🔄)&lt;/strong&gt; at the top of the view.&lt;/li&gt;
&lt;li&gt; The extension will analyze your current workspace and display the interactive graph. You can drag nodes and zoom in/out to explore the structure.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Usage via AI Agent
&lt;/h3&gt;

&lt;p&gt;If you are using an AI assistant within VSCode (like Copilot or an agent that supports the Tool API), you can simply ask it to generate the map for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Prompts:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visualize the dependencies of User Class 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Display the code structure using #codemap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI will automatically invoke the tool and render the graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ Under the Hood
&lt;/h2&gt;

&lt;p&gt;For those interested in the tech stack, here is a quick look at the file structure. It uses TypeScript for the core logic and Webviews for the visualization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;codemap/
├── src/
│   ├── extension.ts              # Entry point
│   ├── analyzer/                 # Code analysis logic (AST)
│   ├── visualizer/               # D3.js Graph visualization
│   ├── tools/                    # Language Model Tool API implementation
│   └── utils/                    # Utilities
├── media/                        # Webview resources (HTML/CSS/JS)
└── out/                          # Compiled files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🤝 Contributing
&lt;/h2&gt;

&lt;p&gt;This project is open source (MIT License). I am actively looking for feedback, bug reports, and contributions.&lt;/p&gt;

&lt;p&gt;If you are interested in AST analysis, data visualization with D3, or VSCode extension development, check out the repository.&lt;/p&gt;

&lt;p&gt;Repository URL: &lt;a href="https://github.com/mk668a/llm-codemap" rel="noopener noreferrer"&gt;https://github.com/mk668a/llm-codemap&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find this tool interesting, please give it a star ⭐️ on GitHub!&lt;/p&gt;

&lt;p&gt;Happy Coding!&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>ai</category>
    </item>
    <item>
      <title>What is Zero-Runtime CSS in JS? Which Library Should You Pick?</title>
      <dc:creator>mk668a</dc:creator>
      <pubDate>Sun, 29 Oct 2023 15:46:31 +0000</pubDate>
      <link>https://dev.to/mk668a/what-is-zero-runtime-css-in-js-which-library-should-you-pick-3npf</link>
      <guid>https://dev.to/mk668a/what-is-zero-runtime-css-in-js-which-library-should-you-pick-3npf</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: The End of CSS in JS and the Transition to Zero-Runtime
&lt;/h2&gt;

&lt;p&gt;Front-end technology is changing rapidly.&lt;/p&gt;

&lt;p&gt;In the midst of such change, React is leading the way among front-end frameworks, and in terms of CSS, CSS in JS is becoming mainstream.&lt;/p&gt;

&lt;p&gt;On a side note, I used to worry about naming and designing CSS, using CSS Lint, and other aspects of efficient CSS management. However, I began to use component libraries like emotion for CSS in JS and MUI frequently, which made me stop thinking about CSS-related matters and made development easier. In recent projects, there are situations where there are no css or scss files present within the project.&lt;/p&gt;

&lt;p&gt;However, believe it or not, CSS in JS is already being called outdated! I was curious about why it was being called outdated and found the following article:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/srmagura" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F710804%2F8be2ddc2-d75f-47fc-a72c-ff8242808815.jpg" alt="srmagura"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/srmagura/why-were-breaking-up-wiht-css-in-js-4g9b" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Why We're Breaking Up with CSS-in-JS&lt;/h2&gt;
      &lt;h3&gt;Sam Magura ・ Oct 16 '22&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#react&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#css&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#typescript&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;From here, I'll explain the contents of the above article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why We're Breaking Up with CSS-in-JS
&lt;/h3&gt;

&lt;p&gt;Sam Magura is known as the second most active maintainer of the CSS-in-JS library, Emotion. From his experience and knowledge, he points out certain issues with using CSS-in-JS. He initially was captivated by the benefits of CSS-in-JS, but it seems he began to feel its limitations through performance issues and other challenges faced while using it with the Spot team.&lt;/p&gt;

&lt;h4&gt;
  
  
  Drawbacks of CSS in JS
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Runtime Overhead&lt;/strong&gt;: CSS-in-JS requires converting the style into plain CSS to insert it into the document when a component is rendered. This conversion process demands additional CPU cycles. This overhead can potentially impact the performance of the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Increased Bundle Size&lt;/strong&gt;: Since users visiting a site need to download the JavaScript of the CSS-in-JS library, their bundle size increases. For example, Emotion is 7.9 kB (minzipped) and styled-components are 12.7 kB. While these libraries aren't massive, they do add to the overall size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluttered React DevTools&lt;/strong&gt;: Libraries like Emotion insert internal components into the React tree. This can make React DevTools cluttered, potentially complicating debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additional Browser Work&lt;/strong&gt;: Frequently inserting CSS rules places more workload on the browser. Especially in React's concurrent rendering mode, when a new rule is inserted, the browser has to verify whether or not that rule applies to the existing tree. This results in a style recalculation for all CSS rules and all DOM nodes while React is rendering, which can be extremely slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatibility with SSR and Component Libraries&lt;/strong&gt;: When using Emotion with server-side rendering or other component libraries that use Emotion, various issues can arise. These include multiple loads of the Emotion instance, not being able to fully control the order of style insertion, and differing SSR support for Emotion between React 17 and React 18.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  The Future of CSS Libraries
&lt;/h4&gt;

&lt;p&gt;As new styling approaches like CSS-in-JS emerge, traditional CSS libraries and frameworks continue to evolve. Preprocessors like Sass and Less provide features such as variables and mixins, making CSS writing more efficient.&lt;/p&gt;

&lt;p&gt;Additionally, utility-first CSS frameworks like Tailwind CSS are gaining popularity. They allow rapid design construction by combining class names.&lt;/p&gt;

&lt;p&gt;Considering the issues with CSS-in-JS, it's anticipated that traditional CSS libraries and the new utility-first approach will play a significant role in future web development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transition to Zero-Runtime CSS in JS
&lt;/h3&gt;

&lt;p&gt;As mentioned in the previous article, there seems to be a problem with the occurrence of overhead. This overhead can decrease the overall page load and rendering speeds. This led to the introduction of zero-runtime CSS in JS.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Zero-Runtime CSS in JS?
&lt;/h2&gt;

&lt;p&gt;Zero-runtime CSS in JS refers to the technique of writing CSS inside JavaScript and generating actual CSS files at build time. Compared to regular CSS in JS, its advantage is that it can reduce the execution time cost of JavaScript while retaining the convenience of writing CSS in JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Zero-Runtime CSS in JS
&lt;/h2&gt;

&lt;p&gt;Zero-runtime CSS in JS is an approach that doesn't dynamically generate and inject CSS during runtime. All styles are generated at build time, which means there's no additional JavaScript overhead during runtime.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: The zero-runtime approach has the potential to improve page load and rendering speeds because there's no overhead from dynamically generating and injecting styles during runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictability&lt;/strong&gt;: Since styles are generated at build time, there's a reduced risk of unexpected style changes or side effects during runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smaller Bundle Size&lt;/strong&gt;: Some zero-runtime libraries can reduce the final bundle size by eliminating unnecessary runtime code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Server-Side Rendering (SSR)&lt;/strong&gt;: Some CSS-in-JS solutions require additional configurations or steps during server-side rendering, but the zero-runtime approach can alleviate or eliminate this issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advantages of Static Analysis&lt;/strong&gt;: With styles generated at build time, tools and linters can more easily perform static analysis of the styles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environmental Compatibility&lt;/strong&gt;: In some environments or frameworks, dynamically injecting styles during runtime can be challenging or not recommended. The zero-runtime approach works seamlessly in these situations.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Using CSS in JS with Next.js
&lt;/h3&gt;

&lt;p&gt;The official Next.js documentation lists libraries that can be used within the App Router's app directory. As of the current date (2023/10/30), it appears that they only support usage within client components.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://nextjs.org/docs/app/building-your-application/styling/css-in-js" rel="noopener noreferrer"&gt;
      nextjs.org
    &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  List of Zero-Runtime CSS-in-JS Libraries
&lt;/h2&gt;

&lt;p&gt;After researching the existing zero-runtime CSS-in-JS, here are the libraries that are still actively developed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linaria&lt;/li&gt;
&lt;li&gt;vanilla-extract&lt;/li&gt;
&lt;li&gt;Panda CSS&lt;/li&gt;
&lt;li&gt;Goober&lt;/li&gt;
&lt;li&gt;Astroturf&lt;/li&gt;
&lt;li&gt;Treat&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So, Which CSS-in-JS Library Should You Use?
&lt;/h2&gt;

&lt;p&gt;I plan to compare each of them from various perspectives to determine the best CSS-in-JS solution.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;th&gt;Main Features&lt;/th&gt;
&lt;th&gt;Static CSS Generation&lt;/th&gt;
&lt;th&gt;TypeScript Support&lt;/th&gt;
&lt;th&gt;SSR Support&lt;/th&gt;
&lt;th&gt;GitHub Stars&lt;/th&gt;
&lt;th&gt;Development Period&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linaria&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/callstack/linaria" rel="noopener noreferrer"&gt;Linaria on GitHub&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;CSS is extracted into a CSS file at build time. Compatible with almost all modern frameworks. Can use dynamic styles based on React props (using CSS variables). Easily locate where styles are defined with CSS source maps. Lint CSS in JS with stylelint. Use any CSS pre-processor like Sass or PostCSS if needed. Supports atomic styles with @linaria/atomic. Compared to regular CSS, selectors are scoped, styles are in the same file as components, making refactoring easier. Interoperable with other CSS in JS libraries. Operates without JavaScript.&lt;/td&gt;
&lt;td&gt;Extracts CSS at build time and outputs it as a static CSS file.&lt;/td&gt;
&lt;td&gt;Offers TypeScript support, but the setup might be a bit intricate.&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;10.9k&lt;/td&gt;
&lt;td&gt;2017/5/21 (Latest: 2023/10/3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vanilla-extract&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/vanilla-extract-css/vanilla-extract" rel="noopener noreferrer"&gt;vanilla-extract on GitHub&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Write styles using locally scoped class names and CSS variables, and generate static CSS files at build time. A slight abstraction of standard CSS. Works with any frontend framework/no framework. Locally scoped CSS variables, @keyframes, and @font-face rules. A high-level theme system that supports multiple themes simultaneously without globals. Utilities for generating variable-based calc expressions. Type-safe styles via CSSType. Optional runtime version for development and testing. Optional API for dynamic runtime theming.&lt;/td&gt;
&lt;td&gt;Generates static CSS at build time.&lt;/td&gt;
&lt;td&gt;Provides type-safe styling with TypeScript.&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;8.8k&lt;/td&gt;
&lt;td&gt;2021/2/21 (Latest: 2023/9/16)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Panda CSS&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/chakra-ui/panda" rel="noopener noreferrer"&gt;Panda CSS on GitHub&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Extracts style objects and style props at build time. Modern CSS output - cascading layers @layer, CSS variables, etc. Compatible with most JavaScript frameworks. Supports recipes and variants like stitches (&lt;a href="https://stitches.dev/" rel="noopener noreferrer"&gt;stitches.dev&lt;/a&gt;). A high-level design token system that supports multiple themes simultaneously. Type-safe styles and autocomplete through code generation. Inspired by projects like Chakra UI, Vanilla Extract, Stitches, Tailwind CSS, Styled System, etc.&lt;/td&gt;
&lt;td&gt;Generates static CSS at build time.&lt;/td&gt;
&lt;td&gt;Provides type-safe styling with TypeScript.&lt;/td&gt;
&lt;td&gt;Supports SSG and SSR.&lt;/td&gt;
&lt;td&gt;3.7k&lt;/td&gt;
&lt;td&gt;2022/1/24 (Latest: 2023/10/29)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goober&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/cristianbote/goober" rel="noopener noreferrer"&gt;Goober on GitHub&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Less than 1KB footprint. Developed with the intent of achieving the existing styled pattern with a smaller footprint. Supports the styled pattern similar to styled-components and emotion. Faster in SSR benchmarks compared to other major CSS-in-JS libraries. Goober is compatible with vanilla JavaScript, and frameworks like React, Vue.js, Angular, Svelte, etc. Integration with tools like Babel, Next.js, Gatsby, etc., is easy through plugins and macros. Supports features like shared styles, automatic prefixing, etc. Supports major browsers and older browsers can be supported using Babel.&lt;/td&gt;
&lt;td&gt;Uses the extractCss function to extract static CSS at build time and injects it into the  tag. Mainly used during server-side rendering.&lt;/td&gt;
&lt;td&gt;Information about explicit TypeScript support is limited.&lt;/td&gt;
&lt;td&gt;Supports SSR, provides extractCss function.&lt;/td&gt;
&lt;td&gt;3k&lt;/td&gt;
&lt;td&gt;2019/1/27 (Latest: 2023/4/19)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Astroturf&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/astroturfcss/astroturf" rel="noopener noreferrer"&gt;Astroturf on GitHub&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Write CSS inside JavaScript files, but operates without adding a runtime layer and works alongside the existing CSS processing pipeline. Avoids the loss of flexibility requiring framework-specific CSS handling and keeps CSS fully static without parsing runtime styles. Can write style definitions inside JavaScript files while using Sass, PostCSS, Less, etc. Enjoys the benefits of styling within JavaScript without runtime overhead while maintaining compatibility with existing CSS tools. Compatible with frameworks and supports React's props feature.&lt;/td&gt;
&lt;td&gt;Generates static CSS without adding a runtime layer and works with the existing CSS processing pipeline.&lt;/td&gt;
&lt;td&gt;Provides TypeScript support, but might require third-party libraries.&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;2.2k&lt;/td&gt;
&lt;td&gt;2016/10/16 (Latest: 2023/5/17)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Treat&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/seek-oss/treat" rel="noopener noreferrer"&gt;Treat on GitHub&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A framework-agnostic library. Optimizes bundle size with theme support and lightweight runtime style definition via static extraction. Supports webpack, React, and TypeScript. Supports legacy browsers.&lt;/td&gt;
&lt;td&gt;Generates all CSS rules at build time and bundles only the generated CSS styles.&lt;/td&gt;
&lt;td&gt;Provides type-safe styling with TypeScript.&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;1.2k&lt;/td&gt;
&lt;td&gt;2019/5/12 (Latest: 2021/4/28)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I am planning to select the best CSS in JS based on the following perspectives:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Library Maintenance&lt;/strong&gt;&lt;br&gt;
Treat hasn't been updated since 2021, suggesting there might be potential issues in its ongoing maintenance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static CSS Generation&lt;/strong&gt;&lt;br&gt;
While there are differences in the process of static CSS generation, every library produces static CSS at build time, reducing runtime overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript Support&lt;/strong&gt;&lt;br&gt;
vanilla-extract, Panda CSS, and Treat all offer problem-free type-safe styling. For other libraries, there might be a need for additional libraries or more complicated configurations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSR Support&lt;/strong&gt;&lt;br&gt;
Panda CSS and Goober support SSR. For the other libraries, there's insufficient information to confirm if they definitely support SSR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;br&gt;
Based on various articles, there doesn't seem to be any difference in performance, like rendering time, among the libraries. As they're converted into static CSS, there doesn't seem to be much variance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Style Writing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linaria
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@linaria/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;modularScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hiDPI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;polished&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fonts&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fonts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Write your styles in `css` tag&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
  text-transform: uppercase;
  font-family: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;
  font-size: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;modularScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;;

  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;hiDPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; {
    font-size: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;modularScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;;
  }
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Then use it as a class name&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt; &lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@linaria/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;families&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sizes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fonts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Write your styles in `styled` tag&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="s2"&gt;`
  font-family: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;families&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serif&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="s2"&gt;`
  font-size: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px;
  color: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;
  border: 1px solid red;

  &amp;amp;:hover {
    border-color: blue;
  }

  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; {
    margin-bottom: 24px;
  }
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Then use the resulting component&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#333&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt; &lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Container&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;vanilla-extract
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// styles.css.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vanilla-extract/css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;themeClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTheme&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arial&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exampleStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fontFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;themeClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exampleStyle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./styles.css.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;section class="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;themeClass&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
    &amp;lt;h1 class="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exampleStyle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;Hello world!&amp;lt;/h1&amp;gt;
  &amp;lt;/section&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Panda CSS
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../styled-system/css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vstack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hstack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../styled-system/patterns&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;hstack&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pink.300&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Box&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red.400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Box&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../styled-system/css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../styled-system/jsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// The className approach&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
    &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue.500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;px&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;rounded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;})}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// The style props approach&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue.500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;py&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;rounded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/styled.button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Goober
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;goober&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Should be called here, and just once&lt;/span&gt;
&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Icon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;span&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;`
    display: flex;
    flex: 1;
    color: red;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;`
    background: dodgerblue;
    color: white;
    border: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;px solid white;

    &amp;amp;:focus,
    &amp;amp;:hover {
        padding: 1em;
    }

    .otherClass {
        margin: 0;
    }

    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; {
        color: black;
    }
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;`
    border-radius: &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// This also works!&lt;/span&gt;

&lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;`
    border-radius: &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Astroturf
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astroturf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
      &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
        color: blue;
        border: 1px solid blue;
        padding: 0 1rem;
      `&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Treat
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Button.treat.js&lt;/span&gt;
&lt;span class="c1"&gt;// ** THIS CODE WON'T END UP IN YOUR BUNDLE EITHER! **&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;treat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brandColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Button.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useStyles&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-treat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;styleRefs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Button.treat.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStyles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;styleRefs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Selection: Panda CSS
&lt;/h3&gt;

&lt;p&gt;After considering various perspectives, I believe that &lt;strong&gt;Panda CSS&lt;/strong&gt; is the overall best choice. However, its style of writing is unique, so if you prefer the writing style of libraries like styled-components or emotion, then Goober might be more to your liking. Additionally, Panda CSS has been developed drawing inspiration from projects like Chakra UI, Vanilla Extract, Stitches, Tailwind CSS, and Styled System. Given that it's a relatively new library, it seems to have effectively incorporated the best features from established libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  (Supplementary Information) Zero-runtime for Headless Components is Emerging
&lt;/h2&gt;

&lt;p&gt;The following Kuma UI provides a hybrid approach with headless components where styles can be described in props and className, making it easy to implement.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Key Features&lt;/th&gt;
&lt;th&gt;GitHub Stars&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Kuma UI&lt;/td&gt;
&lt;td&gt;Achieves a seamless development experience with automatic style completion. All components within the library are style-free, offering the utmost flexibility for users to apply their own styles. Supports any description style with a hybrid approach. Constantly up-to-date with cutting-edge Next.js technology through RSC support. Familiar API design. Extracts styles that can be determined at build time statically, and adopts a method to inject them at runtime by performing a static "dirty check" for styles that might change dynamically. Incorporates the best features inspired by libraries such as Styled System, Chakra UI, Native Base, Panda CSS, Linaria, Vanilla Extract, and more.&lt;/td&gt;
&lt;td&gt;1.3k&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/kuma-ui/kuma-ui" rel="noopener noreferrer"&gt;Kuma UI GitHub&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Style Writing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kuma UI
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Box&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;flexDir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;column&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;row&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Heading&lt;/span&gt;
        &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
          color: red;
          @media (max-width: sm) {
            color: blue;
          }
        `&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Kuma&lt;/span&gt; &lt;span class="nx"&gt;UI&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Heading&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Spacer&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Flex&lt;/span&gt; &lt;span class="nx"&gt;flexDir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`column`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nx"&gt;Headless&lt;/span&gt; &lt;span class="nx"&gt;UI&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="nx"&gt;Library&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Getting&lt;/span&gt; &lt;span class="nx"&gt;Started&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Flex&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Box&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we explored various zero-runtime CSS in JS libraries and tried to determine the best one. Depending on your priorities, such as emphasizing lightweight performance with Goober, your library choice may differ. It's essential to choose a library that suits your project based on each library's unique features. Thank you for reading this far.&lt;/p&gt;

</description>
      <category>css</category>
      <category>typescript</category>
      <category>react</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Chrome Extension with React + CRXJS + Vite + Docker</title>
      <dc:creator>mk668a</dc:creator>
      <pubDate>Tue, 09 May 2023 12:42:36 +0000</pubDate>
      <link>https://dev.to/mk668a/chrome-extension-with-react-crxjs-vite-docker-3pm8</link>
      <guid>https://dev.to/mk668a/chrome-extension-with-react-crxjs-vite-docker-3pm8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you are trying to develop a chrome extension in react, CRXJ is very very useful.&lt;/p&gt;

&lt;p&gt;CRXJS provides speedy extension development experience with vite.&lt;/p&gt;

&lt;p&gt;You can write the file name in manifest.json, and each file update is reflected instantly because the build directory directly references the file you are editing.&lt;/p&gt;

&lt;p&gt;Let's take an example of the actual development process.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Repository
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mk668a/react-vite-crxjs-chrome-boilerplate" rel="noopener noreferrer"&gt;mk668a/react-vite-crxjs-chrome-boilerplate&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Directory structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── Dockerfile
├── docker-compose.yml
├── index.html
├── manifest.config.ts
├── manifest.json
├── options.html
├── package.json
├── public
│&amp;nbsp;&amp;nbsp; └── vite.svg
├── src
│&amp;nbsp;&amp;nbsp; ├── assets
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── favicon.svg
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── logo.svg
│&amp;nbsp;&amp;nbsp; ├── background.ts
│&amp;nbsp;&amp;nbsp; ├── components
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── Button.tsx
│&amp;nbsp;&amp;nbsp; ├── content_scripts
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── content_script.tsx
│&amp;nbsp;&amp;nbsp; ├── options.tsx
│&amp;nbsp;&amp;nbsp; ├── popup.tsx
│&amp;nbsp;&amp;nbsp; └── vite-env.d.ts
├── tsconfig.json
└── vite.config.ts

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a project with React + CRXJS + Vite
&lt;/h2&gt;

&lt;p&gt;Proceed by referring to the CRXJS documentation.&lt;/p&gt;

&lt;p&gt;I have rewritten the steps in this document, modified to fit my environment.&lt;br&gt;
&lt;a href="https://crxjs.dev/vite-plugin/getting-started/react/create-project" rel="noopener noreferrer"&gt;Create a project | CRXJS Vite Plugin&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a project
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npm init vite@latest&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm init vite@latest
Need to &lt;span class="nb"&gt;install &lt;/span&gt;the following packages:
  create-vite@4.3.1
Ok to proceed? &lt;span class="o"&gt;(&lt;/span&gt;y&lt;span class="o"&gt;)&lt;/span&gt; y
✔ Project name: … vite-project
✔ Select a framework: › React
✔ Select a variant: › TypeScript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install CRXJS Vite plugin
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npm i @crxjs/vite-plugin@beta -D&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Install SVGR Vite plugin (Option)
&lt;/h3&gt;

&lt;p&gt;If you want to use svg with React components, install &lt;a href="https://github.com/pd4d10/vite-plugin-svgr" rel="noopener noreferrer"&gt;vite-plugin-svgr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm i vite-plugin-svgr&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Change &lt;code&gt;vite-env.d.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// &amp;lt;reference types="vite/client" /&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;reference types="vite-plugin-svgr/client" /&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update the Vite config
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;crx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ManifestV3Export&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@crxjs/vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./manifest.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;svgr&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite-plugin-svgr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;svgr&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nf"&gt;crx&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ManifestV3Export&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;manifest.json&lt;/code&gt; in the root directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Extension App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open Extension App"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Merge tsconfig(Option)
&lt;/h3&gt;

&lt;p&gt;For simplicity, I merged &lt;code&gt;tsconfig.node.json&lt;/code&gt; into &lt;code&gt;tsconfig.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"composite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"allowSyntheticDefaultImports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"useDefineForClassFields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DOM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DOM.Iterable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"allowJs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"forceConsistentCasingInFileNames"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"resolveJsonModule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"isolatedModules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"noEmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"jsx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"react-jsx"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite.config.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*.json"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start project
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npm run dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;Manage Extensions&lt;/code&gt; page in your browser.&lt;br&gt;
&lt;a&gt;chrome://extensions/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Turn on &lt;code&gt;dveloper mode&lt;/code&gt; switch in the upper right corner.&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Load unpacked&lt;/code&gt; button in the upper left corner and select the &lt;code&gt;dist&lt;/code&gt; directory in your project root directory.&lt;/p&gt;
&lt;h3&gt;
  
  
  Build project
&lt;/h3&gt;

&lt;p&gt;This project must be built if it is to be actually used and uploaded.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create Dockerfile
&lt;/h2&gt;

&lt;p&gt;Build Docker to quickly create an environment ready to start development.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18.15.0-alpine3.16&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extension&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;extension&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;extension&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;5173:5173&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/usr/src/app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn dev --host&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am using an M1 MacBook, so I have written &lt;code&gt;platform: linux/amd64&lt;/code&gt; in the &lt;code&gt;docker-compose.yml&lt;/code&gt; file and turned on &lt;code&gt;Use Rosetta for x86/amd64 emulation of Apple Silicon&lt;/code&gt; of Docker setting is turned on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run Docker
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;docker compose up -d --build&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixed Popup
&lt;/h2&gt;

&lt;p&gt;This is a bit confusing because the file name and function do not match, so we will modify it a bit.&lt;/p&gt;

&lt;p&gt;Delete &lt;code&gt;App.tx&lt;/code&gt; and rename &lt;code&gt;main.tx&lt;/code&gt; to &lt;code&gt;popup.tx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Modify &lt;code&gt;popup.tsx&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;logo&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./assets/logo.svg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Popup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;
                    &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logo&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
                    &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App-logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt; &lt;span class="nx"&gt;Vite&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nx"&gt;Edit&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/code&amp;gt; and save to test HMR updates&lt;/span&gt;&lt;span class="err"&gt;.
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
                        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App-link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://reactjs.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noopener noreferrer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nx"&gt;Learn&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;
                        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App-link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://vitejs.dev/guide/features.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                        &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noopener noreferrer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nx"&gt;Vite&lt;/span&gt; &lt;span class="nx"&gt;Docs&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/header&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StrictMode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Popup&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/React.StrictMode&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix &lt;code&gt;src&lt;/code&gt; attribute of script tag in &lt;code&gt;index.html&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/src/popup.tsx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Content Scripts
&lt;/h2&gt;

&lt;p&gt;Add &lt;code&gt;content_scripts&lt;/code&gt; section in &lt;code&gt;manifest.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Extension App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open Extension App"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content_scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;all_urls&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/content_scripts/content_script.tsx"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make &lt;code&gt;content_scripts&lt;/code&gt; directory in &lt;code&gt;src&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Create sample content_script component in &lt;code&gt;src/content_scripts/content_script.tsx&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/Button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ContentScript&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;ContentScript&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/header&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StrictMode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ContentScript&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/React.StrictMode&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the same time, create a &lt;code&gt;component&lt;/code&gt; directory and &lt;code&gt;Button.tsx&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Background
&lt;/h2&gt;

&lt;p&gt;Add a definition of &lt;code&gt;background&lt;/code&gt; to &lt;code&gt;manifest.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note that if you want to use certain features of the chrome API, it is necessary to add some permissions to &lt;code&gt;permissions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.chrome.com/docs/extensions/mv3/declare_permissions/" rel="noopener noreferrer"&gt;Chrome Extensions Declare permissions&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Extension App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open Extension App"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content_scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;all_urls&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/content_scripts/content_script.tsx"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"service_worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/background.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"contextMenus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"bookmarks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"tabs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"history"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make &lt;code&gt;background.ts&lt;/code&gt; file in &lt;code&gt;src&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;This is the sapmle code to add event listener of changing tab and to get the bookmarks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onUpdated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tabId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;changeInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Change URL: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bookmarks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`bookmarks:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`this is background service worker`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Options
&lt;/h2&gt;

&lt;p&gt;This is an options page which can be accessed by right-clicking the extension icon on the toolbar and selecting Options.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;options_page&lt;/code&gt; section to &lt;code&gt;manifest.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Extension App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open Extension App"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content_scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;all_urls&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/content_scripts/content_script.tsx"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"service_worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/background.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"options_page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"options.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"contextMenus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"bookmarks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"tabs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"history"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;options.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is almost the same as &lt;code&gt;index.html&lt;/code&gt;, but the &lt;code&gt;src&lt;/code&gt; attribute of the &lt;code&gt;script&lt;/code&gt; tag must be changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/vite.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Extension App&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/src/options.tsx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make &lt;code&gt;options.tsx&lt;/code&gt; file in &lt;code&gt;src&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/Button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`this is options page`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/header&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;options&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StrictMode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Options&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/React.StrictMode&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;CRXJS improves the experience of developing extensions with React.&lt;br&gt;
Also, you can easily set it up using Docker.&lt;/p&gt;

&lt;p&gt;I am now trying to create &lt;a href="https://developer.chrome.com/docs/extensions/mv3/devtools/" rel="noopener noreferrer"&gt;a new panel&lt;/a&gt; in the Developer tool. If it works, I will update this post.&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://crxjs.dev/vite-plugin/" rel="noopener noreferrer"&gt;Introduction | CRXJS Vite Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://crxjs.dev/vite-plugin/getting-started/react/create-project" rel="noopener noreferrer"&gt;Create a project | CRXJS Vite Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pd4d10/vite-plugin-svgr" rel="noopener noreferrer"&gt;vite-plugin-svgr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/extensions/mv3/declare_permissions/" rel="noopener noreferrer"&gt;Chrome Extensions Declare permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mk668a/react-vite-crxjs-chrome-boilerplate" rel="noopener noreferrer"&gt;mk668a/react-vite-crxjs-chrome-boilerplate&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>vite</category>
      <category>react</category>
    </item>
  </channel>
</rss>
