<?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: Fin Chen</title>
    <description>The latest articles on DEV Community by Fin Chen (@finfin).</description>
    <link>https://dev.to/finfin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F388239%2F47a56e8c-983a-48e8-af71-a107363dc44e.jpeg</url>
      <title>DEV Community: Fin Chen</title>
      <link>https://dev.to/finfin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/finfin"/>
    <language>en</language>
    <item>
      <title>MCP Apps: When Interactive UI Grows Inside AI Conversations</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Wed, 25 Mar 2026 22:31:03 +0000</pubDate>
      <link>https://dev.to/finfin/mcp-apps-when-interactive-ui-grows-inside-ai-conversations-30d4</link>
      <guid>https://dev.to/finfin/mcp-apps-when-interactive-ui-grows-inside-ai-conversations-30d4</guid>
      <description>&lt;p&gt;In January 2026, the MCP Apps spec landed. It lets MCP Servers embed interactive HTML UIs — charts, forms, dashboards, maps — directly inside conversations in Claude, VS Code, and other AI hosts.&lt;/p&gt;

&lt;p&gt;As a frontend engineer, the moment I saw "interactive HTML inside AI chat," I was hooked. Text-only tool outputs always felt limiting. MCP Apps close the gap between what the LLM produces and what the user actually sees and touches.&lt;/p&gt;

&lt;p&gt;I spent some time digging into the spec and built &lt;a href="https://github.com/finfin/mermaid-mcp-app" rel="noopener noreferrer"&gt;mermaid-mcp-app&lt;/a&gt; — an interactive Mermaid diagram tool I now use daily. This post walks through MCP Apps using that project as the running example.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Are MCP Apps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Three Roles
&lt;/h3&gt;

&lt;p&gt;The architecture has three actors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MCP Server&lt;/strong&gt; — The backend. Registers tools via &lt;code&gt;registerAppTool&lt;/code&gt; and HTML resources via &lt;code&gt;registerAppResource&lt;/code&gt;. A tool's &lt;code&gt;_meta.ui.resourceUri&lt;/code&gt; field binds it to a specific UI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Host&lt;/strong&gt; — The AI interface — Claude Desktop, VS Code Copilot, etc. When the LLM calls a tool, the Host executes it, fetches the HTML from &lt;code&gt;resourceUri&lt;/code&gt;, and renders it in a sandboxed iframe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;View&lt;/strong&gt; — The frontend app running inside that iframe. It uses the &lt;code&gt;App&lt;/code&gt; class to open a postMessage channel with the Host, receives tool results, and can call server tools or push messages back into the conversation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All View ↔ Server communication goes through the Host through JSON-RPC 2.0 over postMessage.&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%2F4yt5ftec2pdmake9emud.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%2F4yt5ftec2pdmake9emud.png" alt="Architecture diagram of MCP Apps' three roles: communication flow between MCP Server, Host, and View" width="755" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Lifecycle
&lt;/h3&gt;

&lt;p&gt;Four phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Discovery&lt;/strong&gt; — The Host connects to the server, reads the tool list, and flags tools carrying &lt;code&gt;_meta.ui&lt;/code&gt; metadata. It can pre-fetch HTML resources here for caching and security review.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Initialize&lt;/strong&gt; — Once the LLM calls a tool, the Host spins up a sandboxed iframe, loads the HTML, and completes a handshake via &lt;code&gt;ui/initialize&lt;/code&gt;. Both sides exchange capabilities: the View declares which display modes it supports; the Host provides theme, container dimensions, and other context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interactive&lt;/strong&gt; — The Host pushes tool input and tool result into the View, which renders the UI. From here, the View can call server tools via &lt;code&gt;callServerTool&lt;/code&gt;, send messages back to the conversation via &lt;code&gt;sendMessage&lt;/code&gt;, or silently sync state to the LLM via &lt;code&gt;updateModelContext&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Teardown&lt;/strong&gt; — When the conversation ends or the user closes the UI, the Host sends &lt;code&gt;ui/resource-teardown&lt;/code&gt; so the View can clean up.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Interactions
&lt;/h3&gt;

&lt;p&gt;The spec provides two APIs for this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sendMessage&lt;/strong&gt; — Acts as if the user typed something in the chat box. The message shows up in the conversation immediately.&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;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;content&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="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`I modified the diagram:\n&lt;/span&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;&lt;span class="s2"&gt;mermaid\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="se"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;updateModelContext&lt;/strong&gt; — A silent sync. No response is triggered, but the LLM will see the data the next time the user sends a message. Each call overwrites the previous — only the latest snapshot is kept.&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;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateModelContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;content&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="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Current Mermaid source:\n&lt;/span&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;&lt;span class="s2"&gt;mermaid\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="se"&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;/code&gt;&lt;/pre&gt;

&lt;/div&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;sendMessage&lt;/th&gt;
&lt;th&gt;updateModelContext&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Triggers LLM response&lt;/td&gt;
&lt;td&gt;Immediately&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visible in conversation&lt;/td&gt;
&lt;td&gt;Appears as user message&lt;/td&gt;
&lt;td&gt;Hidden&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple calls&lt;/td&gt;
&lt;td&gt;Each one is independent&lt;/td&gt;
&lt;td&gt;Last call wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Completed actions that need the LLM to respond&lt;/td&gt;
&lt;td&gt;Continuous state sync — the user decides when to ask&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These two APIs change what an MCP App fundamentally is. It's not "a container for displaying LLM output" anymore. It's a full interactive component: input goes in, the user acts, output flows back to the LLM.&lt;/p&gt;

&lt;p&gt;E.g. &lt;code&gt;sendMessage&lt;/code&gt; after a user fills out a form to continue with the conversation with LLM. &lt;code&gt;updateModelContext&lt;/code&gt; after the user selects a region on a map, silently syncing coordinates until they ask "what restaurants are nearby?" and LLM knows the context.&lt;/p&gt;

&lt;p&gt;Beyond these two core APIs, the View also has access to &lt;code&gt;callServerTool&lt;/code&gt; (invoke server-side tools), &lt;code&gt;openLink&lt;/code&gt; (ask the Host to open external URLs), &lt;code&gt;downloadFile&lt;/code&gt; (the iframe sandbox blocks direct downloads, so the Host handles it), and &lt;code&gt;requestDisplayMode&lt;/code&gt; (switch between inline, fullscreen, or picture-in-picture).&lt;/p&gt;




&lt;h2&gt;
  
  
  Building One: The Mermaid MCP App
&lt;/h2&gt;

&lt;p&gt;Enough theory. Let me walk through the &lt;code&gt;mermaid-mcp-app&lt;/code&gt; I built — an interactive Mermaid tool that embeds diagrams directly in conversations, with drag-to-pan, scroll-to-zoom, and a split-view editor for live syntax editing.&lt;/p&gt;

&lt;h3&gt;
  
  
  30-Second Setup
&lt;/h3&gt;

&lt;p&gt;First, check out what I built. Add this to your Claude Desktop or VS Code MCP config:&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;"mcpServers"&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;"mermaid"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mermaid-mcp-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--stdio"&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;Restart Claude Desktop and ask it to "draw a user authentication flowchart." The diagram appears inline — draggable, zoomable, with an editor for modifying the syntax on the spot.&lt;/p&gt;

&lt;p&gt;There's also a &lt;a href="https://github.com/finfin/mermaid-mcp-app/releases" rel="noopener noreferrer"&gt;packaged Desktop Extension (&lt;code&gt;.mcpb&lt;/code&gt;) on GitHub Releases&lt;/a&gt;. Double-click to install — no terminal needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Side
&lt;/h3&gt;

&lt;p&gt;The server handles three things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Registers the main tool.&lt;/strong&gt; &lt;code&gt;registerAppTool&lt;/code&gt; defines &lt;code&gt;render-mermaid&lt;/code&gt;, with &lt;code&gt;_meta.ui.resourceUri&lt;/code&gt; binding it to the HTML resource:&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="nf"&gt;registerAppTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;render-mermaid&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;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Render Mermaid Diagram&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The Mermaid diagram syntax to render&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&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;light&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;dark&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;forest&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;neutral&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;_meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resourceUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui://mermaid/view.html&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;code&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;content&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="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Serves the HTML resource.&lt;/strong&gt; &lt;code&gt;registerAppResource&lt;/code&gt; exposes the Vite-bundled single HTML file as a &lt;code&gt;ui://&lt;/code&gt; resource. The server sends data only — all rendering happens client-side inside the iframe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internal tools for draft persistence.&lt;/strong&gt; Two additional tools — &lt;code&gt;save-mermaid-draft&lt;/code&gt; and &lt;code&gt;get-mermaid-draft&lt;/code&gt; — let the View persist user edits to server memory and restore state when the iframe is recreated. These are internal; only the View calls them via &lt;code&gt;callServerTool&lt;/code&gt;, not the LLM.&lt;/p&gt;

&lt;h3&gt;
  
  
  View Side
&lt;/h3&gt;

&lt;p&gt;The View is a standard frontend app. It uses the &lt;code&gt;App&lt;/code&gt; class to set up the postMessage channel:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&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;App&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MermaidViewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&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="na"&gt;autoResize&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="p"&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;ontoolinput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&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="c1"&gt;// LLM calls the tool — Host sends arguments before the server processes them&lt;/span&gt;
  &lt;span class="nf"&gt;handleMermaidData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&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;ontoolresult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&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="c1"&gt;// Server finishes processing — Host sends the full result&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;handleMermaidData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ontoolinput&lt;/code&gt; and &lt;code&gt;ontoolresult&lt;/code&gt; fire at different moments. The former triggers when the LLM decides to call the tool (arguments are ready but the server hasn't processed yet); the latter fires after the server returns. For tools that don't need server-side computation — like mermaid-mcp-app — both carry essentially the same data, so you can use &lt;code&gt;ontoolinput&lt;/code&gt; for early rendering. For tools that do compute on the server, the two payloads will differ.&lt;/p&gt;

&lt;p&gt;There's also &lt;code&gt;ontoolinputpartial&lt;/code&gt;. As the LLM streams tool arguments, the Host patches the incomplete JSON into a valid shape and pushes it to the View. Not useful for Mermaid (half-written syntax won't render), but great for text-heavy UIs that want progressive rendering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interactivity in Practice
&lt;/h3&gt;

&lt;p&gt;When the user opens the split-view editor and modifies the Mermaid code, two things happen:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto context sync&lt;/strong&gt; — Each edit triggers a debounced &lt;code&gt;updateModelContext&lt;/code&gt; call, silently syncing the current source to the LLM. No immediate response, but the next time the user asks a question, the LLM already knows the latest diagram state.&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;await&lt;/span&gt; &lt;span class="nx"&gt;mcpApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateModelContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;content&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="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Current Mermaid diagram source (updated by user):\n&lt;/span&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;&lt;span class="s2"&gt;mermaid\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="se"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Send to AI (⌘ Enter)&lt;/strong&gt; — Calls &lt;code&gt;sendMessage&lt;/code&gt; with the full modified source as a user message. The LLM sees it and responds right away.&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;await&lt;/span&gt; &lt;span class="nx"&gt;mcpApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&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="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`I've updated the Mermaid diagram source:\n&lt;/span&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;&lt;span class="s2"&gt;mermaid\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="se"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Lesson Learned During Development
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Iframe Sandbox Restrictions
&lt;/h3&gt;

&lt;p&gt;Every View runs inside a sandboxed iframe with a default CSP (Content Security Policy). Libraries that rely on &lt;code&gt;eval()&lt;/code&gt; won't work out of the box. If your UI needs external resources, you have to declare them via &lt;code&gt;connectDomains&lt;/code&gt;, &lt;code&gt;resourceDomains&lt;/code&gt;, and &lt;code&gt;frameDomains&lt;/code&gt;. The official map example hit exactly this wall — CSP blocked &lt;code&gt;eval()&lt;/code&gt; calls used for binding parsing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viewport Size Management
&lt;/h3&gt;

&lt;p&gt;The spec gives you two mechanisms for iframe dimensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;autoResize&lt;/strong&gt; — Pass &lt;code&gt;autoResize: true&lt;/code&gt; to the SDK. It attaches a &lt;code&gt;ResizeObserver&lt;/code&gt; to detect body changes, temporarily sets &lt;code&gt;html.style.height&lt;/code&gt; to &lt;code&gt;max-content&lt;/code&gt; to measure natural content height, then notifies the Host via &lt;code&gt;ui/notifications/size-changed&lt;/code&gt;. Content drives height; the Host follows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;containerDimensions&lt;/strong&gt; — Query the container constraints anytime via &lt;code&gt;app.getHostContext()?.containerDimensions&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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;HostContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;containerDimensions&lt;/span&gt;&lt;span class="p"&gt;?:&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="na"&gt;height&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="c1"&gt;// fixed: Host controls height, View should fill it&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;maxHeight&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="c1"&gt;// flexible: View decides height, but within a ceiling&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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="na"&gt;width&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="c1"&gt;// fixed: Host controls width&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;maxWidth&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="c1"&gt;// flexible: View decides width&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;h3&gt;
  
  
  Wait for Layout to Settle Before Measuring
&lt;/h3&gt;

&lt;p&gt;The iframe's dimensions shift for all kinds of reasons during loading. If you measure at that point for fit-to-container logic, you'll capture stale values. Use a &lt;code&gt;ResizeObserver&lt;/code&gt; with a debounce — "wait until dimensions stop changing" — before reading the final size.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Persistence Is Your Problem
&lt;/h3&gt;

&lt;p&gt;The Host can destroy and recreate your iframe at any time — conversation scrolls out of view, user switches tabs, Host updates its UI. The Mermaid code your user spent five minutes editing? Gone. No warning.&lt;/p&gt;

&lt;p&gt;My solution: register two internal tools on the server (&lt;code&gt;save-mermaid-draft&lt;/code&gt; / &lt;code&gt;get-mermaid-draft&lt;/code&gt;). The View calls &lt;code&gt;callServerTool&lt;/code&gt; to persist state to server memory on every edit. When the iframe is recreated, it loads the last saved state automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the Ecosystem Stands
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Host support is still limited.&lt;/strong&gt; Confirmed so far: Claude Desktop, VS Code Copilot, Goose, Postman, and MCPJam. The spec requires servers to provide a plain-text fallback, so Hosts that don't support MCP Apps fall back gracefully — the tool doesn't break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The spec keeps moving.&lt;/strong&gt; SEP-1865 was consolidated in November 2025 from the MCP-UI community and OpenAI Apps SDK experience. It's in Final status now but still has active PRs. Future additions on the table include external URL support, built-in state persistence, and View-to-View communication.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/finfin/mermaid-mcp-app" rel="noopener noreferrer"&gt;Mermaid MCP App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.thingsaboutweb.dev/zh-TW/posts/mcp-apps" rel="noopener noreferrer"&gt;MCP Apps：當 AI 對話裡長出了互動式 UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx" rel="noopener noreferrer"&gt;MCP Apps Specification (SEP-1865)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/docs/extensions/apps" rel="noopener noreferrer"&gt;MCP Apps Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/ext-apps" rel="noopener noreferrer"&gt;ext-apps GitHub Repo (SDK + Examples)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.github.io/ext-apps/api/documents/Quickstart.html" rel="noopener noreferrer"&gt;Quickstart Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.github.io/ext-apps/api/" rel="noopener noreferrer"&gt;API Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865" rel="noopener noreferrer"&gt;SEP-1865 Discussion (Proposal Thread)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>frontend</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Awesome Frontend Agent Skills — A Curated List of SKILL.md for Frontend Dev</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Tue, 17 Mar 2026 13:42:39 +0000</pubDate>
      <link>https://dev.to/finfin/awesome-frontend-agent-skills-a-curated-list-of-skillmd-for-frontend-dev-2oha</link>
      <guid>https://dev.to/finfin/awesome-frontend-agent-skills-a-curated-list-of-skillmd-for-frontend-dev-2oha</guid>
      <description>&lt;p&gt;AI coding agents don't know your framework's conventions out of the box. &lt;code&gt;SKILL.md&lt;/code&gt; fixes that — it's a structured file that library authors publish to teach agents the right patterns for their tool.&lt;/p&gt;

&lt;p&gt;The ecosystem is growing fast, though there's skills.sh, but it's a hassel to curate frontend related skills. So I built one for frontend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/finfin/awesome-frontend-skills" rel="noopener noreferrer"&gt;awesome-frontend-skills&lt;/a&gt;&lt;/strong&gt; — 70+ skills across 12 categories, all installable via &lt;code&gt;npx skills add&lt;/code&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frameworks&lt;/strong&gt; — React, Angular, Vue, Next.js, Nuxt, Svelte, TanStack, Remix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI&lt;/strong&gt; — shadcn/ui, Tailwind v4 design systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt; — Playwright, Cypress, Selenium, E2E patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Tools&lt;/strong&gt; — Vite, Nx, Turborepo, monorepo management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animation&lt;/strong&gt; — Three.js, Framer Motion, CSS animation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt; — Clerk, Auth0, Better Auth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt; — Prisma, Supabase, Convex&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile&lt;/strong&gt; — React Native, Expo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video&lt;/strong&gt; — Remotion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many are official — published by the framework teams themselves (Angular, shadcn, Prisma, Supabase, Playwright, etc.).&lt;/p&gt;

&lt;p&gt;Lists skills repo, description and installation commands. That's it.&lt;/p&gt;

&lt;p&gt;Only &lt;code&gt;SKILL.md&lt;/code&gt; files. No &lt;code&gt;.cursorrules&lt;/code&gt;, &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, or &lt;code&gt;llms.txt&lt;/code&gt; — those serve different purposes.&lt;/p&gt;

&lt;p&gt;PRs welcome if you know a frontend repo with a &lt;code&gt;SKILL.md&lt;/code&gt; that's missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/finfin/awesome-frontend-skills" rel="noopener noreferrer"&gt;→ github.com/finfin/awesome-frontend-skills&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>💥 The Day Emoji Caused a Mess : A Developer's 🚨🔍🐛🧪✅ Journey 👨‍💻</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Mon, 07 Jul 2025 13:13:55 +0000</pubDate>
      <link>https://dev.to/finfin/the-day-emoji-caused-a-mess-a-developers-journey-5hf1</link>
      <guid>https://dev.to/finfin/the-day-emoji-caused-a-mess-a-developers-journey-5hf1</guid>
      <description>&lt;h2&gt;
  
  
  The Problem: When Database Migration Breaks Frontend Display
&lt;/h2&gt;

&lt;p&gt;One time we decided to add a 280-character constraint to our posts table, but there's a problem - existing content already contains posts longer than this limit.&lt;/p&gt;

&lt;p&gt;So we write a Python migration script to clean up the existing data to fit in db column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Migration script: Clean up existing long posts
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;truncate_existing_posts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;long_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content__length__gt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;long_posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Truncate to 280 characters
&lt;/span&gt;            &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Truncated &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;long_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; posts to 280 characters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example of what the migration does
&lt;/span&gt;&lt;span class="n"&gt;original_post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Amazing day with the family! 👨‍👨‍👦‍👦❤️ Can&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t wait for more adventures together! 🚀🎨 Life is beautiful when you&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;re surrounded by love and joy!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Original length: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original_post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 132 characters
&lt;/span&gt;
&lt;span class="n"&gt;truncated_post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;original_post&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Well within the limit
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Truncated length: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;truncated_post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 132 characters ✅
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Truncated content: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;truncated_post&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The migration runs successfully. All posts are now 280 characters or fewer according to Python. We deploy the new schema with character validation on the frontend.&lt;/p&gt;

&lt;p&gt;But then users start reporting that their post content becomes invalid when editing&lt;/p&gt;

&lt;h2&gt;
  
  
  The Investigation: When Character Counting Goes Wrong
&lt;/h2&gt;

&lt;p&gt;After some digging we found out their is a weird different length between python and javascript, when using emojis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python
&lt;/span&gt;&lt;span class="n"&gt;text_with_emoji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello 👨‍💻 World&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Python len(): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_with_emoji&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 15 characters
&lt;/span&gt;
&lt;span class="c1"&gt;# Let's see what Python sees
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_with_emoji&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 0: 'H', 1: 'e', 2: 'l', 3: 'l', 4: 'o', 5: ' ', 6: '👨', 7: '\u200d', 8: '💻', 9: ' ', 10: 'W', 11: 'o', 12: 'r', 13: 'l', 14: 'd'
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// JavaScript&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textWithEmoji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello 👨‍💻 World&lt;/span&gt;&lt;span class="dl"&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;`JavaScript length: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;textWithEmoji&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;// 17 characters&lt;/span&gt;

&lt;span class="c1"&gt;// Let's see what JavaScript sees&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="nx"&gt;textWithEmoji&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; 
&lt;span class="c1"&gt;// ['H', 'e', 'l', 'l', 'o', ' ', '👨', '‍', '💻', ' ', 'W', 'o', 'r', 'l', 'd']&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;It turns out that Python counts Unicode code points, while JavaScript counts UTF-16 code units. The same emoji is counted differently.&lt;/p&gt;

&lt;p&gt;Three emojis, three lengths. Why??? &lt;/p&gt;

&lt;h2&gt;
  
  
  The Curiosity Detour
&lt;/h2&gt;

&lt;p&gt;At this point, the fix was obvious - just align the character counting methods between frontend and backend. Problem solved!&lt;/p&gt;

&lt;p&gt;But another thing caught my eye:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simple emoji&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;man&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;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="nx"&gt;man&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 2 in js, 1 in python&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;developer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;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="nx"&gt;developer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 5 in js, 3 in python (?!)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;family&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;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="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 11 in js, 7 in python (!!!)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Why was &lt;code&gt;👨‍👨‍👦‍👦&lt;/code&gt; counting as 11 characters in JavaScript but only 7 in Python? What exactly was hiding inside that "single" family emoji?&lt;/p&gt;

&lt;p&gt;That curiosity led me to study Unicode emoji specifications...&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deep Dive: Unicode Emoji Specifications
&lt;/h2&gt;

&lt;p&gt;So what's really happening under the hood? Let's examine the Unicode specifications that govern emoji behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero-Width Joiners: The Invisible Glue
&lt;/h3&gt;

&lt;p&gt;Some emojis are built with several base emojis, using &lt;strong&gt;Zero-Width Joiners (ZWJ&lt;/strong&gt; U+200D*&lt;em&gt;)&lt;/em&gt;* - invisible characters that glue base emojis together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Let's break down the developer emoji&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;developer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;developer&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Output: ['👨', '‍', '💻']&lt;/span&gt;
&lt;span class="c1"&gt;// That's: man + ZWJ + computer&lt;/span&gt;

&lt;span class="c1"&gt;// The family emoji breakdown&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;family&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Output: ['👨', '‍', '👨', '‍', '👦', '‍', '👦']&lt;/span&gt;
&lt;span class="c1"&gt;// That's: man + ZWJ + man + ZWJ + boy + ZWJ + boy&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Emoji Presentation Selector: The Style Controller
&lt;/h3&gt;

&lt;p&gt;Another invisible character: &lt;strong&gt;U+FE0F VARIATION SELECTOR-16&lt;/strong&gt; (VS16), the &lt;strong&gt;emoji presentation selector&lt;/strong&gt;. This forces a symbol to display in emoji style rather than text style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Medical symbol without emoji presentation selector&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textSymbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚕&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// U+2695 only&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="nx"&gt;textSymbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;

&lt;span class="c1"&gt;// Medical symbol WITH emoji presentation selector  &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emojiSymbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚕️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// U+2695 + U+FE0F&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="nx"&gt;emojiSymbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 2&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emojiSymbol&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// ['⚕', '️']&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Many common emojis include this hidden selector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// These look like single characters but aren't:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❤️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// U+2764 + U+FE0F&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;warning&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚠️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// U+26A0 + U+FE0F&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;heart&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;     &lt;span class="c1"&gt;// ['❤', '️'] - has VS16&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// ['⚠', '️'] - has VS16&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;From the Unicode specification:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;2695 FE0F&lt;/code&gt; (fully-qualified) — ⚕️ medical symbol (emoji presentation)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;2695&lt;/code&gt; (unqualified) — ⚕ medical symbol (text presentation)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Emoji Modifiers: Skin tones
&lt;/h3&gt;

&lt;p&gt;For people emojis, we can use &lt;strong&gt;modifiers&lt;/strong&gt; for customization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Skin tone modifiers&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🧑🏻‍🎨&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// light skin tone&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="nx"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 7&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; 
&lt;span class="c1"&gt;// ['🧑', '🏻', '‍', '🎨']&lt;/span&gt;
&lt;span class="c1"&gt;// That's: person + light skin tone + ZWJ + artist palette&lt;/span&gt;

&lt;span class="c1"&gt;// Professional emojis with skin tone modifiers&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lightDeveloper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👨🏻‍💻&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// light skin male developer&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="nx"&gt;lightDeveloper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 7&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lightDeveloper&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// ['👨', '🏻', '‍', '💻']&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;five skin tone modifiers&lt;/strong&gt; are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🏻 (Light skin tone - U+1F3FB)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🏼 (Medium-light skin tone - U+1F3FC)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🏽 (Medium skin tone - U+1F3FD)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🏾 (Medium-dark skin tone - U+1F3FE)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🏿 (Dark skin tone - U+1F3FF)&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Same base emoji, different lengths due to modifiers&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👋&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// 2 characters&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waveLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👋🏻&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// 4 characters  &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waveDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👋🏿&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// 4 characters&lt;/span&gt;

&lt;span class="c1"&gt;// Comparing different combinations&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doctor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👩‍⚕️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// 5 characters (woman + ZWJ + medical + VS16)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lightDoctor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👩🏻‍⚕️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 7 characters (+ skin tone modifier)&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Flag Sequences: Countries Built from Letters
&lt;/h3&gt;

&lt;p&gt;Here's where it gets really interesting. Country flags are actually sequences of two &lt;strong&gt;Regional Indicator&lt;/strong&gt; characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Flag examples - each is 2 Regional Indicator characters&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usFlag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🇺🇸&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// U+1F1FA (🇺) + U+1F1F8 (🇸) = "US"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taiwanFlag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🇹🇼&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// U+1F1F9 (🇹) + U+1F1FC (🇼) = "TW"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;japanFlag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🇯🇵&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// U+1F1EF (🇯) + U+1F1F5 (🇵) = "JP"&lt;/span&gt;

&lt;span class="c1"&gt;// All flags have length 4 in JavaScript&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="nx"&gt;usFlag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// 4&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="nx"&gt;taiwanFlag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 4&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="nx"&gt;japanFlag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// 4&lt;/span&gt;

&lt;span class="c1"&gt;// Breaking them down shows the Regional Indicators&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usFlag&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;     &lt;span class="c1"&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taiwanFlag&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// ['🇹', '🇼']&lt;/span&gt;

&lt;span class="c1"&gt;// You can even manually construct flags:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manualTW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🇹🇼&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;constructedTW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u{1F1F9}&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u{1F1FC}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Same thing!&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="nx"&gt;manualTW&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;constructedTW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Experiment: Interactive Exploration
&lt;/h2&gt;

&lt;p&gt;At this point, I was fascinated by the complexity. How many different emoji combinations exist? What do they actually contain?&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;"Emoji Architect"&lt;/strong&gt; to explore these compositions interactively. With this tool you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse all ZWJ sequence emojis &lt;/li&gt;
&lt;li&gt;See the real-time breakdown of any emoji (base emoji &amp;amp; modifiers)&lt;/li&gt;
&lt;li&gt;See JavaScript length calculations live&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://www.thingsaboutweb.dev/en/emojiarchitect" rel="noopener noreferrer"&gt;https://www.thingsaboutweb.dev/en/emojiarchitect&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Synchronized Character Counting
&lt;/h2&gt;

&lt;p&gt;Now that we understand the problem, let's fix our backend and frontend validation mismatch.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core Issue: Different Counting Methods
&lt;/h3&gt;

&lt;p&gt;Our original problem stemmed from inconsistent character counting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Python: 
len("Hello 👨‍💻 World") # 15

# JavaScript
"Hello 👨‍💻 World".length # 17

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  My Initial Approach: Visual Character Counting
&lt;/h3&gt;

&lt;p&gt;Initially, I was drawn to visual character counting because it felt like the right user experience. Why should users have to understand Unicode internals just to know if their post fits the character limit? Luckily &lt;code&gt;Intl&lt;/code&gt; can help us…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript implementation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getVisualLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Segmenter&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;segmenter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Segmenter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&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;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grapheme&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="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;segmenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Fallback for older browsers&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;getVisualLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello!! I am a developer 👨‍💻 who loves 🧋 and 🏃‍♂️!!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="c1"&gt;// Result: 46 &lt;/span&gt;
&lt;span class="c1"&gt;// what users actually see&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;For python..&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;regex&lt;/span&gt;  &lt;span class="c1"&gt;# pip install regex
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_visual_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Count grapheme clusters like JavaScript&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s Intl.Segmenter&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\X&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nf"&gt;get_visual_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello!! I am a developer 👨‍💻 who loves 🧋 and 🏃‍♂️!!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="c1"&gt;# Result: 46
# consistent with frontend
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This seemed perfect—users would see exactly what they expect, and complex emoji sequences would count as single characters regardless of their Unicode complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reality Check: Database Constraints
&lt;/h3&gt;

&lt;p&gt;But when I wanted to save data to database….&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Frontend validation passes with visual counting&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello!! I am a developer 👨‍💻 who loves 🧋 and 🏃‍♂️!!&lt;/span&gt;&lt;span class="dl"&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;`Visual length: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;getVisualLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bio&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;// 46 characters ✅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But database insertion fails spectacularly&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;CHAR_LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Hello!! I am a developer 👨&lt;/span&gt;&lt;span class="se"&gt;\U&lt;/span&gt;&lt;span class="nv"&gt;+200D💻 who loves &lt;/span&gt;&lt;span class="se"&gt;\U&lt;/span&gt;&lt;span class="nv"&gt;+1F9CB and 🏃&lt;/span&gt;&lt;span class="se"&gt;\U&lt;/span&gt;&lt;span class="nv"&gt;+200D♂️!!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="cm"&gt;/* 51! Which fails on VARCHAR(50) constraint! */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since there’s no easy way to make database limit content by visual length, the most practical approach is to settle with code point counting.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Second Solution: count code points:&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;codePointLength&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="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Count Unicode code points like Python len() does
     * This matches Python's default string length behavior and database constraints
     */&lt;/span&gt;
    &lt;span class="k"&gt;return&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="nx"&gt;length&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello!! I am a developer 👨‍💻 who loves 🧋 and 🏃‍♂️!!&lt;/span&gt;&lt;span class="dl"&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;`Frontend: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;codePointLength&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     
&lt;span class="c1"&gt;// 51 (which matches Python &amp;amp; DB)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code point counting might not be as user-friendly as visual counting, but it works reliably across your entire stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;After diving deep into Unicode specifications and building production solutions, here's my learning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test Across Entire Stack&lt;/strong&gt; Visual character counting seems ideal, but database constraints and ecosystem compatibility often force you toward Unicode code point counting. Choose the solution that works with your entire stack, not just the frontend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test with Complex Emoji Early&lt;/strong&gt; Don't just test with simple emojis like 😀. Include ZWJ sequences (👨‍💻), skin tone modifiers (👋🏻), and flag emojis (🇹🇼) in your validation logic from day one.
It’s fun to poke around on unicode / emoji, and hope my * &lt;a href="https://www.thingsaboutweb.dev/en/emojiarchitect" rel="noopener noreferrer"&gt;Emoji Architect tool&lt;/a&gt; can be of some help on how emojis are built.*&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>TIL: Many emojis are actually multiple emojis combined together, not single characters</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Sat, 05 Jul 2025 07:18:06 +0000</pubDate>
      <link>https://dev.to/finfin/til-many-emojis-are-actually-multiple-emojis-combined-together-not-single-characters-4oap</link>
      <guid>https://dev.to/finfin/til-many-emojis-are-actually-multiple-emojis-combined-together-not-single-characters-4oap</guid>
      <description>&lt;p&gt;Ever wondered why some emojis take up more character count than expected? (Especially when dealing with input character count) Turns out many emojis are actually combinations of simpler ones!&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of composite emojis:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;👨‍👨‍👦‍👦 = 👨+👨+👦+👦 (family of four men)&lt;/li&gt;
&lt;li&gt;👩‍💻 = 👩+💻 (woman technologist)&lt;/li&gt;
&lt;li&gt;🏳️‍🌈 = 🏳️+🌈 (rainbow flag)&lt;/li&gt;
&lt;li&gt;👨‍🍳 = 👨+🍳 (man cook)&lt;/li&gt;
&lt;li&gt;🧑🏻‍🎨 = 🧑 + 🏻 + 🎨 (artist: light skin tone)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developer gotcha: Different programming languages handle Unicode differently, so emoji length calculations can vary between frontend and backend. Always test your character limits with composite emojis. &lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript examples:
&lt;/h2&gt;

&lt;p&gt;For javascript, Intl.Segmenter can be a great help&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;family&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👨‍👨‍👦‍👦&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Default length - counts UTF-16 code units&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="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 11&lt;/span&gt;

&lt;span class="c1"&gt;// Destructuring - counts grapheme clusters  &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="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 7&lt;/span&gt;

&lt;span class="c1"&gt;// See the actual components&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; 
&lt;span class="c1"&gt;// ['👨', '‍', '👨', '‍', '👦', '‍', '👦']&lt;/span&gt;

&lt;span class="c1"&gt;// For accurate user-visible character count&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;segmenter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Segmenter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&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;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grapheme&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;segmenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Playground
&lt;/h2&gt;

&lt;p&gt;I got curious about all possible combinations, so I made "Emoji Architect" to explore these emojis, with this tool you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse all composite emojis&lt;/li&gt;
&lt;li&gt;See the breakdown of any emoji&lt;/li&gt;
&lt;li&gt;Filter combinations by base emoji components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔗 &lt;a href="https://www.thingsaboutweb.dev/en/emojiarchitect" rel="noopener noreferrer"&gt;https://www.thingsaboutweb.dev/en/emojiarchitect&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More explanation and ways of building emojis coming soon...&lt;/p&gt;

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

&lt;p&gt;Best reference is the spec&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.unicode.org/reports/tr51/" rel="noopener noreferrer"&gt;https://www.unicode.org/reports/tr51/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Things About Code Review: Balancing Code Quality and Development Speed</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Wed, 01 Jan 2025 12:54:55 +0000</pubDate>
      <link>https://dev.to/finfin/things-about-code-review-balancing-code-quality-and-development-speed-1nkd</link>
      <guid>https://dev.to/finfin/things-about-code-review-balancing-code-quality-and-development-speed-1nkd</guid>
      <description>&lt;p&gt;After leading product teams for several years, I’ve come to deeply appreciate Code Review as an effective tool, though it comes with its fair share of pitfalls. Drawing from my experience and research, I’ve compiled some insights to share with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Challenges in Software Development
&lt;/h2&gt;

&lt;p&gt;During the software development process, many of us encounter situations like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Team members taking leave or being absent&lt;/strong&gt;: Development or review schedules get delayed due to unexpected absences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced code maintainability&lt;/strong&gt;: As new features are added, code complexity increases, and development time gets prolonged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower-than-expected output quality&lt;/strong&gt;: Features often launch with bugs. &lt;strong&gt;QA, PMs, managers, clients, and users&lt;/strong&gt; all ask the same question: “Why does our product have so many issues?”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developers lacking confidence in their code&lt;/strong&gt;: They might worry about missing requirements or potential system failures due to edge cases. This not only affects individual efficiency but can also slow down the entire team.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding a Gatekeeper: Code Review
&lt;/h2&gt;

&lt;p&gt;To address these challenges, many teams choose to implement Code Review. Here’s what most people expect Code Review to achieve:&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Hope Code Review Can Accomplish
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Foster teamwork and knowledge sharing&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Increase the team’s bus factor by ensuring more members are familiar with critical code.&lt;/li&gt;
&lt;li&gt;Help new developers quickly adapt to the team’s style and best practices.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve code quality and maintainability&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Ensure code adheres to team standards.&lt;/li&gt;
&lt;li&gt;Reduce technical debt, making future maintenance easier.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ensure specifications are met and reduce bugs&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Verify that implementations align with requirements, avoiding missed details.&lt;/li&gt;
&lt;li&gt;Identify potential issues through multiple sets of eyes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boost deployment confidence&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;With team-wide endorsement, deployments become more reassuring.&lt;/li&gt;
&lt;li&gt;Especially in cross-departmental collaborations, Code Review helps minimize iterative fixes caused by oversight, strengthening team trust.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Current State and Challenges of Code Review
&lt;/h2&gt;

&lt;p&gt;While the idea is appealing, the reality is often much tougher. Here are some common challenges:&lt;/p&gt;

&lt;h3&gt;
  
  
  Time-Consuming
&lt;/h3&gt;

&lt;p&gt;Code Review is an extremely time-intensive task. For most teams, it takes an average of &lt;strong&gt;6 hours per developer per week&lt;/strong&gt;, and my personal record was spending over 30 hours in a week just on reviews. This means dedicating several hours every workday to reviewing others’ code, leaving little time to focus on one’s own development progress.&lt;/p&gt;

&lt;h3&gt;
  
  
  PRs Take Days to Merge
&lt;/h3&gt;

&lt;p&gt;Due to the time-consuming nature of reviews, the entire Pull Request (PR) process often stretches out over several days or more. This is particularly true for &lt;strong&gt;large and complex PRs&lt;/strong&gt;. As the time from opening to merging a PR increases, the rhythm of development iterations slows down as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limited Effectiveness in Identifying Issues
&lt;/h3&gt;

&lt;p&gt;Research shows that &lt;strong&gt;only 14% of review feedback addresses actual issues&lt;/strong&gt;. This indicates that while a significant amount of time is spent on Code Review, its direct impact on reducing errors is limited.&lt;/p&gt;

&lt;p&gt;At this point, let’s pause and reflect on an important question:&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is the Essence of Code Review?
&lt;/h2&gt;

&lt;p&gt;Here’s my answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The essence of Code Review is to enhance output quality and the team’s technical level through the exchange of knowledge and perspectives.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sounds ideal. Developers hope that reviews can genuinely improve code quality. However, on the other hand, the resources invested in Code Review pose a significant burden on development teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Balancing Code Quality and Development Efficiency in Code Review
&lt;/h2&gt;

&lt;p&gt;Now, let’s return to the core topic of this article: improving code quality through Code Review while minimizing its impact on development efficiency. Here, we’ll break Code Review down into three levels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review Execution&lt;/strong&gt;: This focuses on how Code Review is conducted, including choosing the right format, balancing synchronous and asynchronous methods, defining the scope of reviews, and identifying key review points.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Knowledge Exchange&lt;/strong&gt;: Beyond execution, Code Review is a process that fosters interaction and knowledge transfer between reviewers and developers. At this level, we need to consider how to improve efficiency in communication and ensure smooth knowledge flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Overall Development Process&lt;/strong&gt;: From a broader perspective, this level examines Code Review within the context of the entire development workflow, identifying areas for optimization. This might include shifting tasks earlier in the process, working with smaller task units, or leveraging automation tools to streamline reviews.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These three levels decompose Code Review into points, lines, and planes. The &lt;strong&gt;review execution&lt;/strong&gt; level is the point, the &lt;strong&gt;knowledge exchange&lt;/strong&gt; level connects the points into lines, and the &lt;strong&gt;overall development process&lt;/strong&gt; forms the complete plane. In the following sections, we’ll explore the key aspects and implementation strategies for each of these levels.&lt;/p&gt;




&lt;h2&gt;
  
  
  Review Execution
&lt;/h2&gt;

&lt;p&gt;The way Code Review is conducted directly impacts its efficiency and effectiveness. It can be further broken down into &lt;strong&gt;&lt;em&gt;review formats&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;review focus areas&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Different Review Formats
&lt;/h2&gt;

&lt;p&gt;The execution of Code Review can be broadly categorized into &lt;strong&gt;synchronous&lt;/strong&gt; and &lt;strong&gt;asynchronous&lt;/strong&gt; approaches, each suited for specific scenarios and offering unique characteristics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synchronous Formats
&lt;/h3&gt;

&lt;p&gt;Synchronous Code Reviews are conducted in real-time, requiring the simultaneous participation of two or more people:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pair Programming&lt;/strong&gt;: Two people work together in real-time, with one writing the code and the other reviewing and providing feedback on the spot.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Live Review&lt;/strong&gt;: A real-time session where the developer and reviewer collaborate to review and discuss the code, quickly resolving issues and ensuring code quality through immediate interaction.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code Review Meeting&lt;/strong&gt;: A team-wide review meeting where key code changes are discussed in-depth to gather multiple perspectives and improve the overall quality of outputs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code Walkthrough&lt;/strong&gt;: A more knowledge-sharing-oriented activity, where the developer explains the code’s logic and functionality step-by-step. Team members mainly observe and ask questions to enhance their understanding and learn from the code.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Synchronous reviews are characterized by their immediacy and interactivity, making them ideal for quickly resolving issues or engaging in deep discussions. However, they require participants to align their schedules, increasing time costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Asynchronous Formats
&lt;/h3&gt;

&lt;p&gt;Asynchronous Code Reviews don’t require participants to be present at the same time. All reviews and feedback are conducted through digital mediums, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PR Review&lt;/strong&gt;: Developers submit a Pull Request (PR), and reviewers can examine the code changes, leave comments, and discuss at their convenience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Asynchronous reviews offer high flexibility, making them suitable for distributed teams and cross-time-zone collaborations. However, the lack of real-time interaction can limit discussions, especially for complex changes.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Core Difference Between Synchronous and Asynchronous Reviews
&lt;/h3&gt;

&lt;p&gt;Synchronous formats emphasize real-time interaction, making them suitable for complex or urgent changes. Asynchronous formats, on the other hand, focus on flexibility, allowing participants to complete reviews at their own pace. Both have their specific use cases. Below is a comparison table outlining the characteristics and applications of various review formats.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparison Table: Features and Applications of Different Code Review Formats
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Format&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Core Characteristics&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Use Cases&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pair Programming&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Synchronous collaboration where two developers work together, with one writing code and the other reviewing and providing feedback in real-time.&lt;/td&gt;
&lt;td&gt;- Complex feature development&lt;br&gt;- High-risk code implementation&lt;br&gt;- Onboarding new developers&lt;/td&gt;
&lt;td&gt;- Instant feedback for quick problem resolution&lt;br&gt;- Reduces code defects&lt;br&gt;- Promotes knowledge sharing&lt;/td&gt;
&lt;td&gt;- Requires synchronized schedules&lt;br&gt;- High demands on time and manpower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Live Review&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real-time code review where the reviewer and developer discuss code details directly.&lt;/td&gt;
&lt;td&gt;- Urgent changes, quick validation&lt;br&gt;- Complex code requiring detailed discussion&lt;br&gt;- Mentorship for newcomers&lt;/td&gt;
&lt;td&gt;- Immediate interaction reduces communication overhead&lt;br&gt;- Ideal for addressing high-complexity issues&lt;br&gt;- Builds team trust&lt;/td&gt;
&lt;td&gt;- Consumes time for multiple participants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Review Meeting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A formal code review involving the entire team, focusing on critical code changes for in-depth discussion.&lt;/td&gt;
&lt;td&gt;- Significant architectural or design changes&lt;br&gt;- High-risk features&lt;br&gt;- Team knowledge sharing&lt;/td&gt;
&lt;td&gt;- Multiple perspectives for comprehensive checks&lt;br&gt;- Encourages team learning&lt;br&gt;- Enhances design transparency&lt;/td&gt;
&lt;td&gt;- Very high time costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Walkthrough&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A synchronous activity where developers explain the code’s logic and design step-by-step while reviewers ask questions and provide suggestions.&lt;/td&gt;
&lt;td&gt;- Mentorship for newcomers&lt;br&gt;- Understanding complex code&lt;br&gt;- Familiarizing team members with new system modules&lt;/td&gt;
&lt;td&gt;- Supports growth of new team members&lt;br&gt;- Helps the team understand complex system logic&lt;br&gt;- Encourages developers to reflect on their code design&lt;/td&gt;
&lt;td&gt;- More teaching-focused, less efficient than targeted code reviews&lt;br&gt;- Can consume significant time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PR Review&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Asynchronous code review conducted via tools (e.g., GitHub/GitLab), where reviewers examine changes and leave comments.&lt;/td&gt;
&lt;td&gt;- Reviewing routine development changes&lt;br&gt;- Distributed or cross-time-zone teams&lt;/td&gt;
&lt;td&gt;- Flexible timing&lt;br&gt;- Review records available for future reference&lt;br&gt;- Can integrate with automation tools (e.g., CI/CD) for higher efficiency&lt;/td&gt;
&lt;td&gt;- Lack of real-time interaction may hinder communication&lt;br&gt;- Complex changes may be harder to understand&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Choosing the Right Format
&lt;/h2&gt;

&lt;p&gt;When conducting Code Reviews, you can determine the most suitable review format based on the "complexity" and "urgency" of the development task. These tasks can be categorized into four quadrants to guide the decision-making process.&lt;/p&gt;

&lt;p&gt;The more complex the change, the greater the need for direct human involvement to ensure thorough discussion and examination. Conversely, the more urgent the task, the faster the review needs to proceed. For simpler tasks that have passed testing and are deemed reliable, why not skip the review and deploy directly?&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%2Fm7hlvlz53kg6vjduvrgr.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%2Fm7hlvlz53kg6vjduvrgr.png" alt="Image description" width="800" height="702"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Review Layers and Key Focus Areas
&lt;/h2&gt;

&lt;p&gt;Code Review encompasses multiple layers. Below is an overview of the different layers and their key review points.&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%2Fg8bkfnlotx1mlwd3wd5t.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%2Fg8bkfnlotx1mlwd3wd5t.png" alt="Image description" width="800" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Coding Guideline
&lt;/h3&gt;

&lt;p&gt;Coding guideline is aimed at maintaining project consistency so all developers can quickly understand and extend the code. Key points include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Follow project code style and naming conventions&lt;/strong&gt;: Ensure code is cleanly formatted and consistent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readability&lt;/strong&gt;: Code should be clear and simple, enabling others to understand it quickly. This includes naming variables in a way that clearly reflects their purpose or intent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code style rules can largely be automated using static code analysis tools to check formatting and simple naming conventions, such as ensuring variables are named in CamelCase. However, readability often requires manual review. The good news is that some of this work can be offloaded to AI tools like Copilot, Cursor, or Windsurf. For instance, the following image shows how I used Cursor Chat to "check the readability of code within a file."&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%2Fil08hn57fkxk19g9yce0.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%2Fil08hn57fkxk19g9yce0.png" alt="Image description" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;Testing is a form of self-review and includes the following key focus areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensuring tests align with requirements and cover all specified aspects.&lt;/li&gt;
&lt;li&gt;Verifying that all tests pass, particularly for reasonable tests targeting new features.&lt;/li&gt;
&lt;li&gt;Checking edge-case tests to ensure extreme or abnormal scenarios are handled correctly.&lt;/li&gt;
&lt;li&gt;Addressing non-functional requirements (NFRs), such as performance, stability, and security testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing is typically integrated with CI/CD tools for automation.&lt;/p&gt;




&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;p&gt;Good documentation is essential for team knowledge sharing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Thorough documentation of new features&lt;/strong&gt;: Background, purpose, design logic, and implementation details should be clearly recorded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relating documentation updated&lt;/strong&gt;: This includes README files, API documentation, user guides, etc., ensuring they reflect the current state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readability and accessibility of documentation&lt;/strong&gt;: Documentation should be understandable and searchable when needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code is often the single source of truth for engineers, but documentation may be scattered across different locations. Thus, it’s crucial to ensure documentation is both comprehensive and &lt;strong&gt;easy to find&lt;/strong&gt; and &lt;strong&gt;up-to-date&lt;/strong&gt;. Missing documents provide no value, and outdated information can lead to incorrect development directions. While automation for documentation is limited, AI combined with document templates can help generate drafts.&lt;/p&gt;




&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Implementation is naturally a focal point of Code Review, with the following key areas of focus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fulfilling functional requirements&lt;/strong&gt;: Ensuring all specified needs are met without omissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct and concise logic&lt;/strong&gt;: Avoid unnecessary complexity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robustness&lt;/strong&gt;: Handle unexpected inputs, edge cases, and failures gracefully without crashing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure&lt;/strong&gt;: Considered risks such as SQL injection, XSS, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt;: Including sufficient logs, traces, or metrics to support maintenance and status monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementation have various levels of complexity, thus manual involvement is often required. The good news is that AI review tools (or simple prompts) can handle reviews of lower complexity.&lt;/p&gt;




&lt;h3&gt;
  
  
  Boundaries
&lt;/h3&gt;

&lt;p&gt;Boundaries refer to the areas outside the scope of the current changes that might be affected by them — this includes systems, external resources, or users. For instance, changing a database attribute affects the database table, its associated data model, anyone using the model, and any other areas interacting with the database attribute. Changing a button affects the page and users interacting with it. Review focus areas include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimizing impact&lt;/strong&gt;: Ensure the boundaries are simplified and dependencies are reduced while meeting requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency and the Principle of Least Surprise&lt;/strong&gt;: Ensure developers interacting with the boundaries can quickly understand their behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hiding internal implementation details&lt;/strong&gt;: Expose only the necessary functions or interfaces to the outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing systems working properly&lt;/strong&gt;: Ensure all affected parts function correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of adding a subway line to a metropolis. The construction scope, integration with existing structures, coordination with current roads, and even the connection between stations and traffic flow are all boundary issues. The more complex the functionality and changes, the more intricate the boundaries become. Fortunately, much of this complexity can be shifted left(earlier) in the development process, which will be further discussed in the "Development Process" section.&lt;/p&gt;




&lt;p&gt;The higher layers of these five aspects are easier to automate. Ideally, we should automate the top layers as much as possible and focus our efforts on the last two: implementation and boundary reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Code Review Tools
&lt;/h2&gt;

&lt;p&gt;Recently, I started using a tool called &lt;strong&gt;Code Rabbit AI&lt;/strong&gt;. It's a cool AI Code Review tool, and very easy to integrate. Tools like this can improve the efficiency of Code Reviews and are particularly helpful for finding foundational issues.&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%2Fcddiktrhz06s5efif8m1.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%2Fcddiktrhz06s5efif8m1.png" alt="Image description" width="752" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s so much to discuss about AI tools that it’s worth dedicating an entire article to the topic. Here are some key observations and tips for using AI tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Thorough but Junior-Level Reviews&lt;/strong&gt;
AI can efficiently and instantly help detect syntax errors, semantic issues, simple logical mistakes, and API usage inconsistencies. These alone can elevate the basic quality of the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear Specifications Are Key&lt;/strong&gt;
Like humans, AI tools perform better when given clear specifications. Well-defined spec sheets or requirement documents significantly enhance the relevance and quality of AI output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t Expect a SENIOR AI to Save You&lt;/strong&gt;
AI typically struggles with complex functionalities, architectural designs, or system-level problems. For core architecture-related reviews, manual input is still essential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control Scope&lt;/strong&gt;
When the number of files or the scope of changes is too large, the quality of AI suggestions may decrease. Setting reasonable change boundaries ensures the tool remains effective.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI can save time by handling minor issues, which is one of its key benefits — it takes on the burden of highlighting small, tedious details that might seem nitpicky if pointed out by human reviewers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving Review Execution Efficiency
&lt;/h2&gt;

&lt;p&gt;Here’s a summary of actionable steps for improving execution efficiency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choose the Right Code Review Format&lt;/strong&gt;
For urgent and complex tasks, synchronous formats like Live Reviews or Pair Programming are ideal, while for routine changes, asynchronous PR Reviews offer more flexibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Utilize Automation Tools&lt;/strong&gt;
Incorporate automated processes like static code analysis, unit testing, and integration testing into CI/CD pipelines to ensure basic code quality is verified before submission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage AI-Assisted Reviews&lt;/strong&gt;
AI serves as a versatile assistant, reducing the time engineers spend on straightforward logic and allowing them to focus on discussing more complex and valuable functionalities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on Implementation and Boundary Reviews&lt;/strong&gt;
Reviewers should concentrate on assessing the implementation and boundaries, especially areas that are too complex for automated tools to handle.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Knowledge Exchange
&lt;/h2&gt;

&lt;p&gt;Now that we’ve covered &lt;strong&gt;Review Execution&lt;/strong&gt;, let’s shift our focus to &lt;strong&gt;Knowledge Exchange&lt;/strong&gt; — the critical interactions between developers and reviewers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Correct understanding leads to effective feedback.
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The greatest challenge in Code Review is understanding the content and the reasons behind the changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The essence of Code Review lies in enhancing the team's technical capabilities and output quality through knowledge exchange. However, this exchange is inherently challenging. Research shows that reviewers often spend significant time digesting the background information of a PR (Pull Request), especially when changes involve complex logic or multiple modules. A lack of sufficient context can easily lead to misplaced review focus or unconstructive feedback. This is particularly true in large teams or long-term projects where reviewers have varying levels of background knowledge, making clear and specific descriptions crucial for understanding.&lt;/p&gt;

&lt;p&gt;Here are some practical actions reviewers can take:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read PR Descriptions and Commit Histories&lt;/strong&gt;\
Ensure you understand the goals and scope of the changes, focusing on valuable aspects of the review to avoid wasting time on irrelevant details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refer to Tests and Related Documentation&lt;/strong&gt;\
Use test cases and specification documents to understand the design objectives and expected behavior of the changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask Questions and Request Additional Context&lt;/strong&gt;\
When encountering unclear or hard-to-understand areas, reviewers should ask questions and request clarification instead of making assumptions or skipping over details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose Effective Methods for Gaining Information&lt;/strong&gt;\
Depending on the complexity of the changes, use appropriate review formats — for instance, asynchronous methods for smaller changes, and synchronous formats like Live Reviews or Code Walkthroughs for complex changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document Key Information During Discussions&lt;/strong&gt;\
Record important background information or decision rationale during the review process for future reference. This benefits not only the reviewer but also helps other team members understand the considerations behind the changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These practices help reviewers quickly obtain accurate context within limited time, improving the quality of their feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Point 1: More Efficient Information Sharing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visuals &amp;gt; Spoken &amp;gt; Text&lt;/strong&gt;\
Code Review doesn’t need to rely solely on text feedback. For especially complex changes, invite the developer to conduct a Code Walkthrough, explaining logic line by line or illustrating the entire functionality flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Face-to-Face &amp;gt; Online&lt;/strong&gt;\
Face-to-face communication is more efficient, as it captures body language and physical nuances, enabling immediate feedback and interaction, reducing misunderstandings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronous &amp;gt; Asynchronous&lt;/strong&gt;\
Synchronous methods like Live Reviews or Pair Programming enable more precise and immediate information exchange, avoiding delays or information gaps often caused by asynchronous communication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Positive Tone &amp;gt; Negative&lt;/strong&gt;\
Using a positive or constructive tone enhances the acceptance of reviews and improves communication outcomes. Instead of merely pointing out problems, suggest alternatives or highlight learning opportunities. Research shows that reviews with &lt;strong&gt;positive or neutral tones&lt;/strong&gt; are considered effective 80% of the time, compared to only 57% for reviews with negative tones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Link Code to Related Documentation&lt;/strong&gt;\
This aligns with the "findability" principle mentioned earlier. The faster reviewers can locate relevant information, the quicker they can make informed judgments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Point 2: The API Principle
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.axios.com/2022/06/03/simple-workplace-principle-assume-positive-intent" rel="noopener noreferrer"&gt;API here stands for &lt;strong&gt;Assume Positive Intent&lt;/strong&gt;&lt;/a&gt;. This principle encourages us to assume that everyone wants to do their best and to interpret their actions or intentions in a positive light. This is especially critical in text-heavy asynchronous Code Reviews, where the lack of tone or context can lead to misunderstandings.&lt;/p&gt;

&lt;p&gt;When we assume others have good intentions, we are more inclined to help them achieve their goals, leading to more constructive feedback. For example, we naturally help children explore the world, such as learning to walk (goal), by offering support and encouragement. In contrast, if we view someone as an adversary, we’re more likely to hope for their failure and withhold assistance.&lt;/p&gt;

&lt;p&gt;The API Principle builds trust within teams, making Code Review communications smoother and reducing conflicts caused by tone or misunderstandings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Point 3: Be Clear and Actionable
&lt;/h2&gt;

&lt;p&gt;Suggestions should clearly identify problems while guiding actionable solutions. However, what’s considered "clear and actionable" varies by individual. Consider these two pieces of feedback:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"Consider addressing some edge cases."&lt;/li&gt;
&lt;li&gt;"In the &lt;code&gt;calculateCAGR&lt;/code&gt; function, consider adding checks for whether &lt;code&gt;starting_value&lt;/code&gt; and &lt;code&gt;ending_value&lt;/code&gt; are zero. Currently, the function throws a &lt;code&gt;ZeroDivisionError&lt;/code&gt; when inputs are zero."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For experienced developers, feedback like #1 may suffice. However, for newcomers or colleagues unfamiliar with the code, feedback like #2, with more details, is necessary. Effective reviews should adapt to the audience, helping team members of varying experience levels better understand and apply the suggestions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case Analysis
&lt;/h2&gt;

&lt;p&gt;At this point, let’s use the well-known case as an example in our discussion.&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%2F8chfl8d61i4rfet9o0cj.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%2F8chfl8d61i4rfet9o0cj.png" alt="Image description" width="502" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s evaluate this Code Review comment using the points we discussed earlier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Efficient Information Sharing&lt;/strong&gt;: The reviewer is the creator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Principle&lt;/strong&gt;: Extremely aggressive in tone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear and Actionable&lt;/strong&gt;: The feedback provides explicit steps for improvement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, here’s the question: from a Code Review perspective, is this type of feedback good or bad? How does it impact code quality and the team dynamic — positively or negatively?&lt;/p&gt;




&lt;h2&gt;
  
  
  Development Process Perspective
&lt;/h2&gt;

&lt;p&gt;Previously, we discussed what reviewers can do, including the execution of reviews and knowledge exchange between reviewers and developers. Now, let’s zoom out and look at the overall development process — what optimizations can be made from this broader perspective?&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%2Fmdzxztow49t9lerifnm3.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%2Fmdzxztow49t9lerifnm3.png" alt="Image description" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Clearly Define and Document Specifications During the Planning Phase
&lt;/h2&gt;

&lt;p&gt;Both the execution and knowledge exchange sections emphasized the importance of &lt;strong&gt;specifications&lt;/strong&gt;. The planning phase is a critical stage for influencing development efficiency and quality. If requirements are clearly defined and specifications are thoroughly documented during this phase, unnecessary discussions and revisions during Code Review can be minimized. For example, if the specifications already detail the API input/output formats, error-handling logic, and performance requirements, reviewers can focus on validating the implementation against these requirements instead of debating the details of the requirements themselves.&lt;/p&gt;

&lt;p&gt;Defining specifications early has another benefit: it helps identify unnecessary features. If a feature is deemed non-essential during the planning phase, it won’t be implemented, and there’s no need to spend time reviewing it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Best Code is No Code At All.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a reminder, &lt;strong&gt;documentation findability&lt;/strong&gt; is crucial. During development, ensure that code, documents, and specifications are tightly linked. This not only facilitates the current Code Review process but also helps team members better understand the system in the future. This can include linking related documents directly in the code or converting specifications into test cases. Doing so allows reviewers and developers to quickly reference the context and rationale behind changes, greatly improving communication efficiency and confidence.&lt;/p&gt;

&lt;p&gt;Next, let’s examine two statistical charts to understand the relationship between change size and review efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR Size vs. Review Effectiveness
&lt;/h2&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%2Fxoh5jmdukys0sj8tw4mf.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%2Fxoh5jmdukys0sj8tw4mf.png" alt="Image description" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This chart shows that &lt;strong&gt;there is a negative correlation between the number of files changed and its review effectiveness density&lt;/strong&gt;. As the number of files increases, the effectiveness of the review decreases. A reasonable explanation is that large scope overwhelm reviewers, making it harder for them to fully understand the changes and reducing the value of their feedback.&lt;/p&gt;




&lt;h2&gt;
  
  
  PR Size vs. Review Duration
&lt;/h2&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%2Fm2iff99e9s2pyvxiffoy.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%2Fm2iff99e9s2pyvxiffoy.png" alt="Image description" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's see the second chart, the PR merge time peaks at around 2,000. When the number of modified lines in a PR is under 2,000, &lt;strong&gt;the more lines modified, the longer the review and merge process takes&lt;/strong&gt;. For PRs with fewer than 50 lines, the average merge time is less than 2 days (about 36 hours). However, for PRs with 500-1,000 lines, the average time climbs to nearly 4 days. Interestingly, when the number of lines exceeds 2,000, the merge time stops increasing. This could be due to several reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Large PRs might involve formatting changes, requiring minimal review.&lt;/li&gt;
&lt;li&gt;PRs with over 2,000 lines might be too challenging to review thoroughly, leading reviewers to skip them altogether.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Ask a programmer to review 10 lines of code, he'll find 10 issues.&lt;br&gt;
Ask him to do 500 lines and he'll say it looks good.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Overall, for changes exceeding 100 lines, the average merge time rises to 4 days — a concerning statistic for development speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reduce Pull Requests Size
&lt;/h2&gt;

&lt;p&gt;The larger the scope of changes, the bigger the PR becomes. This often requires more complex architecture, detailed specifications, and significantly more time to understand and review. So, can we reverse this trend? By breaking down features into smaller tasks and PRs, we can reduce the scope of changes, simplify the architecture and specifications, and shorten the time needed for reviews.&lt;/p&gt;

&lt;p&gt;Follow these steps to reduce PR size:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Make Specifications and Documentation Clear&lt;/strong&gt;\
Reiterating the importance of proper planning: clear and detailed planning ensures that subsequent implementation has a solid foundation and is easier to split into smaller tasks and delegate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Break Requirements into Simple Tasks&lt;/strong&gt;\
Divide requirements into small, manageable development tasks that can be completed in half a day. This helps developers stay focused and avoids submitting large-scale changes all at once. For example, a feature could be broken down into separate PRs for the UI, API integration, and backend implementation. This approach not only simplifies reviews but also makes the context of each PR more explicit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clarify Task Dependencies&lt;/strong&gt;\
Clearly define the dependencies between tasks to ensure the sequence of execution is logical and efficient. For example, if the backend API implementation is a prerequisite for frontend development, the backend PR must be prioritized and completed first. This ensures that the frontend team can proceed without unnecessary delays caused by waiting for backend changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement: One Task, One PR&lt;/strong&gt;\
Follow the "one task, one PR" principle to keep each PR’s scope manageable and clear. This approach reduces the cognitive load for each review and minimizes the risk of errors or misunderstandings caused by intertwined tasks. Consider &lt;a href="https://blog.logrocket.com/using-stacked-pull-requests-in-github/" rel="noopener noreferrer"&gt;leveraging &lt;strong&gt;Stacked PRs&lt;/strong&gt;&lt;/a&gt; for better task separation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target: LOC &amp;lt; 500&lt;/strong&gt;\
Studies show that when a PR’s lines of code (LOC) are fewer than 500, reviewers are most efficient and accurate. Therefore, we should aim to limit each PR’s changes to this range. This constraint can reduce the reviewer’s workload and ensure higher-quality reviews.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Self Review: Pre-Review Preparation
&lt;/h2&gt;

&lt;p&gt;The person who understands a PR best is its author. Self Review, in theory, costs less than having a Reviewer perform the audit. Therefore, consider conducting a Self Review before submitting your code for Code Review. This not only improves the quality of the PR but also reduces the workload for the Reviewer.&lt;/p&gt;

&lt;p&gt;There are two approaches to Self Review:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Act as if you are the Reviewer and carefully review your own PR.&lt;/li&gt;
&lt;li&gt;Use AI coding tools to assist with the review:\
Tools like Copilot, Cursor, or Windsurf now offer chat functionalities. You can use these tools to have AI review your code or even suggest real-time modifications.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here are some sample prompts for reviewing implementation and boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I want you to perform a code review, ensuring the following checklist is satisfied:

1. **Fulfilling functional requirements**: Ensuring all specified needs are met without omissions.
2. **Correct and concise logic**: Avoid unnecessary complexity.
3. **Robustness**: Handle unexpected inputs, edge cases, and failures gracefully without crashing.
4. **Secure**: Considered risks such as SQL injection, XSS, etc.
5. **Observability**: Including sufficient logs, traces, or metrics to support maintenance and status monitoring.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Boundary Review Prompt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I want you to perform a code review, ensuring the following checklist is satisfied:

1. **Minimizing impact**: Ensure the interface are simplified and dependencies are reduced while meeting requirements.
2. **Consistency and the Principle of Least Surprise**: Ensure developers interacting with the interface can quickly understand their behavior.
3. **Hiding internal implementation details**: Expose only the necessary functions or interfaces to the outside.
4. **Existing systems working properly**: Ensure all affected parts function correctly.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: In these prompts, the term *&lt;/em&gt;&lt;code&gt;interface&lt;/code&gt;** is used to describe boundaries because it is easier to identify and assess within the editor. Making "interface" a more practical term for this context.*&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Ways AI Tools Can Help
&lt;/h2&gt;

&lt;p&gt;Beyond assisting with Self Review, AI tools can contribute to other stages of development and Code Review, making the process more efficient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generate PR Descriptions (GitHub Copilot):&lt;/strong&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%2Fr6uem1ohhnzvqxj4zobv.png" alt="Image description" width="800" height="510"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate Specifications / Test Cases:&lt;/strong&gt;
Engineers can use AI to produce artifacts such as API documentation, mock data, or even test cases to assist with testing or validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create Explanations:&lt;/strong&gt;
Visualize code workflows, explain complex logic, and provide additional context to make understanding easier.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;You Don’t Always Need Code Review&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While Code Review is an essential practice for improving code quality, it’s not always necessary to invest resources in it. Here are scenarios where skipping or simplifying Code Review might be appropriate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Confidence, High Test Coverage:&lt;/strong&gt;
If the project has comprehensive test coverage and there’s confidence in the deployment process, reliance on Code Review can be reduced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small-Scale, Low-Risk Changes:&lt;/strong&gt;
Changes such as modifying text, configuration files, or non-critical parts of the codebase can be self-reviewed and deployed directly by developers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experimental Features:&lt;/strong&gt;
Some experimental features may be short-lived or rewritten soon. In such cases, Code Review may not be a worthwhile investment.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;To summarize, here are some key points about Code Review:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choose the Right Format:&lt;/strong&gt;
For complex changes, opt for synchronous reviews to maximize effectiveness. For routine changes, asynchronous PR Reviews allow team members to work at their own pace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage Automation:&lt;/strong&gt;
Tools like static code analysis, automated testing, and CI/CD pipelines are standard in modern development. Additionally, AI-assisted Code Review can help detect basic issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on High-Value Areas:&lt;/strong&gt;
Direct review efforts toward the architecture, boundaries, and core logic of the code. Simple issues, such as coding style, can be handled by automation tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define Clear Specifications and Requirements:&lt;/strong&gt;
During the planning phase, clearly define requirements and create supporting documentation. A well-defined goal reduces misunderstandings, minimizes non-compliant behaviors (bugs), and helps reviewers focus on whether changes meet the requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce Task Scope and PR Size:&lt;/strong&gt;
Break tasks into manageable units and limit each PR to fewer than 500 lines of code. This reduces review time and improves review quality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self Review First:&lt;/strong&gt;
Conduct a self-check before submission, using AI tools if needed, to ensure the PR meets basic quality standards.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;I cannot stress this enough: Code Review is more than just a quality improvement tool — it’s a powerful process for building team cohesion and facilitating knowledge sharing. I hope these insights have given you new perspectives and practical approaches to enhance your Code Review practices. If you’d like to continue the conversation, feel free to connect with me on social media!&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2405.18216" rel="noopener noreferrer"&gt;A Survey on Modern Code Review: Progresses, Challenges and Opportunities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2203.09095" rel="noopener noreferrer"&gt;Automating Code Review Activities by Large-Scale Pre-training&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2404.10703" rel="noopener noreferrer"&gt;An Empirical Study on Code Review Activity Prediction and Its Impact in Practice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2401.05136" rel="noopener noreferrer"&gt;Code Review Automation: Strengths and Weaknesses of the State of the Art&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://graphite.dev/blog/types-of-code-review" rel="noopener noreferrer"&gt;Types of Code Reviews: Improve Performance, Velocity, and Quality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.morling.dev/blog/the-code-review-pyramid/" rel="noopener noreferrer"&gt;The Code Review Pyramid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>codereview</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Things About Nonce &amp; CSRF Token: Differences, Use Cases, and How They Work</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Sat, 21 Dec 2024 00:46:41 +0000</pubDate>
      <link>https://dev.to/finfin/things-about-nonce-csrf-token-differences-use-cases-and-how-they-work-2nk1</link>
      <guid>https://dev.to/finfin/things-about-nonce-csrf-token-differences-use-cases-and-how-they-work-2nk1</guid>
      <description>&lt;p&gt;To prevent web attacks, &lt;strong&gt;Nonce (Number Used Once)&lt;/strong&gt; and &lt;strong&gt;CSRF Token (Cross-Site Request Forgery Token)&lt;/strong&gt; are two common and important security mechanisms for modern websites. Although both are related to web security, their design goals, problems addressed, and application scenarios are distinct. This article introduces the workings of these two mechanisms, their differences, and their appropriate use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Nonce? Why do we need it?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;nonce&lt;/strong&gt; is a randomly generated string used only once to ensure the uniqueness of each request or operation. It is typically generated by the server and included with each request. By storing and validating the nonce on the server, it can effectively prevent attackers from intercepting and re-executing requests, ensuring each request is executed only once.&lt;/p&gt;

&lt;p&gt;For instance, when users accidentally click the submit button multiple times in a web form, it could result in multiple orders being created. The nonce mechanism prevents the server from processing duplicate requests. It also safeguards against replay attacks, which attempt to exploit intercepted legitimate requests to perform multiple actions, leading to unauthorized operations.&lt;/p&gt;

&lt;p&gt;A good analogy for a nonce is an &lt;strong&gt;event ticket&lt;/strong&gt;. Each ticket has a unique number and can only be used once to gain entry to the event. Once scanned at the entrance, the system marks it as used, ensuring the same ticket cannot be reused to enter again. Similarly, a nonce ensures that each request is unique and cannot be duplicated or reused maliciously.&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%2Fwiwyyozm677z29bb0c2p.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%2Fwiwyyozm677z29bb0c2p.png" alt="Image description" width="800" height="732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases for Nonce
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scenario 1: Prevent Duplicate Form Submissions
&lt;/h3&gt;

&lt;p&gt;In scenarios requiring order submissions, users might accidentally click the submit button multiple times, resulting in multiple requests. Without a verification mechanism, the server could process these duplicate requests, causing multiple orders or tasks to be executed repeatedly.&lt;/p&gt;

&lt;p&gt;To solve this issue, a nonce can be used to ensure the uniqueness of each form submission. The server generates a random nonce and embeds it in the form. When the user submits the form, the server verifies whether the nonce has already been used. If it has, the request is rejected.&lt;/p&gt;

&lt;p&gt;Consider a shopping website, a user may unintentionally click the "Submit" button multiple times after submitting an order. The server uses a nonce to validate each request's uniqueness, ensuring the order is not submitted multiple times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secnario 1 Implementation
&lt;/h3&gt;

&lt;p&gt;Form:&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;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/submit-order"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"nonce"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"123456789abcdef"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"orderDetails"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit Order&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server Validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isNonceValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;invalidateNonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid nonce!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scenario 2: Prevent Replay Attacks
&lt;/h3&gt;

&lt;p&gt;Attackers may intercept legitimate API requests and send identical requests multiple times for malicious purposes. To prevent such replay attacks, a nonce can ensure each request is unique. The server checks whether the nonce has already been used to determine the request's validity.&lt;/p&gt;

&lt;p&gt;A RESTful API for executing deductions could be maliciously exploited by repeatedly sending the same deduction request. A nonce ensures that the request is executed at most once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secnario 2 Implementation
&lt;/h3&gt;

&lt;p&gt;Request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;POST /transactions HTTP/1.1
Host: example.com
Content-Type: application/json
Authorization: Bearer &amp;lt;access_token&amp;gt;

&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"nonce"&lt;/span&gt;: &lt;span class="s2"&gt;"abcdef123456"&lt;/span&gt;,
    &lt;span class="s2"&gt;"amount"&lt;/span&gt;: 100.00,
    &lt;span class="s2"&gt;"account_id"&lt;/span&gt;: &lt;span class="s2"&gt;"1234567890"&lt;/span&gt;,
    &lt;span class="s2"&gt;"transaction_type"&lt;/span&gt;: &lt;span class="s2"&gt;"deduction"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server Validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isNonceValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;executeTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&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="nx"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&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="nx"&gt;transaction_type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;invalidateNonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid or reused nonce!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Special Scenario: Content Security Policy (CSP)
&lt;/h3&gt;

&lt;p&gt;There is a specific use of nonces under Content Security Policy (CSP) that provides robust protection for web applications. A &lt;strong&gt;CSP nonce&lt;/strong&gt; is a randomly generated string, used only once, to control the execution of inline scripts or styles. It is generated by the server and included in both the CSP header and authorized &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags. The process is similar to the previously introduced nonce mechanism, but here, validation happens in the browser. The browser enforces CSP rules, ensuring only scripts or styles with the correct nonce are executed, blocking unauthorized ones.&lt;/p&gt;

&lt;p&gt;For example, a webpage displaying user-specific data may require dynamically generated inline JavaScript. By embedding a nonce in the server-generated CSP header and the authorized scripts, only the intended scripts are executed, while unauthorized ones are blocked. This ensures critical inline scripts can run safely without exposing the page to malicious script injections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Special Scenario Implementation
&lt;/h3&gt;

&lt;p&gt;Server-Set CSP Header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Content-Security-Policy: script-src &lt;span class="s1"&gt;'self'&lt;/span&gt; &lt;span class="s1"&gt;'nonce-abcdef123456'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTML Page:&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;nonce=&lt;/span&gt;&lt;span class="s"&gt;"abcdef123456"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is a secure script.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This script will be blocked by CSP.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Only embedded scripts with the correct nonce will execute.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Other embedded or injected external scripts will be blocked by CSP.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The console of this page will show &lt;code&gt;This is a secure script.&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is a CSRF Token? Why do we need it?
&lt;/h2&gt;

&lt;p&gt;A CSRF Token (Cross-Site Request Forgery Token) is a security measure designed to prevent cross-site request forgery (CSRF) attacks. Its purpose is to verify that each request comes from the user's legitimate operation rather than a malicious site's induced request.&lt;/p&gt;

&lt;p&gt;A typical CSRF attack exploits the victim's logged-in state to send malicious requests to a trusted website without the victim's authorization or knowledge. For example, an attacker might forge a request to transfer funds, change passwords, or delete user accounts.&lt;/p&gt;

&lt;p&gt;The CSRF Token is usually generated by the server and bound to the session. The server sends the token to the user's browser and requires the user to include this token in every request. Upon receiving the request, the server verifies whether the token matches the value stored in the user's session. If the token is invalid or missing, the request is rejected.&lt;/p&gt;

&lt;p&gt;This mechanism verifies the legitimacy of requests, preventing attackers from impersonating users through cross-site methods to perform sensitive operations, thus enhancing application security.&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%2F5rryhe25a63ud1wxuydz.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%2F5rryhe25a63ud1wxuydz.png" alt="Image description" width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases for CSRF Tokens
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scenario: Enhance Security for Sensitive Actions
&lt;/h3&gt;

&lt;p&gt;Attackers may exploit a user's logged-in state to initiate high-risk actions like transferring funds. To prevent such situations, a CSRF Token serves as an effective validation method.&lt;/p&gt;

&lt;p&gt;Users must include a CSRF Token to protect fund transfer requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Form:&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;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/transfer-funds"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"csrf_token"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"abcd1234efgh"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"amount"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Transfer Amount"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"to_account"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Recipient Account"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit Transfer&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server Validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="nx"&gt;csrf_token&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;csrf_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;processTransfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&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="nx"&gt;to_account&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid CSRF token!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Differences Between Nonce and CSRF Token
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Nonce&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;CSRF Token&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Main Purpose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prevent replay attacks, duplicate submissions&lt;/td&gt;
&lt;td&gt;Prevent cross-site request forgery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Usage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ensure request uniqueness&lt;/td&gt;
&lt;td&gt;Validate request legitimacy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Implementation Level&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Commonly used in APIs or form submissions&lt;/td&gt;
&lt;td&gt;Commonly used in APIs or form submissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reuse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single-use (non-reusable)&lt;/td&gt;
&lt;td&gt;May be reused (e.g., within a session)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Nonce ensures request uniqueness to avoid duplicates and works with CSP to allow only authorized scripts to execute.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CSRF Token prevents Cross-Site Request Forgery by verifying the random string included in each request matches the server-generated token, ensuring the request originates from a legitimate user.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Can Nonce and CSRF Tokens Be Used Together?
&lt;/h3&gt;

&lt;p&gt;Yes, these mechanisms address different issues and can be used together without conflict. Implementation depends on the specific problem you aim to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  References:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Replay_attack" rel="noopener noreferrer"&gt;Replay Attack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP" rel="noopener noreferrer"&gt;CSP Nonces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.explainthis.io/en/swe/what-is-csrf" rel="noopener noreferrer"&gt;What is CSRF?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Flowchart Components and Examples</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Tue, 17 Dec 2024 00:59:57 +0000</pubDate>
      <link>https://dev.to/finfin/getting-started-with-flowcharts-basic-components-and-practical-examples-4aj4</link>
      <guid>https://dev.to/finfin/getting-started-with-flowcharts-basic-components-and-practical-examples-4aj4</guid>
      <description>&lt;p&gt;Flowcharts are a powerful form of diagram that use a few simple elements to illustrate the sequence of events, possible branches, and more. They help readers focus on &lt;strong&gt;"what will happen next and the sequential relationships between each part of the process."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Flowcharts can be applied in various scenarios, such as reviewing incidents, planning system functionalities, or even organizing trips and events.&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%2F99cbjf5jmea8a4vfijju.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%2F99cbjf5jmea8a4vfijju.png" alt="Image description" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Symbols of a Flowchart
&lt;/h2&gt;

&lt;p&gt;Here is an introduction to the basic elements commonly found in flowcharts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start and End (Start/End):&lt;/strong&gt;&lt;br&gt;
Used to mark the beginning or end of a process.&lt;br&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%2Fkbkpstdl23wyljfixe4v.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%2Fkbkpstdl23wyljfixe4v.png" alt="Image description" width="544" height="266"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Process:&lt;/strong&gt;&lt;br&gt;
Represents a single step within the process.&lt;br&gt;&lt;br&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%2F336946lw6j2nn2qwz242.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%2F336946lw6j2nn2qwz242.png" alt="Image description" width="458" height="250"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Predefined Process:&lt;/strong&gt;&lt;br&gt;
Indicates a subprocess or detailed flow defined elsewhere.&lt;br&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%2Ffhtgamu7z8w2yy90ys2t.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%2Ffhtgamu7z8w2yy90ys2t.png" alt="Image description" width="516" height="278"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Delay:&lt;/strong&gt;&lt;br&gt;
A step that requires waiting.&lt;br&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%2Focxkfaet65ixkv099e2l.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%2Focxkfaet65ixkv099e2l.png" alt="Image description" width="512" height="276"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Decision:&lt;/strong&gt;&lt;br&gt;
Represents a decision or branching point, leading to different paths.&lt;br&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%2Fg4jgq4uk7ig9fgd0nvpt.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%2Fg4jgq4uk7ig9fgd0nvpt.png" alt="Image description" width="540" height="272"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Preparation/Initialization:&lt;/strong&gt;&lt;br&gt;
A step involving preparation or initialization.&lt;br&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%2Fu1sghe0b4yapa3bvevjb.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%2Fu1sghe0b4yapa3bvevjb.png" alt="Image description" width="512" height="276"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Input/Output:&lt;/strong&gt;&lt;br&gt;
Marks the input or output of data.&lt;br&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%2Fd4prv0dantc15wuy2sdn.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%2Fd4prv0dantc15wuy2sdn.png" alt="Image description" width="470" height="238"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manual Input:&lt;/strong&gt;&lt;br&gt;
Indicates a step requiring human interaction, such as manual input or operation.&lt;br&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%2F7vjp0rrpzh5wtfzmf0c3.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%2F7vjp0rrpzh5wtfzmf0c3.png" alt="Image description" width="496" height="322"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AND/OR:&lt;/strong&gt;&lt;br&gt;
Logical operators used to combine or branch process flows.&lt;br&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%2Fovn2lrh2cjw8xf0u4m9a.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%2Fovn2lrh2cjw8xf0u4m9a.png" alt="Image description" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document/Multiple Documents:&lt;/strong&gt;&lt;br&gt;
Represents a document or a set of documents.&lt;br&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%2Fsgzk2762o6zc6xcv3t78.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%2Fsgzk2762o6zc6xcv3t78.png" alt="Image description" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database:&lt;/strong&gt;&lt;br&gt;
Denotes a system used for storing data, especially in software contexts.&lt;br&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%2F1sdbz86fesjdr8k64n8p.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%2F1sdbz86fesjdr8k64n8p.png" alt="Image description" width="524" height="404"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Connector/Off-page Connector:&lt;/strong&gt;&lt;br&gt;
Used when there isn’t enough space to continue the flow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Connector:&lt;/strong&gt; Used for connections on the same page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off-page Connector:&lt;/strong&gt; Used for linking to a flow on a different page.
&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%2F2vhixdhmh0fgypz2cnvj.png" alt="Image description" width="800" height="343"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Arrow:&lt;/strong&gt;&lt;br&gt;
The core of a flowchart, showing the next step in the process. Typically, arrows point forward. Dotted lines may be used for annotations or indirect references to another symbol or explanatory block.&lt;br&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%2Folvz3hsvcqbtrbp8ea6d.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%2Folvz3hsvcqbtrbp8ea6d.png" alt="Image description" width="796" height="366"&gt;&lt;/a&gt;&lt;br&gt;
With these symbols, you can create clear and effective flowcharts for a variety of purposes!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;p&gt;Here are two examples showcasing how these icons can be applied:&lt;/p&gt;

&lt;h3&gt;
  
  
  Login Process
&lt;/h3&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%2Fe3fu1hd52ro63xdzdo7e.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%2Fe3fu1hd52ro63xdzdo7e.png" alt="Image description" width="800" height="1263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Recruitment Process
&lt;/h3&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%2Fkjvbfg90oro55tjcr9pm.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%2Fkjvbfg90oro55tjcr9pm.png" alt="Image description" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Flowchart for Better Problem-Solving
&lt;/h2&gt;

&lt;p&gt;Powerful tools like draw.io offer built-in flowchart symbols, making them feature-rich but often accompanied by a steeper learning curve. In contrast, simpler and more intuitive tools like Whimsical and Excalidraw require users to create their own symbol libraries, offering greater flexibility but requiring a bit of setup.&lt;/p&gt;

&lt;p&gt;Since I personally rely on Excalidraw for creating flowcharts, all the symbols featured in this article were drawn using it. If you’re an Excalidraw user as well, I’ve created a &lt;a href="https://libraries.excalidraw.com/?theme=light&amp;amp;sort=default#finfin-flow-chart-symbols" rel="noopener noreferrer"&gt;Flow Chart Symbols library&lt;/a&gt; that you can explore. Give it a try, and I’d love to hear your feedback to make it even better!&lt;/p&gt;

&lt;p&gt;Flowcharts make it easier to visualize the entire picture, and Excalidraw’s intuitive design simplifies the process of creating and refining these visualizations. By clearly mapping out processes, we can quickly identify and focus on areas that require improvement or fixing, leaving more time for deeper problem-solving and generating insights. This makes it an invaluable tool for enhancing clarity and driving effective solutions.&lt;/p&gt;

</description>
      <category>visualization</category>
      <category>development</category>
    </item>
    <item>
      <title>Naming with Acronyms in PascalCase and camelCase</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Sat, 07 Dec 2024 03:02:07 +0000</pubDate>
      <link>https://dev.to/finfin/naming-with-acronyms-in-pascalcase-and-camelcase-4m3</link>
      <guid>https://dev.to/finfin/naming-with-acronyms-in-pascalcase-and-camelcase-4m3</guid>
      <description>&lt;p&gt;Naming is hard, really. There is a famous browser API &lt;code&gt;XMLHttpRequest&lt;/code&gt;, it’s famous in part for its mixed naming strategy. Here, "XML" is fully capitalized, while "Http" follows a capitalized first letter, leading to an inconsistent and somewhat perplexing naming style.&lt;/p&gt;

&lt;p&gt;…until you bumped into it yourself. See how you will name these:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;xml http request&lt;/code&gt;, &lt;code&gt;new customer id&lt;/code&gt;, &lt;code&gt;supports IPv6 on iOS&lt;/code&gt;, &lt;code&gt;YouTube importer&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Handling Acronyms in Naming Conventions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When incorporating acronyms into identifiers, developers generally adopt one of two approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Preserve Original Capitalization:&lt;/strong&gt; Maintain the acronym's original uppercase letters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treat Acronyms as Words:&lt;/strong&gt; Apply standard naming conventions, capitalizing only the first letter in PascalCase or the first letter of subsequent words in camelCase.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For instance, consider the term "XML HTTP request":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Preserving Original Capitalization:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PascalCase: &lt;code&gt;XMLHTTPRequest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;camelCase: &lt;code&gt;xMLHTTPRequest&lt;/code&gt; or &lt;code&gt;xmlHTTPRequest&lt;/code&gt; (both appear awkward and conflict with camelCase norms)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Treating Acronyms as Words:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PascalCase: &lt;code&gt;XmlHttpRequest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;camelCase: &lt;code&gt;xmlHttpRequest&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The second approach aligns better with naming conventions, ensuring consistency and readability.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Challenges with Special Cases&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Certain names, like "iOS," intentionally start with a lowercase letter. In such cases, adhering strictly to original capitalization can clash with naming conventions. For example, in PascalCase, "iOS Adapter" would become &lt;code&gt;IOSAdapter&lt;/code&gt;, which alters the intended capitalization.&lt;/p&gt;

&lt;p&gt;Similarly, names containing numbers, such as "IPv6," present challenges. Deciding whether to treat "IPv6" as a single word or split it into "IpV6" affects the final identifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Google's Guidelines&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Fortunately, Google offers a &lt;a href="https://google.github.io/styleguide/jsguide.html#naming-camel-case-defined" rel="noopener noreferrer"&gt;systematic approach to handle these scenarios&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Convert to ASCII and Remove Apostrophes:&lt;/strong&gt; For example, "Müller's algorithm" becomes "Muellers algorithm."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Split into Words:&lt;/strong&gt; Divide based on spaces and remaining punctuation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apply Common Conventions:&lt;/strong&gt; If a word has a conventional camelCase form, use it (e.g., "AdWords" becomes "adWords").&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Combine According to Naming Rules:&lt;/strong&gt; Assemble the words following the desired naming convention.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notably, Google's guidelines don't specify exact treatments for acronyms, but the process of splitting into words typically treats acronyms as individual words, leading to identifiers like &lt;code&gt;XmlHttpRequest&lt;/code&gt; or &lt;code&gt;xmlHttpRequest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages of Treating Acronyms as Words&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This method simplifies transitions between different naming conventions (e.g., camelCase to snake_case) and ensures consistency across various codebases. For instance, converting &lt;code&gt;XmlHttpRequest&lt;/code&gt; to snake_case results in &lt;code&gt;xml_http_request&lt;/code&gt;, which is straightforward. In contrast, preserving original capitalization can complicate such conversions.&lt;/p&gt;

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

&lt;p&gt;When dealing with acronyms in naming conventions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Split the term into individual words or components.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apply the standard naming convention rules to each component.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach promotes clarity and consistency, though it may require adjusting to seeing acronyms like "ID" rendered as "Id" or "URL" as "Url."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adhere to the convention, the naming from first section would be:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;"XML HTTP request" → &lt;code&gt;XmlHttpRequest&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"new customer ID" → &lt;code&gt;NewCustomerId&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"supports IPv6 on iOS" → &lt;code&gt;SupportsIpv6OnIos&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"YouTube importer" → &lt;code&gt;YouTubeImporter&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By adopting this strategy, developers can maintain a consistent and readable codebase, even when faced with complex naming scenarios. &lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://google.github.io/styleguide/jsguide.html#naming-camel-case-defined" rel="noopener noreferrer"&gt;https://google.github.io/styleguide/jsguide.html#naming-camel-case-defined&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/15526107/acronyms-in-camelcase" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/15526107/acronyms-in-camelcase&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>softwareengineering</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Implementing Dark Mode and Theme Switching using Tailwind v4 and Next.js</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Fri, 29 Nov 2024 23:37:59 +0000</pubDate>
      <link>https://dev.to/finfin/implementing-dark-mode-and-theme-switching-using-tailwind-v4-and-nextjs-30f2</link>
      <guid>https://dev.to/finfin/implementing-dark-mode-and-theme-switching-using-tailwind-v4-and-nextjs-30f2</guid>
      <description>&lt;p&gt;Dark mode support has become a fundamental aspect of modern web applications, and I recently tackled this feature for my personal blog using Tailwind CSS v4 beta with Next.js 15. In this article, I'll walk through my journey of implementing a dynamic theme toggle, share my learnings of both frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Goal: Customizable Website Appearance
&lt;/h2&gt;

&lt;p&gt;Before diving into the implementation details, let's examine what we're building. The goal is to create a theme selection feature that provides a seamless experience while respecting user preferences. When visiting the site, users will have three theme options available: Light, Dark, and Auto (System setting). This behavior mirrors &lt;a href="https://stackoverflow.com/users/preferences/" rel="noopener noreferrer"&gt;Stack Overflow's theme selection system&lt;/a&gt;, which provides an intuitive and well-tested approach to theme management.&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%2Frv7m1jg6cl8lmif9gmsu.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%2Frv7m1jg6cl8lmif9gmsu.png" alt="Stack Overflow's theme selection" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This mechanism use Auto mode as default, which adapts to the user's device theme preferences. Users can then override this by selecting either Light or Dark mode, and their choice will persist across visits to provide a consistent experience. This combination of intelligent defaults and preserved user preferences ensures the site remains comfortable to use across different viewing conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Tailwind v4
&lt;/h2&gt;

&lt;p&gt;To begin experimenting with Tailwind v4, I first needed to set up the framework in my project. The installation process proved remarkably straightforward, even simpler than its predecessor, version 3.&lt;/p&gt;

&lt;p&gt;Working with Next.js, the setup process consisted of just three simple steps. The first step was installing the required packages through npm: &lt;a href="https://tailwindcss.com/docs/v4-beta#installing-with-post-css" rel="noopener noreferrer"&gt;reference&lt;/a&gt;&lt;br&gt;
&lt;/p&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;tailwindcss@next @tailwindcss/postcss@next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, update postcss configuration to use Tailwind's new PostCSS plugin. The configuration is notably simpler compared to previous versions, requiring just a single plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tailwindcss/postcss&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, imported Tailwind into main CSS file using a straightforward import statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 0: Leveraging Tailwind's Default Dark Mode
&lt;/h2&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%2Fpwu80tlikgyvrcik8b6w.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.amazonaws.com%2Fuploads%2Farticles%2Fpwu80tlikgyvrcik8b6w.gif" alt="Default behavior of tailwind dark variant" width="726" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tailwind provides built-in support for dark mode through its &lt;code&gt;dark&lt;/code&gt; variant. When we use utility classes like &lt;code&gt;text-black dark:text-white&lt;/code&gt;, Tailwind will automatically switch between these styles based on the user's system preferences.&lt;/p&gt;

&lt;p&gt;Let's look at a simple blog post card component and understand how Tailwind handles its dark mode styling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;BlogCard&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="nx"&gt;content&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;article&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;"text-black bg-white dark:text-white dark:bg-black p-4 rounded-lg"&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;h2&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;"text-xl text-gray-900 dark:text-gray-100"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&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;p&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;"text-gray-600 dark:text-gray-300"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&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;article&lt;/span&gt;&lt;span class="p"&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;p&gt;When Tailwind processes our utility classes, it generates CSS that uses the &lt;code&gt;prefers-color-scheme&lt;/code&gt; media query. For example, the &lt;code&gt;dark:text-white&lt;/code&gt; class gets transformed into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dark&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nd"&gt;:text-white&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@media&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="py"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-white&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This CSS transformation reveals two important aspects of how Tailwind v4 works. First, it uses the &lt;code&gt;prefers-color-scheme&lt;/code&gt; media query to detect system theme preferences. When a user's system is set to dark mode, any styles within this media query automatically activate. Second, and notably in v4, it leverages CSS variables for colors. Instead of hardcoding color values, Tailwind now uses CSS custom properties (like &lt;code&gt;var(--color-white)&lt;/code&gt;), making the entire theming system more flexible and performant. These variables are defined at the root level of your CSS, allowing for easy theme customization and manipulation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Custom Theme Control
&lt;/h2&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%2Fazaxhty7gkvorfftiocd.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.amazonaws.com%2Fuploads%2Farticles%2Fazaxhty7gkvorfftiocd.gif" alt="Image description" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we'll enhance our setup to support explicit theme selection through a dropdown select. This involves three key pieces: configuring Tailwind to respect our custom theme attribute, creating a theme management system, and an user interface for theme selection.&lt;/p&gt;

&lt;p&gt;One of the most significant changes when implementing dark mode in v4 is the shift in how dark variants are configured. In Tailwind v3, dark mode configuration was managed through the &lt;code&gt;tailwind.config.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** For Tailwind v3 */&lt;/span&gt;
&lt;span class="cm"&gt;/** @type {import('tailwindcss').Config} */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;darkMode&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;variant&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="s1"&gt;&amp;amp;:not(.light *)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In v4, we define our dark variant behavior alongside our Tailwind import, in the main css file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@variant&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;data-theme&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of relying heavily on the JavaScript configuration file, v4 moves configuration options directly into CSS. For example, we can now define custom colors as CSS variables (&lt;code&gt;--color-primary: #ff0000;&lt;/code&gt;), configure variants using &lt;code&gt;@variant&lt;/code&gt; directives.&lt;/p&gt;

&lt;p&gt;Now our configuration tells Tailwind to apply dark styles when it finds a &lt;code&gt;data-theme="dark"&lt;/code&gt; attribute, rather than relying on system preferences. &lt;/p&gt;

&lt;p&gt;Then, we need a place to manage this &lt;code&gt;data-theme&lt;/code&gt; attribute. Hence, let’s create our theme management system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ThemeProvider.jsx&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;createContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useContext&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="nx"&gt;useEffect&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&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;ThemeContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&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;ThemeProvider&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="kd"&gt;const&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;setTheme&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Synchronize theme state with HTML data attribute&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&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;theme&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Provide theme context to children&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;&lt;/span&gt;&lt;span class="nc"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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;setTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&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="c1"&gt;// Custom hook for easy theme access&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;useTheme&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;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ThemeContext&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;context&lt;/span&gt; &lt;span class="o"&gt;===&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;useTheme must be used within a ThemeProvider&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;return&lt;/span&gt; &lt;span class="nx"&gt;context&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;Then, we create a select component that gives users explicit control over their theme choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ThemeSelect.jsx&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;ThemeSelect&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTheme&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;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; 
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&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;"bg-white dark:bg-gray-800 text-black dark:text-white p-2 rounded border border-gray-300 dark:border-gray-600"&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;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"light"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Light&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&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;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Dark&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&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;select&lt;/span&gt;&lt;span class="p"&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;p&gt;Finally, we wrap our application with the ThemeProvider and place the ThemeSelect component where we want the theme controls to appear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// App.jsx&lt;/span&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"min-h-screen bg-white dark:bg-gray-900"&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;nav&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;"p-4"&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;ThemeSelect&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;nav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Rest of your application */&lt;/span&gt;&lt;span class="si"&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="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt;&lt;span class="p"&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;p&gt;Now user can change the theme by selecting instead of changing system setting. Next, we'll extend this feature to support an "auto" option that respects system preferences , just like phase 0.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Note About Generated CSS and Browser Compatibility
&lt;/h3&gt;

&lt;p&gt;Tailwind v4 beta generates dark mode utilities using modern CSS nesting syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dark&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nd"&gt;:text-white&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;&amp;amp;:where([data-theme="dark"],&lt;/span&gt; &lt;span class="err"&gt;[data-theme="dark"]&lt;/span&gt; &lt;span class="err"&gt;*)&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-white&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As CSS nesting is a &lt;a href="https://caniuse.com/css-nesting" rel="noopener noreferrer"&gt;relatively new feature&lt;/a&gt;, there are ongoing discussions about browser compatibility in the Tailwind community (see &lt;a href="https://github.com/tailwindlabs/tailwindcss/issues/14753" rel="noopener noreferrer"&gt;GitHub issue #14753&lt;/a&gt;). Since v4 is in beta, this implementation might change based on community feedback and compatibility concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: System-Aware Theme Switching
&lt;/h2&gt;

&lt;p&gt;After setting up manual theme selection in Phase 1, we're now ready to add system preference detection. &lt;/p&gt;

&lt;p&gt;The centerpiece of this enhancement is the &lt;code&gt;window.matchMedia('(prefers-color-scheme: dark)')&lt;/code&gt; API. This gives us a way to not only check the current system theme but also respond when it changes. Think of it like subscribing to your system's theme announcements - whenever the user toggles their system theme, our site will hear about it and react accordingly. It’s an javascript equivalent of &lt;code&gt;@media (prefers-color-scheme: dark)&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;First, let's update our theme management to react to system preferences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ThemeProvider.jsx&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;createContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useContext&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="nx"&gt;useEffect&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&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;ThemeContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&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;ThemeProvider&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="kd"&gt;const&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;setTheme&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// Update data-theme accordingly if user selects light or dark&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;theme&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&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;}&lt;/span&gt;

    &lt;span class="c1"&gt;// For auto mode, we need to watch system preferences&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediaQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Set initial theme based on system preference&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;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&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="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Update theme when system preference changes&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&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;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&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="s1"&gt;light&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="nx"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleChange&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;gt;&lt;/span&gt; &lt;span class="nx"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleChange&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;theme&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;&lt;/span&gt;&lt;span class="nc"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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;setTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&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;p&gt;Now we expand our select component to include the auto option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;ThemeSelect&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTheme&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;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; 
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&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;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"light"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Light&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&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;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Dark&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&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;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Auto&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&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;select&lt;/span&gt;&lt;span class="p"&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;p&gt;Users can now choose between explicit light/dark themes or let their system preferences automatically control the theme. When in auto mode, the site smoothly transitions between themes as system preferences change, creating a seamless experience that respects both user choice and system settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Persisting Theme Preferences
&lt;/h2&gt;

&lt;p&gt;Our theme system works well, but it resets to 'auto' whenever the user refreshes the page. Let's improve the user experience by remembering their theme preference using localStorage. This way, when users return to our site, they'll see their chosen theme immediately.&lt;/p&gt;

&lt;p&gt;We'll modify our ThemeProvider to read and write to localStorage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ThemeProvider.jsx&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getInitialTheme&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Handle server-side rendering scenario&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Get theme from localStorage, defaulting to 'auto' if not found&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&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;function&lt;/span&gt; &lt;span class="nf"&gt;ThemeProvider&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="c1"&gt;// Initialize state with the result of getInitialTheme()&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&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="nx"&gt;getInitialTheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Rest of the provider implementation...&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="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;},&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="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="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&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;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediaQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&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;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&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="s1"&gt;light&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;handleChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&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;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&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="s1"&gt;light&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="nx"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleChange&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;gt;&lt;/span&gt; &lt;span class="nx"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleChange&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;theme&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;&lt;/span&gt;&lt;span class="nc"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&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;setTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&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;p&gt;A critical aspect is ensuring we have a valid theme value before the component even mounts by separating the initial theme logic into &lt;code&gt;getInitialTheme&lt;/code&gt;, and pass it directly to &lt;code&gt;useState&lt;/code&gt;. This prevents any potential flash of incorrect theme during the initial render. &lt;/p&gt;

&lt;p&gt;Now user preferences can persist across browser sessions, creating a more consistent and personalized experience. Whether a user prefers explicit light/dark modes or automatic system-based switching, their choice will be remembered the next time they visit our site.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Note About System Preferences and Browser Behavior
&lt;/h3&gt;

&lt;p&gt;Our &lt;code&gt;matchMedia&lt;/code&gt; implementation detects theme preferences as reported by the browser, not directly from the operating system. Some browsers, like Arc, have their own theme settings that can override the system preferences. This means in 'auto' mode, the theme will respond to whatever the browser reports as the current preference, which might come from either the system or browser-specific settings.&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%2Fc15xhunudtmdro4z25ht.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.amazonaws.com%2Fuploads%2Farticles%2Fc15xhunudtmdro4z25ht.gif" alt="Customizing appearance in Arc will override color scheme detection" width="726" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus Step: Using next-themes Library
&lt;/h2&gt;

&lt;p&gt;While building a custom theme implementation has been instructive, in real world we might want a shortcut, this is where you may consider using the &lt;a href="https://github.com/pacocoursey/next-themes" rel="noopener noreferrer"&gt;next-themes library&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To switch to next-themes, we first install the package:&lt;br&gt;
&lt;/p&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;next-themes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can replace our custom ThemeProvider with the one from next-themes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;ThemeProvider&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;next-themes&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;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt; &lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"data-theme"&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;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="si"&gt;}&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;ThemeProvider&lt;/span&gt;&lt;span class="p"&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;p&gt;&lt;code&gt;next-themes&lt;/code&gt; replaces the ThemeProvider we just created, with the same &lt;code&gt;useTheme&lt;/code&gt; hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;useTheme&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;next-themes&lt;/span&gt;&lt;span class="dl"&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;ThemeSelect&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTheme&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;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; 
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&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;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"light"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Light&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&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;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Dark&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&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;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Auto&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&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;select&lt;/span&gt;&lt;span class="p"&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why do I get server/client mismatch error?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When using &lt;code&gt;useTheme&lt;/code&gt;, you will use see a hydration mismatch error when rendering UI that relies on the current theme. This is because many of the values returned by &lt;code&gt;useTheme&lt;/code&gt; are undefined on the server, since we can't read &lt;code&gt;localStorage&lt;/code&gt; until mounting on the client. Add a &lt;code&gt;suppressHydrationWarning&lt;/code&gt; to the html tag to ignore this. See &lt;a href="https://github.com/pacocoursey/next-themes/tree/main?tab=readme-ov-file#avoid-hydration-mismatch" rel="noopener noreferrer"&gt;++example++&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pacocoursey/next-themes/tree/main?tab=readme-ov-file#faq" rel="noopener noreferrer"&gt;https://github.com/pacocoursey/next-themes/tree/main?tab=readme-ov-file#faq&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this implementation, we've actually built several core features that mirror next-themes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Theme management through &lt;code&gt;ThemeProvider&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatic handling of browser storage synchronization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Protection against flash of wrong theme on page load&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Support for system theme changes across all modern browsers&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While next-themes provides additional fine-grained features, understanding how to build a theme system from scratch, as we did in the previous steps, gives valuable insights into how theme switching works under the hood.&lt;/p&gt;

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

&lt;p&gt;Through this implementation journey, I've learned a lot: from Tailwind v4's elegant CSS-based approach and leveraging the &lt;code&gt;matchMedia&lt;/code&gt; API for system preferences, to solving the flash of wrong theme challenge. Hope this article helps you understand the nuances of implementing dark mode and inspires you to create more accessible and user-friendly web applications.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com/blog/tailwindcss-v4-alpha" rel="noopener noreferrer"&gt;Open-sourcing our progress on Tailwind CSS v4.0
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com/blog/tailwindcss-v4-beta" rel="noopener noreferrer"&gt;Tailwind CSS v4.0 Beta 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com/docs/v4-beta" rel="noopener noreferrer"&gt;Tailwind v4.0 Beta Document&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tailwindcss</category>
      <category>webdev</category>
      <category>nextjs</category>
      <category>react</category>
    </item>
    <item>
      <title>Thing You May Not Know About CSS Variables - 2. Using var() and Cool Examples</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Wed, 13 Nov 2024 10:36:00 +0000</pubDate>
      <link>https://dev.to/finfin/the-surprising-details-of-css-variables-2-using-var-and-cool-examples-jcj</link>
      <guid>https://dev.to/finfin/the-surprising-details-of-css-variables-2-using-var-and-cool-examples-jcj</guid>
      <description>&lt;p&gt;This is the second half of my CSS Variable post, &lt;a href="https://dev.to/finfin/things-you-may-not-know-about-css-variables-1-naming-rules-and-value-assignments-5aa9"&gt;the first half is here&lt;/a&gt;.&lt;br&gt;
In this article we'll look into the details of &lt;code&gt;var()&lt;/code&gt;. And two cool examples: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://codepen.io/finfin/pen/VwoqWPZ" rel="noopener noreferrer"&gt;Animation using CSS Variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codepen.io/finfin/pen/VwoqgKx" rel="noopener noreferrer"&gt;Pure CSS dark mode toggle with system setting detection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfm1mo5y3c85noseaki5.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.amazonaws.com%2Fuploads%2Farticles%2Fjfm1mo5y3c85noseaki5.gif" alt="Pure CSS dark mode toggle with system setting detection Preview" width="618" height="427"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Using &lt;code&gt;var()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;var()&lt;/code&gt; accesses custom property values (CSS variables). Its syntax is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;custom-property-name&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="nt"&gt;fallback-value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Basic Rules
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The first parameter must be a CSS variable:&lt;/strong&gt; Direct values, such as &lt;code&gt;var(20px)&lt;/code&gt;, will result in an error, as &lt;code&gt;var()&lt;/code&gt; only accepts custom property names.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;var()&lt;/code&gt; cannot replace property names:&lt;/strong&gt; In other words, you cannot write something like &lt;code&gt;var(--prop-name): 20px;&lt;/code&gt; because &lt;code&gt;var()&lt;/code&gt; is limited to use in property values only.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.foo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Error, 20px is not a CSS variable */&lt;/span&gt;

  &lt;span class="py"&gt;--prop-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;var(--prop-name):&lt;/span&gt; &lt;span class="err"&gt;20px;&lt;/span&gt; &lt;span class="c"&gt;/* Error, cannot use var() this way */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Detailed Behaviors
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;var(--b, fallback_value)&lt;/code&gt; Fallbacks:&lt;/strong&gt; The second parameter acts as a fallback value, used when &lt;code&gt;--b&lt;/code&gt; is invalid.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;var(--c,)&lt;/code&gt; Syntax with an Empty Fallback:&lt;/strong&gt; If the fallback value is left empty, the syntax remains valid and will default to an empty value if &lt;code&gt;--c&lt;/code&gt; is invalid.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple Comma:&lt;/strong&gt; In &lt;code&gt;var(--d, var(--e), var(--f), var(--g))&lt;/code&gt;, everything after the first comma is treated as fallback, so if &lt;code&gt;--d&lt;/code&gt; is invalid, the &lt;code&gt;var()&lt;/code&gt; expression evaluates &lt;code&gt;var(--e), var(--f), var(--g)&lt;/code&gt; as one fallback, to determine the result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;var()&lt;/code&gt; as a Complete CSS Token:&lt;/strong&gt; The function acts as a complete CSS token, like &lt;code&gt;20px&lt;/code&gt; would. Therefore, &lt;code&gt;var(--size)var(--unit)&lt;/code&gt; will not create &lt;code&gt;20px&lt;/code&gt; and is considered invalid.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;initial&lt;/code&gt; with CSS Variables:&lt;/strong&gt; Assigning &lt;code&gt;initial&lt;/code&gt; to a CSS variable means it is invalid. To display &lt;code&gt;initial&lt;/code&gt; as a value, it must be placed in the fallback.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;url()&lt;/code&gt; and &lt;code&gt;var()&lt;/code&gt; Usage:&lt;/strong&gt; Since &lt;code&gt;url()&lt;/code&gt; is treated as a complete CSS token, you need to define the full &lt;code&gt;url()&lt;/code&gt; within the variable.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* 1. */&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Uses 20px if --b is invalid */&lt;/span&gt;

  &lt;span class="c"&gt;/* 2. */&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--c&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Falls back to 20px if --c is invalid */&lt;/span&gt;

  &lt;span class="c"&gt;/* 3. */&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fonts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;"lucida grande"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tahoma&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Uses fallback font stack if --fonts is invalid */&lt;/span&gt;

  &lt;span class="c"&gt;/* 4. */&lt;/span&gt;
  &lt;span class="py"&gt;--text-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--text-unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--text-size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--text-unit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Invalid, as it does not resolve to 12px */&lt;/span&gt;

  &lt;span class="c"&gt;/* 5. */&lt;/span&gt;
  &lt;span class="py"&gt;--initialized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--initialized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Results in background: initial */&lt;/span&gt;

  &lt;span class="c"&gt;/* 6. */&lt;/span&gt;
  &lt;span class="py"&gt;--invalid-url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"https://useme.medium.com"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url(var(--invalid-url)&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Invalid, as url() cannot parse var() */&lt;/span&gt;

  &lt;span class="py"&gt;--valid-url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url(https://useme.medium.com)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--valid-url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Correct usage */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Variable Resolution and Scope
&lt;/h3&gt;

&lt;p&gt;CSS variables, like other CSS properties, follow CSS-specific rules for scope and specificity. Understanding how these factors affect CSS variables allows for more precise control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global and Scoped Variables:&lt;/strong&gt;&lt;br&gt;
Variables defined in &lt;code&gt;:root&lt;/code&gt; are applied globally, while those defined in selectors have a more limited scope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;   &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--main-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Globally applied */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--main-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Scoped, applies only within .container */&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;Priority by Specificity:&lt;/strong&gt; &lt;br&gt;
Higher specificity will override lower specificity for CSS variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;   &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--main-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--main-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Overrides :root definition */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.section&lt;/span&gt; &lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--main-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Shows green */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--main-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Shows blue */&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 html"&gt;&lt;code&gt;   &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"section"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This text will use .section's --main-color and be green.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

   &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This text will use :root's --main-color and be blue.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Calculating Values Based on Specificity Order:&lt;/strong&gt; &lt;br&gt;
Like CSS properties, variables are resolved based on specificity in ascending order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;   &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--red&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="py"&gt;--green&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="py"&gt;--blue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="py"&gt;--background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--red&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--green&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--blue&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--green&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--background&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;In this example, the background color of &lt;code&gt;.box&lt;/code&gt; remains white, as &lt;code&gt;--background&lt;/code&gt; was resolved to &lt;code&gt;rgb(255, 255, 255)&lt;/code&gt; before &lt;code&gt;.box&lt;/code&gt; redefined &lt;code&gt;--green: 0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reevaluating Variables with Pseudo-Classes:&lt;/strong&gt; &lt;br&gt;
Variables change based on pseudo-class states when defined at the same level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;   &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--red&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="py"&gt;--green&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="py"&gt;--blue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--red&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--green&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--blue&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
     &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--background&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.box&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--green&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Changes background color on hover */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Next, let’s explore some advanced use cases for CSS variables:&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage Example A: Animations
&lt;/h2&gt;

&lt;p&gt;CSS variables cannot be directly animated because the browser cannot infer the data type. To resolve this, use &lt;code&gt;@property&lt;/code&gt; to define the variable's type and initial value, enabling the browser to understand how to animate the variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@property&lt;/span&gt; &lt;span class="n"&gt;--green&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"&amp;lt;number&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;initial-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inherits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--green&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--green&lt;/span&gt; &lt;span class="m"&gt;0.5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.section&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--green&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&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 html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"section"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hover to see CSS variables + animation in action.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, &lt;code&gt;@property&lt;/code&gt; is used to declare &lt;code&gt;--green&lt;/code&gt; as a &lt;code&gt;&amp;lt;number&amp;gt;&lt;/code&gt; type with an initial value of 255. When hovering over &lt;code&gt;.section&lt;/code&gt;, &lt;code&gt;--green&lt;/code&gt; changes to 50, creating a smooth color transition effect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codepen.io/finfin/pen/VwoqWPZ" rel="noopener noreferrer"&gt;CodePen example&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Usage Example B: Light/Dark Mode Toggle
&lt;/h2&gt;

&lt;p&gt;If you want to implement theme switching for light and dark modes, it’s helpful to extract color values into variables that adjust automatically based on the &lt;code&gt;prefers-color-scheme&lt;/code&gt; setting. Here’s how you can manage this using CSS variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#FBFBFB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--container-background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#EBEBEB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--headline-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0077EE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#333333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;    
    &lt;span class="py"&gt;--background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#121212&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--container-background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#555555&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--headline-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#94B2E6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&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;&lt;strong&gt;Adding a Manual Toggle that Aligns with System Preferences&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While the system setting controls the theme by default, we may want to give users the option to manually toggle between light and dark themes. To achieve this, we can add a checkbox to toggle the state. Ideally, when the checkbox is selected, it indicates dark mode, and when unselected, it represents light mode.&lt;/p&gt;

&lt;p&gt;However, CSS cannot automatically detect system settings and change the checkbox state accordingly, especially in dark mode. To handle this limitation, we can use CSS variables and the &lt;code&gt;:has()&lt;/code&gt; selector to control theme switching based on the checkbox state.&lt;/p&gt;

&lt;p&gt;I wanted to try achieving this entirely with CSS, but since a user’s system may be set to either light or dark mode, CSS alone can’t automatically check the checkbox in dark mode.&lt;/p&gt;

&lt;p&gt;If we can’t move the mountain, we’ll route the path. Here’s the workaround:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We’ll use CSS to create a toggle switch, where the visual “OFF” state represents light mode, and “ON” represents dark mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpgvdf6z5dzt032ury1lv.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%2Fpgvdf6z5dzt032ury1lv.png" alt="ON state: Light Theme" width="180" height="120"&gt;&lt;/a&gt;&lt;br&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%2Foyaxy7215ne2uckec27e.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%2Foyaxy7215ne2uckec27e.png" alt="OFF state: Dark Theme" width="212" height="132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;When system sets to light mode:&lt;/strong&gt; When the checkbox is unselected, it corresponds to the “OFF” state (light mode). When selected, it corresponds to the “ON” state (dark mode).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;When system sets to dark mode:&lt;/strong&gt; Since the system preference is reversed, the visual state also inverts. When the checkbox is unselected, it corresponds to “ON” (dark mode). When selected, it corresponds to “OFF” (light mode).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve this effect, we need two main elements:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: Variables that Change Based on System Setting and Checkbox State&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#FBFBFB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--container-background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#EBEBEB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--headline-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0077EE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#333333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;:root:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"checkbox"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#121212&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--container-background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#555555&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--headline-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#94B2E6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;    
    &lt;span class="py"&gt;--background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#121212&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--container-background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#555555&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--headline-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#94B2E6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;:root:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"checkbox"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#FBFBFB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--container-background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#EBEBEB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--headline-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0077EE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#333333&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;&lt;strong&gt;Second: Toggle Behavior Based on System Settings for &lt;code&gt;checked&lt;/code&gt; State and ON/OFF Representation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The light and dark mode CSS properties are reversed depending on the system setting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Toggle Switch Styles */&lt;/span&gt;
&lt;span class="nc"&gt;.switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;34px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Hide the checkbox element to style a custom switch */&lt;/span&gt;
&lt;span class="nc"&gt;.switch&lt;/span&gt; &lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Slider styling for the switch background */&lt;/span&gt;
&lt;span class="nc"&gt;.slider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--slider-bg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.4s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;34px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Slider knob styling */&lt;/span&gt;
&lt;span class="nc"&gt;.slider&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;26px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;26px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.4s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Dark mode styles: make the switch look "checked" by default */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.slider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#94b2e6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.slider&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;26px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Move knob to the right */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;/* Invert checked state in dark mode to look "unchecked" */&lt;/span&gt;
  &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;.slider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;.slider&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Move knob to the left */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Light mode styles: make the switch look "unchecked" by default */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.slider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.slider&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Knob on the left */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;/* Invert checked state in light mode to look "checked" */&lt;/span&gt;
  &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;.slider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#94b2e6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;.slider&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;26px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Move knob to the right */&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;Simplifying Variable Setup with CSS Variable Tricks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here we’ll use &lt;a href="https://github.com/propjockey/css-sweeper?tab=readme-ov-file#css-is-a-programming-language-thanks-to-the-space-toggle-trick" rel="noopener noreferrer"&gt;Space Toggle&lt;/a&gt; technique to simplify variable settings. Here’s the code, followed by an explanation of how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--ON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Default state variable to use for switching colors */&lt;/span&gt;
  &lt;span class="py"&gt;--OFF&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Alternative state variable for switching colors */&lt;/span&gt;

  &lt;span class="c"&gt;/* Set default color variables based on light mode */&lt;/span&gt;
  &lt;span class="py"&gt;--light&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ON&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--OFF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c"&gt;/* Define custom properties for colors used in light and dark modes */&lt;/span&gt;
  &lt;span class="py"&gt;--background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#fbfbfb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#121212&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--container-background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ebebeb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#555555&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--headline-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#0077ee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#94b2e6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#333333&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;:root:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"checkbox"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--light&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--OFF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ON&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;The key here is in the line &lt;code&gt;--background-color: var(--light, #fbfbfb) var(--dark, #121212);&lt;/code&gt;. Here, the background color depends on the values of &lt;code&gt;--light&lt;/code&gt; and &lt;code&gt;--dark&lt;/code&gt;, effectively simulating an &lt;code&gt;if/else&lt;/code&gt; in the property.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt; Initially, &lt;code&gt;--light: var(--ON);&lt;/code&gt; and &lt;code&gt;--ON: initial;&lt;/code&gt; make &lt;code&gt;--ON&lt;/code&gt; an invalid state. Meanwhile, &lt;code&gt;--OFF&lt;/code&gt; is set as an empty string. When applied to &lt;code&gt;var(--light, #fbfbfb) var(--dark, #121212)&lt;/code&gt;, the invalid &lt;code&gt;--light&lt;/code&gt; variable will default to &lt;code&gt;#fbfbfb&lt;/code&gt;, and the valid &lt;code&gt;--dark&lt;/code&gt; variable (empty) allows &lt;code&gt;--background-color&lt;/code&gt; to equal &lt;code&gt;#fbfbfb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All the other color variables follow the same logic, adjusting based on the state of &lt;code&gt;--light&lt;/code&gt; and &lt;code&gt;--dark&lt;/code&gt;. This way, each color variable only needs to be defined once.&lt;/p&gt;

&lt;p&gt;Switching states becomes simple. If dark mode is active, use &lt;code&gt;--light: var(--OFF);&lt;/code&gt; and &lt;code&gt;--dark: var(--ON);&lt;/code&gt;. In light mode, reverse them. Though not immediately intuitive, this method is currently the most effective with CSS. If there are better solutions, they are worth exploring.&lt;/p&gt;

&lt;p&gt;Complete example: &lt;a href="https://codepen.io/finfin/pen/VwoqgKx" rel="noopener noreferrer"&gt;CodePen Example&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;CSS continues to evolve, with CSS variables available in major browsers since 2016. New features like &lt;code&gt;@property&lt;/code&gt; and &lt;code&gt;:has()&lt;/code&gt; are expanding CSS variables’ flexibility even further. Combined with other new tools, CSS variables are becoming more powerful—for instance, they can now enhance scroll-driven animations to create visually dynamic effects. As a core element for storing state in CSS, much like variables in any programming language, a solid understanding of CSS variables will prove invaluable for more sophisticated styling and design down the road.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/42330075/is-there-a-way-to-interpolate-css-variables-with-url/42331003#42331003" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/42330075/is-there-a-way-to-interpolate-css-variables-with-url/42331003#42331003&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kizu.dev/cyclic-toggles/#was-this-always-possible" rel="noopener noreferrer"&gt;https://kizu.dev/cyclic-toggles/#was-this-always-possible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/afif/what-no-one-told-you-about-css-variables-553o"&gt;https://dev.to/afif/what-no-one-told-you-about-css-variables-553o&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hackernoon.com/cool-css-variable-tricks-to-try-uyu35e9" rel="noopener noreferrer"&gt;https://hackernoon.com/cool-css-variable-tricks-to-try-uyu35e9&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lea.verou.me/blog/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/" rel="noopener noreferrer"&gt;https://lea.verou.me/blog/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Thing You May Not Know About CSS Variables - 1. Naming Rules and Value Assignments</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Thu, 07 Nov 2024 04:39:50 +0000</pubDate>
      <link>https://dev.to/finfin/things-you-may-not-know-about-css-variables-1-naming-rules-and-value-assignments-5aa9</link>
      <guid>https://dev.to/finfin/things-you-may-not-know-about-css-variables-1-naming-rules-and-value-assignments-5aa9</guid>
      <description>&lt;p&gt;In my exploration of CSS variables, I discovered a lot of exciting potential and subtle details that aren’t always apparent at first glance. Like many developers, I initially used CSS variables in simple, straightforward ways, rarely encountering edge cases. This approach is common and effective for many purposes, but it means there’s a lot more to explore and experiment with. In my view, gaining a deeper understanding of CSS variable naming rules and valid value assignments can significantly expand the range and flexibility of their applications.&lt;/p&gt;

&lt;p&gt;I’ve compiled my research and insights here, aiming to provide a more thorough and comprehensive look at CSS variables. My hope is that this article will be a resource to help both you and myself unlock the full potential of CSS variables and discover new possibilities in styling.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This series assumes you’re already familiar with the basics of CSS variables, such as the &lt;code&gt;--name: value&lt;/code&gt; syntax and the &lt;code&gt;var(--name)&lt;/code&gt; function, or that you’re comfortable with the foundational concepts covered in the first third of this &lt;a href="https://blog.logrocket.com/how-to-use-css-variables-like-a-pro/" rel="noopener noreferrer"&gt;CSS Variables Introduction&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Naming Rules
&lt;/h1&gt;

&lt;p&gt;When using CSS variables, naming is the first essential step. Here are some key guidelines for naming CSS variables:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Basic Prefix&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;All custom property names must begin with two hyphens (&lt;code&gt;--&lt;/code&gt;), for example, &lt;code&gt;--main-color&lt;/code&gt;. This prefix helps the browser distinguish custom properties from native CSS properties, reducing the chance of conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Case Sensitivity&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;CSS custom property names are case-sensitive, meaning &lt;code&gt;--main-color&lt;/code&gt; and &lt;code&gt;--Main-Color&lt;/code&gt; are treated as two entirely different variables. It's important to maintain consistent casing throughout your code to avoid mistakenly referencing variables with mismatched cases, which can lead to parsing errors.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;   &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--main-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="py"&gt;--Main-Color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Case difference, treated as a separate property */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.box1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--main-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Refers to --main-color, resulting in blue */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.box2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--Main-Color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Refers to --Main-Color, resulting in red */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Reserved Words:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A standalone &lt;code&gt;--&lt;/code&gt; is reserved and may have specific purposes in future specifications. Therefore, avoid using it as a custom property name.&lt;br&gt;
   &lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;   &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;--&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Invalid: a standalone -- is reserved */&lt;/span&gt;
     &lt;span class="py"&gt;--custom-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Valid: custom property avoids reserved name */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--custom-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Uses the custom property */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Naming Restrictions:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When naming CSS variables, it’s crucial to consider allowed characters and conventions, which improve readability and prevent conflicts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Allowed Characters:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alphabetic Characters:&lt;/strong&gt; Uppercase and lowercase English letters (A-Z, a-z)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Numbers:&lt;/strong&gt; 0-9&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hyphens:&lt;/strong&gt; &lt;code&gt;-&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Underscores:&lt;/strong&gt; &lt;code&gt;_&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unicode Characters:&lt;/strong&gt; Emojis or non-Latin characters (Unicode range U+0080 and above)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escaping Special Characters:&lt;/strong&gt;
If a variable name includes special characters (such as &lt;code&gt;&amp;amp;&lt;/code&gt; or &lt;code&gt;?&lt;/code&gt;), it can be represented using escape characters (&lt;code&gt;\&lt;/code&gt;) or Unicode codes. For example, &lt;code&gt;&amp;amp;&lt;/code&gt; can be written as &lt;code&gt;\26&lt;/code&gt;. (&lt;a href="https://www.w3.org/TR/CSS22/syndata.html#characters" rel="noopener noreferrer"&gt;Refer to Syntax and basic data types - Characters and case&lt;/a&gt; for more details).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variable Length:&lt;/strong&gt;
While there are no strict limits on variable name length, for readability, it’s recommended to keep names under 50 characters. I did some experiment with Chromium, it can supports variable name length up to 1 million characters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
   The following example demonstrates the flexibility of CSS variable naming and how to handle special characters properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;   &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="c"&gt;/* Naming with Unicode characters */&lt;/span&gt;
     &lt;span class="py"&gt;--primary-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3498db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="py"&gt;--secondary-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e74c3c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

     &lt;span class="c"&gt;/* Using an Emoji as a variable name */&lt;/span&gt;
     &lt;span class="err"&gt;--😺:&lt;/span&gt; &lt;span class="err"&gt;#2ecc71;&lt;/span&gt;

     &lt;span class="c"&gt;/* Special characters with escape sequences, resulting in --B&amp;amp;W? */&lt;/span&gt;
     &lt;span class="err"&gt;--B\&amp;amp;W\?:&lt;/span&gt; &lt;span class="err"&gt;#2ecc72;&lt;/span&gt;

     &lt;span class="c"&gt;/* Using Unicode escape codes, equivalent to --B&amp;amp;W? */&lt;/span&gt;
     &lt;span class="err"&gt;--B\26W\3&lt;/span&gt;&lt;span class="py"&gt;F&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#abcdef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--B&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Result is #abcdef */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.container-alt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--B&lt;/span&gt;&lt;span class="err"&gt;\&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="err"&gt;\?&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Result is #abcdef */&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Assigning Values
&lt;/h1&gt;

&lt;p&gt;CSS variables can hold various types of values, but they also need to follow specific syntax rules. Let’s look at some examples of valid value assignments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--string-with-semicolon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"font-size: 16px;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Strings can include semicolons */&lt;/span&gt;
  &lt;span class="py"&gt;--complex-calc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c"&gt;/* Complex calculations with calc() */&lt;/span&gt;
  &lt;span class="py"&gt;--negative-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                     &lt;span class="c"&gt;/* Negative values */&lt;/span&gt;
  &lt;span class="py"&gt;--multiple-values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c"&gt;/* Multiple values, e.g., for margin or padding */&lt;/span&gt;
  &lt;span class="py"&gt;--unitless-number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                      &lt;span class="c"&gt;/* Unitless numbers */&lt;/span&gt;
  &lt;span class="py"&gt;--nested-var&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--string-with-semicolon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c"&gt;/* Nested variables using var() */&lt;/span&gt;
  &lt;span class="py"&gt;--empty-string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;                            &lt;span class="c"&gt;/* Empty string */&lt;/span&gt;
  &lt;span class="py"&gt;--color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;               &lt;span class="c"&gt;/* Color functions */&lt;/span&gt;
  &lt;span class="py"&gt;--special-chars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Content: \"Hello\""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c"&gt;/* Strings with special characters */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As these examples show, CSS variables support a wide range of values. However, since CSS variables are part of CSS properties, they must follow general CSS syntax, such as matching quotes and parentheses. Let’s explore some values that don’t meet these requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invalid Values
&lt;/h2&gt;

&lt;p&gt;Assigning invalid values to CSS variables can cause parsing errors, which may lead to issues in subsequent styles. Here are some examples of invalid value assignments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--incomplete-string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c"&gt;/* Invalid: missing closing quote */&lt;/span&gt;
  &lt;span class="py"&gt;--incomplete-url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;://&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Invalid: missing closing parenthesis */&lt;/span&gt;
  &lt;span class="py"&gt;--unmatched-parenthesis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Invalid: unclosed parenthesis in calc() */&lt;/span&gt;
  &lt;span class="py"&gt;--invalid-exclamation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c"&gt;/* Invalid: only valid exclamation use is !important */&lt;/span&gt;
  &lt;span class="py"&gt;--invalid-var&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--undefined-variable&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* Invalid: var() references an undefined variable with no fallback */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using &lt;code&gt;!important&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;CSS variables, like other CSS properties, support the &lt;code&gt;!important&lt;/code&gt; flag, which affects priority. Here’s an example showing how &lt;code&gt;!important&lt;/code&gt; impacts variable precedence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.cls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#target&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--text-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&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;In this example, both &lt;code&gt;#target&lt;/code&gt; and &lt;code&gt;.cls&lt;/code&gt; affect the &lt;code&gt;--text-color&lt;/code&gt; variable on the &lt;code&gt;div&lt;/code&gt;. By selector specificity, &lt;code&gt;#target&lt;/code&gt; should override &lt;code&gt;.cls&lt;/code&gt;. However, because &lt;code&gt;--text-color&lt;/code&gt; in &lt;code&gt;.cls&lt;/code&gt; uses &lt;code&gt;!important&lt;/code&gt;, the final color applied to &lt;code&gt;div&lt;/code&gt; will be green. It’s important to note that &lt;code&gt;!important&lt;/code&gt; only impacts priority among variables and does not propagate to the property referencing the variable. The resulting &lt;code&gt;color&lt;/code&gt; is simply &lt;code&gt;green&lt;/code&gt;, not &lt;code&gt;green !important&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;initial&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--invalid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial&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;Using &lt;code&gt;initial&lt;/code&gt; as a CSS variable value is a unique behavior. When assigned to a variable, &lt;code&gt;initial&lt;/code&gt; makes the variable invalid, causing it to fail when accessed with &lt;code&gt;var()&lt;/code&gt;. This differs from an empty value (&lt;code&gt;--empty: ;&lt;/code&gt;), which won’t cause a property to become invalid. Using &lt;code&gt;initial&lt;/code&gt; effectively “resets” a variable, canceling its definition. (&lt;a href="https://www.w3.org/TR/css-variables-1/#guaranteed-invalid" rel="noopener noreferrer"&gt;Reference&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency Cycles
&lt;/h2&gt;

&lt;p&gt;CSS variables support referencing other variables, but if a dependency cycle occurs, all variables in the cycle become invalid. Here’s an example of a dependency cycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--one&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--two&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--two&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--three&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--three&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--one&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;2&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;In this example, &lt;code&gt;--one&lt;/code&gt;, &lt;code&gt;--two&lt;/code&gt;, and &lt;code&gt;--three&lt;/code&gt; reference each other, creating a cycle. As a result, all three variables become invalid due to the circular dependency, which prevents them from resolving. (&lt;a href="https://www.w3.org/TR/css-variables-1/#cycles" rel="noopener noreferrer"&gt;Reference&lt;/a&gt;)&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In this article, we’ve taken a deep dive into the rules of naming and value assignments for CSS variables, covering how to properly define and manage these variables while avoiding common parsing errors. With this foundational knowledge, you should be able to use CSS variables more effectively in your future development projects. &lt;/p&gt;

&lt;p&gt;The next article will explore how to use &lt;code&gt;var()&lt;/code&gt; to retrieve CSS variable values, along with specific application scenarios to make your use of CSS variables even more flexible and powerful.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>A cursor trailing effect library for this Christmas</title>
      <dc:creator>Fin Chen</dc:creator>
      <pubDate>Sat, 25 Dec 2021 00:22:54 +0000</pubDate>
      <link>https://dev.to/finfin/a-cursor-trailing-effect-library-for-this-christmas-2mkg</link>
      <guid>https://dev.to/finfin/a-cursor-trailing-effect-library-for-this-christmas-2mkg</guid>
      <description>&lt;p&gt;We did a cursor trailing effect on our website. Learned how to do holiday effects the right way, experimented with Canvas and Transition, built a NPM library cursor-effect. &lt;br&gt;
Here's our story...&lt;/p&gt;
&lt;h1&gt;
  
  
  How it started
&lt;/h1&gt;

&lt;p&gt;I saw an ancient HTML spell the other day:&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;MARQUEE&amp;gt;&amp;lt;BLINK&amp;gt;&lt;/span&gt;
  How would you suppose this element behave?
&lt;span class="nt"&gt;&amp;lt;/BLINK&amp;gt;&amp;lt;/MARQUEE&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Surprised by the fact it still works&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fkpqjxukk12qmuh1dp94h.png" class="article-body-image-wrapper"&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%2Farticles%2Fkpqjxukk12qmuh1dp94h.png" alt="Good job safari..."&gt;&lt;/a&gt;&lt;/p&gt;
Good job safari



&lt;p&gt;It reminds me of the old days where cursor trail is a sexy effect. So I went googling but most result are telling you how to set cursor trail on Windows&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fk8uw3bgu23bvzisg07k2.jpeg" class="article-body-image-wrapper"&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%2Farticles%2Fk8uw3bgu23bvzisg07k2.jpeg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily there's &lt;a href="https://tholman.com/cursor-effects/" rel="noopener noreferrer"&gt;Cursor Effects (tholman.com)&lt;/a&gt; , which seem to be used on StackOverflow before. So we implemented the same effect on &lt;a href="https://yourator.co" rel="noopener noreferrer"&gt;our site: Yourator&lt;/a&gt;, with some customization tweaks. This post is what we've learned from implementing this effect, and we also published the effect as an npm library: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yourator/cursor-trails" rel="noopener noreferrer"&gt;Cursor Trails: https://github.com/yourator/cursor-trails&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2F4fh83ktiufv9700k3ybx.gif" class="article-body-image-wrapper"&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%2Farticles%2F4fh83ktiufv9700k3ybx.gif" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
the effect is up on production site til this weekend



&lt;h1&gt;
  
  
  Learn from the original cursor-effect library
&lt;/h1&gt;

&lt;p&gt;src: &lt;a href="https://github.com/tholman/cursor-effects/blob/master/src/snowflakeCursor.js" rel="noopener noreferrer"&gt;https://github.com/tholman/cursor-effects/blob/master/src/snowflakeCursor.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fb3e15679wz06ichbnrsr.png" class="article-body-image-wrapper"&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%2Farticles%2Fb3e15679wz06ichbnrsr.png" alt="init"&gt;&lt;/a&gt;&lt;/p&gt;
skeleton of init method



&lt;p&gt;We only needed snowflake effect, so this is what we'll be talking about. The main flow of starts from &lt;code&gt;init&lt;/code&gt;, which contains the basic working flow of this effect:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Initialize environment (canvas) for snowflakes&lt;/li&gt;
&lt;li&gt;Draw emoji character (possibleEmoji) on canvas&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bindEvents&lt;/code&gt; listens for mouse &amp;amp; touch event &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loop&lt;/code&gt; updates snowflake continuously &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;bindEvents&lt;/code&gt; listens for mouse &amp;amp; touch event
&lt;/h2&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fyhrmza4tayvzq4v0smqx.png" class="article-body-image-wrapper"&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%2Farticles%2Fyhrmza4tayvzq4v0smqx.png" alt="bindEvents"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're doing the same thing in &lt;code&gt;onMouseMove&lt;/code&gt; and &lt;code&gt;onTouchMove&lt;/code&gt; : call &lt;code&gt;addParticle&lt;/code&gt; upon the event and create a snowflake at where cursor is.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;onWindowResize&lt;/code&gt; is responsible for adjusting canvas size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Why not use CSS width: 100%, height: 100% ?

Since canvas is a canvas with assigned resolution, CSS can only adjust its visual size. If you create a canvas with 100px x 100px and stretch it to 200px x 1000px, then you'll have a 100px x 100px canvas (and pixels in it) which it stretched 2x wide and 10x long. So we need to adjust size of canvas according to window size.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="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%2Farticles%2Fous8ix16i8oi3f9scfks.png" class="article-body-image-wrapper"&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%2Farticles%2Fous8ix16i8oi3f9scfks.png" alt="loop"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last line of &lt;code&gt;init&lt;/code&gt; was a &lt;code&gt;loop()&lt;/code&gt; call, to create an infinite loop using requestAnimationFrame. This loop is responsible to update snowflake position and behaviors, it will call &lt;code&gt;update&lt;/code&gt; on each snowflake (particles), or clean up expiring snowflakes. This is the most CPU intensive part.&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2F1r8hrud6qqaxpjgnattg.png" class="article-body-image-wrapper"&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%2Farticles%2F1r8hrud6qqaxpjgnattg.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More detail on particle update: manage its own lifespan, position, rotating and draw snowflake emoji on canvas accordingly&lt;/p&gt;

&lt;h1&gt;
  
  
  Improvements
&lt;/h1&gt;

&lt;p&gt;The original cursor-effect repo is a effect we need. But to be used on our site we have to add some improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use image arrays to render customizable images &lt;/li&gt;
&lt;li&gt;We want to have more control over snowflake's behavior such as: appearing frequency, it's speed and life, etc.&lt;/li&gt;
&lt;li&gt;Touch event on touch device will trigger mousemove and touchstart at the same time, generating two (almost overlapping) snowflakes at once.&lt;/li&gt;
&lt;li&gt;We want to import this library through npm for easier maintainence&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What we do
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Use image arrays to render customizable images
&lt;/h2&gt;

&lt;p&gt;Change the  &lt;code&gt;fillText&lt;/code&gt; with &lt;code&gt;drawImage&lt;/code&gt;, also add calculation for snowflake opacity: &lt;code&gt;globalAlpha&lt;/code&gt; . Since there are several canvas context manipulation, we use &lt;code&gt;save&lt;/code&gt; &amp;amp; &lt;code&gt;restore&lt;/code&gt; to prevent polluting original context.&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2F9qn079sm9bgqwvs805sw.png" class="article-body-image-wrapper"&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%2Farticles%2F9qn079sm9bgqwvs805sw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And since image loading is async, we need &lt;code&gt;loadImage&lt;/code&gt; handles the image url array&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fue4z881n6ohpdcbrgizi.png" class="article-body-image-wrapper"&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%2Farticles%2Fue4z881n6ohpdcbrgizi.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the help of promise all (or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled" rel="noopener noreferrer"&gt;Promise.allSettled&lt;/a&gt;) , to load image before &lt;code&gt;init()&lt;/code&gt; call&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fczwb6veg7z01ihnafd3z.png" class="article-body-image-wrapper"&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%2Farticles%2Fczwb6veg7z01ihnafd3z.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Control snowflake's behavior over initialization options
&lt;/h2&gt;

&lt;p&gt;Main benefit is this boosts prototyping and discussion productivity, you can live-tweak and quickly show the result, or even hand the prototype to stakeholders and let them decide the behaviors.&lt;/p&gt;

&lt;p&gt;This part is simple, just don't forget the option defaults&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Faorr2t951ke0dxf72jqb.png" class="article-body-image-wrapper"&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%2Farticles%2Faorr2t951ke0dxf72jqb.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Touch event on touch device will trigger mousemove and touchstart
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bindEvent&lt;/code&gt; method listens for &lt;code&gt;mousemove&lt;/code&gt; &lt;code&gt;touchstart&lt;/code&gt; &lt;code&gt;touchmove&lt;/code&gt; , but Touch event on touch device will trigger mousemove and touchstart on user touch, causing excess particle creation, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent#event_order" rel="noopener noreferrer"&gt;you can see touch event order it on MDN&lt;/a&gt;. To prevent this we need to detect if this device is touch device or not&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fo29ztll8o1jumju1kod0.png" class="article-body-image-wrapper"&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%2Farticles%2Fo29ztll8o1jumju1kod0.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  NPM-ify for easier maintainence
&lt;/h2&gt;

&lt;p&gt;Uses ESM and published on NPM, use when in need.&lt;/p&gt;

&lt;p&gt; &lt;code&gt;npm install cursor-trails&lt;/code&gt; &lt;/p&gt;

&lt;h1&gt;
  
  
  Something learned about Canvas and Transition
&lt;/h1&gt;

&lt;p&gt;Which we adjust cursor-effect to deal with image loading, FPS drops significantly, thought it was because we throw too many image creating at a short time. Even rewrite one version using CSS Transition, just to find out it was because mass creating of SVG element consumes a lot of CPU.&lt;/p&gt;

&lt;p&gt;Canvas is very effective with drawing bitmap image in a fixed space. While CSS Transition suits animating DOM elements on the page. So creating lots of image element on canvas is more smooth then creating &lt;a href="" class="article-body-image-wrapper"&gt;&lt;img&gt;&lt;/a&gt; and transform it.&lt;/p&gt;

&lt;p&gt;I must highlight chrome's devtool's &lt;strong&gt;"rendering"&lt;/strong&gt; tab (edge has it also), especially two checked in this image&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fif3ued05j1dne73984m5.png" class="article-body-image-wrapper"&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%2Farticles%2Fif3ued05j1dne73984m5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;it shows fps and paint areas, as gif below&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fkzfpi0s7o6tekdru56ev.gif" class="article-body-image-wrapper"&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%2Farticles%2Fkzfpi0s7o6tekdru56ev.gif" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
This was a version where we use CSS Transition, you can see image repaint areas



&lt;h1&gt;
  
  
  Other considerations
&lt;/h1&gt;

&lt;p&gt;The effect is sexy (in a retro 90s way), but we shouldn't forgot this is not the main purpose of users on our site (they are here for job searching and career development). So after some discussion we decided to let this sexy feature reside only in the main search section at home page. It's spacey, it is what users first sees, it wont interfere with other thing users want to do. Hope this gets some balance with Christmas ambient and user's opration.&lt;/p&gt;

&lt;p&gt;We we're planning to use &lt;code&gt;prefers-reduced-motion&lt;/code&gt; to deal with low-end devices, but due to time limitation, that will be put on the roadmap.&lt;/p&gt;

&lt;p&gt;This library now only have snow falling effect, hope we can have more strategies on particle behavior. Maybe even customizable strategy, ex: fixed, floating, fadeout of cursor trailing effects.&lt;/p&gt;

&lt;p&gt;That's about it. &lt;br&gt;
🎄❄️🧑‍🎄 Merry Christmas 🎄❄️🧑‍🎄&lt;/p&gt;

&lt;p&gt;Here's our repo (again): &lt;a href="https://github.com/yourator/cursor-trails" rel="noopener noreferrer"&gt;https://github.com/yourator/cursor-trails&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;stackoverflow's april fools day effect
&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%2Farticles%2Ftgrm7sln8fwimba9v1m7.gif" alt="Image description"&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="http://web.archive.org/web/20200805225612/https://noahyamamoto.com/blog/mousetrailanimation" rel="noopener noreferrer"&gt;Mouse Trail - Noah Yamamoto (archive.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.humphd.org/the-technology-of-nostalgia/" rel="noopener noreferrer"&gt;The technology of nostalgia (humphd.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://css-tricks.com/when-to-use-svg-vs-when-to-use-canvas/" rel="noopener noreferrer"&gt;When to Use SVG vs. When to Use Canvas - CSS-Tricks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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