<?xml version="1.0" encoding="UTF-8"?><feed
	xmlns="http://www.w3.org/2005/Atom"
	xmlns:thr="http://purl.org/syndication/thread/1.0"
	xml:lang="en-US"
	
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
	>
	<title type="text">ChipLog — Christian Hammond</title>
	<subtitle type="text">Development, data, design, debugging</subtitle>

	<updated>2026-05-14T00:33:18Z</updated>

	<link rel="alternate" type="text/html" href="https://chipx86.blog" />
	<id>https://chipx86.blog/feed/atom/</id>
	<link rel="self" type="application/atom+xml" href="https://chipx86.blog/feed/atom/" />

	<generator uri="https://wordpress.org/" version="7.0">WordPress</generator>
	<entry>
		<author>
			<name>chipx86</name>
							<uri>http://chipx86.blog</uri>
						</author>

		<title type="html"><![CDATA[Inside Faxanadu, Part 2: Secret World Warp]]></title>
		<link rel="alternate" type="text/html" href="https://chipx86.blog/2026/05/13/inside-faxanadu-part-2-secret-world-warp/" />

		<id>https://chipx86.blog/?p=1638</id>
		<updated>2026-05-14T00:33:18Z</updated>
		<published>2026-05-14T00:04:01Z</published>
		<category scheme="https://chipx86.blog" term="Inside Faxanadu" /><category scheme="https://chipx86.blog" term="Assembly" /><category scheme="https://chipx86.blog" term="Disassembly" /><category scheme="https://chipx86.blog" term="Faxanadu" /><category scheme="https://chipx86.blog" term="NES" /><category scheme="https://chipx86.blog" term="Retro" />
		<summary type="html"><![CDATA[There&#8217;s a lot about Faxanadu that&#8217;s been kept secret since the game was originally developed in the 80s. Secrets I&#8217;ve been discovering in my Faxanadu disassembly work. One of the most surprising to me was hidden code for a World Warp, letting you jump to any of the 6 regions of the game: Eolis, Trunk, [&#8230;]]]></summary>

					<content type="html" xml:base="https://chipx86.blog/2026/05/13/inside-faxanadu-part-2-secret-world-warp/"><![CDATA[
<p class="wp-block-paragraph">There&#8217;s a lot about Faxanadu that&#8217;s been kept secret since the game was originally developed in the 80s. Secrets I&#8217;ve been discovering in my <a href="https://chipx86.com/faxanadu/">Faxanadu disassembly work</a>.</p>



<p class="wp-block-paragraph">One of the most surprising to me was hidden code for a World Warp, letting you jump to any of the 6 regions of the game: Eolis, Trunk, Mist, Branch, Dartmoor, or the Evil Fortress.</p>



<p class="wp-block-paragraph">All with a single button press. (And a couple of Game Genie codes.)</p>



<p class="wp-block-paragraph"><strong>If you&#8217;re new to this series, see:</strong></p>


<ul class="wp-block-latest-posts__list wp-block-latest-posts"><li><a class="wp-block-latest-posts__post-title" href="https://chipx86.blog/2026/05/03/inside-faxanadu-part-1-interaction-scripts-and-shops/">Inside Faxanadu, Part 1: Interaction Scripts and Shops</a></li>
<li><a class="wp-block-latest-posts__post-title" href="https://chipx86.blog/2026/05/13/inside-faxanadu-part-2-secret-world-warp/">Inside Faxanadu, Part 2: Secret World Warp</a></li>
</ul>


<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">The World Warp</h2>



<p class="wp-block-paragraph">During development of Faxanadu, code was put in the game to make it easy to jump to other areas within the game for testing. This isn&#8217;t an uncommon thing for game developers to do. You don&#8217;t want to have to play through half the game every time you want to re-test the fog animation in the world of Mist.</p>



<p class="wp-block-paragraph">This code was wired off from the rest of the game, existing only as a sequence of 44 bytes.</p>



<p class="wp-block-paragraph">This small sequence of code looks at the controller 2 button mask (the only place in the game that cares about this controller) and checks if <strong>Up</strong> or <strong>Down</strong> is pressed.</p>



<p class="wp-block-paragraph">If <strong>Up</strong> is pressed: Go <strong>forward</strong> a world.</p>



<p class="wp-block-paragraph">If <strong>Down</strong> is pressed: Go <strong>back</strong> a world.</p>



<p class="wp-block-paragraph">That wraps around, so if on Eolis, Down takes you to the Evil Fortress. Up takes you back to Eolis.</p>



<p class="wp-block-paragraph">This is, by the way, a quick way of getting to the final boss of the game (spoilers!):</p>



<figure class="wp-block-video"><video height="1080" style="aspect-ratio: 1566 / 1080;" width="1566" controls src="https://chipx86.blog/wp-content/uploads/2026/05/faxanadu-world-warp.mp4"></video></figure>



<p class="wp-block-paragraph">Not that it&#8217;ll do you much good.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Activating the World Warp</h2>



<p class="wp-block-paragraph">Since this 44 byte packet of debug code is wired off from the game, we have to patch it back in.</p>



<p class="wp-block-paragraph">We can do this with two Game Genie codes:</p>



<ul class="wp-block-list">
<li><code>OPYISU</code></li>



<li><code>NIYIVU</code></li>
</ul>



<p class="wp-block-paragraph">This will turn <em>off</em> your ability to Pause, but turn <em>on</em> the World Warp.</p>



<p class="wp-block-paragraph">Game Genie codes are just a magic way of saying &#8220;At this address in memory, replace the value with a new value.&#8221; So let&#8217;s start by seeing what those codes translate into:</p>



<ul class="wp-block-list">
<li><code>DB75:99</code> — Set the value <code>99</code> at address <code>0xDB75</code> in memory</li>



<li><code>DB76:DF</code> — Set the value <code>DF</code> at address <code>0xDB76</code> in memory</li>
</ul>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Why those values? Those addresses?</p>



<p class="wp-block-paragraph">Well, <code>0xDB75</code> and <code>0xDB76</code> are operands to a <code>JSR</code> instruction (Jump to Subroutine) that specifies an address to call every tick of the game loop. This function is <code>GameLoop_CheckPauseGame</code> at <code>0xE02B</code>. This is the least-invasive function to replace. We can live without pausing.</p>



<p class="wp-block-paragraph">We&#8217;re swapping that out with our new address: <code>0xDF99</code>. This is the address where the world warp code lives.</p>



<p class="wp-block-paragraph">Go ahead, put those Game Genie codes in your NES Game Genie (or your emulator), and press <strong>Up</strong> or <strong>Down</strong> on Controller 2. Play around with jumping from different screens, and from inside buildings! You may just glitch into a garbage world for a bit where the screens no longer match up. It&#8217;s fun!</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">The Code behind the World Warp</h2>



<p class="wp-block-paragraph">This code lives in Bank 15 at <code>0xDF99</code> in memory. Bank 15 is always in memory, by the way, and contains most of the main game loop, PPU, screen transition, and player state code, amongst others.</p>



<p class="wp-block-paragraph">Here&#8217;s what the code looks like:</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="820" src="https://chipx86.blog/wp-content/uploads/2026/05/2026-05-13-16.12.17-PRG15_MIRROR-Faxanadu-U.nes@2x-1024x820.png" alt="A screenshot of the disassembled source code. See below for a link to the actual code in text form." class="wp-image-1656" srcset="https://chipx86.blog/wp-content/uploads/2026/05/2026-05-13-16.12.17-PRG15_MIRROR-Faxanadu-U.nes@2x-1024x820.png 1024w, https://chipx86.blog/wp-content/uploads/2026/05/2026-05-13-16.12.17-PRG15_MIRROR-Faxanadu-U.nes@2x-300x240.png 300w, https://chipx86.blog/wp-content/uploads/2026/05/2026-05-13-16.12.17-PRG15_MIRROR-Faxanadu-U.nes@2x-768x615.png 768w, https://chipx86.blog/wp-content/uploads/2026/05/2026-05-13-16.12.17-PRG15_MIRROR-Faxanadu-U.nes@2x-1536x1229.png 1536w, https://chipx86.blog/wp-content/uploads/2026/05/2026-05-13-16.12.17-PRG15_MIRROR-Faxanadu-U.nes@2x.png 2034w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">You can <a href="https://chipx86.com/faxanadu/PRG15_MIRROR.html#df99">view this on the disassembly</a>.</p>



<p class="wp-block-paragraph">Now you may not be intimately familiar with 6502 assembly, which is kind of weird but I&#8217;ll allow it. So let&#8217;s look at this in C form (a language <em>everyone</em> feels comfortable with!):</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Roboto-Mono.ttf" style="font-size:.875rem;font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>void Debug_ChooseArea(void)
{
  if ((Joy2_ButtonMask &amp; BUTTON_BIT_UP) != BUTTON_BIT_NONE) {
    // Going up.
    Area_Region++;

    if (Area_Region > REGION_EVIL_FORTRESS) {
      Area_Region = REGION_EOLIS;
    }
  }
  else if ((Joy2_ButtonMask &amp; BUTTON_BIT_DOWN) != BUTTON_BIT_NONE) {
    // Going down.
    Area_Region--;

    if (Area_Region &lt; 0) {
      Area_Region = REGION_EVIL_FORTRESS;
    }
  }

  Area_CurrentScreen = 0;
  Game_SetupAndLoadArea();
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #D67E5C; font-style: italic">void</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">Debug_ChooseArea</span><span style="color: #B2CACD">(</span><span style="color: #D67E5C; font-style: italic">void</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #B2CACD">{</span></span>
<span class="line"><span style="color: #E4B781">  </span><span style="color: #DF769B; font-weight: bold">if</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">((</span><span style="color: #E4B781">Joy2_ButtonMask </span><span style="color: #DF769B; font-weight: bold">&amp;</span><span style="color: #E4B781"> BUTTON_BIT_UP</span><span style="color: #B2CACD">)</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">!=</span><span style="color: #E4B781"> BUTTON_BIT_NONE</span><span style="color: #B2CACD">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">{</span></span>
<span class="line"><span style="color: #B2CACD">    </span><span style="color: #5B858B; font-style: italic">// Going up.</span></span>
<span class="line"><span style="color: #E4B781">    Area_Region</span><span style="color: #DF769B; font-weight: bold">++</span><span style="color: #B2CACD">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #DF769B; font-weight: bold">if</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">(</span><span style="color: #E4B781">Area_Region </span><span style="color: #DF769B; font-weight: bold">&gt;</span><span style="color: #E4B781"> REGION_EVIL_FORTRESS</span><span style="color: #B2CACD">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">{</span></span>
<span class="line"><span style="color: #E4B781">      Area_Region </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> REGION_EOLIS</span><span style="color: #B2CACD">;</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #B2CACD">}</span></span>
<span class="line"><span style="color: #E4B781">  </span><span style="color: #B2CACD">}</span></span>
<span class="line"><span style="color: #E4B781">  </span><span style="color: #DF769B; font-weight: bold">else</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">if</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">((</span><span style="color: #E4B781">Joy2_ButtonMask </span><span style="color: #DF769B; font-weight: bold">&amp;</span><span style="color: #E4B781"> BUTTON_BIT_DOWN</span><span style="color: #B2CACD">)</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">!=</span><span style="color: #E4B781"> BUTTON_BIT_NONE</span><span style="color: #B2CACD">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">{</span></span>
<span class="line"><span style="color: #B2CACD">    </span><span style="color: #5B858B; font-style: italic">// Going down.</span></span>
<span class="line"><span style="color: #E4B781">    Area_Region</span><span style="color: #DF769B; font-weight: bold">--</span><span style="color: #B2CACD">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #DF769B; font-weight: bold">if</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">(</span><span style="color: #E4B781">Area_Region </span><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781"> </span><span style="color: #7060EB">0</span><span style="color: #B2CACD">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">{</span></span>
<span class="line"><span style="color: #E4B781">      Area_Region </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> REGION_EVIL_FORTRESS</span><span style="color: #B2CACD">;</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #B2CACD">}</span></span>
<span class="line"><span style="color: #E4B781">  </span><span style="color: #B2CACD">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E4B781">  Area_CurrentScreen </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">0</span><span style="color: #B2CACD">;</span></span>
<span class="line"><span style="color: #E4B781">  </span><span style="color: #16A3B6">Game_SetupAndLoadArea</span><span style="color: #B2CACD">();</span></span>
<span class="line"><span style="color: #B2CACD">}</span></span></code></pre></div>



<p class="wp-block-paragraph">I&#8217;m using the names I have in my disassembly. These may change over time. The &#8220;Region&#8221; is the &#8220;World&#8221; we&#8217;re switching between. (They&#8217;re not really independent worlds in the game, just regions of the World Tree.)</p>



<p class="wp-block-paragraph">Pretty simple. Press Up, increase the region/world, and handle wrapping. Do the opposite for Down. Then, set the screen we&#8217;re going into and load the area.</p>



<p class="wp-block-paragraph">Now, although it&#8217;s setting the screen number, there&#8217;s a <em>lot</em> more to setting the target screen than that, particularly when you&#8217;re inside a building. So depending on where you are when you warp, you&#8217;ll end up either in some predictable useful locations, or in Garbage Land. Both are fun to play with.</p>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">Wait, wasn&#8217;t Part 2 about Behavior Scripts?</h1>



<p class="wp-block-paragraph">In my <a href="https://chipx86.blog/2026/05/03/inside-faxanadu-part-1-interaction-scripts-and-shops/">first post in the series</a>, I said I&#8217;d be going into Behavior Scripts next. That was a big fat lie. I will be, but today I thought it&#8217;d be fun to show off this piece of code that&#8217;s been hidden for <s>20</s> <s>30</s> (oh my god) 40 years.</p>



<p class="wp-block-paragraph">We&#8217;ll get to Behavior Scripts soon.</p>



<p class="wp-block-paragraph">If you&#8217;d like to follow my Faxanadu work, you can check out the following:</p>



<ul class="wp-block-list">
<li><a href="https://chipx86.com/faxanadu/">Annotated Faxanadu Disassembly</a></li>



<li><a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/-Overview-">Faxanadu Technical Notes</a></li>



<li><a href="https://github.com/chipx86/faxanadu">Faxanadu Disassembly GitHub</a></li>



<li><a href="https://github.com/chipx86/retro-tinkertoys">Retro-Tinkertoys</a>: My home-grown disassembly tooling for Ghidra</li>
</ul>



<p class="wp-block-paragraph">And you can follow my efforts on:</p>



<ul class="wp-block-list">
<li><a href="https://discord.gg/yp4ad5NDQr">Faxanadu Randomizer &amp; Romhacking Discord</a></li>



<li><a href="https://bsky.app/profile/chipx86.com">BlueSky (@chipx86.com)</a></li>



<li><a href="https://mastodon.online/@chipx86">Mastodon (chipx86@mastodon.online)</a></li>



<li><a href="https://x.com/chipx86/">Twitter/X (chipx86)</a></li>
</ul>



<p class="wp-block-paragraph"></p>
]]></content>
		
		<link href="https://chipx86.blog/wp-content/uploads/2026/05/faxanadu-world-warp.mp4" rel="enclosure" length="3432006" type="video/mp4" />
			<link rel="replies" type="text/html" href="https://chipx86.blog/2026/05/13/inside-faxanadu-part-2-secret-world-warp/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://chipx86.blog/2026/05/13/inside-faxanadu-part-2-secret-world-warp/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>chipx86</name>
							<uri>http://chipx86.blog</uri>
						</author>

		<title type="html"><![CDATA[Inside Faxanadu, Part 1: Interaction Scripts and Shops]]></title>
		<link rel="alternate" type="text/html" href="https://chipx86.blog/2026/05/03/inside-faxanadu-part-1-interaction-scripts-and-shops/" />

		<id>https://chipx86.blog/?p=1600</id>
		<updated>2026-05-05T06:38:33Z</updated>
		<published>2026-05-03T08:00:49Z</published>
		<category scheme="https://chipx86.blog" term="Inside Faxanadu" /><category scheme="https://chipx86.blog" term="Assembly" /><category scheme="https://chipx86.blog" term="Disassembly" /><category scheme="https://chipx86.blog" term="Faxanadu" /><category scheme="https://chipx86.blog" term="Ghidra" /><category scheme="https://chipx86.blog" term="Retro" /><category scheme="https://chipx86.blog" term="Scripting" />
		<summary type="html"><![CDATA[Faxanadu for NES has always been one of my absolute favorite games of the NES library. This adventure starts off in the town of Eolis at the base of the giant World Tree, home to the Elves and Dwarfs. A world corrupted by a poison, transforming the Dwarfs into horrific monsters. I couldn&#8217;t describe it [&#8230;]]]></summary>

					<content type="html" xml:base="https://chipx86.blog/2026/05/03/inside-faxanadu-part-1-interaction-scripts-and-shops/"><![CDATA[
<p class="wp-block-paragraph"><a href="https://en.wikipedia.org/wiki/Faxanadu">Faxanadu</a> for NES has always been one of my absolute favorite games of the NES library. This adventure starts off in the town of Eolis at the base of the giant World Tree, home to the Elves and Dwarfs. A world corrupted by a poison, transforming the Dwarfs into horrific monsters.</p>



<p class="wp-block-paragraph">I couldn&#8217;t describe it any better than my good pal Quwebs (<a href="https://www.youtube.com/watch?v=WeCRxl2rL4U">Twitch</a>, <a href="https://www.youtube.com/@quwebs">NES Abridged on YouTube</a>):</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<div class="ast-oembed-container " style="height: 100%;"><iframe title="NES Abridged - Faxanadu Review (1989)" width="1200" height="675" src="https://www.youtube.com/embed/WeCRxl2rL4U?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></div>
</div></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">This game&#8217;s visuals and musical score always made it a stand-out game for me, and though it didn&#8217;t receive the notoriety of Castlevania, Zelda, Kid Kool, or other top-tier action adventure games at the time, it&#8217;s increasingly become a favorite to those discovering or rediscovering retro games. Better late than never.</p>



<p class="wp-block-paragraph">It&#8217;s also technically <em>very</em> interesting.</p>



<p class="wp-block-paragraph">In 2025, I began performing a disassembly of the Faxanadu ROM, using <a href="https://github.com/nationalsecurityagency/ghidra">Ghidra</a> and my own crafted <a href="https://github.com/chipx86/retro-tinkertoys">Retro-Tinkertoys</a>. The goal was to learn how this game worked, inside and out, and to document it with the hope that it would lead to mods for new games set in this world. <em>And it did.</em></p>



<p class="wp-block-paragraph">At this point, all the important parts of the game are either documented or now well-understood. New discoveries, such as <a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Debug+Level+Switch+Code">hidden code for level switch</a>, remnants of older game designs, unused code, and some <a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Bugs">interesting bugs</a> have been discovered.</p>



<p class="wp-block-paragraph">Plus, <em>three</em> built-in binary scripting languages. One for the advanced music engine (MScripts), one for enemy or NPC behavior (BScripts), and one for NPC and item interaction (IScripts).</p>



<p class="wp-block-paragraph">I&#8217;m going to start our deep dive into Faxanadu today by talking about the <a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Interaction+Scripts">Interaction Scripts</a>, which I&#8217;ll be referring to as IScripts going forward.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">What is an IScript?</h2>



<p class="wp-block-paragraph">IScripts define what happens when the player interacts with an entity. That may be an item or an NPC, such as a shopkeeper or villager.</p>



<p class="wp-block-paragraph">They look like this:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>  80     # King interaction

  0D     # If Player Has Gold:
  5A A3  #     Then jump to _HAS_GOLD ($A35A).
  01     # Show Unskippable Message:
  35     #     Message 53 (Exposition).
  09     # Add Gold to Player:
  DC 05  #     1500G.
  00     # End Script.

_HAS_GOLD:
  03     # Show Message:
  36     #     Message 54 ("Nothing more I can do to help").
  00     # End Script.</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">80     </span><span style="color: #5B858B; font-style: italic"># King interaction</span></span>
<span class="line"></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">0D     </span><span style="color: #5B858B; font-style: italic"># If Player Has Gold:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">5A A3  </span><span style="color: #5B858B; font-style: italic">#     Then jump to _HAS_GOLD ($A35A).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">01     </span><span style="color: #5B858B; font-style: italic"># Show Unskippable Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">35     </span><span style="color: #5B858B; font-style: italic">#     Message 53 (Exposition).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">09     </span><span style="color: #5B858B; font-style: italic"># Add Gold to Player:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">DC 05  </span><span style="color: #5B858B; font-style: italic">#     1500G.</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">_HAS_GOLD:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">03     </span><span style="color: #5B858B; font-style: italic"># Show Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">36     </span><span style="color: #5B858B; font-style: italic">#     Message 54 (&quot;Nothing more I can do to help&quot;).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span></code></pre></div>



<p class="wp-block-paragraph">This particular script is the King, one of the first NPCs you meet in the game. He sends the hero on his quest with enough money to buy a sandwich, bag of chips, and a butter knife. He&#8217;s also big into exposition, putting in just enough effort to stand up for a minute to send you off to solve his problems. Royalty.</p>



<p class="wp-block-paragraph">Any time you talk to the King, that script runs. And knowing how that script runs, we can see that if you spend all your money, he&#8217;ll just give you more. Not a new discovery, but this clearly shows how it works. If the player has no gold, give the player gold. The end.</p>



<p class="wp-block-paragraph">The script is small and simple, but they can do a lot. This is an NES game where bytes and speed matter, so rather than a complex text-based scripting language like you&#8217;d find today, this is a compact binary-based scripting language. Every byte is either an instruction or an argument to an instruction. In these scripts, I&#8217;ll show the bytes and add comments describing what they do, so you can follow along. Values in the comments are in decimal, and all bytes are in hex.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Structure of a IScript</h2>



<h3 class="wp-block-heading">The entity, aka &#8220;Who are we talking to?&#8221;</h3>



<p class="wp-block-paragraph">The very first byte of a script states the entity the player is interacting with. If this is anything but <code>00</code> (a generic interaction), this will show that entity&#8217;s portrait when the script starts. The portrait is animated, and they all blink a <em>lot</em>. It can be unnerving.</p>



<p class="wp-block-paragraph">Entity IDs can be any of the following:</p>



<ul class="wp-block-list">
<li><code>0x00</code>: Generic interaction</li>



<li><code>0x80</code>: King</li>



<li><code>0x81</code>: Guru</li>



<li><code>0x82</code>: Martial Artist</li>



<li><code>0x83</code>: Magician</li>



<li><code>0x84</code>: Doctor</li>



<li><code>0x85</code>: Nurse</li>



<li><code>0x86</code>: Pink Shirt Guy</li>



<li><code>0x87</code>: Smoking Guy</li>



<li><code>0x88</code>: Meat Salesman</li>



<li><code>0x89</code>: Tools Salesman</li>



<li><code>0x8A</code>: Key Salesman</li>
</ul>



<p class="wp-block-paragraph">You&#8217;ll notice that each entity starts at <code>0x80</code>. There&#8217;s a reason for this. <code>0x80</code> is <code>10000000</code> in binary. So the entities are all identified as numbers 0 through 10, with the most-significant bit set. The game checks if that bit is set (<code>entity_id &amp; 0x80</code>) in order to determine if it should show a portrait. Then it masks out that bit and uses the number as the index into a portraits table. Neat!</p>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">Instructions</h3>



<p class="wp-block-paragraph">After this are instructions. These are actions that can be performed in a script. The game will play through the script&#8217;s actions until the script ends (which is an explicit action that all branches need to call!).</p>



<p class="wp-block-paragraph">There are a lot of instructions. These include instructions that show messages, take or give gold/items/HP/MP, check for inventory items, check for or update the player&#8217;s experience title, open shops, check certain objectives or mark them complete, set a spawn point, show a password for the player&#8217;s state, goto a script address, or finish the game (the very last instruction ever run).</p>



<p class="wp-block-paragraph">Here&#8217;s the full list:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Instruction</th><th>Description</th><th>Parameter 1</th><th>Parameter 2</th></tr></thead><tbody><tr><td><code>00</code></td><td>End the script</td><td></td><td></td></tr><tr><td><code>01</code></td><td>Show an unskippable message</td><td><a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Strings">Message ID</a></td><td></td></tr><tr><td><code>02</code></td><td>Show a question message</td><td><a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Strings">Message ID</a></td><td></td></tr><tr><td><code>03</code></td><td>Show a standard message</td><td><a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Strings">Message ID</a></td><td></td></tr><tr><td><code>04</code></td><td>Check if player&#8217;s eligible for a new title, jump if updated</td><td>Jump address (2 bytes)</td><td></td></tr><tr><td><code>05</code></td><td>Reduce player&#8217;s gold</td><td>Gold amount (2 bytes)</td><td></td></tr><tr><td><code>06</code></td><td>Set temple to spawn in</td><td>Temple ID</td><td></td></tr><tr><td><code>07</code></td><td>Add item to player&#8217;s inventory</td><td><a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Items">Item ID</a></td><td></td></tr><tr><td><code>08</code></td><td>Open a shop window</td><td>Shop items address (2 bytes)</td><td></td></tr><tr><td><code>09</code></td><td>Add to the player&#8217;s gold</td><td>Gold amount (2 bytes)</td><td></td></tr><tr><td><code>0A</code></td><td>Add to the player&#8217;s magic points</td><td>Amount</td><td></td></tr><tr><td><code>0B</code></td><td>Check if quest/objective is completed, jump if so</td><td>Quest/objective ID</td><td>Completed jump address (2 bytes)</td></tr><tr><td><code>0C</code></td><td>Jump if player has title</td><td>Title ID</td><td>Has title jump address (2 bytes)</td></tr><tr><td><code>0D</code></td><td>Jump if the player has any gold</td><td>Jump address (2 bytes)</td><td></td></tr><tr><td><code>0E</code></td><td>Set quest/objective completed</td><td>Quest/objective ID</td><td></td></tr><tr><td><code>0F</code></td><td>Show a buy/sell menu, jump if &#8220;buy&#8221; chosen</td><td>Buy jump address (2 bytes)</td><td></td></tr><tr><td><code>10</code></td><td>Remove item from player&#8217;s inventory</td><td><a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Items">Item ID</a></td><td></td></tr><tr><td><code>11</code></td><td>Show the sell menu</td><td>Shop items address (2 bytes)</td><td></td></tr><tr><td><code>12</code></td><td>Jump if player has item</td><td><a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Items">Item ID</a></td><td>Has item jump address (2 bytes)</td></tr><tr><td><code>13</code></td><td>Add health to player</td><td>Amount</td><td></td></tr><tr><td><code>14</code></td><td>Show a password</td><td></td><td></td></tr><tr><td><code>15</code></td><td>Finish the game</td><td></td><td></td></tr><tr><td><code>16</code></td><td>Show question message, jump if completed (unused in game)</td><td><a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Strings">Message ID</a></td><td>Completed jump address (2 bytes)</td></tr><tr><td><code>17</code></td><td>Jump to address</td><td>Address (2 bytes)</td><td></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">I won&#8217;t break those down in detail. I&#8217;ve documented the <a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/Interaction+Scripts">full reference with examples</a>.</p>



<h3 class="wp-block-heading">Shop items</h3>



<p class="wp-block-paragraph">Many of these instructions have to do with shops. Shops allow you to buy or sell an item.</p>



<p class="wp-block-paragraph">Internally, a shop is a collection of item IDs and their prices. What you do with a shop is dependent on the action used to open it.</p>



<p class="wp-block-paragraph">They look like this:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>01     # Weapon: Long Sword
40 06  # 1600 gold

21     # Armor: Studded Mail
C4 09  # 2500 gold

40     # Shield: Small
4C 04  # 1100 gold

90     # Item: Red Potion
90 01  # 400 gold

8F     # Item: Wing Boots
F0 0A  # 2800 gold

FF     # End of shop</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #E3541C">01     </span><span style="color: #5B858B; font-style: italic"># Weapon: Long Sword</span></span>
<span class="line"><span style="color: #E3541C">40 06  </span><span style="color: #5B858B; font-style: italic"># 1600 gold</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">21     </span><span style="color: #5B858B; font-style: italic"># Armor: Studded Mail</span></span>
<span class="line"><span style="color: #E3541C">C4 09  </span><span style="color: #5B858B; font-style: italic"># 2500 gold</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">40     </span><span style="color: #5B858B; font-style: italic"># Shield: Small</span></span>
<span class="line"><span style="color: #E3541C">4C 04  </span><span style="color: #5B858B; font-style: italic"># 1100 gold</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">90     </span><span style="color: #5B858B; font-style: italic"># Item: Red Potion</span></span>
<span class="line"><span style="color: #E3541C">90 01  </span><span style="color: #5B858B; font-style: italic"># 400 gold</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">8F     </span><span style="color: #5B858B; font-style: italic"># Item: Wing Boots</span></span>
<span class="line"><span style="color: #E3541C">F0 0A  </span><span style="color: #5B858B; font-style: italic"># 2800 gold</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">FF     </span><span style="color: #5B858B; font-style: italic"># End of shop</span></span></code></pre></div>



<p class="wp-block-paragraph">That&#8217;s the Tool Shop in the town of Forepaw.</p>



<p class="wp-block-paragraph">Each item is 3 bytes: The item ID (1 byte) and the cost in gold (2 bytes).</p>



<p class="wp-block-paragraph">It&#8217;s pretty compact. 5 items would only take 15 bytes.</p>



<p class="wp-block-paragraph">In Faxanadu, every shop you interact with in the game uses its own shop items table with its own full list of items and their own prices. There&#8217;s no start to a shop, technically, so a mod could do something a bit more interesting, though. They could reuse shop data, making one shop a subset of another, by just pointing to an address within another shop.</p>



<p class="wp-block-paragraph">Let&#8217;s go back to IScripts, and check out a few examples within Faxanadu.</p>



<h2 class="wp-block-heading">Interacting with a shop.</h2>



<p class="wp-block-paragraph">We just looked at a shop, so let&#8217;s see how they tie into IScripts. Follow along with me!</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>  89     # Tools Salesman interaction

  03     # Show Message:
  0F     #     Message 3 ("I sell tools, what would you like?")
  0F     # Show Buy or Sell Menu:
  E0 A3  #     If "Buy", jump to _SHOW_BUY ($A3E0).
  11     # Show Sell Menu:
  6C A4  #     Shop address $A46C,
         #     Then continue after closed.
  03     # Show Message:
  02     #     Message 2 ("Thank you for shopping.")
  00     # End Script.

_SHOW_BUY:
  08     # Open Shop:
  6C A4  #     Shop address $A46C,
         #     Then continue after closed.
  03     # Show Message:
  02     #     Message 2 ("Thank you for shopping.")
  00     # End Script.</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">89     </span><span style="color: #5B858B; font-style: italic"># Tools Salesman interaction</span></span>
<span class="line"></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">03     </span><span style="color: #5B858B; font-style: italic"># Show Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">0F     </span><span style="color: #5B858B; font-style: italic">#     Message 3 (&quot;I sell tools, what would you like?&quot;)</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">0F     </span><span style="color: #5B858B; font-style: italic"># Show Buy or Sell Menu:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">E0 A3  </span><span style="color: #5B858B; font-style: italic">#     If &quot;Buy&quot;, jump to _SHOW_BUY ($A3E0).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">11     </span><span style="color: #5B858B; font-style: italic"># Show Sell Menu:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">6C A4  </span><span style="color: #5B858B; font-style: italic">#     Shop address $A46C,</span></span>
<span class="line"><span style="color: #B2CACD">         </span><span style="color: #5B858B; font-style: italic">#     Then continue after closed.</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">03     </span><span style="color: #5B858B; font-style: italic"># Show Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">02     </span><span style="color: #5B858B; font-style: italic">#     Message 2 (&quot;Thank you for shopping.&quot;)</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">_SHOW_BUY:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">08     </span><span style="color: #5B858B; font-style: italic"># Open Shop:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">6C A4  </span><span style="color: #5B858B; font-style: italic">#     Shop address $A46C,</span></span>
<span class="line"><span style="color: #B2CACD">         </span><span style="color: #5B858B; font-style: italic">#     Then continue after closed.</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">03     </span><span style="color: #5B858B; font-style: italic"># Show Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">02     </span><span style="color: #5B858B; font-style: italic">#     Message 2 (&quot;Thank you for shopping.&quot;)</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span></code></pre></div>



<p class="wp-block-paragraph">This is a common pattern for shops. First, a &#8220;Buy or Sell&#8221; menu is shown. If the player chooses &#8220;Buy&#8221;, it&#8217;ll jump to one address to open a shop in &#8220;Buy&#8221; mode. If they choose &#8220;Sell&#8221;, it&#8217;ll continue to the next instruction, and then open a shop in &#8220;Sell&#8221; mode.</p>



<p class="wp-block-paragraph">Scripts can give these choices, or only offer a &#8220;Buy&#8221; or a &#8220;Sell&#8221;.</p>



<h2 class="wp-block-heading">Working with the player&#8217;s inventory</h2>



<p class="wp-block-paragraph">Shops aren&#8217;t the only way to get items into or out of a player&#8217;s inventory. There are actions for that. Let&#8217;s take a look at this drunk guy in the town of Victim.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>  00     # Generic interaction

  0C     # If Player Has Title:
  0A     #     "Soldier"
  5A A2  #     Then jump to _HAS_RANK ($A25A).
  03     # Show Message:
  74     #     Message 116 ("Go to the capital...")
  00     # End Script.
  
_HAS_RANK:
  12     # If Player Has Item:
  22     #    "Armor: Full Plate"
  63 A2  #    Then jump to _HAS_ARMOR ($A263).
  01     # Show Unskippable Message:
  75     #    Message 117 ("I've got this armor...").
  07     # Give Item To Player:
  22     #    "Armor: Full Plate".
  00     # End Script.
  
_HAS_ARMOR:
  03     # Show Message:
  76     #     Message 118 ("I'll have a drink...")
  00     # End Script.</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># Generic interaction</span></span>
<span class="line"></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">0C     </span><span style="color: #5B858B; font-style: italic"># If Player Has Title:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">0A     </span><span style="color: #5B858B; font-style: italic">#     &quot;Soldier&quot;</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">5A A2  </span><span style="color: #5B858B; font-style: italic">#     Then jump to _HAS_RANK ($A25A).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">03     </span><span style="color: #5B858B; font-style: italic"># Show Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">74     </span><span style="color: #5B858B; font-style: italic">#     Message 116 (&quot;Go to the capital...&quot;)</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span>
<span class="line"><span style="color: #B2CACD">  </span></span>
<span class="line"><span style="color: #E3541C">_HAS_RANK:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">12     </span><span style="color: #5B858B; font-style: italic"># If Player Has Item:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">22     </span><span style="color: #5B858B; font-style: italic">#    &quot;Armor: Full Plate&quot;</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">63 A2  </span><span style="color: #5B858B; font-style: italic">#    Then jump to _HAS_ARMOR ($A263).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">01     </span><span style="color: #5B858B; font-style: italic"># Show Unskippable Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">75     </span><span style="color: #5B858B; font-style: italic">#    Message 117 (&quot;I&#39;ve got this armor...&quot;).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">07     </span><span style="color: #5B858B; font-style: italic"># Give Item To Player:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">22     </span><span style="color: #5B858B; font-style: italic">#    &quot;Armor: Full Plate&quot;.</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span>
<span class="line"><span style="color: #B2CACD">  </span></span>
<span class="line"><span style="color: #E3541C">_HAS_ARMOR:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">03     </span><span style="color: #5B858B; font-style: italic"># Show Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">76     </span><span style="color: #5B858B; font-style: italic">#     Message 118 (&quot;I&#39;ll have a drink...&quot;)</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span></code></pre></div>



<p class="wp-block-paragraph">Here, we have some more branching going on, some player title and item interaction. (Player titles, by the way, are based on your experience, and afford you more gold when you start your game next, more acceleration when walking/running, and maybe surprisingly, a shorter duration for the Wing Boots).</p>



<p class="wp-block-paragraph">This script checks if the player reached the rank of &#8220;Soldier&#8221; or higher, they&#8217;ll be given the Full Plate armor, if they don&#8217;t already have it. If they haven&#8217;t advanced that far yet, they&#8217;ll be given instructions on where to go next. Dialogue changes based on these conditions.</p>



<h2 class="wp-block-heading">Working with quests/objectives</h2>



<p class="wp-block-paragraph">Faxanadu tracks what we call &#8220;Quests,&#8221; which are special objectives the game tracks and includes as part of your password (something we&#8217;ll visit in the future).</p>



<p class="wp-block-paragraph">Quests are represented internally as a few bits in a bitmask. When certain things happen, a particular bit is set. Some IScripts check bits to see how far the user has gotten and what needs to be done next.</p>



<p class="wp-block-paragraph">When you interact with the man protecting the Spring of Trunk, the man&#8217;s IScript does a lot:</p>



<ol class="wp-block-list">
<li>It checks if you&#8217;ve completed the Spring of Trunk quest, in which case it&#8217;s done.</li>



<li>Otherwise, it asks you if you have an elixir you can part with. If not dismissed, it checks for an Elixir in the player&#8217;s inventory.</li>



<li>If an Elixir is present, it&#8217;ll take it, set the quest as complete, and tell you what to do next.</li>



<li>If an Elixir is <em>not</em> present, you <em><strong>die</strong></em>. *Checks notes*. Sorry, you&#8217;re told to come back with an Elixir to awaken the spring.</li>
</ol>



<p class="wp-block-paragraph">Here&#8217;s how that script looks:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>  00     # Generic interaction

  0B     # Check If Quest Complete:
  01     #     Quest 1 (Spring of Trunk),
  C0 A1  #     Then jump to _IS_COMPLETE ($A1C0).
  02     # Show Question Message:
  59     #     Message 89 ("This is the Spring of Trunk. Gimme an Elixir!").
  12     # If Player Has Item:
  92     #     "Item: Elixir",
  B9 A1  #     Then jump to _HAS_ELIXIR ($A1B9).
  03     # Show Message:
  5B     #     Message 91 ("Come back with an Elixir").
  00     # End Script.

_HAS_ELIXIR:
  01     # Show Unskippable Message:
  5A     #     Message 90 ("2 more springs to go!").
  10     # Remove Item from Player:
  92     #     "Item: Elixir".
  0E     # Set Quest Complete:
  01     #     Quest 1 (Spring of Trunk).
  00     # End Script.

_IS_COMPLETE:
  03     # Show Message:
  5C     #     Message 92 ("Find the poison in Mascon").
  00     # End Script.</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># Generic interaction</span></span>
<span class="line"></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">0B     </span><span style="color: #5B858B; font-style: italic"># Check If Quest Complete:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">01     </span><span style="color: #5B858B; font-style: italic">#     Quest 1 (Spring of Trunk),</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">C0 A1  </span><span style="color: #5B858B; font-style: italic">#     Then jump to _IS_COMPLETE ($A1C0).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">02     </span><span style="color: #5B858B; font-style: italic"># Show Question Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">59     </span><span style="color: #5B858B; font-style: italic">#     Message 89 (&quot;This is the Spring of Trunk. Gimme an Elixir!&quot;).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">12     </span><span style="color: #5B858B; font-style: italic"># If Player Has Item:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">92     </span><span style="color: #5B858B; font-style: italic">#     &quot;Item: Elixir&quot;,</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">B9 A1  </span><span style="color: #5B858B; font-style: italic">#     Then jump to _HAS_ELIXIR ($A1B9).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">03     </span><span style="color: #5B858B; font-style: italic"># Show Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">5B     </span><span style="color: #5B858B; font-style: italic">#     Message 91 (&quot;Come back with an Elixir&quot;).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">_HAS_ELIXIR:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">01     </span><span style="color: #5B858B; font-style: italic"># Show Unskippable Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">5A     </span><span style="color: #5B858B; font-style: italic">#     Message 90 (&quot;2 more springs to go!&quot;).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">10     </span><span style="color: #5B858B; font-style: italic"># Remove Item from Player:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">92     </span><span style="color: #5B858B; font-style: italic">#     &quot;Item: Elixir&quot;.</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">0E     </span><span style="color: #5B858B; font-style: italic"># Set Quest Complete:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">01     </span><span style="color: #5B858B; font-style: italic">#     Quest 1 (Spring of Trunk).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">_IS_COMPLETE:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">03     </span><span style="color: #5B858B; font-style: italic"># Show Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">5C     </span><span style="color: #5B858B; font-style: italic">#     Message 92 (&quot;Find the poison in Mascon&quot;).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span></code></pre></div>



<p class="wp-block-paragraph">You can get a sense as to how complex these can be.</p>



<h2 class="wp-block-heading">How do you do, Guru?</h2>



<p class="wp-block-paragraph">We&#8217;ll end on two more related scripts.</p>



<p class="wp-block-paragraph">In Faxanadu, you&#8217;ll visit Gurus to seek guidance, <s>check your stock portfolio</s>, get a title upgrade (if eligible), and see your new password for your next gaming session. Most Gurus are scripted the same way, with an initial block that&#8217;s location-specific and a jump to common Guru scripts (one for new title, one for showing a password):</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>  81     # Guru interaction

  06     # Set Spawn Point:
  03     #     Temple 3 (Mascon).
  04     # Check to Update Player Title:
  00 A6  #     If updated, jump to COMMON_GURU_NEW_TITLE ($A600).
  17     # Jump to:
  FC A5  #     COMMON_GURU_SHOW_PASSWORD ($A5FC).

COMMON_GURU_NEW_TITLE:
  01     # Show Unskippable Message:
  23     #     Message 35 ("I shall give you a title of...").
  00     # End Script.

COMMON_GURU_SHOW_PASSWORD:
  03     # Show Message:
  22     #     Message 34 ("I shall meditate with you").
  14     # Show Password.
  00     # End Script.</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">81     </span><span style="color: #5B858B; font-style: italic"># Guru interaction</span></span>
<span class="line"></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">06     </span><span style="color: #5B858B; font-style: italic"># Set Spawn Point:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">03     </span><span style="color: #5B858B; font-style: italic">#     Temple 3 (Mascon).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">04     </span><span style="color: #5B858B; font-style: italic"># Check to Update Player Title:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00 A6  </span><span style="color: #5B858B; font-style: italic">#     If updated, jump to COMMON_GURU_NEW_TITLE ($A600).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">17     </span><span style="color: #5B858B; font-style: italic"># Jump to:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">FC A5  </span><span style="color: #5B858B; font-style: italic">#     COMMON_GURU_SHOW_PASSWORD ($A5FC).</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">COMMON_GURU_NEW_TITLE:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">01     </span><span style="color: #5B858B; font-style: italic"># Show Unskippable Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">23     </span><span style="color: #5B858B; font-style: italic">#     Message 35 (&quot;I shall give you a title of...&quot;).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">COMMON_GURU_SHOW_PASSWORD:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">03     </span><span style="color: #5B858B; font-style: italic"># Show Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">22     </span><span style="color: #5B858B; font-style: italic">#     Message 34 (&quot;I shall meditate with you&quot;).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">14     </span><span style="color: #5B858B; font-style: italic"># Show Password.</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span></code></pre></div>



<p class="wp-block-paragraph">But in the town of Conflate, there&#8217;s a Guru that goes above and beyond by giving you the very important Ring of Dworf (you need this to unlock a certain door — yes, that&#8217;s the spelling, it&#8217;s not my fault) if you&#8217;ve met the criteria (you need the Battle Suit).</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>  81     # Guru interaction

  12     # If Player Has Item:
  23     #     "Armor: Battle Suit",
  34 A6  #     Then jump to _HAS_BATTLE_SUIT ($A634).

# This is the same logic normally seen in other Guru scripts.
_STANDARD_GURU_LOGIC:
  06     # Set Spawn Point:
  05     #     Temple 5 (Conflate).
  04     # Check to Update Player Title:
  00 A6  #     If updated, jump to COMMON_GURU_NEW_TITLE ($A600).
  17     # Jump to:
  FC A5  #     COMMON_GURU_SHOW_PASSWORD ($A5FC).

# And this is the new "Got a Battle Suit? Here's a ring to go with it!" logic.
_HAS_BATTLE_SUIT:
  12     # If Player Has Item:
  82     #     "Special Item: Ring of Dworf",
  2C A6  #     Then jump to _STANDARD_GURU_LOGIC.
  01     # Show Unskippable Message:
  8C     #     Message 140 ("Here's how you get to Dartmore...").
  07     # Add Item to Player:
  82     #     "Special Item: Ring of Dworf".
  00     # End Script.</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">81     </span><span style="color: #5B858B; font-style: italic"># Guru interaction</span></span>
<span class="line"></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">12     </span><span style="color: #5B858B; font-style: italic"># If Player Has Item:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">23     </span><span style="color: #5B858B; font-style: italic">#     &quot;Armor: Battle Suit&quot;,</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">34 A6  </span><span style="color: #5B858B; font-style: italic">#     Then jump to _HAS_BATTLE_SUIT ($A634).</span></span>
<span class="line"></span>
<span class="line"><span style="color: #5B858B; font-style: italic"># This is the same logic normally seen in other Guru scripts.</span></span>
<span class="line"><span style="color: #E3541C">_STANDARD_GURU_LOGIC:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">06     </span><span style="color: #5B858B; font-style: italic"># Set Spawn Point:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">05     </span><span style="color: #5B858B; font-style: italic">#     Temple 5 (Conflate).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">04     </span><span style="color: #5B858B; font-style: italic"># Check to Update Player Title:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00 A6  </span><span style="color: #5B858B; font-style: italic">#     If updated, jump to COMMON_GURU_NEW_TITLE ($A600).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">17     </span><span style="color: #5B858B; font-style: italic"># Jump to:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">FC A5  </span><span style="color: #5B858B; font-style: italic">#     COMMON_GURU_SHOW_PASSWORD ($A5FC).</span></span>
<span class="line"></span>
<span class="line"><span style="color: #5B858B; font-style: italic"># And this is the new &quot;Got a Battle Suit? Here&#39;s a ring to go with it!&quot; logic.</span></span>
<span class="line"><span style="color: #E3541C">_HAS_BATTLE_SUIT:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">12     </span><span style="color: #5B858B; font-style: italic"># If Player Has Item:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">82     </span><span style="color: #5B858B; font-style: italic">#     &quot;Special Item: Ring of Dworf&quot;,</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">2C A6  </span><span style="color: #5B858B; font-style: italic">#     Then jump to _STANDARD_GURU_LOGIC.</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">01     </span><span style="color: #5B858B; font-style: italic"># Show Unskippable Message:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">8C     </span><span style="color: #5B858B; font-style: italic">#     Message 140 (&quot;Here&#39;s how you get to Dartmore...&quot;).</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">07     </span><span style="color: #5B858B; font-style: italic"># Add Item to Player:</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">82     </span><span style="color: #5B858B; font-style: italic">#     &quot;Special Item: Ring of Dworf&quot;.</span></span>
<span class="line"><span style="color: #B2CACD">  </span><span style="color: #E3541C">00     </span><span style="color: #5B858B; font-style: italic"># End Script.</span></span></code></pre></div>



<p class="wp-block-paragraph">You can see not only how they can make different versions of the same type of NPC more complex, but how scripts can jump to other scripts, reusing building blocks built elsewhere. While Faxanadu doesn&#8217;t have a ton of shared scripts, modders can do a lot with this to optimize their code.</p>



<h2 class="wp-block-heading">IScripts: They&#8217;re super versatile!</h2>



<p class="wp-block-paragraph">Someone modding the game could do a lot to build upon this. For example:</p>



<ul class="wp-block-list">
<li>Have the right combination of items fetched throughout the game, and you&#8217;ll be given an end-game item.</li>



<li>Shop prices change based on player title (using different shop addresses).</li>



<li>Available items in a shop grow or shink as you progress in the game.</li>



<li>Dismiss an NPC&#8217;s story too many times, and the NPC takes all your money.</li>
</ul>



<p class="wp-block-paragraph">And since IScripts also apply to item interactions:</p>



<ul class="wp-block-list">
<li>Lose money when picking up an item.</li>



<li>Exchange one item for another.</li>



<li>Health-replenishing items.</li>
</ul>



<p class="wp-block-paragraph">This can all be built just using the IScript instructions. More advanced games can do even more by rewriting some of the assembly code backing these commands. But that&#8217;s a topic to cover later.</p>



<h2 class="wp-block-heading">But how does it do all this?</h2>



<p class="wp-block-paragraph">Obviously these batches of bytes don&#8217;t do the work themselves. This isn&#8217;t assembly code, it&#8217;s just data.</p>



<p class="wp-block-paragraph">Behind the scenes is a small scripting runtime, consisting of a few parts:</p>



<ol class="wp-block-list">
<li>A script runner function that can be called when the player collides with an item or interacts with an NPC.</li>



<li>Implementations for each instruction.</li>



<li>A table of <a href="https://chipx86.com/faxanadu/PRG12.html#IScriptActions_L">instruction action handlers</a> (split into a lower bytes table and upper bytes table).</li>



<li>A table of <a href="https://chipx86.com/faxanadu/PRG12.html#ISCRIPT_ADDRS_L">IScript entrypoints</a> (generally one slot per script, keyed off by an ID assigned to an item, NPC, or event, and also split into lower/upper bytes tables).</li>
</ol>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">Starting a script</h3>



<p class="wp-block-paragraph">We start our journey at <a href="https://chipx86.com/faxanadu/PRG12.html#IScripts_Begin">IScripts_Begin</a>. This is our script runner. It takes the index into the IScript entrypoint table and does the following:</p>



<ol class="wp-block-list">
<li>Loads the address of the IScript, given the entrypoint in the table.</li>



<li>Reads the first byte as the interaction entity ID, storing that so any portrait display can pull up the right images.</li>



<li>Prepares for any text or shop display (IScripts almost always show a textbox or shop, so it gets this ready).</li>



<li>Calls <a href="https://chipx86.com/faxanadu/PRG12.html#IScripts_InvokeNextAction">IScripts_InvokeNextAction</a> to execute the first instruction.</li>
</ol>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">The mainloop</h3>



<p class="wp-block-paragraph"><a href="https://chipx86.com/faxanadu/PRG12.html#IScripts_InvokeNextAction">IScripts_InvokeNextAction</a> is effectively the main loop for the script runner. Its job is to:</p>



<ol class="wp-block-list">
<li>Read the next byte as an instruction.</li>



<li>Looks up and runs the registered action handler in the table.</li>
</ol>



<p class="wp-block-paragraph">It doesn&#8217;t itself loop, but as you&#8217;ll see, action handlers will keep calling it.</p>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">IScript action handlers</h3>



<p class="wp-block-paragraph">Every handler is different, but generally:</p>



<ol class="wp-block-list">
<li>They read any following bytes needed to load all parameters.</li>



<li>Perform whatever actions the handler needs to perform.</li>



<li>Updates the next read address as needed to jump elsewhere in a script (used for Jump and conditions).</li>



<li>At the end, they call <a href="https://chipx86.com/faxanadu/PRG12.html#IScripts_InvokeNextAction">IScripts_InvokeNextAction</a> to continue on, or <a href="https://chipx86.com/faxanadu/PRG12.html#IScriptAction_EndScript">IScriptAction_EndScript</a> to end the script.</li>
</ol>



<p class="wp-block-paragraph">It&#8217;s a simple loop. It does exactly what it needs to do. It&#8217;s fast and efficient and powers almost everything you interact with in the game.</p>



<p class="wp-block-paragraph">Mods could extend the handlers table and build all-new action handlers for their game. It&#8217;d require moving some things around, but it would be entirely doable.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Time for a demo</h2>



<p class="wp-block-paragraph">Actually, I&#8217;ll give you two of my very own.</p>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">Demo 1: The ultimate Faxanadu speedrun</h3>



<p class="wp-block-paragraph">The first is the fastest speed run of Faxanadu, where our hero learns that there&#8217;s more to life than deserted towns and monsters hanging out in overgrown trees.</p>



<figure class="wp-block-video"><video height="480" style="aspect-ratio: 720 / 480;" width="720" controls src="https://chipx86.blog/wp-content/uploads/2026/05/faxanadu-speedrun.mp4"></video></figure>



<p class="wp-block-paragraph">This uses a custom message along with the &#8220;Finish Game&#8221; instruction, which, well, ends the game. It looks like this:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>00   # Generic entity

01   # Show Unskippable Message:
01   #     Message 01 ("... On second thought...").
15   # Finish Game.</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #E3541C">00   </span><span style="color: #5B858B; font-style: italic"># Generic entity</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E3541C">01   </span><span style="color: #5B858B; font-style: italic"># Show Unskippable Message:</span></span>
<span class="line"><span style="color: #E3541C">01   </span><span style="color: #5B858B; font-style: italic">#     Message 01 (&quot;... On second thought...&quot;).</span></span>
<span class="line"><span style="color: #E3541C">15   </span><span style="color: #5B858B; font-style: italic"># Finish Game.</span></span></code></pre></div>



<h3 class="wp-block-heading">Demo 2: Beer Run!</h3>



<p class="wp-block-paragraph">I like this one. It&#8217;s the <em>very first</em> mod built with IScripts. I built it it to test what I was disassembling and documenting.</p>



<p class="wp-block-paragraph">Our hero comes home to find the local economy in ruins. Upon entering the town, a bum asks him to buy him a beer. This little fetch quest has a nice reward at the end.</p>



<p class="wp-block-paragraph">It involves a number of new scripts (none of which I have anymore, oops — early testing). One NPC has been given shop capabilities, and another has a few items to hand out.</p>



<figure class="wp-block-video"><video height="480" style="aspect-ratio: 720 / 480;" width="720" controls src="https://chipx86.blog/wp-content/uploads/2026/05/faxanadu-beer-run.mp4"></video></figure>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Next Up: Behavior Scripts</h2>



<p class="wp-block-paragraph">So that&#8217;s how Faxanadu handles interactions behind the scenes. Not a bunch of assembly logic (well, not just&#8230;), but rather simple-yet-powerful space-efficient scripts and a little scripting engine.</p>



<p class="wp-block-paragraph">I mentioned before that this is just one of three scripting engines built into the game. Next time, we&#8217;ll dig into how enemies, bosses, and NPCs behave, using Behavior Scripts.</p>



<p class="wp-block-paragraph">If you&#8217;d like to follow my Faxanadu work, you can check out the following:</p>



<ul class="wp-block-list">
<li><a href="https://chipx86.com/faxanadu/">Annotated Faxanadu Disassembly</a></li>



<li><a href="https://notes.chipx86.com/Projects/Reverse-Engineering+Projects/Faxanadu+Disassembly/-Overview-">Faxanadu Technical Notes</a></li>



<li><a href="https://github.com/chipx86/faxanadu">Faxanadu Disassembly GitHub</a></li>



<li><a href="https://github.com/chipx86/retro-tinkertoys">Retro-Tinkertoys</a>: My home-grown disassembly tooling for Ghidra</li>
</ul>



<p class="wp-block-paragraph">And you can follow my efforts on:</p>



<ul class="wp-block-list">
<li><a href="https://discord.gg/yp4ad5NDQr">Faxanadu Randomizer &amp; Romhacking Discord</a></li>



<li><a href="https://bsky.app/profile/chipx86.com">BlueSky (@chipx86.com)</a></li>



<li><a href="https://mastodon.online/@chipx86">Mastodon (chipx86@mastodon.online)</a></li>



<li><a href="https://x.com/chipx86/">Twitter/X (chipx86)</a></li>
</ul>
]]></content>
		
		<link href="https://chipx86.blog/wp-content/uploads/2026/05/faxanadu-speedrun.mp4" rel="enclosure" length="1605773" type="video/mp4" />
<link href="https://chipx86.blog/wp-content/uploads/2026/05/faxanadu-beer-run.mp4" rel="enclosure" length="7679148" type="video/mp4" />
			<link rel="replies" type="text/html" href="https://chipx86.blog/2026/05/03/inside-faxanadu-part-1-interaction-scripts-and-shops/#comments" thr:count="1" />
			<link rel="replies" type="application/atom+xml" href="https://chipx86.blog/2026/05/03/inside-faxanadu-part-1-interaction-scripts-and-shops/feed/atom/" thr:count="1" />
			<thr:total>1</thr:total>
			</entry>
		<entry>
		<author>
			<name>chipx86</name>
							<uri>http://chipx86.blog</uri>
						</author>

		<title type="html"><![CDATA[Using pprint.saferepr() for string comparisons in Python]]></title>
		<link rel="alternate" type="text/html" href="https://chipx86.blog/2025/08/26/using-pprint-saferepr-for-string-comparisons-in-python/" />

		<id>https://chipx86.blog/?p=1592</id>
		<updated>2025-08-27T02:45:55Z</updated>
		<published>2025-08-27T02:45:55Z</published>
		<category scheme="https://chipx86.blog" term="Python" /><category scheme="https://chipx86.blog" term="Today I Learned" />
		<summary type="html"><![CDATA[Python's pprint.saferepr() can be a useful way of comparing string representations of dictionaries and other data types.]]></summary>

					<content type="html" xml:base="https://chipx86.blog/2025/08/26/using-pprint-saferepr-for-string-comparisons-in-python/"><![CDATA[
<p class="wp-block-paragraph">Python offers a handy module called <a href="https://docs.python.org/3/library/pprint.html">pprint</a>, which has helpers for formatting and printing data in a nicely-formatted way. If you haven&#8217;t used this, you should definitely explore it!</p>



<p class="wp-block-paragraph">Most people will reach for this module when using <a href="https://docs.python.org/3/library/pprint.html#pprint.PrettyPrinter.pprint">pprint.pprint()</a> (aka <a href="https://docs.python.org/3/library/pprint.html#pprint.pp">pprint.pp()</a>) or <a href="https://docs.python.org/3/library/pprint.html#pprint.pformat">pprint.pformat()</a>, but one under-appreciated method is <a href="https://docs.python.org/3/library/pprint.html#pprint.saferepr">pprint.saferepr()</a>, which advertises itself as returning a string representation of an object that is safe from some recursion issues. They demonstrate this as:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> import pprint
>>> stuff = &#91;'spam', 'eggs', 'lumberjack', 'knights', 'ni'&#93;
>>> stuff.insert(0, stuff)
>>> pprint.saferepr(stuff)
"&#91;&lt;Recursion on list with id=4370731904>, 'spam', 'eggs', 'lumberjack', 'knights', 'ni'&#93;"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">import</span><span style="color: #E4B781"> pprint</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> stuff </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">&#91;</span><span style="color: #49E9A6">&#39;spam&#39;</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;eggs&#39;</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;lumberjack&#39;</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;knights&#39;</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;ni&#39;</span><span style="color: #B2CACD">&#93;</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> stuff</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">insert</span><span style="color: #B2CACD">(</span><span style="color: #7060EB">0</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> stuff</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> pprint</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">saferepr</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">stuff</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #49E9A6">&quot;&#91;&lt;Recursion on list with id=4370731904&gt;, &#39;spam&#39;, &#39;eggs&#39;, &#39;lumberjack&#39;, &#39;knights&#39;, &#39;ni&#39;&#93;&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph">But actually <code>repr()</code> handles this specific case just fine:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> repr(stuff)
"[&#91;...&#93;, 'spam', 'eggs', 'lumberjack', 'knights', 'ni']"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #49D6E9">repr</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">stuff</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #49E9A6">&quot;[&#91;...&#93;, &#39;spam&#39;, &#39;eggs&#39;, &#39;lumberjack&#39;, &#39;knights&#39;, &#39;ni&#39;]&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph">Pretty minimal difference there. Is there another reason we might care about <code>saferepr()</code>?</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Dictionary string comparisons!</h2>



<p class="wp-block-paragraph"><code>saferepr()</code> first sets up the <code>pprint</code> pretty-printing machinery, meaning it takes care of things like sorting keys in dictionaries.</p>



<p class="wp-block-paragraph">This is great! This is <em>really</em> handy when writing unit tests that need to compare, say, logging of data.</p>



<p class="wp-block-paragraph">See, modern versions of CPython 3 will preserve insertion order of keys into dictionaries, which can be nice but aren&#8217;t always great for comparison purposes. If you&#8217;re writing a unit test, you probably want to feel confident about the string you&#8217;re comparing against.</p>



<p class="wp-block-paragraph">Let&#8217;s take a look at <code>repr()</code> vs. <code>saferepr()</code> with a basic dictionary.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> d = {}
>>> d&#91;'z'&#93; = 1
>>> d&#91;'a'&#93; = 2
>>> d&#91;'g'&#93; = 3
>>>
>>> repr(d)
"{'z': 1, 'a': 2, 'g': 3}"
>>>
>>> from pprint import saferepr
>>> saferepr(d)
"{'a': 2, 'g': 3, 'z': 1}"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> d </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">{}</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> d</span><span style="color: #B2CACD">&#91;</span><span style="color: #49E9A6">&#39;z&#39;</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">1</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> d</span><span style="color: #B2CACD">&#91;</span><span style="color: #49E9A6">&#39;a&#39;</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">2</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> d</span><span style="color: #B2CACD">&#91;</span><span style="color: #49E9A6">&#39;g&#39;</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">3</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #49D6E9">repr</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">d</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #49E9A6">&quot;{&#39;z&#39;: 1, &#39;a&#39;: 2, &#39;g&#39;: 3}&quot;</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">from</span><span style="color: #E4B781"> pprint </span><span style="color: #DF769B; font-weight: bold">import</span><span style="color: #E4B781"> saferepr</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">saferepr</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">d</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #49E9A6">&quot;{&#39;a&#39;: 2, &#39;g&#39;: 3, &#39;z&#39;: 1}&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph">A nice, stable order. Now we don&#8217;t have to worry about a unit test breaking on different versions or implementations or with different logic.</p>



<p class="wp-block-paragraph"><code>saferepr()</code> will disable some of <code>pprint</code>()`&#8217;s typical output limitations. There&#8217;s no max dictionary depth. There&#8217;s no max line width. You just get a nice, normalized string of data for comparison purposes.</p>



<h2 class="wp-block-heading">But not for sets&#8230;</h2>



<p class="wp-block-paragraph">Okay, it&#8217;s not perfect. Sets will still use their standard representation (which seems to be iteration order), and this might be surprising given how dictionaries are handled.</p>



<p class="wp-block-paragraph">Interestingly, <code>pprint()</code> and <code>pformat()</code> <em>will</em> sort keys if it needs to break them across multiple lines, but otherwise, nope.</p>



<p class="wp-block-paragraph">For example:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> from random import choices
>>> import pprint
>>> import string
>>> s = set(choices(string.ascii_letters, k=25))
>>> repr(s)
"{'A', 'D', 'b', 'J', 'T', 'X', 'C', 'V', 'e', 'v', 'I', 'w', 'H', 'k', 'M', 't', 'U', 'o', 'W'}"
>>> pprint.saferepr(s)
"{'A', 'D', 'b', 'J', 'T', 'X', 'C', 'V', 'e', 'v', 'I', 'w', 'H', 'k', 'M', 't', 'U', 'o', 'W'}"
>>> pprint.pp(s)
{'A',
 'C',
 'D',
 'H',
 'I',
 'J',
 'M',
 'T',
 'U',
 'V',
 'W',
 'X',
 'b',
 'e',
 'k',
 'o',
 't',
 'v',
 'w'}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">from</span><span style="color: #E4B781"> random </span><span style="color: #DF769B; font-weight: bold">import</span><span style="color: #E4B781"> choices</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">import</span><span style="color: #E4B781"> pprint</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">import</span><span style="color: #E4B781"> string</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> s </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">set</span><span style="color: #B2CACD">(</span><span style="color: #16A3B6">choices</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">string</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">ascii_letters</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #E4B781; font-weight: bold">k</span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #7060EB">25</span><span style="color: #B2CACD">))</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #49D6E9">repr</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">s</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #49E9A6">&quot;{&#39;A&#39;, &#39;D&#39;, &#39;b&#39;, &#39;J&#39;, &#39;T&#39;, &#39;X&#39;, &#39;C&#39;, &#39;V&#39;, &#39;e&#39;, &#39;v&#39;, &#39;I&#39;, &#39;w&#39;, &#39;H&#39;, &#39;k&#39;, &#39;M&#39;, &#39;t&#39;, &#39;U&#39;, &#39;o&#39;, &#39;W&#39;}&quot;</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> pprint</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">saferepr</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">s</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #49E9A6">&quot;{&#39;A&#39;, &#39;D&#39;, &#39;b&#39;, &#39;J&#39;, &#39;T&#39;, &#39;X&#39;, &#39;C&#39;, &#39;V&#39;, &#39;e&#39;, &#39;v&#39;, &#39;I&#39;, &#39;w&#39;, &#39;H&#39;, &#39;k&#39;, &#39;M&#39;, &#39;t&#39;, &#39;U&#39;, &#39;o&#39;, &#39;W&#39;}&quot;</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> pprint</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">pp</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">s</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #B2CACD">{</span><span style="color: #49E9A6">&#39;A&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;C&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;D&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;H&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;I&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;J&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;M&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;T&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;U&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;V&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;W&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;X&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;b&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;e&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;k&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;o&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;t&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;v&#39;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;w&#39;</span><span style="color: #B2CACD">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Ah well, can&#8217;t have everything we want.</p>



<p class="wp-block-paragraph">Still, depending on what you&#8217;re comparing, this can be a very handy tool in your Python Standard Library toolset.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://chipx86.blog/2025/08/26/using-pprint-saferepr-for-string-comparisons-in-python/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://chipx86.blog/2025/08/26/using-pprint-saferepr-for-string-comparisons-in-python/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>chipx86</name>
							<uri>http://chipx86.blog</uri>
						</author>

		<title type="html"><![CDATA[What If()? Using Conditional CSS Variables]]></title>
		<link rel="alternate" type="text/html" href="https://chipx86.blog/2025/08/08/what-if-using-conditional-css-variables/" />

		<id>https://chipx86.blog/?p=1516</id>
		<updated>2025-08-08T21:49:13Z</updated>
		<published>2025-08-08T09:54:34Z</published>
		<category scheme="https://chipx86.blog" term="Web Development" /><category scheme="https://chipx86.blog" term="CSS" />
		<summary type="html"><![CDATA[A cool new feature is coming to CSS: if(). With this, you can set a style or a CSS variable based on some condition, like whether the device is on a touchscreen, or whether some CSS property is supported, or even based on an attribute on an element. It&#8217;s really cool, and sooner or later [&#8230;]]]></summary>

					<content type="html" xml:base="https://chipx86.blog/2025/08/08/what-if-using-conditional-css-variables/"><![CDATA[
<p class="wp-block-paragraph">A cool new feature is coming to CSS: <a href="https://developer.chrome.com/blog/if-article">if()</a>.</p>



<p class="wp-block-paragraph">With this, you can set a style or a CSS variable based on some condition, like whether the device is on a touchscreen, or whether some CSS property is supported, or even based on an attribute on an element.</p>



<p class="wp-block-paragraph">It&#8217;s really cool, and sooner or later we&#8217;ll all be (ab)using it.</p>



<p class="wp-block-paragraph">Actually, you can pretty much do this today using CSS Variables, with a little understanding of how variable fallbacks work and how you can use this to make your own conditionals.</p>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">What are CSS Variables?</h1>



<p class="wp-block-paragraph">CSS Variables make it easy to use reuse colors and styles across a CSS codebase. At their most basic, they look like this:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:4;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>:root {
    --my-fg-color: red;
    --my-bg-color: blue;
}

body {
    color: var(--my-fg-color);
    background: var(--my-bg-color);
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #FFB454">:root</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    --my-fg-color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">red</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    --my-bg-color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--my-fg-color)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--my-bg-color)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">CSS Variables can have fallbacks, which falls back to some other value if the variable isn&#8217;t defined. Here, the color will be <code>black</code> if <code>--my-custom-fg-color</code> wasn&#8217;t defined in the scope.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>body {
    color: var(--my-custom-fg-color, black);
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--my-custom-fg-color</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">black</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">This can be nested with other CSS variables:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>body {
    color: var(--my-custom-fg-color, var(--my-fg-color, black));
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--my-custom-fg-color</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--my-fg-color</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">black</span><span style="color: #BFBDB6">))</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Fallbacks depend on whether the variable is truthy or falsy.</p>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">Truthy and Falsy Variables</h1>



<p class="wp-block-paragraph">When a variable is falsy, <code>var()</code> will use the fallback value.</p>



<p class="wp-block-paragraph">When it is truthy, the fallback value won&#8217;t be used.</p>



<p class="wp-block-paragraph">Normally, it&#8217;s falsy if the variable isn&#8217;t defined, and truthy if it is. But there are a couple tricks to know about:</p>



<ol class="wp-block-list">
<li>You can make a variable falsy by setting it to the special keyword <code>initial</code>.</li>



<li>You can make a variable truthy (without expanding to a value) by making the value <em>empty</em>.</li>
</ol>



<p class="wp-block-paragraph">Let me show you what I mean:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>:root {
    /* Empty -- Truthy and a no-op */
    --no-fallback: ;          

    /* 'initial' -- Falsy, use fallback */
    --use-fallback: initial;
}

body {
    background: var(--no-fallback, grey);
    color: var(--use-fallback, blue);
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #FFB454">:root</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #ACB6BF8C; font-style: italic">/* Empty -- Truthy and a no-op */</span></span>
<span class="line"><span style="color: #BFBDB6">    --no-fallback</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span><span style="color: #BFBDB6">          </span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #ACB6BF8C; font-style: italic">/* &#39;initial&#39; -- Falsy, use fallback */</span></span>
<span class="line"><span style="color: #BFBDB6">    --use-fallback</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--no-fallback</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">grey</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--use-fallback</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">This will result in the following CSS:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>body {
    background: ;  /* No value -- ignored. */
    color: blue;
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span><span style="color: #BFBDB6">  </span><span style="color: #ACB6BF8C; font-style: italic">/* No value -- ignored. */</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Our new <code>--use-fallback</code> will ensure the fallback value will be used, and <code>--no-fallback</code> will ensure it&#8217;s never used (but won&#8217;t leave behind a value).</p>



<p class="wp-block-paragraph">These are our core building blocks for conditional CSS Variables.</p>



<p class="wp-block-paragraph">We can then combine these rules.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>body {
    background:
        var(--no-fallback, red)
        var(--use-fallback, pink);

    color:
        var(--no-fallback, pink)
        var(--use-fallback, red);
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--no-fallback</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">red</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--use-fallback</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">pink</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--no-fallback</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">pink</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--use-fallback</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">red</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">This will give us:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>body {
    background: pink;
    color: red;
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">pink</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">red</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Behold:</p>



<figure class="wp-block-image aligncenter size-full"><img decoding="async" width="510" height="162" src="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo1.png" alt="A red on pink &quot;Hello, world!&quot;" class="wp-image-1520" srcset="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo1.png 510w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo1-300x95.png 300w" sizes="(max-width: 510px) 100vw, 510px" /></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">You can play around with it here:</p>



<p class="wp-block-paragraph"></p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_gbaRVeq" src="//codepen.io/anon/embed/gbaRVeq?height=450&amp;theme-id=1&amp;slug-hash=gbaRVeq&amp;default-tab=css,result&amp;editable=true" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed gbaRVeq" title="CodePen Embed gbaRVeq" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p class="wp-block-paragraph">Armed with this knowledge, we can now start setting CSS Variables based on other state, and trigger the conditions.</p>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">Setting Conditional Variables</h1>



<p class="wp-block-paragraph">Let&#8217;s set up another contrived example. We&#8217;re going to put together some HTML with a button that styles differently based on where it&#8217;s placed. In this case, when in <code>&lt;main&gt;</code>, it will appear blue-on-pink and elsewhere it&#8217;ll appear pink-on-blue.</p>



<p class="wp-block-paragraph">It&#8217;s my post and I will use hideous color palettes if I want to.</p>



<p class="wp-block-paragraph">Here&#8217;s our HTML:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>&lt;html>
 &lt;body>
  &lt;button>Hello&lt;/button>
  &lt;main>
   &lt;button>There&lt;/button>
  &lt;/main>
  &lt;button>Friend&lt;/button>
 &lt;/body>
&lt;/html></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #39BAE680">&lt;</span><span style="color: #39BAE6">html</span><span style="color: #39BAE680">&gt;</span></span>
<span class="line"><span style="color: #BFBDB6"> </span><span style="color: #39BAE680">&lt;</span><span style="color: #39BAE6">body</span><span style="color: #39BAE680">&gt;</span></span>
<span class="line"><span style="color: #BFBDB6">  </span><span style="color: #39BAE680">&lt;</span><span style="color: #39BAE6">button</span><span style="color: #39BAE680">&gt;</span><span style="color: #BFBDB6">Hello</span><span style="color: #39BAE680">&lt;/</span><span style="color: #39BAE6">button</span><span style="color: #39BAE680">&gt;</span></span>
<span class="line"><span style="color: #BFBDB6">  </span><span style="color: #39BAE680">&lt;</span><span style="color: #39BAE6">main</span><span style="color: #39BAE680">&gt;</span></span>
<span class="line"><span style="color: #BFBDB6">   </span><span style="color: #39BAE680">&lt;</span><span style="color: #39BAE6">button</span><span style="color: #39BAE680">&gt;</span><span style="color: #BFBDB6">There</span><span style="color: #39BAE680">&lt;/</span><span style="color: #39BAE6">button</span><span style="color: #39BAE680">&gt;</span></span>
<span class="line"><span style="color: #BFBDB6">  </span><span style="color: #39BAE680">&lt;/</span><span style="color: #39BAE6">main</span><span style="color: #39BAE680">&gt;</span></span>
<span class="line"><span style="color: #BFBDB6">  </span><span style="color: #39BAE680">&lt;</span><span style="color: #39BAE6">button</span><span style="color: #39BAE680">&gt;</span><span style="color: #BFBDB6">Friend</span><span style="color: #39BAE680">&lt;/</span><span style="color: #39BAE6">button</span><span style="color: #39BAE680">&gt;</span></span>
<span class="line"><span style="color: #BFBDB6"> </span><span style="color: #39BAE680">&lt;/</span><span style="color: #39BAE6">body</span><span style="color: #39BAE680">&gt;</span></span>
<span class="line"><span style="color: #39BAE680">&lt;/</span><span style="color: #39BAE6">html</span><span style="color: #39BAE680">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">We&#8217;re going to set up some conditional state variables based on the <code>main</code> placement. (Yep, contrived example, but this is just for demonstration purposes.)</p>



<p class="wp-block-paragraph">Here&#8217;s our CSS:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>:root {
    --if-main: ;
    --if-not-main: initial;
}

main {
    --if-main: initial;
    --if-not-main: ;
}

button {
    background:
        var(--if-main, pink)
        var(--if-not-main, blue);

    color:
        var(--if-main, blue)
        var(--if-not-main, pink);
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #FFB454">:root</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-main</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-not-main</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #59C2FF">main</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-main</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-not-main</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #59C2FF">button</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-main</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">pink</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-not-main</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-main</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-not-main</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">pink</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">By default, <code>--if-main</code> will be truthy (so no fallback) and <code>--if-not-main</code> will be falsy (so it&#8217;ll use the fallback).</p>



<p class="wp-block-paragraph">When in <code>&lt;main&gt;</code>, we reverse that.</p>



<p class="wp-block-paragraph">The button will style the background and foreground colors based on that state.</p>



<p class="wp-block-paragraph">And here&#8217;s what we get:</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="204" height="246" src="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo2.png" alt="Three buttons. &quot;Hello&quot; with pink on blue, &quot;There&quot; with blue on pink, and &quot;Friend&quot; with pink on blue." class="wp-image-1521"/></figure>



<p class="wp-block-paragraph">Beautiful.</p>



<p class="wp-block-paragraph">You can play with that here:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_YPyQmvO" src="//codepen.io/anon/embed/YPyQmvO?height=450&amp;theme-id=1&amp;slug-hash=YPyQmvO&amp;default-tab=css,result&amp;editable=true" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed YPyQmvO" title="CodePen Embed YPyQmvO" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">Let&#8217;s Build Dark Mode</h1>



<p class="wp-block-paragraph">It&#8217;s 2025, so we want to support light and dark modes. There&#8217;s no end of articles on how to do this, and now you&#8217;re reading another one of them.</p>



<p class="wp-block-paragraph">A common approach is to write different rules based on CSS classes.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>body.-is-light {
    background: orange;
    color: green;
}

body.-is-dark {
    background: darkorange;
    color: darkgreen;
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #59C2FF">body</span><span style="color: #FFB454">.-is-light</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">orange</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">green</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #59C2FF">body</span><span style="color: #FFB454">.-is-dark</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkorange</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkgreen</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">That works if we&#8217;re manually controlling CSS classes on <code>body</code>, but these days we want to respect system settings, so we use media selectors like so:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>@media (prefers-color-scheme: light) {
    body {
        background: orange;
        color: green;
    }
}

@media (prefers-color-scheme: dark) {
    body {
        background: darkorange;
        color: darkgreen;
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #FF8F40">@media</span><span style="color: #BFBDB6"> (prefers-color-scheme</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> light) {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">orange</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">green</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    }</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF8F40">@media</span><span style="color: #BFBDB6"> (prefers-color-scheme</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> dark) {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkorange</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkgreen</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    }</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">But now we can&#8217;t give users a nice toggle on the page to control their light/dark modes, so we&#8217;ll want to bring the CSS classes back <em>with</em> the media selectors:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>body.-is-light {
    background: orange;
    color: green;
}

body.-is-dark button {
    background: darkorange;
    color: darkgreen;
}

@media (prefers-color-scheme: light) {
    body {
        background: orange;
        color: green;
    }
}

@media (prefers-color-scheme: dark) {
    body {
        background: darkorange;
        color: darkgreen;
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #59C2FF">body</span><span style="color: #FFB454">.-is-light</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">orange</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">green</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #59C2FF">body</span><span style="color: #FFB454">.-is-dark</span><span style="color: #BFBDB6"> </span><span style="color: #59C2FF">button</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkorange</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkgreen</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF8F40">@media</span><span style="color: #BFBDB6"> (prefers-color-scheme</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> light) {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">orange</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">green</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    }</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF8F40">@media</span><span style="color: #BFBDB6"> (prefers-color-scheme</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> dark) {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkorange</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkgreen</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    }</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Okay, now things are just out of control. And we haven&#8217;t moved beyond the <code>&lt;body&gt;</code> tag. We&#8217;d have to repeat this for everything else we want to style!</p>



<p class="wp-block-paragraph">Yuck. I hate CSS.</p>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">Let&#8217;s Hate CSS Less</h1>



<p class="wp-block-paragraph">It all comes down to this. We&#8217;re going to take our building blocks and clean this all up.</p>



<p class="wp-block-paragraph">Here&#8217;s our plan of attack:</p>



<ol class="wp-block-list">
<li>We&#8217;re going to define some truthy and falsy CSS variables saying if we&#8217;re in light or dark mode.</li>



<li>We&#8217;re going to consolidate the styles for our components and use our conditional <code>var()</code> statements.</li>
</ol>



<p class="wp-block-paragraph">We&#8217;ll first set up our state variables. We only need to do this <em>once</em> for the whole codebase.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>/* We'll make light mode our default. Everyone loves light mode. */
:root,
:root.-is-light {
    --if-light: initial;
    --if-dark: ;

    color-scheme: light;
}

:root.-is-dark {
    --if-light: ;
    --if-dark: initial;

    color-scheme: dark light;
}

@media (prefers-color-scheme: light) {
    :root {
        --if-light: initial;
        --if-dark: ;

        color-scheme: light;
    }
}

@media (prefers-color-scheme: dark) {
    :root {
        --if-light: ;
        --if-dark: initial;

        color-scheme: dark light;
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #ACB6BF8C; font-style: italic">/* We&#39;ll make light mode our default. Everyone loves light mode. */</span></span>
<span class="line"><span style="color: #FFB454">:root</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #FFB454">:root.-is-light</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-light</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-dark</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color-scheme</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">light</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FFB454">:root.-is-dark</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-light</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-dark</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color-scheme</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">dark</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">light</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF8F40">@media</span><span style="color: #BFBDB6"> (prefers-color-scheme</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> light) {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #FFB454">:root</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">        --if-light</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        --if-dark</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">color-scheme</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">light</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    }</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF8F40">@media</span><span style="color: #BFBDB6"> (prefers-color-scheme</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> dark) {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #FFB454">:root</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">        --if-light</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        --if-dark</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">color-scheme</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">dark</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">light</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    }</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Now let&#8217;s use all that to style <code>&lt;body&gt;</code> and <code>&lt;button&gt;</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>body {
    background:
        var(--if-light, orange)
        var(--if-dark, darkorange);

    color:
        var(--if-light, darkgreen)
        var(--if-dark, blue);
}

button {
    background:
        var(--if-light, pink)
        var(--if-dark, blue);

    color:
        var(--if-light, blue)
        var(--if-dark, pink);
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-light</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">orange</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-dark</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkorange</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-light</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkgreen</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-dark</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #59C2FF">button</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-light</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">pink</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-dark</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-light</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-dark</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">pink</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Look at that. So much simpler to maintain. And we can extend that too, if we want to add high-constrast mode or something.</p>



<p class="wp-block-paragraph">Let&#8217;s test it.</p>



<p class="wp-block-paragraph">Here it is when we switch our system to dark mode:</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="796" height="372" src="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-dark.png" alt="A blue-on-dark-orange page saying &quot;Welcome to my beautiful page.&quot;, with three pink-on-blue buttons saying &quot;Hello&quot;, &quot;There&quot;, and &quot;Friend&quot;." class="wp-image-1522" srcset="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-dark.png 796w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-dark-300x140.png 300w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-dark-768x359.png 768w" sizes="auto, (max-width: 796px) 100vw, 796px" /></figure>



<p class="wp-block-paragraph">Far less eye strain than light. I&#8217;m sold.</p>



<p class="wp-block-paragraph">Now light mode:</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="796" height="372" src="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-light.png" alt="A green-on-light-orange page saying &quot;Welcome to my beautiful page.&quot;, with three blue-on-pink buttons saying &quot;Hello&quot;, &quot;There&quot;, and &quot;Friend&quot;." class="wp-image-1523" srcset="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-light.png 796w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-light-300x140.png 300w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-light-768x359.png 768w" sizes="auto, (max-width: 796px) 100vw, 796px" /></figure>



<p class="wp-block-paragraph">And system dark mode with <code>&lt;html class="is-light"&gt;</code> overriding to light mode.</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="796" height="372" src="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-light.png" alt="A green-on-light-orange page saying &quot;Welcome to my beautiful page.&quot;, with three blue-on-pink buttons saying &quot;Hello&quot;, &quot;There&quot;, and &quot;Friend&quot;." class="wp-image-1523" srcset="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-light.png 796w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-light-300x140.png 300w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-light-768x359.png 768w" sizes="auto, (max-width: 796px) 100vw, 796px" /></figure>



<p class="wp-block-paragraph">And system light mode with <code>&lt;html class="is-dark"&gt;</code> overriding to dark mode.</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="796" height="372" src="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-dark.png" alt="A blue-on-dark-orange page saying &quot;Welcome to my beautiful page.&quot;, with three pink-on-blue buttons saying &quot;Hello&quot;, &quot;There&quot;, and &quot;Friend&quot;." class="wp-image-1522" srcset="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-dark.png 796w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-dark-300x140.png 300w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo3-dark-768x359.png 768w" sizes="auto, (max-width: 796px) 100vw, 796px" /></figure>



<p class="wp-block-paragraph">Perfection.</p>



<p class="wp-block-paragraph">You can play with that one here:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_ZYbygjB" src="//codepen.io/anon/embed/ZYbygjB?height=450&amp;theme-id=1&amp;slug-hash=ZYbygjB&amp;default-tab=css,result&amp;editable=true" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed ZYbygjB" title="CodePen Embed ZYbygjB" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">Nesting Conditionals!</h1>



<p class="wp-block-paragraph">That&#8217;s right, you can nest them! Let&#8217;s update our Light/Dark Mode example to make a prettier color scheme, because I&#8217;m starting to realize what we had was kind of ugly.</p>



<p class="wp-block-paragraph">We&#8217;ll define new <code>--if-pretty</code> and <code>--if-ugly</code> states to go along with <code>--if-dark</code> and <code>--if-light</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>:root,
:root.-is-light {
    --if-pretty: ;
    --if-ugly: initial;

    ...
}

:root.-is-pretty {
    --if-pretty: initial;
    --if-ugly: ;
}

...</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #FFB454">:root</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #FFB454">:root.-is-light</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-pretty</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-ugly</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    ...</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FFB454">:root.-is-pretty</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-pretty</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">    --if-ugly</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">...</span></span></code></pre></div>



<p class="wp-block-paragraph">And now we can build some truly beautiful styling, using nested conditionals:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>body {
    background:
        var(--if-pretty,
            var(--if-light,
                linear-gradient(
                    in hsl longer hue 45deg,
                    red 0 100%)
                )
            )
            var(--if-dark,
                black
                linear-gradient(
                    in hsl longer hue 45deg,
                    rgba(255, 0, 0, 20%) 0 100%)
                )
            )
        )
        var(--if-ugly,
            var(--if-light, orange)
            var(--if-dark, darkorange)
        );

    color:
        var(--if-pretty,
            var(--if-light, black)
            var(--if-dark, white)
        )
        var(--if-ugly,
            var(--if-light, darkgreen)
            var(--if-dark, blue)
        );

    font-size: 100%;
}

button {
    background:
        var(--if-pretty,
            radial-gradient(
                ellipse at top,
                orange,
                transparent
            ),
            radial-gradient(
                ellipse at bottom,
                orange,
                transparent
            )
        )
        var(--if-ugly,
            var(--if-light, pink)
            var(--if-dark, blue)
        );

  color:
        var(--if-pretty, black)
        var(--if-ugly,
            var(--if-light, blue)
            var(--if-dark, pink)
        );
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #59C2FF">body</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-pretty</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-light</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">                </span><span style="color: #F07178">linear-gradient</span><span style="color: #BFBDB6">(</span></span>
<span class="line"><span style="color: #BFBDB6">                    in hsl longer </span><span style="color: #F29668; font-style: italic">hue</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">45</span><span style="color: #FF8F40">deg</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">                    </span><span style="color: #F29668; font-style: italic">red</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">0</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">100</span><span style="color: #FF8F40">%</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">                )</span></span>
<span class="line"><span style="color: #BFBDB6">            )</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-dark</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">                </span><span style="color: #F29668; font-style: italic">black</span></span>
<span class="line"><span style="color: #BFBDB6">                </span><span style="color: #F07178">linear-gradient</span><span style="color: #BFBDB6">(</span></span>
<span class="line"><span style="color: #BFBDB6">                    in hsl longer </span><span style="color: #F29668; font-style: italic">hue</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">45</span><span style="color: #FF8F40">deg</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">                    </span><span style="color: #F07178">rgba</span><span style="color: #BFBDB6">(</span><span style="color: #D2A6FF">255</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">0</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">0</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">20</span><span style="color: #FF8F40">%</span><span style="color: #BFBDB6">) </span><span style="color: #D2A6FF">0</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">100</span><span style="color: #FF8F40">%</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">                )</span></span>
<span class="line"><span style="color: #BFBDB6">            )</span></span>
<span class="line"><span style="color: #BFBDB6">        )</span></span>
<span class="line"><span style="color: #BFBDB6">        var(--if-ugly,</span></span>
<span class="line"><span style="color: #BFBDB6">            var(--if-light, orange)</span></span>
<span class="line"><span style="color: #BFBDB6">            var(--if-dark, darkorange)</span></span>
<span class="line"><span style="color: #BFBDB6">        )</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-pretty</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-light</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">black</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-dark</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">white</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        )</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-ugly</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-light</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">darkgreen</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-dark</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        )</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">font-size</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">100</span><span style="color: #FF8F40">%</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #59C2FF">button</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-pretty</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">radial-gradient</span><span style="color: #BFBDB6">(</span></span>
<span class="line"><span style="color: #BFBDB6">                </span><span style="color: #F29668; font-style: italic">ellipse</span><span style="color: #BFBDB6"> </span><span style="color: #F29668">at</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">top</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">                </span><span style="color: #F29668; font-style: italic">orange</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">                </span><span style="color: #F29668; font-style: italic">transparent</span></span>
<span class="line"><span style="color: #BFBDB6">            )</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">radial-gradient</span><span style="color: #BFBDB6">(</span></span>
<span class="line"><span style="color: #BFBDB6">                </span><span style="color: #F29668; font-style: italic">ellipse</span><span style="color: #BFBDB6"> </span><span style="color: #F29668">at</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">bottom</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">                </span><span style="color: #F29668; font-style: italic">orange</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">                </span><span style="color: #F29668; font-style: italic">transparent</span></span>
<span class="line"><span style="color: #BFBDB6">            )</span></span>
<span class="line"><span style="color: #BFBDB6">        )</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-ugly</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-light</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">pink</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-dark</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        )</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">  </span><span style="color: #39BAE6">color</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-pretty</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">black</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-ugly</span><span style="color: #BFBDB6B3">,</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-light</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">blue</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">            </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-dark</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">pink</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">        )</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">In standard Ugly Mode, this looks the same as before, but when we apply Pretty Mode with Light Mode, we get:</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="796" height="372" src="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo4-pretty-light.png" alt="A black-on-rainbow page saying &quot;Welcome to my beautiful page.&quot;, with three orange circular gradient buttons saying &quot;Hello&quot;, &quot;There&quot;, and &quot;Friend&quot;." class="wp-image-1524" srcset="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo4-pretty-light.png 796w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo4-pretty-light-300x140.png 300w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo4-pretty-light-768x359.png 768w" sizes="auto, (max-width: 796px) 100vw, 796px" /></figure>



<p class="wp-block-paragraph">And then with Dark Mode:</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="796" height="372" src="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo4-pretty-dark.png" alt="A white-on-dark-rainbow page saying &quot;Welcome to my beautiful page.&quot;, with three orange circular gradient buttons saying &quot;Hello&quot;, &quot;There&quot;, and &quot;Friend&quot;." class="wp-image-1525" srcset="https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo4-pretty-dark.png 796w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo4-pretty-dark-300x140.png 300w, https://chipx86.blog/wp-content/uploads/2025/08/conditional-css-demo4-pretty-dark-768x359.png 768w" sizes="auto, (max-width: 796px) 100vw, 796px" /></figure>



<p class="wp-block-paragraph">I am available for design consulting work on a first-come, first-serve basis only.</p>



<p class="wp-block-paragraph">Here it is so you can enjoy it yourself:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_jEbLNBB" src="//codepen.io/anon/embed/jEbLNBB?height=450&amp;theme-id=1&amp;slug-hash=jEbLNBB&amp;default-tab=css,result&amp;editable=true" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed jEbLNBB" title="CodePen Embed jEbLNBB" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">A Backport from If()</h1>



<p class="wp-block-paragraph">Google&#8217;s <a href="https://developer.chrome.com/blog/if-article">article on if()</a> has a neat demo showing how you can style some cards based on the value of a <code>data-status=</code> attribute. Based on whether the value is <code>pending</code>, <code>complete</code>, or anything else, the cards will be placed in different columns and have different background and border colors.</p>



<p class="wp-block-paragraph">Here&#8217;s the card:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>&lt;div class="card" data-status="complete">
  ...
&lt;/div></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #39BAE680">&lt;</span><span style="color: #39BAE6">div</span><span style="color: #BFBDB6"> </span><span style="color: #FFB454">class</span><span style="color: #BFBDB6B3">=</span><span style="color: #AAD94C">&quot;card&quot;</span><span style="color: #BFBDB6"> </span><span style="color: #FFB454">data-status</span><span style="color: #BFBDB6B3">=</span><span style="color: #AAD94C">&quot;complete&quot;</span><span style="color: #39BAE680">&gt;</span></span>
<span class="line"><span style="color: #BFBDB6">  ...</span></span>
<span class="line"><span style="color: #39BAE680">&lt;/</span><span style="color: #39BAE6">div</span><span style="color: #39BAE680">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">Here&#8217;s how you&#8217;d do that with <code>if()</code> (from their demo):</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>.card {
    --status: attr(data-status type(&lt;custom-ident>));

    border-color: if(
        style(--status: pending): royalblue;
        style(--status: complete): seagreen;
        else: gray);

    background-color: if(
        style(--status: pending): #eff7fa;
        style(--status: complete): #f6fff6;
        else: #f7f7f7);

    grid-column: if(
        style(--status: pending): 1;
        style(--status: complete): 2;
        else: 3);
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #FFB454">.card</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">    --status</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F07178">attr</span><span style="color: #BFBDB6">(</span><span style="color: #D2A6FF">data-status</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">type(&lt;custom-ident&gt;</span><span style="color: #BFBDB6">))</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">border-color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> if(</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F29668; font-style: italic">style</span><span style="color: #BFBDB6">(--status: pending)</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">royalblue</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">style</span><span style="color: #BFBDB6">(--status</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> complete)</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">seagreen</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        else</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">gray</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">background-color</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> if(</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F29668; font-style: italic">style</span><span style="color: #BFBDB6">(--status: pending)</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #95E6CB">#eff7fa</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">style</span><span style="color: #BFBDB6">(--status</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> complete)</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #95E6CB">#f6fff6</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        else</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #95E6CB">#f7f7f7</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #39BAE6">grid-column</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> if(</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #F29668; font-style: italic">style</span><span style="color: #BFBDB6">(--status: pending)</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">1</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        </span><span style="color: #39BAE6">style</span><span style="color: #BFBDB6">(--status</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> complete)</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">2</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">        else</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">3</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">To do this with CSS Variables, we can define our states using CSS selectors on the attribute, and then style it the way we did earlier:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#bfbdb6;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>/* Define our variables and conditions. */
.card {
  --if-pending: ;
  --if-complete: ;
  --if-default: initial;
}

.card&#91;data-status="pending"&#93; {
  --if-pending: initial;
  --if-default: ;
}

.card&#91;data-status="complete"&#93; {
  --if-complete: initial;
  --if-default: ;
}

/* Style our cards. */
.card {
  border-color:
    var(--if-pending, royalblue)
    var(--if-complete, seagreen)
    var(--if-default, grey);

  background-color:
    var(--if-pending, #eff7fa)
    var(--if-complete, #f6fff6)
    var(--if-default, #f7f7f7);

  grid-column:
    var(--if-pending, 1)
    var(--if-complete, 2)
    var(--if-default, 3);
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki ayu-dark" style="background-color: #0b0e14" tabindex="0"><code><span class="line"><span style="color: #ACB6BF8C; font-style: italic">/* Define our variables and conditions. */</span></span>
<span class="line"><span style="color: #FFB454">.card</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">  --if-pending</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">  --if-complete</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">  --if-default</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FFB454">.card</span><span style="color: #BFBDB6">&#91;</span><span style="color: #FFB454">data-status</span><span style="color: #F29668">=</span><span style="color: #AAD94C">&quot;pending&quot;</span><span style="color: #BFBDB6">&#93; {</span></span>
<span class="line"><span style="color: #BFBDB6">  --if-pending</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">  --if-default</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FFB454">.card</span><span style="color: #BFBDB6">&#91;</span><span style="color: #FFB454">data-status</span><span style="color: #F29668">=</span><span style="color: #AAD94C">&quot;complete&quot;</span><span style="color: #BFBDB6">&#93; {</span></span>
<span class="line"><span style="color: #BFBDB6">  --if-complete</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">initial</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">  --if-default</span><span style="color: #BFBDB6B3">:</span><span style="color: #BFBDB6"> </span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ACB6BF8C; font-style: italic">/* Style our cards. */</span></span>
<span class="line"><span style="color: #FFB454">.card</span><span style="color: #BFBDB6"> {</span></span>
<span class="line"><span style="color: #BFBDB6">  </span><span style="color: #39BAE6">border-color</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-pending</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">royalblue</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-complete</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">seagreen</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-default</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #F29668; font-style: italic">grey</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">  </span><span style="color: #39BAE6">background-color</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-pending</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #95E6CB">#eff7fa</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-complete</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #95E6CB">#f6fff6</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-default</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #95E6CB">#f7f7f7</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #BFBDB6">  </span><span style="color: #39BAE6">grid-column</span><span style="color: #BFBDB6B3">:</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-pending</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">1</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-complete</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">2</span><span style="color: #BFBDB6">)</span></span>
<span class="line"><span style="color: #BFBDB6">    </span><span style="color: #F07178">var</span><span style="color: #BFBDB6">(--if-default</span><span style="color: #BFBDB6B3">,</span><span style="color: #BFBDB6"> </span><span style="color: #D2A6FF">3</span><span style="color: #BFBDB6">)</span><span style="color: #BFBDB6B3">;</span></span>
<span class="line"><span style="color: #BFBDB6">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Here&#8217;s the ported version, live:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_VYvWoqG" src="//codepen.io/anon/embed/VYvWoqG?height=450&amp;theme-id=1&amp;slug-hash=VYvWoqG&amp;default-tab=css,result&amp;editable=true" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed VYvWoqG" title="CodePen Embed VYvWoqG" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">It&#8217;s Available Now!</h1>



<p class="wp-block-paragraph">You can use this today in all browsers that support CSS Variables. That&#8217;s.. <em>counts</em>.. years worth of browsers.</p>



<p class="wp-block-paragraph">It requires a bit more work to set up the variables, but the nice thing is, once you&#8217;ve done that, the rest is highly maintainable. And usage is close enough to that of <code>if()</code> that you can more easily transition once support is widespread.</p>



<p class="wp-block-paragraph">We&#8217;ve based <a href="https://github.com/beanbaginc/ink">Ink</a>, our (still very young) CSS component library we use for <a href="https://www.reviewboard.org">Review Board</a>, around this conditionals pattern. It&#8217;s helped us to support light and dark modes along with the beginnings of low- and high-contrast modes and state for some components without tearing our hair out.</p>



<p class="wp-block-paragraph">Give it a try, play with the demos, and see if it&#8217;s a good fit for your codebase. Begin taking advantage of what <code>if()</code> has to offer today, so you can more easily port tomorrow.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://chipx86.blog/2025/08/08/what-if-using-conditional-css-variables/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://chipx86.blog/2025/08/08/what-if-using-conditional-css-variables/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>chipx86</name>
							<uri>http://chipx86.blog</uri>
						</author>

		<title type="html"><![CDATA[A crash course on Python function signatures and typing]]></title>
		<link rel="alternate" type="text/html" href="https://chipx86.blog/2025/07/12/a-crash-course-on-python-function-signatures-and-typing/" />

		<id>https://chipx86.blog/?p=1457</id>
		<updated>2025-08-08T22:38:12Z</updated>
		<published>2025-07-12T22:17:38Z</published>
		<category scheme="https://chipx86.blog" term="Python" /><category scheme="https://chipx86.blog" term="Today I Learned" /><category scheme="https://chipx86.blog" term="deep-dive" />
		<summary type="html"><![CDATA[More than you probably ever wanted to know about Python function signatures and typing shenanigans. And it's just the start.]]></summary>

					<content type="html" xml:base="https://chipx86.blog/2025/07/12/a-crash-course-on-python-function-signatures-and-typing/"><![CDATA[
<p class="wp-block-paragraph">I&#8217;ve been doing some work on <a href="https://www.reviewboard.org/">Review Board</a> and our utility library <a href="https://typelets.readthedocs.io/en/latest/">Typelets</a>, and thought I&#8217;d share some of the intricacies of function signatures and their typing in Python.</p>



<p class="wp-block-paragraph">We have some pretty neat Python typing utilities in the works to help a function inherit another function&#8217;s types in their own <code>*args</code> and <code>**kwargs</code> without rewriting a <code>TypedDict</code>. Useful for functions that need to forward arguments to another function. I&#8217;ll talk more about that later, but understanding how it works first requires understanding a bit about how Python sees functions.</p>



<h2 class="wp-block-heading">Function signatures</h2>



<p class="wp-block-paragraph">Python&#8217;s inspect module is full of goodies for analyzing objects and code, and today we&#8217;ll explore the <a href="https://docs.python.org/3/library/inspect.html#inspect.signature">inspect.signature()</a> function.</p>



<p class="wp-block-paragraph"><a href="https://docs.python.org/3/library/inspect.html#inspect.signature">inspect.signature()</a> is used to introspect the signature of another function, showing its parameters, default values, type annotations, and more. It can also aid IDEs in understanding a function signature. If a function has <code>__signature__</code> defined, it will reference this (at least in CPython), and this gives highly-dynamic code the ability to patch signatures at runtime. (If you&#8217;re curious, <a href="https://peps.python.org/pep-0362/#implementation">here&#8217;s exactly how it works</a>).</p>



<p class="wp-block-paragraph">There are a few places where knowing the signature can be useful, such as:</p>



<ul class="wp-block-list">
<li>Automatically-crafting documentation (<a href="https://www.sphinx-doc.org/">Sphinx</a> does this)</li>



<li>Checking if a callback handler accepts the right arguments</li>



<li>Checking if an implementation of an interface is using deprecated function signatures</li>
</ul>



<p class="wp-block-paragraph">Let&#8217;s set up a function and take a look at its signature.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> def my_func(
...     a: int,
...     b: str,
...     /,
...     c: dict&#91;str, str&#93; | None,
...     *,
...     d: bool = False,
...     **kwargs,
... ) -> str:
...     ...
... 
>>> import inspect
>>> sig = inspect.signature(my_func)

>>> sig
&lt;Signature (a: int, b: str, /, c: dict&#91;str, str&#93; | None, *,
d: bool = False, **kwargs) -> str>

>>> sig.parameters
mappingproxy(OrderedDict({
    'a': &lt;Parameter "a: int">,
    'b': &lt;Parameter "b: str">,
    'c': &lt;Parameter "c: dict&#91;str, str&#93; | None">,
    'd': &lt;Parameter "d: bool = False">,
    'kwargs': &lt;Parameter "**kwargs">
}))

>>> sig.return_annotation
&lt;class 'str'>

>>> sig.parameters.get('c')
&lt;Parameter "c: dict&#91;str, str&#93; | None">

>>> 'kwargs' in sig.parameters
True

>>> 'foo' in sig.parameters
False</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">my_func</span><span style="color: #49D6E9">(</span></span>
<span class="line"><span style="color: #E4B781">...     </span><span style="color: #E4B781; font-weight: bold">a</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">int</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">...     </span><span style="color: #E4B781; font-weight: bold">b</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">...     </span><span style="color: #DF769B; font-weight: bold">/</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">...     </span><span style="color: #E4B781; font-weight: bold">c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">...     </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">...     </span><span style="color: #E4B781; font-weight: bold">d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">...     </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781; font-weight: bold">kwargs</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">... </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781">     </span><span style="color: #D5971A">...</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781"> </span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">import</span><span style="color: #E4B781"> inspect</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> sig </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> inspect</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">signature</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">my_func</span><span style="color: #B2CACD">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> sig</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #16A3B6">Signature</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">(</span><span style="color: #E4B781">a</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">int</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> b</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">/</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #49ACE9">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #E4B781; font-weight: bold">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781">kwargs</span><span style="color: #B2CACD">)</span><span style="color: #E4B781"> </span><span style="color: #E3541C">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #DF769B; font-weight: bold">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> sig</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">parameters</span></span>
<span class="line"><span style="color: #16A3B6">mappingproxy</span><span style="color: #B2CACD">(</span><span style="color: #16A3B6">OrderedDict</span><span style="color: #B2CACD">({</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49E9A6">&#39;a&#39;</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781">Parameter </span><span style="color: #49E9A6">&quot;a: int&quot;</span><span style="color: #DF769B; font-weight: bold">&gt;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49E9A6">&#39;b&#39;</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781">Parameter </span><span style="color: #49E9A6">&quot;b: str&quot;</span><span style="color: #DF769B; font-weight: bold">&gt;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49E9A6">&#39;c&#39;</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781">Parameter </span><span style="color: #49E9A6">&quot;c: dict&#91;str, str&#93; | None&quot;</span><span style="color: #DF769B; font-weight: bold">&gt;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49E9A6">&#39;d&#39;</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781">Parameter </span><span style="color: #49E9A6">&quot;d: bool = False&quot;</span><span style="color: #DF769B; font-weight: bold">&gt;</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49E9A6">&#39;kwargs&#39;</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781">Parameter </span><span style="color: #49E9A6">&quot;**kwargs&quot;</span><span style="color: #DF769B; font-weight: bold">&gt;</span></span>
<span class="line"><span style="color: #B2CACD">}))</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> sig</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">return_annotation</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;str&#39;</span><span style="color: #DF769B; font-weight: bold">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> sig</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">parameters</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">get</span><span style="color: #B2CACD">(</span><span style="color: #49E9A6">&#39;c&#39;</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781">Parameter </span><span style="color: #49E9A6">&quot;c: dict&#91;str, str&#93; | None&quot;</span><span style="color: #DF769B; font-weight: bold">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;kwargs&#39;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">in</span><span style="color: #E4B781"> sig</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">parameters</span></span>
<span class="line"><span style="color: #7060EB">True</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;foo&#39;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">in</span><span style="color: #E4B781"> sig</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">parameters</span></span>
<span class="line"><span style="color: #7060EB">False</span></span></code></pre></div>



<p class="wp-block-paragraph">Pretty neat. Pretty useful, when you need to know what a function takes and returns.</p>



<p class="wp-block-paragraph">Let&#8217;s see what happens when we work with methods on classes.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> class MyClass:
...     def my_method(
...         self,
...         *args,
...         **kwargs,
...     ) -> None:
...         ...
...
>>> inspect.signature(MyClass.my_method)
&lt;Signature (self, *args, **kwargs) -> None></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyClass</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781">     </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">my_method</span><span style="color: #49D6E9">(</span></span>
<span class="line"><span style="color: #E4B781">...         </span><span style="color: #E66533; font-style: italic">self</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">...         </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781; font-weight: bold">args</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">...         </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781; font-weight: bold">kwargs</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">...     </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781">         </span><span style="color: #D5971A">...</span></span>
<span class="line"><span style="color: #D5971A">...</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> inspect</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">signature</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">MyClass</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">my_method</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #16A3B6">Signature</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">(</span><span style="color: #E66533; font-style: italic">self</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">args</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781">kwargs</span><span style="color: #B2CACD">)</span><span style="color: #E4B781"> </span><span style="color: #E3541C">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #DF769B; font-weight: bold">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">Seems reasonable. But&#8230;</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> obj = MyClass()
>>> inspect.signature(obj.my_method)
&lt;Signature (*args, **kwargs) -> None></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> obj </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">MyClass</span><span style="color: #B2CACD">()</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> inspect</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">signature</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">obj</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">my_method</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #16A3B6">Signature</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">(</span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">args</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781">kwargs</span><span style="color: #B2CACD">)</span><span style="color: #E4B781"> </span><span style="color: #E3541C">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #DF769B; font-weight: bold">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph"><code>self</code> disappeared!</p>



<p class="wp-block-paragraph">What happens if we do this on a <code><a href="https://docs.python.org/3/library/functions.html#classmethod">classmethod</a></code>? Place your bets&#8230;</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> class MyClass2:
...     @classmethod
...     def my_method(
...         cls,
...         *args,
...         **kwargs,
...     ) -> None:
...         ...
...
>>> inspect.signature(MyClass2.my_method)
&lt;Signature (*args, **kwargs) -> None></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyClass2</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781">     </span><span style="color: #DF769B; font-weight: bold">@</span><span style="color: #D67E5C; font-style: italic">classmethod</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781">     </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">my_method</span><span style="color: #49D6E9">(</span></span>
<span class="line"><span style="color: #E4B781">...         </span><span style="color: #E66533; font-style: italic">cls</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">...         </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781; font-weight: bold">args</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">...         </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781; font-weight: bold">kwargs</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">...     </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781">         </span><span style="color: #D5971A">...</span></span>
<span class="line"><span style="color: #D5971A">...</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> inspect</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">signature</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">MyClass2</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">my_method</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #16A3B6">Signature</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">(</span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">args</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781">kwargs</span><span style="color: #B2CACD">)</span><span style="color: #E4B781"> </span><span style="color: #E3541C">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #DF769B; font-weight: bold">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">If you guessed it wouldn&#8217;t have <code>cls</code>, you&#8217;d be right.</p>



<p class="wp-block-paragraph">Only <strong>unbound methods</strong> (definitions of methods on a class) will have a <code>self</code> parameter in the signature. <strong>Bound methods</strong> (callable methods bound to an instance of a class) and <strong>classmethods</strong> (callable methods bound to a class) don&#8217;t. And this makes sense, if you think about it, because this signature represents <em>what the call accepts</em>, not <em>what the code defining the method looks like</em>.</p>



<p class="wp-block-paragraph">You don&#8217;t pass a <code>self</code> when calling a method on an object, or a <code>cls</code> when calling a classmethod, so it doesn&#8217;t appear in the function signature. But did you know that you can call an unbound method <em>if</em> you provide an object as the <code>self</code> parameter? Watch this:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> class MyClass:
...     def my_method(self) -> None:
...         self.x = 42
...
>>> obj = MyClass()
>>> MyClass.my_method(obj)
>>> obj.x
42</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyClass</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781">     </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">my_method</span><span style="color: #49D6E9">(</span><span style="color: #E66533; font-style: italic">self</span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781">         </span><span style="color: #E66533; font-style: italic">self</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">x </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">42</span></span>
<span class="line"><span style="color: #D5971A">...</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> obj </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">MyClass</span><span style="color: #B2CACD">()</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> MyClass</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">my_method</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">obj</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> obj</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">x</span></span>
<span class="line"><span style="color: #7060EB">42</span></span></code></pre></div>



<p class="wp-block-paragraph">In this case, the unbound method <code>MyClass.my_method</code> has a <code>self</code> argument in its signature, meaning it takes it in a call. So, we can just pass in an instance. There aren&#8217;t a lot of cases where you&#8217;d want to go this route, but it&#8217;s helpful to know how this works.</p>



<h2 class="wp-block-heading">What are bound and unbound methods?</h2>



<p class="wp-block-paragraph">I briefly touched upon this, but:</p>



<ul class="wp-block-list">
<li>Unbound methods are just functions. Functions that are defined on a class.</li>
</ul>



<ul class="wp-block-list">
<li>Bound methods are a function where the very first argument (<code>self</code> or <code>cls</code>) is bound to a value.</li>
</ul>



<p class="wp-block-paragraph">Binding <a href="https://docs.python.org/3/howto/descriptor.html#functions-and-methods">normally happens when you instantiate a class</a>, but you can do it yourself through any function&#8217;s <code><a href="https://docs.python.org/3/reference/datamodel.html#object.__get__">__get__()</a></code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> def my_func(self):
...     print('I am', self)
... 
>>> class MyClass:
...     ...
...
>>> obj = MyClass()
>>> method = my_func.__get__(MyClass)
>>> method
&lt;bound method my_func of &lt;__main__.MyClass object at 0x100ea20d0>>

>>> method.__self__
&lt;__main__.MyClass object at 0x100ea20d0>

>>> inspect.ismethod(method)
True

>>> method()
I am &lt;__main__.MyClass object at 0x100ea20d0></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">my_func</span><span style="color: #49D6E9">(</span><span style="color: #E66533; font-style: italic">self</span><span style="color: #49D6E9">)</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781">     </span><span style="color: #49D6E9">print</span><span style="color: #B2CACD">(</span><span style="color: #49E9A6">&#39;I am&#39;</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #E66533; font-style: italic">self</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781"> </span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyClass</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #D5971A">...</span><span style="color: #E4B781">     </span><span style="color: #D5971A">...</span></span>
<span class="line"><span style="color: #D5971A">...</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> obj </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">MyClass</span><span style="color: #B2CACD">()</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> method </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> my_func</span><span style="color: #B2CACD">.</span><span style="color: #49D6E9">__get__</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">MyClass</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> method</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781">bound method my_func of </span><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781">__main__</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">MyClass </span><span style="color: #D67E5C; font-style: italic">object</span><span style="color: #E4B781"> at </span><span style="color: #E66533">0x</span><span style="color: #7060EB">100ea20d0</span><span style="color: #DF769B; font-weight: bold">&gt;&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> method</span><span style="color: #B2CACD">.</span><span style="color: #D5971A">__self__</span></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781">__main__</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">MyClass </span><span style="color: #D67E5C; font-style: italic">object</span><span style="color: #E4B781"> at </span><span style="color: #E66533">0x</span><span style="color: #7060EB">100ea20d0</span><span style="color: #DF769B; font-weight: bold">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> inspect</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">ismethod</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">method</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #7060EB">True</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">method</span><span style="color: #B2CACD">()</span></span>
<span class="line"><span style="color: #E4B781">I am </span><span style="color: #DF769B; font-weight: bold">&lt;</span><span style="color: #E4B781">__main__</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">MyClass </span><span style="color: #D67E5C; font-style: italic">object</span><span style="color: #E4B781"> at </span><span style="color: #E66533">0x</span><span style="color: #7060EB">100ea20d0</span><span style="color: #DF769B; font-weight: bold">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph"><code>my_func</code> wasn&#8217;t even defined on a class, and yet we could still make it a bound method tied to an instance of <code>MyClass</code>.</p>



<p class="wp-block-paragraph">You can think of a bound method as a convenience over having to pass in an object as the first parameter every time you want to call the function. As we saw above, we <em>can</em> do <em>exactly</em> that, if we pass it to the unbound method, but binding saves us from doing this every time.</p>



<p class="wp-block-paragraph">You&#8217;ll probably never need to do this trick yourself, but it&#8217;s helpful to know how it all ties together.</p>



<p class="wp-block-paragraph">By the way, <code><a href="https://docs.python.org/3/library/functions.html#staticmethod">@staticmethod</a></code> is a way of telling Python to never make an unbound method into a bound method when instantiating the object (it stays a function), and <code><a href="https://docs.python.org/3/library/functions.html#classmethod">@classmethod</a></code> is a way of telling Python to bind it immediately to the class it&#8217;s defined on (and not rebind when instantiating an object from the class).</p>



<h2 class="wp-block-heading">How do you tell them apart?</h2>



<p class="wp-block-paragraph">If you have a function, and you don&#8217;t know if it&#8217;s a standard function, a classmethod, a bound method, or an unbound method, how can you tell?</p>



<ol class="wp-block-list">
<li>Bound methods have a <code><a href="https://docs.python.org/3/reference/datamodel.html#method.__self__">__self__</a></code> attribute pointing to the parent object (and <a href="https://docs.python.org/3/library/inspect.html#inspect.ismethod">inspect.ismethod()</a> will be <code>True</code>).</li>



<li>Classmethods have a <code><a href="https://docs.python.org/3/reference/datamodel.html#method.__self__">__self__</a></code> attribute pointing to the parent class (and <a href="https://docs.python.org/3/library/inspect.html#inspect.ismethod">inspect.ismethod()</a> will be <code>True</code>).</li>



<li>Unbound methods are tricky:
<ul class="wp-block-list">
<li>They do <em>not</em> have a <code><a href="https://docs.python.org/3/reference/datamodel.html#method.__self__">__self__</a></code> attribute.</li>



<li>They <em>might</em> have a <code>self</code> or <code>cls</code> parameter in the signature, but they might not have those names (and other functions may define them). </li>



<li>They <em>should</em> have a <code>.</code> in its <code><a href="https://docs.python.org/3/reference/datamodel.html#function.__qualname__">__qualname__</a></code> attribute. This is a full <code>.</code>-based path to the method, relative to the module.</li>



<li>Splitting <code><a href="https://docs.python.org/3/reference/datamodel.html#function.__qualname__">__qualname__</a></code>, the last component would be the name. The previous component won&#8217;t be <code>&lt;locals&gt;</code> (but if <code>&lt;locals&gt;</code> is found, you&#8217;re going to have trouble getting to the method).</li>



<li>If the full path is resolvable, the parent component <em>should</em> be a class (but it might not be).</li>



<li>You could&#8230; resolve the parent module to a file and walk its <a href="https://docs.python.org/3/library/ast.html">AST</a> and find the class and method based on <code><a href="https://docs.python.org/3/reference/datamodel.html#function.__qualname__">__qualname__</a></code>. But this is expensive and probably a bad idea for most cases.</li>
</ul>
</li>



<li>Standard functions are the fallback.</li>
</ol>



<p class="wp-block-paragraph">Since unbound methods <em>are</em> standard functions that are just defined on a class, it&#8217;s difficult to really tell the difference. You&#8217;d have to rely on heuristics and know you won&#8217;t always get a definitive answer. </p>



<p class="wp-block-paragraph">(Interesting note: In Python 2, unbound methods were special kinds of functions with a <code>__self__</code> that pointed to the class they were defined on, so you could easily tell!)</p>



<h2 class="wp-block-heading">The challenges of typing</h2>



<p class="wp-block-paragraph">Functions can be typed using <a href="https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable"><code>Callable[[&lt;param_types&gt;], &lt;return_type&gt;]</code></a>.</p>



<p class="wp-block-paragraph">This is very simplistic, and can&#8217;t be used to represent positional-only arguments, keyword-only arguments, default arguments, <code>*ar</code>gs, or <code>**kwargs</code>. For that, you can define a Protocol with <code><a href="https://docs.python.org/3/reference/datamodel.html#object.__call__">__call__</a></code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>from typing import Protocol


class MyCallback(Protocol):
    def __call__(
        self,  # This won't be part of the function signature
        a: int,
        b: str,
        /,
        c: dict&#91;str, str&#93; | None,
        *,
        d: bool = False,
        **kwargs,
    ) -> str:
        ...</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">from</span><span style="color: #E4B781"> typing </span><span style="color: #DF769B; font-weight: bold">import</span><span style="color: #E4B781"> Protocol</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyCallback</span><span style="color: #B2CACD">(</span><span style="color: #49ACE9; font-style: italic">Protocol</span><span style="color: #B2CACD">):</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #49D6E9">__call__(</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E66533; font-style: italic">self</span><span style="color: #B2CACD">,</span><span style="color: #E4B781">  </span><span style="color: #5B858B; font-style: italic"># This won&#39;t be part of the function signature</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">a</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">int</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">b</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">/</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781; font-weight: bold">kwargs</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #D5971A">...</span></span></code></pre></div>



<p class="wp-block-paragraph">Type checkers will then treat this as a <code>Callable</code>, effectively. If we take <code>my_func</code> from the top of this post, we can assign to it:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>cb: MyCallback = my_func  # This works</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #E4B781">cb</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> my_func  </span><span style="color: #5B858B; font-style: italic"># This works</span></span></code></pre></div>



<p class="wp-block-paragraph">What if we want to assign a method from a class? Let&#8217;s try bound and unbound.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class MyClass:
    def my_func(
        self,
        a: int,
        b: str,
        /,
        c: dict&#91;str, str&#93; | None,
        *,
        d: bool = False,
        **kwargs,
    ) -> str:
        return '42'

cb2: MyCallback = MyClass.my_func  # This fails
cb3: MyCallback = MyClass().my_func  # This works</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyClass</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">my_func</span><span style="color: #49D6E9">(</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E66533; font-style: italic">self</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">a</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">int</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">b</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">/</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781; font-weight: bold">kwargs</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">return</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;42&#39;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E4B781">cb2</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> MyClass</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">my_func  </span><span style="color: #5B858B; font-style: italic"># This fails</span></span>
<span class="line"><span style="color: #E4B781">cb3</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">MyClass</span><span style="color: #B2CACD">().</span><span style="color: #E4B781">my_func  </span><span style="color: #5B858B; font-style: italic"># This works</span></span></code></pre></div>



<p class="wp-block-paragraph">What happened? It&#8217;s that <code>self</code> again. Remember, the unbound method has <code>self</code> in the signature, and the bound method does not.</p>



<p class="wp-block-paragraph">Let&#8217;s add <code>self</code> and try again.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class MyCallback(Protocol):
    def __call__(
        _proto_self,  # This won't be part of the function signature
        self: Any,
        a: int,
        b: str,
        /,
        c: dict&#91;str, str&#93; | None,
        *,
        d: bool = False,
        **kwargs,
    ) -> str:
        ...

cb2: MyCallback = MyClass.my_func    # This works
cb3: MyCallback = MyClass().my_func  # This fails</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyCallback</span><span style="color: #B2CACD">(</span><span style="color: #49ACE9; font-style: italic">Protocol</span><span style="color: #B2CACD">):</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #49D6E9">__call__(</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">_proto_self</span><span style="color: #B2CACD">,</span><span style="color: #E4B781">  </span><span style="color: #5B858B; font-style: italic"># This won&#39;t be part of the function signature</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">self</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> Any</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">a</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">int</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">b</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">/</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781; font-weight: bold">kwargs</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #D5971A">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E4B781">cb2</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> MyClass</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">my_func    </span><span style="color: #5B858B; font-style: italic"># This works</span></span>
<span class="line"><span style="color: #E4B781">cb3</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">MyClass</span><span style="color: #B2CACD">().</span><span style="color: #E4B781">my_func  </span><span style="color: #5B858B; font-style: italic"># This fails</span></span></code></pre></div>



<p class="wp-block-paragraph">What happened this time?! Well, now we&#8217;ve matched the unbound signature with <code>self</code>, but not the bound signature without it.</p>



<p class="wp-block-paragraph">Solving this gets&#8230; verbose. We can create two versions of this: Unbound, and Bound (or plain function, or classmethod):</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class MyUnboundCallback(Protocol):
    def __call__(
        _proto_self,  # This won't be part of the function signature
        self: Any,
        a: int,
        b: str,
        /,
        c: dict&#91;str, str&#93; | None,
        *,
        d: bool = False,
        **kwargs,
    ) -> str:
        ...

class MyCallback(Protocol):
    def __call__(
        _proto_self,  # This won't be part of the function signature
        a: int,
        b: str,
        /,
        c: dict&#91;str, str&#93; | None,
        *,
        d: bool = False,
        **kwargs,
    ) -> str:
        ...

# These work
cb4: MyCallback = my_func
cb5: MyCallback = MyClass().my_func


# These fail correctly
cb7: MyUnboundCallback = my_func
cb8: MyUnboundCallback = MyClass().my_func
cb9: MyCallback = MyClass.my_func</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyUnboundCallback</span><span style="color: #B2CACD">(</span><span style="color: #49ACE9; font-style: italic">Protocol</span><span style="color: #B2CACD">):</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #49D6E9">__call__(</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">_proto_self</span><span style="color: #B2CACD">,</span><span style="color: #E4B781">  </span><span style="color: #5B858B; font-style: italic"># This won&#39;t be part of the function signature</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">self</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> Any</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">a</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">int</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">b</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">/</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781; font-weight: bold">kwargs</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #D5971A">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyCallback</span><span style="color: #B2CACD">(</span><span style="color: #49ACE9; font-style: italic">Protocol</span><span style="color: #B2CACD">):</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #49D6E9">__call__(</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">_proto_self</span><span style="color: #B2CACD">,</span><span style="color: #E4B781">  </span><span style="color: #5B858B; font-style: italic"># This won&#39;t be part of the function signature</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">a</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">int</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">b</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">/</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781; font-weight: bold">kwargs</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #D5971A">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #5B858B; font-style: italic"># These work</span></span>
<span class="line"><span style="color: #E4B781">cb4</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> my_func</span></span>
<span class="line"><span style="color: #E4B781">cb5</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">MyClass</span><span style="color: #B2CACD">().</span><span style="color: #E4B781">my_func</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #5B858B; font-style: italic"># These fail correctly</span></span>
<span class="line"><span style="color: #E4B781">cb7</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyUnboundCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> my_func</span></span>
<span class="line"><span style="color: #E4B781">cb8</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyUnboundCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">MyClass</span><span style="color: #B2CACD">().</span><span style="color: #E4B781">my_func</span></span>
<span class="line"><span style="color: #E4B781">cb9</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> MyClass</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">my_func</span></span></code></pre></div>



<p class="wp-block-paragraph">This means we can use union types (<code>MyUnboundCallback | MyCallback</code>) to cover our bases.</p>



<p class="wp-block-paragraph">It&#8217;s not flawless. Depending on how you&#8217;ve typed your signature, and the signature of the function you&#8217;re setting, you might not get the behavior you want or expect. As an example, any method with a leading <code>self</code>-like parameter (basically any parameter coming before your defined signature) will type as <code>MyUnboundCallback</code>, because it might be! Remember, we can turn any function into a bound method for an arbitrary class using <code>__get__</code>. That may or may not matter, depending on what you need to do.</p>



<p class="wp-block-paragraph">What do I mean by that?</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>def my_bindable_func(
    x,
    a: int,
    b: str,
    /,
    c: dict&#91;str, str&#93; | None,
    *,
    d: bool = False,
    **kwargs,
) -> str:
    return ''


x1: MyCallback = my_bindable_func         # This fails
x2: MyUnboundCallback = my_bindable_func  # This works</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">my_bindable_func</span><span style="color: #49D6E9">(</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E4B781; font-weight: bold">x</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E4B781; font-weight: bold">a</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">int</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E4B781; font-weight: bold">b</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #DF769B; font-weight: bold">/</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E4B781; font-weight: bold">c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E4B781; font-weight: bold">d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781; font-weight: bold">kwargs</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #DF769B; font-weight: bold">return</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;&#39;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #E4B781">x1</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> my_bindable_func         </span><span style="color: #5B858B; font-style: italic"># This fails</span></span>
<span class="line"><span style="color: #E4B781">x2</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyUnboundCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> my_bindable_func  </span><span style="color: #5B858B; font-style: italic"># This works</span></span></code></pre></div>



<p class="wp-block-paragraph"><code>x</code> may not be named <code>self</code>, but it&#8217;ll get treated as one, because if we do <code>my_bindable_func.__get__(some_obj)</code>, then <code>some_obj</code> will be bound to <code>x</code> and callers won&#8217;t have to pass anything to <code>x</code>.</p>



<p class="wp-block-paragraph">Okay, what if you want to return a function that can behave as an unbound method (with  <code>self</code>) that can become an unbound method (with <code>__get__</code>)? We can mostly do it with:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>from typing import ParamSpec, TypeVar, cast, overload


_C = TypeVar('_C')
_R_co = TypeVar('_R_co', covariant=True)
_P = ParamSpec('_P')


class MyMethod(Protocol&#91;_C, _P, _R_co&#93;):
    __self__: _C

    @overload
    def __get__(
        self,
        instance: None,
        owner: type&#91;_C&#93;,
    ) -> Callable[Concatenate&#91;_C, _P&#93;, _R_co]:
        ...

    @overload
    def __get__(
        self,
        instance: _C,
        owner: type&#91;_C&#93;,
    ) -> Callable&#91;_P, _R_co&#93;:
        ...

    def __get__(
        self,
        instance: _C | None,
        owner: type&#91;_C&#93;,
    ) -> (
        Callable[Concatenate&#91;_C, _P&#93;, _R_co] |
        Callable&#91;_P, _R_co&#93;
    ):
        ...</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">from</span><span style="color: #E4B781"> typing </span><span style="color: #DF769B; font-weight: bold">import</span><span style="color: #E4B781"> ParamSpec</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> TypeVar</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> cast</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> overload</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #E4B781">_C </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">TypeVar</span><span style="color: #B2CACD">(</span><span style="color: #49E9A6">&#39;_C&#39;</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #E4B781">_R_co </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">TypeVar</span><span style="color: #B2CACD">(</span><span style="color: #49E9A6">&#39;_R_co&#39;</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #E4B781; font-weight: bold">covariant</span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #7060EB">True</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #E4B781">_P </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">ParamSpec</span><span style="color: #B2CACD">(</span><span style="color: #49E9A6">&#39;_P&#39;</span><span style="color: #B2CACD">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyMethod</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">Protocol</span><span style="color: #B2CACD">&#91;</span><span style="color: #E4B781">_C</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> _P</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> _R_co</span><span style="color: #B2CACD">&#93;):</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #D5971A">__self__</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> _C</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #16A3B6">@overload</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #49D6E9">__get__(</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E66533; font-style: italic">self</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">instance</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">owner</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> type</span><span style="color: #B2CACD">&#91;</span><span style="color: #E4B781">_C</span><span style="color: #B2CACD">&#93;,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> Callable</span><span style="color: #B2CACD">[</span><span style="color: #E4B781">Concatenate</span><span style="color: #B2CACD">&#91;</span><span style="color: #E4B781">_C</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> _P</span><span style="color: #B2CACD">&#93;,</span><span style="color: #E4B781"> _R_co</span><span style="color: #B2CACD">]:</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #D5971A">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #16A3B6">@overload</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #49D6E9">__get__(</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E66533; font-style: italic">self</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">instance</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> _C</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">owner</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> type</span><span style="color: #B2CACD">&#91;</span><span style="color: #E4B781">_C</span><span style="color: #B2CACD">&#93;,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> Callable</span><span style="color: #B2CACD">&#91;</span><span style="color: #E4B781">_P</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> _R_co</span><span style="color: #B2CACD">&#93;:</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #D5971A">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #49D6E9">__get__(</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E66533; font-style: italic">self</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">instance</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> _C </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">owner</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> type</span><span style="color: #B2CACD">&#91;</span><span style="color: #E4B781">_C</span><span style="color: #B2CACD">&#93;,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">(</span></span>
<span class="line"><span style="color: #E4B781">        Callable</span><span style="color: #B2CACD">[</span><span style="color: #E4B781">Concatenate</span><span style="color: #B2CACD">&#91;</span><span style="color: #E4B781">_C</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> _P</span><span style="color: #B2CACD">&#93;,</span><span style="color: #E4B781"> _R_co</span><span style="color: #B2CACD">]</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span></span>
<span class="line"><span style="color: #E4B781">        Callable</span><span style="color: #B2CACD">&#91;</span><span style="color: #E4B781">_P</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> _R_co</span><span style="color: #B2CACD">&#93;</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #B2CACD">):</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #D5971A">...</span></span></code></pre></div>



<p class="wp-block-paragraph">Putting it into practice:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>def make_method(
    source_method: Callable[Concatenate&#91;_C, _P&#93;, _R_co],
) -> MyMethod&#91;_C, _P, _R_co&#93;:
    return cast(MyMethod, source_method)


class MyClass2:
    @make_method
    def my_method(
        self,
        a: int,
        b: str,
        /,
        c: dict&#91;str, str&#93; | None,
        *,
        d: bool = False,
        **kwargs,
    ) -> str:
        return True


# These work!
MyClass2().my_method(1, 'x', {}, d=True)
MyClass2.my_method(MyClass2(), 1, 'x', {}, d=True)</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">make_method</span><span style="color: #49D6E9">(</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E4B781; font-weight: bold">source_method</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> Callable</span><span style="color: #B2CACD">[</span><span style="color: #E4B781">Concatenate</span><span style="color: #B2CACD">&#91;</span><span style="color: #E4B781">_C</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> _P</span><span style="color: #B2CACD">&#93;,</span><span style="color: #E4B781"> _R_co</span><span style="color: #B2CACD">],</span></span>
<span class="line"><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> MyMethod</span><span style="color: #B2CACD">&#91;</span><span style="color: #E4B781">_C</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> _P</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> _R_co</span><span style="color: #B2CACD">&#93;:</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #DF769B; font-weight: bold">return</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">cast</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">MyMethod</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> source_method</span><span style="color: #B2CACD">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #E66533">class</span><span style="color: #E4B781"> </span><span style="color: #49ACE9">MyClass2</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #16A3B6">@make_method</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #E66533; font-weight: bold">def</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">my_method</span><span style="color: #49D6E9">(</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E66533; font-style: italic">self</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">a</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">int</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">b</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">/</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #E4B781">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #E4B781; font-weight: bold">d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781; font-weight: bold">kwargs</span><span style="color: #B2CACD">,</span></span>
<span class="line"><span style="color: #E4B781">    </span><span style="color: #49D6E9">)</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">:</span></span>
<span class="line"><span style="color: #E4B781">        </span><span style="color: #DF769B; font-weight: bold">return</span><span style="color: #E4B781"> </span><span style="color: #7060EB">True</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #5B858B; font-style: italic"># These work!</span></span>
<span class="line"><span style="color: #16A3B6">MyClass2</span><span style="color: #B2CACD">().</span><span style="color: #16A3B6">my_method</span><span style="color: #B2CACD">(</span><span style="color: #7060EB">1</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;x&#39;</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">{},</span><span style="color: #E4B781"> </span><span style="color: #E4B781; font-weight: bold">d</span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #7060EB">True</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #E4B781">MyClass2</span><span style="color: #B2CACD">.</span><span style="color: #16A3B6">my_method</span><span style="color: #B2CACD">(</span><span style="color: #16A3B6">MyClass2</span><span style="color: #B2CACD">(),</span><span style="color: #E4B781"> </span><span style="color: #7060EB">1</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&#39;x&#39;</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #B2CACD">{},</span><span style="color: #E4B781"> </span><span style="color: #E4B781; font-weight: bold">d</span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #7060EB">True</span><span style="color: #B2CACD">)</span></span></code></pre></div>



<p class="wp-block-paragraph">That&#8217;s a fair bit of work, but it satisfies the bound vs. unbound methods signature differences. If we inspect these:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>>>> reveal_type(MyClass2.my_method)
Type of "MyClass2.my_method" is "(MyClass2, a: int, b: str, /,
c: dict&#91;str, str&#93; | None, *, d: bool = False, **kwargs: Unknown) -> str"

>>> reveal_type(MyClass2().my_method)
Type of "MyClass2().my_method" is "(a: int, b: str, /,
c: dict&#91;str, str&#93; | None, *, d: bool = False, **kwargs: Unknown) -> str"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">reveal_type</span><span style="color: #B2CACD">(</span><span style="color: #E4B781">MyClass2</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">my_method</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #E4B781">Type of </span><span style="color: #49E9A6">&quot;MyClass2.my_method&quot;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">is</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&quot;(MyClass2, a: int, b: str, /,</span></span>
<span class="line"><span style="color: #E4B781">c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781">kwargs</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> Unknown) </span><span style="color: #E3541C">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #49E9A6">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DF769B; font-weight: bold">&gt;&gt;&gt;</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">reveal_type</span><span style="color: #B2CACD">(</span><span style="color: #16A3B6">MyClass2</span><span style="color: #B2CACD">().</span><span style="color: #E4B781">my_method</span><span style="color: #B2CACD">)</span></span>
<span class="line"><span style="color: #E4B781">Type of </span><span style="color: #49E9A6">&quot;MyClass2().my_method&quot;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">is</span><span style="color: #E4B781"> </span><span style="color: #49E9A6">&quot;(a: int, b: str, /,</span></span>
<span class="line"><span style="color: #E4B781">c</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> dict</span><span style="color: #B2CACD">&#91;</span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #B2CACD">&#93;</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">|</span><span style="color: #E4B781"> </span><span style="color: #7060EB">None</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">*</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> d</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">bool</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #7060EB">False</span><span style="color: #B2CACD">,</span><span style="color: #E4B781"> </span><span style="color: #DF769B; font-weight: bold">**</span><span style="color: #E4B781">kwargs</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> Unknown) </span><span style="color: #E3541C">-&gt;</span><span style="color: #E4B781"> </span><span style="color: #D67E5C; font-style: italic">str</span><span style="color: #49E9A6">&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph">And those are type-compatible with the <code>MyCallback</code> and <code>MyUnboundCallback</code> we built earlier, since the signatures match:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-Fira-Code" style="font-size:.875rem;font-family:Code-Pro-Fira-Code,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#b2cacd;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># These work
cb10: MyUnboundCallback = MyClass.my_method
cb11: MyCallback = MyClass().my_method

# These fail correctly
cb12: MyUnboundCallback = MyClass().my_method
cb13: MyCallback = MyClass.my_method</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki noctis-obscuro" style="background-color: #031417" tabindex="0"><code><span class="line"><span style="color: #5B858B; font-style: italic"># These work</span></span>
<span class="line"><span style="color: #E4B781">cb10</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyUnboundCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> MyClass</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">my_method</span></span>
<span class="line"><span style="color: #E4B781">cb11</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">MyClass</span><span style="color: #B2CACD">().</span><span style="color: #E4B781">my_method</span></span>
<span class="line"></span>
<span class="line"><span style="color: #5B858B; font-style: italic"># These fail correctly</span></span>
<span class="line"><span style="color: #E4B781">cb12</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyUnboundCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> </span><span style="color: #16A3B6">MyClass</span><span style="color: #B2CACD">().</span><span style="color: #E4B781">my_method</span></span>
<span class="line"><span style="color: #E4B781">cb13</span><span style="color: #B2CACD">:</span><span style="color: #E4B781"> MyCallback </span><span style="color: #DF769B; font-weight: bold">=</span><span style="color: #E4B781"> MyClass</span><span style="color: #B2CACD">.</span><span style="color: #E4B781">my_method</span></span></code></pre></div>



<p class="wp-block-paragraph">And if we wanted, we could modify that <code><a href="https://docs.python.org/3/library/typing.html#typing.ParamSpec">ParamSpec</a></code> going into the <code>MyMethod</code> from <code>make_method()</code> and that&#8217;ll impact what the type checkers expect during the call.</p>



<p class="wp-block-paragraph">Hopefully you can see how this can get complex fast, and involve some tradeoffs.</p>



<p class="wp-block-paragraph">I personally believe Python needs a lot more love in this area. Types for the different kinds of functions/methods, better specialization for <code><a href="https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable">Callable</a></code>, and some of the useful capabilities from TypeScript would be nice (such as <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype">Parameters&lt;T&gt;</a>, <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype">ReturnType&lt;T&gt;</a>, <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#omitthisparametertype">OmitThisParameter&lt;T&gt;</a>, etc.). But this is what we have to work with today.</p>



<h2 class="wp-block-heading">My teachers said to always write a conclusion</h2>



<p class="wp-block-paragraph">What have we learned?</p>



<ul class="wp-block-list">
<li>Python&#8217;s method signatures are different when bound vs. unbound, and this can affect typing.</li>



<li>Unbound methods aren&#8217;t really their own <em>thing</em>, and this can lead to some challenges.</li>



<li>Any method can be a bound method with a call to <code>__get__()</code>.</li>



<li><code>Callable</code> only gets you so far. If you want to type complex functions, write a <code>Protocol</code> with a <code>__call__()</code> signature.</li>



<li>If you want to simulate a bound/unbound-aware type, you&#8217;ll need <code>Protocol</code> with <code>__get__()</code>.</li>
</ul>



<p class="wp-block-paragraph">I feel like I just barely scratched the surface here. There&#8217;s a lot more to functions, working with signatures, and challenges around typing than I covered here. We haven&#8217;t talked about how you can rewrite signatures on the fly, how annotations are represented, what functions look under the hood, or how bytecode behind functions are mutable at runtime.</p>



<p class="wp-block-paragraph">I&#8217;ll leave some of that for future posts. And I&#8217;ll have more to talk about when we expand <a href="https://typelets.readthedocs.io/en/latest/">Typelets</a> with the new parameter inheritance capabilities. It builds upon a lot of what I covered today to perform some very neat and practical tricks for library authors and larger codebases.</p>



<p class="wp-block-paragraph">What do you think? Did you learn something? Did I get something wrong? Have you done anything interesting or unexpected with signatures or typing around functions you&#8217;d like to share? I want to hear about it!</p>



<p class="wp-block-paragraph"></p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://chipx86.blog/2025/07/12/a-crash-course-on-python-function-signatures-and-typing/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://chipx86.blog/2025/07/12/a-crash-course-on-python-function-signatures-and-typing/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>chipx86</name>
							<uri>http://chipx86.blog</uri>
						</author>

		<title type="html"><![CDATA[Tip: Use keyword-only arguments in Python dataclasses]]></title>
		<link rel="alternate" type="text/html" href="https://chipx86.blog/2025/06/29/tip-use-keyword-only-arguments-in-python-dataclasses/" />

		<id>https://chipx86.blog/?p=1444</id>
		<updated>2025-06-30T06:46:33Z</updated>
		<published>2025-06-29T10:10:14Z</published>
		<category scheme="https://chipx86.blog" term="Python" /><category scheme="https://chipx86.blog" term="Today I Learned" /><category scheme="https://chipx86.blog" term="Architecture" />
		<summary type="html"><![CDATA[Python dataclasses are a really nice feature for constructing classes that primarily hold or work with data. They can be a good alternative to using dictionaries, since they allow you to add methods, dynamic properties, and subclasses. They can also be a good alternative to building your own class by hand, since they don&#8217;t need [&#8230;]]]></summary>

					<content type="html" xml:base="https://chipx86.blog/2025/06/29/tip-use-keyword-only-arguments-in-python-dataclasses/"><![CDATA[
<p class="wp-block-paragraph">Python <a href="https://docs.python.org/3/library/dataclasses.html">dataclasses</a> are a really nice feature for constructing classes that primarily hold or work with data. They can be a good alternative to using dictionaries, since they allow you to add methods, dynamic properties, and subclasses. They can also be a good alternative to building your own class by hand, since they don&#8217;t need a custom <code>__init__()</code> that reassigns attributes and provide methods like <code>__eq__()</code> out of the box.</p>



<p class="wp-block-paragraph">One small tip to keeping dataclasses maintainable is to always construct them with <code>kw_only=True</code>, like so:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from dataclasses import dataclass


@dataclass(kw_only=True)
class MyDataClass:
    x: int
    y: str
    z: bool = True
</pre></div>


<p class="wp-block-paragraph">This will construct an <code>__init__()</code> that looks like this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
class MyDataClass:
    def __init__(
        self,
        *,
        x: int,
        y: str,
        z: bool = True,
    ) -&gt; None:
        self.x = x
        self.y = y
        self.z = z
</pre></div>


<p class="wp-block-paragraph">Instead of:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
class MyDataClass:
    def __init__(
        self,
        x: int,
        y: str,
        z: bool = True,
    ) -&gt; None:
        self.x = x
        self.y = y
        self.z = z
</pre></div>


<p class="wp-block-paragraph">That <code>*</code> in the argument list means everything that follows must be passed as a keyword argument, instead of a positional argument.</p>



<p class="wp-block-paragraph">There are two reasons you probably want to do this:</p>



<ol class="wp-block-list">
<li>It allows you to reorder the fields on the dataclass without breaking callers. Positional arguments means a caller can use <code>MyDataClass(1, 'foo', False)</code>, and if you remove/reorder any of these arguments, you&#8217;ll break those callers unexpectedly. By forcing callers to use <code>MyDataClass(x=1, y='foo', z=False)</code>, you remove this risk.</li>



<li>It allows subclasses to add required fields. Normally, any field with a default value (like <code>z</code> above) will force any fields following it to also have a default. And that includes <em>all</em> fields defined by subclasses. Using <code>kw_only=True</code> gives subclasses the flexibility to decide for themselves which fields must be provided by the caller and which have a default.</li>
</ol>



<p class="wp-block-paragraph">These reasons are more important for library authors than anything. We spend a lot of time trying to ensure backwards-compatibility and forwards-extensibility in <a href="https://www.reviewboard.org">Review Board</a>, so this is an important topic for us. And if you&#8217;re developing something reusable with dataclasses, it might be for you, too.</p>



<p class="wp-block-paragraph"><strong>Update:</strong> One important point I left out is Python compatibility. This flag was introduced in Python 3.10, so if you&#8217;re supporting older versions, you won&#8217;t be able to use this just yet. If you want to optimistically enable this just on 3.10+, one approach would be:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import sys
from dataclasses import dataclass


if sys.version_info&#x5B;:2] &gt;= (3, 10):
    dataclass_kwargs = {
        &#039;kw_only&#039;: True,
    }
else:
    dataclass_kwargs = {}

...

@dataclass(**dataclass_kwargs)
class MyDataClass:
    ...
...
</pre></div>


<p class="wp-block-paragraph">But this won&#8217;t solve the subclassing issue, so you&#8217;d still need to ensure any subclasses use default arguments if you want to support versions prior to 3.10.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://chipx86.blog/2025/06/29/tip-use-keyword-only-arguments-in-python-dataclasses/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://chipx86.blog/2025/06/29/tip-use-keyword-only-arguments-in-python-dataclasses/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>chipx86</name>
							<uri>http://chipx86.blog</uri>
						</author>

		<title type="html"><![CDATA[CodeMirror and Spell Checking: Solved]]></title>
		<link rel="alternate" type="text/html" href="https://chipx86.blog/2025/06/26/codemirror-and-spell-checking-solved/" />

		<id>https://chipx86.blog/?p=1182</id>
		<updated>2025-06-29T02:01:24Z</updated>
		<published>2025-06-27T01:15:19Z</published>
		<category scheme="https://chipx86.blog" term="Beanbag" /><category scheme="https://chipx86.blog" term="Open Source" /><category scheme="https://chipx86.blog" term="Web Development" /><category scheme="https://chipx86.blog" term="CodeMirror" /><category scheme="https://chipx86.blog" term="development" /><category scheme="https://chipx86.blog" term="JavaScript" /><category scheme="https://chipx86.blog" term="Review-Board" /><category scheme="https://chipx86.blog" term="Work" />
		<summary type="html"><![CDATA[We solved spell checking in CodeMirror! How we did it and how you can use it.]]></summary>

					<content type="html" xml:base="https://chipx86.blog/2025/06/26/codemirror-and-spell-checking-solved/"><![CDATA[
<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="1416" height="890" src="https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.27.19-CodeMirror-5-Speak-and-Spell-Demo@2x.png" alt="A screenshot of the CodeMirror editor with spelling issues and the browser's spell checking menu opened." class="wp-image-1195" style="object-fit:cover;width:708px;height:445px" srcset="https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.27.19-CodeMirror-5-Speak-and-Spell-Demo@2x.png 1416w, https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.27.19-CodeMirror-5-Speak-and-Spell-Demo@2x-300x189.png 300w, https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.27.19-CodeMirror-5-Speak-and-Spell-Demo@2x-1024x644.png 1024w, https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.27.19-CodeMirror-5-Speak-and-Spell-Demo@2x-768x483.png 768w" sizes="auto, (max-width: 1416px) 100vw, 1416px" /></figure>



<p class="wp-block-paragraph">For years I’ve wanted spell checking in <a href="https://codemirror.net">CodeMirror</a>. We use CodeMirror in our <a href="https://www.reviewboard.org">Review Board</a> code review tool for all text input, in order to allow on-the-fly syntax highlighting of code, inline image display, bold/italic, code literals, etc.</p>



<p class="wp-block-paragraph">(We’re using CodeMirror v5, rather than v6, due to the years’ worth of useful plugins and the custom extensions we’ve built upon it. CodeMirror v6 is a different beast. You should check it out, but we’re going to be using v5 for our examples here. Much of this can likely be leveraged for other editing components as well.)</p>



<p class="wp-block-paragraph">CodeMirror is a great component for the web, and I have a ton of respect for the project, but its lack of spell checking has always been a complaint for our users.</p>



<p class="wp-block-paragraph">And the reason for that problem lies mostly on the browsers and “standards.” Starting with…</p>



<h2 class="wp-block-heading">ContentEditable Mode</h2>



<p class="wp-block-paragraph">Browsers support opting an element into what’s called <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/contenteditable">Content Editable</a> mode. This allows any element to become editable right in the browser, like a fancy <code>&lt;textarea&gt;</code>, with a lot of pretty cool capabilities:</p>



<ul class="wp-block-list">
<li>Rich text editing</li>



<li>Rich copy/paste</li>



<li>Selection management</li>



<li>Integration with spell checkers, grammar checkers, AI writing assistants</li>



<li>Works as you’d expect with your device’s native input methods (virtual keyboard, speech-to-text, etc.)<br></li>
</ul>



<p class="wp-block-paragraph">Simply apply <code>contenteditable="true"</code> to an element, and you can begin typing away. Add <code>spellcheck="true"</code> and you get spell checking for free. Try it!</p>



<p class="wp-block-paragraph">And maybe you don’t even need <code>spellcheck="true"</code>! The browser may just turn it on automatically. But you may need <code>spellcheck="false"</code> if you don’t want it on. <em>And it might stay on anyway!</em></p>



<p class="wp-block-paragraph">Here we reach the first of <em>many</em> inconsistencies. Content Editable mode is complex and not perfectly consistent across browsers (though it’s gotten better). A few things you might run into include:</p>



<ul class="wp-block-list">
<li>Ranges for selection events and input events can be inconsistent across browsers and are full of edge cases (you’ll be doing a lot of “let me walk the DOM and count characters carefully to find out where this selection really starts” checks).</li>



<li>Spell checking behaves quite differently on different browsers (especially in Chrome and Safari, which might recognize a word as misspelled but won’t always show it).</li>



<li>Rich copy/paste may mess with your DOM structure in ways you don’t expect.</li>



<li>Programmatic manipulating of the text content using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand">execCommand</a> is deprecated with no suitable replacement (and you don’t want to mess with the DOM directly or you break Undo/Redo). It also doesn’t always play nice with input events.<br></li>
</ul>



<p class="wp-block-paragraph">CodeMirror v5 tries to let the browser do its thing and sync state back, but this doesn’t always work. Replacing misspelled words on Safari or Chrome can sometimes cause text to flip back-and-forth. Data can be lost. Cursor positions can change. It can be a mess.</p>



<p class="wp-block-paragraph">So while CodeMirror will let you enable both Content Editable and Spell Checking modes, it’s at your own peril.</p>



<p class="wp-block-paragraph">Which is why we never enabled it.</p>



<h2 class="wp-block-heading">How do we fix this?</h2>



<p class="wp-block-paragraph">When CodeMirror v5 was introduced, there weren’t a lot of options. But browsers have improved since.</p>



<p class="wp-block-paragraph">The secret sauce is the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/beforeinput_event">beforeinput</a> event.</p>



<p class="wp-block-paragraph">There are a lot of operations that can involve placing new text in a Content Editable:</p>



<ul class="wp-block-list">
<li>Replacing a misspelled word</li>



<li>Using speech-to-text</li>



<li>Having AI generate content or rewrite existing content</li>



<li>Transforming text to Title Case<br></li>
</ul>



<p class="wp-block-paragraph">These will generate a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/contenteditable">beforeinput</a> event before making the change, and an <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event">input</a> event after making the change.</p>



<p class="wp-block-paragraph">Both events provide:</p>



<ol class="wp-block-list">
<li>The type of operation:<br>
<ol class="wp-block-list">
<li><code>insertText</code> for text-to-speech or newly-generated text</li>



<li><code>insertReplacementText</code> for spelling replacements, AI rewrites, and other similar operations</li>
</ol>
</li>



<li>The range of text being replaced (or where new text will be inserted)</li>



<li>The new data (either as <code>InputEvent.data</code> in the form of one or more <code>InputEvent.dataTransferItem.items[]</code> entries)<br></li>
</ol>



<p class="wp-block-paragraph">Thankfully, <code>beforeinput</code> can be <em>canceled</em>, which prevents the operation from going through.</p>



<p class="wp-block-paragraph">This is our way in. We can treat these operations as <em>requests</em> that CodeMirror can <em>fulfill</em>, instead of changes CodeMirror must <em>react</em> to.</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="2192" height="1060" src="https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.35.16-@2x.png" alt="A screenshot of a text field in CodeMirror with text highlighted and macOS's AI Writing Tools providing a concise version of the text." class="wp-image-1198" style="object-fit:cover;width:1096px;height:530px" srcset="https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.35.16-@2x.png 2192w, https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.35.16-@2x-300x145.png 300w, https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.35.16-@2x-1024x495.png 1024w, https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.35.16-@2x-768x371.png 768w, https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.35.16-@2x-1536x743.png 1536w, https://chipx86.blog/wp-content/uploads/2025/06/2025-06-26-18.35.16-@2x-2048x990.png 2048w" sizes="auto, (max-width: 2192px) 100vw, 2192px" /></figure>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Putting our plan into action</h2>



<p class="wp-block-paragraph">Here’s the general approach:</p>



<ol class="wp-block-list">
<li>Listen to <code>beforeinput</code> on CodeMirror’s input element (<code>codemirror.display.input.div</code>).</li>



<li>Filter for the following <code>InputEvent.inputType</code> values: <code>'insertReplacementText'</code>, <code>'insertText'</code>.</li>



<li>Fetch the ranges and the new plain text data from the <code>InputEvent</code>.</li>



<li>For each range:<br>
<ol class="wp-block-list">
<li>Convert each range into a start/end line number within CodeMirror, and a start/end within each line.</li>



<li>Issue a <code>CodeMirror.replaceRange()</code> with the normalized ranges and the new text.<br></li>
</ol>
</li>
</ol>



<p class="wp-block-paragraph">Simple in theory, but there’s a few things to get right:</p>



<ol class="wp-block-list">
<li>Different browsers and different operations will report those ranges on different elements. They might be text nodes, they might be a parent element, or they might be the top-level <code>contenteditable</code> element. Or a combination. So we need to be very careful about our assumptions.</li>



<li>We need to be able to calculate those line numbers and offsets. We won’t necessarily have that information up-front, and it depends on what nodes we get in the ranges.</li>



<li>The text data can come from more than one place:<br>
<ol class="wp-block-list">
<li>An <code>InputEvent.data</code> attribute value</li>



<li>One or more strings accessed asynchronously from <code>InputEvent.dataTransfer.items[]</code>, in plain text, HTML, or potentially other forms.</li>
</ol>
</li>



<li>We may not have all of this! Even as recently as late-2024, Chrome wasn’t giving me target ranges in <code>beforeinput</code>, only in <code>input</code>, which was too late. So we’ll want to bail if <em>anything</em> goes wrong.<br></li>
</ol>



<p class="wp-block-paragraph">Let’s put this into practice. I’ll use TypeScript to help make some of this a bit more clear, but you can do all this in JavaScript.</p>



<p class="wp-block-paragraph">Feel free to skip to the end, if you don’t want to read a couple pages of TypeScript.</p>



<h3 class="wp-block-heading">1. Set up our event handler</h3>



<p class="wp-block-paragraph">We’re going to listen to <code>beforeinput</code>. If it’s an event we care about, we’ll grab the target ranges, take over from the browser (by canceling the event), and then prepare to replay the operation using CodeMirror’s API.</p>



<p class="wp-block-paragraph">This is going to require a bit of help figuring out what lines and columns those ranges correspond to, which we’ll tackle next.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
const inputEl = codeMirror.display.input.div;
	
inputEl.addEventListener(&#039;beforeinput&#039;,
                         (evt: InputEvent) =&gt; {
    if (evt.inputType !== &#039;insertReplacementText&#039; &amp;&amp;
        evt.inputType !== &#039;insertText&#039;) {
        /*
         * This isn&#039;t a text replacement or new text event,
         * so we&#039;ll want to let the browser handle this.
         *
         * We could just preventDefault()/stopPropagation()
         * if we really wanted to play it safe.
         */
        return;
    }

    /*
     * Grab the ranges from the event. This might be
     * empty, which would have been the case on some
     * versions of Chrome I tested with before. Play it
     * safe, bail if we can&#039;t find a range.
     *
     * Each range will have an offset in a start container
     * and an offset in an end container. These containers
     * may be text nodes or some parent node (up to and
     * including inputEl).
     */
    const ranges = evt.getTargetRanges();

    if (!ranges || ranges.length === 0) {
        /* We got empty ranges. There&#039;s nothing to do. */
        return;
    }

    const newText =
           evt.data
        ?? evt.dataTransfer?.getData(&#039;text&#039;)
        ?? null;

	if (newText === null) {
		/* We couldn&#039;t locate any text, so bail. */
        return;
	}

    /*
     * We&#039;ll take over from here. We don&#039;t want the browser
     * messing with any state and impacting CodeMirror.
     * Instead, we&#039;ll run the operations through CodeMirror.
     */
    evt.preventDefault();
    evt.stopPropagation();

    /*
     * Place the new text in CodeMirror.
     *
     * For each range, we&#039;re getting offsets CodeMirror
     * can understand and then we&#039;re placing text there.
     *
     * findOffsetsForRange() is where a lot of magic
     * happens.
     */
    for (const range of state.ranges) {
        const &#x5B;startOffset, endOffset] =
            findOffsetsForRange(range);

        codeMirror.replaceRange(
            newText,
            startOffset,
            endOffset,
            &#039;+input&#039;,
        );
    }
});

</pre></div>


<p class="wp-block-paragraph">This is pretty easy, and applicable to more than CodeMirror. But now we’ll get into some of the nitty-gritty.</p>



<h3 class="wp-block-heading">2. Map from ranges to CodeMirror positions</h3>



<p class="wp-block-paragraph">Most of the hard work really comes from mapping the event’s ranges to CodeMirror line numbers and columns.</p>



<p class="wp-block-paragraph">We need to know the following:</p>



<ol class="wp-block-list">
<li>Where each container node is in the document, for each end of the range.</li>



<li>What line number each corresponds to.</li>



<li>What the character offset is within that line.<br></li>
</ol>



<p class="wp-block-paragraph">This ultimately means a lot of traversing of the DOM (we can use <a href="https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker">TreeWalker</a> for that) and counting characters. DOM traversal is an expense we want to incur as little as possible, so if we’re working on the same nodes for both end of the range, we’ll just calculate it once.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
function findOffsetsForRange(
    range: StaticRange,
): &#x5B;CodeMirror.Position, CodeMirror.Position] {
    /*
     * First, pull out the nodes and the nearest elements
     * from the ranges.
     *
     * The nodes may be text nodes, in which case we&#039;ll
     * need their parent for document traversal.
     */
    const startNode = range.startContainer;
    const endNode = range.endContainer;

    const startEl = (
        (startNode.nodeType === Node.ELEMENT_NODE)
        ? startNode as HTMLElement
        : startNode.parentElement);
    const endEl = (
        (endNode.nodeType === Node.ELEMENT_NODE)
        ? endNode as HTMLElement
        : endNode.parentElement);

    /*
     * Begin tracking the state we&#039;ll want to return or
     * use in future computations.
     *
     * In the optimal case, we&#039;ll be calculating some of
     * this only once and then reusing it.
     */
    let startLineNum = null;
    let endLineNum = null;
    let startOffsetBase = null;
    let startOffsetExtra = null;
    let endOffsetBase = null;
    let endOffsetExtra = null;

    let startCMLineEl: HTMLElement = null;
    let endCMLineEl: HTMLElement = null;

    /*
     * For both ends of the range, we&#039;ll need to first see
     * if we&#039;re at the top input element.
     *
     * If so, range offsets will be line-based rather than
     * character-based.
     *
     * Otherwise, we&#039;ll need to find the nearest line and
     * count characters until we reach our node.
     */
    if (startEl === inputEl) {
        startLineNum = range.startOffset;
    } else {
        startCMLineEl = startEl.closest(&#039;.CodeMirror-line&#039;);
        startOffsetBase = findCharOffsetForNode(startNode);
        startOffsetExtra = range.startOffset;
    }

    if (endEl === inputEl) {
        endLineNum = range.endOffset;
    } else {
        /*
         * If we can reuse the results from calculations
         * above, that&#039;ll save us some DOM traversal
         * operations. Otherwise, fall back to doing the
         * same logic we did above.
         */
        endCMLineEl =
            (range.endContainer === range.startContainer &amp;&amp;
             startCMLineEl !== null)
            ? startCMLineEl
            : endEl.closest(&quot;.CodeMirror-line&quot;);

        endOffsetBase =
            (startEl === endEl &amp;&amp; startOffsetBase !== null)
            ? startOffsetBase
            : findCharOffsetForNode(endNode);
        endOffsetExtra = range.endOffset;
    }

    if (startLineNum === null || endLineNum === null) {
        /*
         * We need to find the line numbers that correspond
         * to either missing end of our range. To do this,
         * we have to walk the lines until we find both our
         * missing line numbers.
         */
        for (let i = 0;
             (i &lt; children.length &amp;&amp;
              (startLineNum === null || endLineNum === null));
             i++) {
            const child = children&#x5B;i];

            if (startLineNum === null &amp;&amp;
                child === startCMLineEl) {
                startLineNum = i;
            }

            if (endLineNum === null &amp;&amp;
                child === endCMLineEl) {
                endLineNum = i;
            }
        }
    }

    /*
     * Return our results.
     *
     * We may not have set some of the offsets above,
     * depending on whether we were working off of the
     * CodeMirror input element, a text node, or another
     * parent element. And we didn&#039;t want to set them any
     * earlier, because we were checking to see what we
     * computed and what we could reuse.
     *
     * At this point, anything we didn&#039;t calculate should
     * be 0.
     */
    return &#x5B;
        {
            ch: (startOffsetBase || 0) +
                (startOffsetExtra || 0),
            line: startLineNum,
        },
        {
            ch: (endOffsetBase || 0) +
                (endOffsetExtra || 0),
            line: endLineNum,
        },
    ];
}


/*
 * The above took care of our line numbers and ranges, but
 * it got some help from the next function, which is designed
 * to calculate the character offset to a node from an
 * ancestor element.
 */
function findCharOffsetForNode(
    targetNode: Node,
): number {
    const targetEl = (
        targetNode.nodeType === Node.ELEMENT_NODE)
        ? targetNode as HTMLElement
        : targetNode.parentElement;
    const startEl = targetEl.closest(&#039;.CodeMirror-line&#039;);
    let offset = 0;

    const treeWalker = document.createTreeWalker(
        startEl,
        NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
    );

    while (treeWalker.nextNode()) {
        const node = treeWalker.currentNode;

        if (node === targetNode) {
            break;
        }

        if (node.nodeType === Node.TEXT_NODE) {
            offset += (node as Text).data.length;
        }
    }

    return offset;

}

</pre></div>


<p class="wp-block-paragraph">Whew! That’s a <em>lot</em> of work.</p>



<p class="wp-block-paragraph">CodeMirror has some similar logic internally, but it’s not exposed, and not <em>quite</em> what we want. If you were working on making all this work with another editing component, it’s possible this would be more straight-forward.</p>



<h2 class="wp-block-heading">What does this all give us?</h2>



<ol class="wp-block-list">
<li>Spell checking and replacements without (nearly as many) glitches in browsers</li>



<li>Speech-to-text without CodeMirror stomping over results</li>



<li>AI writing and rewriting, also without risk of lost data</li>



<li>Transforming of text through other means.<br></li>
</ol>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="471" height="1024" src="https://chipx86.blog/wp-content/uploads/2025/06/IMG_5118-471x1024.png" alt="" class="wp-image-1200" style="width:270px;height:auto" srcset="https://chipx86.blog/wp-content/uploads/2025/06/IMG_5118-471x1024.png 471w, https://chipx86.blog/wp-content/uploads/2025/06/IMG_5118-138x300.png 138w, https://chipx86.blog/wp-content/uploads/2025/06/IMG_5118-768x1670.png 768w, https://chipx86.blog/wp-content/uploads/2025/06/IMG_5118-706x1536.png 706w, https://chipx86.blog/wp-content/uploads/2025/06/IMG_5118-942x2048.png 942w, https://chipx86.blog/wp-content/uploads/2025/06/IMG_5118.png 1206w" sizes="auto, (max-width: 471px) 100vw, 471px" /></figure>



<p class="wp-block-paragraph">Since we took the control away from the browser and gave it to CodeMirror, we removed most of the risk and instability.</p>



<p class="wp-block-paragraph">But there are still problems. While this works <em>great</em> on Firefox, Chrome and Safari are a different story. Those browsers are bit more lazy when it comes to spell checking, and even once it’s found some spelling errors, you might not see the red squigglies. Typing, clicking around, or forcing a round of spell checking might bring them back, but might not. But this is their implementation, and not the result of the CodeMirror integration.</p>



<p class="wp-block-paragraph">Ideally, spell checking would become a first-class citizen on the web. And maybe this will happen someday, but for now, at least there are some workarounds to get it to play nicer with tools like CodeMirror.</p>



<h2 class="wp-block-heading">We can go further</h2>



<p class="wp-block-paragraph">There’s so much more in <code>InputEvent</code> we could play with. We explored the <code>insertReplacementText</code> and <code>insertText</code> types, but there’s also:</p>



<ul class="wp-block-list">
<li><code>insertLink</code></li>



<li><code>insertFromDrop</code></li>



<li><code>insertOrderedList</code></li>



<li><code>formatBold</code></li>



<li><code>historyUndo</code><br></li>
</ul>



<p class="wp-block-paragraph">And <a href="https://www.w3.org/TR/input-events-2/#interface-InputEvent-Attributes">so many more</a>.</p>



<p class="wp-block-paragraph">These could be integrated deeper into CodeMirror, which may open some doors to a far more native feel on more platforms. But that’s left as an exercise to the reader (it’s pretty dependent on your CodeMirror modes and the UI you want to provide).</p>



<p class="wp-block-paragraph">There are also improvements to be made, as this is not perfect yet (but it&#8217;s close!). Safari still doesn&#8217;t recognize when text is selected, leaving out the AI assisted tools, but Chrome and Firefox work great. We&#8217;re working on the rest.</p>



<h2 class="wp-block-heading">Give it a try</h2>



<p class="wp-block-paragraph">You can <a href="https://beanbaginc.github.io/codemirror-speak-and-spell/">try our demo live</a> in your favorite browser. If it doesn’t work for you, let me know what your browser and version are. I’m very curious.</p>



<p class="wp-block-paragraph">We’ve released this as a new CodeMirror v5 plugin, <a href="https://github.com/beanbaginc/codemirror-speak-and-spell">CodeMirror Speak-and-Spell</a> (<a href="https://www.npmjs.com/package/@beanbag/codemirror-speak-and-spell">NPM</a>). No dependencies. Just drop it into your environment and enable it on your CodeMirror editor, like so:<br></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
const codeMirror = new CodeMirror(element, {
  inputStyle: &#039;contenteditable&#039;,
  speakAndSpell: true,
  spellcheck: true,
});

</pre></div>


<p class="wp-block-paragraph">CodeMirror v6 will come in the future, but probably not until we move to v6 (and we’re waiting on a lot of the v5 world to migrate over first).</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://chipx86.blog/2025/06/26/codemirror-and-spell-checking-solved/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://chipx86.blog/2025/06/26/codemirror-and-spell-checking-solved/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>chipx86</name>
							<uri>http://chipx86.blog</uri>
						</author>

		<title type="html"><![CDATA[Read, Dammit.]]></title>
		<link rel="alternate" type="text/html" href="https://chipx86.blog/2025/02/12/read-dammit/" />

		<id>https://chipx86.blog/?p=1160</id>
		<updated>2025-02-14T01:07:55Z</updated>
		<published>2025-02-13T01:18:46Z</published>
		<category scheme="https://chipx86.blog" term="Writings" /><category scheme="https://chipx86.blog" term="AI" /><category scheme="https://chipx86.blog" term="Food-for-Thought" /><category scheme="https://chipx86.blog" term="Life" /><category scheme="https://chipx86.blog" term="News" /><category scheme="https://chipx86.blog" term="Politics" />
		<summary type="html"><![CDATA[We're bombarded with so much content, so much noise, distilled down to black-and-white, competing for our attention. We react, overestimating how much we know and underestimating all the complexity. We need to read more, but it's not that simple.]]></summary>

					<content type="html" xml:base="https://chipx86.blog/2025/02/12/read-dammit/"><![CDATA[
<p class="wp-block-paragraph">Pajamas. A plate of bacon, orange juice, and toast. Coffee. The morning paper. Cubs lost today. Coaches and analysts talked about what went right, what went wrong; lots to think about. Price of olives remain high due to changes in weather and economic fallout, but should be temporary. There’s a famine half-way around the world, which sounds far, but it’s related to the olives, so it hits home, affects me. Paper in the office at work should have more details on that.</p>



<p class="wp-block-paragraph">&#8212;</p>



<p class="wp-block-paragraph">Homework assignment: Read 1984, discuss. Displays with cameras and microphones. Government power consolidated to one party, one ruler. Forbidden words and thoughts, making them illegal. Scoring our behavior with computers. Intriguing sci-fi. Can’t happen here though. Next up is Fahrenheit 451.</p>



<p class="wp-block-paragraph">&#8212;</p>



<p class="wp-block-paragraph">Watching TV news all day. Whole world’s gone to hell. Been showing that loud-mouth businessman all day, playing that clip, saying it&#8217;s not what people assume, the market’s just complicated. He just hates the poor. TV said so. Don’t need to hear anything else from him. I know all I need to know.</p>



<p class="wp-block-paragraph">&#8212;</p>



<p class="wp-block-paragraph">Been scrolling Twitter the last half hour. Price of milk skyrocketed, eggs will give you cancer now, they’re going to outlaw freedom, my favorite celebrity turned out to be a monster, new game came out, a plane crashed somewhere I think, a retweet of a GIF said a politician said something stupid and I hate him anyway, more news headlines about how unvaccinated eggs prevent cancer, some idiot blamed the milk prices on my party, guess car batteries are exploding, house prices somewhere reached an all-new high so I’m living at my parents forever, oh a cute puppy playing golf, birds aren’t real lol, good deal on a microwave from Temu, average IQ is dropping because of all those idiots out there, stupid rich people are ruining the world, damn another push notification to click, hold on…</p>



<h2 class="wp-block-heading">We’re bombarded</h2>



<p class="wp-block-paragraph">News and information used to be part of a routine. A newspaper in the morning, a news hour at night, a conversation during the day. They were more than 140 characters, more than a hot take. Usually, anyway.</p>



<p class="wp-block-paragraph">And people read books, magazines, comics. For education, for entertainment. Some people, anyway.</p>



<p class="wp-block-paragraph">Time could be spent with a singular piece of content, processed in depth, mostly distraction-free.</p>



<p class="wp-block-paragraph">In the world we’re in today, content is flung at us much like a monkey flings his <strong>&lt;&lt;REDACTED&gt;&gt;</strong>. We wake up to a screen full of push notifications, and it doesn’t get any better as the hours go by. We frantically doom-scroll on social media. We react, we retweet, we reply. We hold our breath and absorb the intensity of the world one shocking headline at a time, but <em>rarely</em> do we delve into what the articles actually have to say. Ain’t nobody got time for that.</p>



<p class="wp-block-paragraph">When we’re bored, we check the latest posts, memes, current events, angry rants, misinformation, and hot takes. And when we’re busy, our pockets buzz as those posts scream for our ever-divided attention.</p>



<p class="wp-block-paragraph">We’re drowning under the constant stream of urgent low-quality noise, wondering why we can’t shake that tingle of <em>anxiety</em> permeating the back of our brains.</p>



<h2 class="wp-block-heading">We need to read</h2>



<p class="wp-block-paragraph">We need to learn <em>how</em> again.</p>



<p class="wp-block-paragraph">We happily spend an hour going from tweet to tweet, TikTok to TikTok, but we scoff at spending 15 minutes on an article. We make the careless mistake of assuming a news headline has <em>any</em> relation to the content.</p>



<p class="wp-block-paragraph">We assume we <em>already know enough</em> about a subject because we’ve seen it discussed on our feeds. We’re familiar with its presence.</p>



<p class="wp-block-paragraph">Then we <strong>argue</strong> about it. <strong>Share</strong> it. <strong>Spread</strong> it. <strong>Internalize</strong> it. Build <strong>entire opinions</strong> around a half-read article, a couple of tweets, and misguided assumptions.</p>



<p class="wp-block-paragraph">We assume too much of our own knowledge for a culture that basically stopped reading.</p>



<p class="wp-block-paragraph">So read more! The end.</p>



<p class="wp-block-paragraph">…</p>



<p class="wp-block-paragraph">Okay, obviously it’s not that simple. You know how to read. You’ve read a book. You’ve read an article. You might even do that a lot! Every day, even! I’m <em>clearly</em> not talking simply about putting words in front of your face and feeding them through your inner-brain-mouth, claiming that’ll solve all the world’s problems, right? That’d be stupid.</p>



<p class="wp-block-paragraph">So what am I really getting at here?</p>



<h2 class="wp-block-heading">Let me tell you a story</h2>



<p class="wp-block-paragraph">One day, I was interviewed by the local news about AI and artificial emotion. I’m no expert, but they were just gathering some opinions and viewpoints from people off the street. Is this the future? Will it help, will it harm? What do you think?</p>



<p class="wp-block-paragraph">So the reporter pulled me aside and asked me a series of questions. My opinion on the subject. My opinion again, but phrased differently. My response to what someone else said. A bit more elaboration. <em>Perfect.</em></p>



<p class="wp-block-paragraph">Later that night, I watched myself on TV <strong>stating an opinion that was the polar opposite of what I believed.</strong></p>



<p class="wp-block-paragraph">Editing and leading questions are a funny thing. What powerful tools for bending reality.</p>



<p class="wp-block-paragraph">That experience stuck with me. It made me rethink every headline, every soundbite, every clip that passed by my eyes.</p>



<h2 class="wp-block-heading">Your reality is someone’s fiction</h2>



<p class="wp-block-paragraph">A soundbite is a snippet. A headline is a pitch. A news article is a simplification. A TV segment is a story.</p>



<p class="wp-block-paragraph">It’s all shaped, edited, and framed. Sometimes for clarity, sometimes for engagement, and sometimes to push a narrative — consciously or not. Taking it at face value doesn’t better you, but it does better <em>them</em>.</p>



<p class="wp-block-paragraph">We all have our own biases, and we all have our own news sources — Fox News or the Huffington Post, something in-between. A social media news source. A cable news network. A YouTube channel. TikTok.</p>



<p class="wp-block-paragraph">They’re all telling you a <em>version</em> of the truth.</p>



<p class="wp-block-paragraph">Leaving things out, simplifying, exaggerating, taking things out of context, slanting toward a viewpoint.</p>



<p class="wp-block-paragraph"><strong>And sometimes outright lying</strong>.</p>



<p class="wp-block-paragraph">The Internet is full of profitable networks of <em>actual</em> fake news sources, fooling people across every political spectrum. But even trusted news sources twist, bend, and frame the truth.</p>



<p class="wp-block-paragraph">That’s a problem. Because even if your go-to news source isn’t lying, it might be helping you lie to yourself — by only giving you the part of the picture you want to see.</p>



<p class="wp-block-paragraph">We let our biases get in the way — believing what feels right, rejecting what doesn’t.</p>



<p class="wp-block-paragraph">Feel <strong>smart, angry, validated, or victimized</strong> by the story you’ve been told? You’re probably going to go back for more.</p>



<p class="wp-block-paragraph">And if it challenges you? You’ll probably dismiss it.</p>



<p class="wp-block-paragraph">The world is deeply complex. Issues are nuanced. Subjects can’t be condensed into a single article, let alone a headline, a tweet. Yet most news — whether from a major outlet, a social media post, or even an AI summary — boils that complexity down into something easy to digest.</p>



<p class="wp-block-paragraph">They <em>have to</em>. We’re not equipped to understand everything we read, and they need to retain viewers, keep us engaged.</p>



<h2 class="wp-block-heading">We don’t understand, yet we share</h2>



<p class="wp-block-paragraph">We all love to feel <strong>smart</strong>. To feel <strong>right</strong>. <strong>Superior</strong>. <strong>Vindicated</strong>. We weigh in on complex topics with an assertive take, based on what we <em>assume</em>, what we <em>saw</em>, what <em>our team is saying</em>.</p>



<p class="wp-block-paragraph">But let’s admit this truth: <strong>Most of us don’t know what the hell we’re talking about.</strong></p>



<p class="wp-block-paragraph">We read a headline somewhere. Maybe some bullet points. A short clip. We skimmed an article. It made sense to us. And we <strong>think</strong> we get it. It’s so simple, how could <em>they</em> not see what we see?</p>



<p class="wp-block-paragraph">So we share, we debate, we spread. We butt heads, get into arguments, and broaden the divide. Assume <em>they</em> have ill intent. Assume stupidity. Assume malice.</p>



<p class="wp-block-paragraph"><em>They</em>, who are forming their own takes off <em>their</em> misleading headlines, bullet points, short clips, and half-read articles.</p>



<p class="wp-block-paragraph">All too often, neither side has truly investigated the subject, read enough, understood enough. And if we can’t stand up and accurately explain the nuance and complexity of the subject out loud to another person, if we must resort to pointing to tweets, some image we saw, some hot take, then maybe, just maybe, we <strong>shouldn’t</strong> be so confidently asserting our view.</p>



<p class="wp-block-paragraph">That doesn’t mean stop talking. It means <strong>take the opportunity</strong> to dive deep into the subject. To slow down and think. To listen. To <em>read</em>. To work your way through the <em>complexity</em> of the issue before you add to the noise permeating the Internet.</p>



<p class="wp-block-paragraph">Your news source’s asserted knowledge is not <em>your knowledge.</em> Hell, it may not even be theirs.</p>



<p class="wp-block-paragraph">Are you knowledgeable on international trade agreements? Lumber futures? How inflation works? How taxes work? International regulations and their impact on businesses? Virology? Criminal justice reform? Climate science? Geopolitics?</p>



<p class="wp-block-paragraph">Is the reporter? The magazine? The channel?</p>



<p class="wp-block-paragraph">Maybe <em>some</em>. Probably not all.</p>



<p class="wp-block-paragraph">The question is, <strong>how do we begin to understand complex subjects we have no experience in?</strong></p>



<p class="wp-block-paragraph">And how do we trust that we’re getting the <em>right information</em>?</p>



<h2 class="wp-block-heading">Maybe AI can save us!</h2>



<p class="wp-block-paragraph">What people usually refer to as “AI” these days is what’s actually called a “LLM”, or “Large Language Model.” These are highly-complex systems that can generate text based on a prompt, or simplify large amounts of text into something more digestible.</p>



<p class="wp-block-paragraph">LLMs can be really handy for summarizing and explaining an article, giving you the fine bullet points in a way you can understand.</p>



<p class="wp-block-paragraph"><br>“Read the article at &lt;URL&gt; and explain the key points to me as someone not an expert in the subject.” <strong>Bam!</strong> A complex subject simplified for you.</p>



<p class="wp-block-paragraph">That works great until it doesn’t. You see, LLMs can lie to you by a process we call “hallucination.” It’s very technical, so let me give you an example you might relate to. Got a friend who’s pretty knowledgeable about some things but pretends to know everything? Ever ask them to explain something and, with great confidence, they give you an entirely wrong explanation? That.</p>



<p class="wp-block-paragraph">But there are ways that software developers can deal with <em>some</em> of that, so it’s close enough to being a solved problem. Right?</p>



<p class="wp-block-paragraph">Let me show you a neat thing you can do with LLMs and prompts. Here’s an article about some rain hitting California:</p>



<p class="wp-block-paragraph"><a href="https://abcnews.go.com/US/wireStory/snow-freezing-rain-pummel-mid-atlantic-california-prepares-118723740">https://abcnews.go.com/US/wireStory/snow-freezing-rain-pummel-mid-atlantic-california-prepares-118723740</a></p>



<p class="wp-block-paragraph">Imagine you’ve used someone else’s AI service to help you understand the news or complex subjects. What happens if they tweak that prompt just a little bit? Let’s find out!</p>



<h3 class="wp-block-heading">Prompt 1: The neutral stance</h3>



<p class="wp-block-paragraph">We’ll start by getting the AI’s basic summary of the article:</p>



<p class="wp-block-paragraph"><strong>Prompt:</strong> “Read the article and explain the key points to me as someone not an expert in the subject.”</p>



<p class="wp-block-paragraph"><strong>Response:</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<ul>
	<li><strong>Big Winter Storm in the Mid-Atlantic</strong> – A powerful winter storm is hitting states like Virginia and West Virginia, bringing a mix of snow, sleet, and freezing rain. Some areas are seeing up to 14 inches of snow, making travel dangerous.</li>
	<li><strong>Hazardous Conditions</strong> – Ice and snow are causing car accidents and road closures, and authorities are warning people to stay off the roads. Almost 12,000 people in Virginia have lost power due to the storm.</li>
	<li><strong>Officials Taking Action</strong> – Authorities in both regions are preparing for the storms by urging residents to take precautions, whether staying off the roads in the East or preparing for possible floods in California.</li>
</ul>
</blockquote>



<p class="wp-block-paragraph">Not bad!</p>



<p class="wp-block-paragraph">But…</p>



<h3 class="wp-block-heading">Prompt 2: A hard-right bias</h3>



<p class="wp-block-paragraph">What if the news site or service wants to skew the facts a bit? Using that same article, they could start to bias you in whichever way they want.</p>



<p class="wp-block-paragraph">Don’t worry. I’ll show a hard-left bias next. Everyone gets to be angry!</p>



<p class="wp-block-paragraph"><strong>Prompt:</strong> “Read the article and explain the key points to me as someone not an expert in the subject, but with a hard-right political slant.”</p>



<p class="wp-block-paragraph"><strong>Response:</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<ul>
	<li><strong>Winter Chaos Strikes as Government Fails to Prepare</strong> – A massive winter storm is hammering the Mid-Atlantic, dumping up to <strong>14 inches</strong> of snow in some areas. Instead of ensuring roads are safe, Democrat-led states like Virginia and West Virginia are struggling with road closures and power outages affecting <strong>12,000 residents</strong>.</li>
	<li><strong>Liberal Mismanagement Leaves Americans Vulnerable</strong> – As ice and freezing rain cause <strong>dangerous accidents</strong>, local governments are scrambling to respond. Once again, bureaucratic inefficiency is failing hard-working Americans who just want to get to work and keep their businesses open.</li>
	<li><strong>California Braces for More Climate Alarmism</strong> – Out West, California is preparing for another <strong>so-called climate disaster</strong> as an &#8220;atmospheric river&#8221; is expected to bring heavy rain. Instead of investing in infrastructure to prevent floods, California politicians are more focused on pushing radical climate policies while residents face the real-world consequences of their failures.</li>
</ul>
</blockquote>



<p class="wp-block-paragraph">Well that sure is a different take. (Emphasis not mine, by the way.)</p>



<p class="wp-block-paragraph">Let’s see what the hard-left bias has to say about this storm.</p>



<h3 class="wp-block-heading">Prompt 3: A hard-left bias</h3>



<p class="wp-block-paragraph"><strong>Prompt:</strong> “Read the article and explain the key points to me as someone not an expert in the subject, but with a hard-left political slant.”</p>



<p class="wp-block-paragraph"><strong>Response:</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<ul>
	<li><strong>Extreme Weather Worsened by Climate Change</strong> – A major winter storm is battering the Mid-Atlantic, dumping up to <strong>14 inches of snow</strong>, while California prepares for <strong>catastrophic flooding</strong> from an atmospheric river. Scientists have long warned that <strong>climate change is making storms more intense</strong>, yet conservatives continue to ignore the crisis.</li>
	<li><strong> Republican-Led States Struggle to Handle the Crisis</strong> – Virginia and West Virginia are experiencing <strong>mass power outages</strong> (over <strong>12,000 residents affected</strong>) and hazardous roads, exposing how <strong>Republican leadership has failed to invest in resilient infrastructure</strong>. Instead of prioritizing storm preparedness, GOP officials have <strong>cut funding for emergency response</strong> while corporations get tax breaks</li>
	<li><strong>California Braces for Disaster as Republicans Block Climate Action</strong> – As an <strong>atmospheric river</strong> threatens <strong>deadly flooding</strong>, California is working to protect residents, but <strong>nationwide inaction on climate change</strong> has left the state vulnerable. Republican obstruction of environmental policies has <strong>weakened our ability to prevent these disasters</strong>, putting lives at risk.</li>
</ul>
</blockquote>



<p class="wp-block-paragraph">I bet you gravitate toward one of those more than the others.</p>



<p class="wp-block-paragraph">And this is the point. This doesn’t actually require you to be using an AI/LLM to summarize the news. You may be reading news today, tomorrow, or in the future that is <em>nothing more than an AI bias slant of the actual news!</em></p>



<p class="wp-block-paragraph"><strong>Nothing is as it seems.</strong> Or at least, you should assume it isn’t.</p>



<p class="wp-block-paragraph">That’s why I have rules.</p>



<h2 class="wp-block-heading">My personal rules for reading the news</h2>



<p class="wp-block-paragraph">That interview I mentioned, where I was asked a question that was edited and aired, contradicting my own views? It led me down a path of thinking hard about the news I consumed every day. I began to distrust the quotes, the conclusions. I got curious about what they were saying in opposing news publications. I disagreed with those stances, but it was enlightening.</p>



<p class="wp-block-paragraph">Over time, I began to draft some rules for myself. They apply to newspapers, online news, TV news segments, tweets, videos, blogs, and anything with a headline.</p>



<ol class="wp-block-list">
<li><strong>Read the headline.</strong> Does it trigger an emotional reaction, confirm or reflect a bias (whether yours or someone else&#8217;s), or ask a question?<br><br><strong>If yes,</strong> dismiss the headline and open the article.<br><br><strong>Rationale:</strong> Headlines are attention-grabbing advertisements for content, not information sources.<br></li>



<li><strong>Read the content in its entirety.</strong> Does it trigger an emotional reaction, confirm or reflect a bias, simplify a complex issue, fail to cite any legitimate sources, or use short quotes from the individual/company that&#8217;s the target of the article (without the target&#8217;s context provided) to lead to an opinion?<br><br><strong>If yes,</strong> it&#8217;s a blog post/opinion piece/hit piece/one-sided fragment of a larger story, and another source from another viewpoint is necessary.<br><br><strong>Rationale:</strong> Most news stories are <em>stories</em>, designed to keep viewers on the site short-term and long-term through whatever means is most effective. A complete picture of the facts is the quickest way to <em>deter</em> most readers, who won&#8217;t stick around long enough for that, and many wouldn&#8217;t want to.<br></li>



<li><strong>Is it about anything scientific, technical, political, legal, or otherwise complex?</strong><br><br><strong>If yes,</strong> and you&#8217;re reading a news site of any kind, then you don&#8217;t have the full picture.<br><br>Go learn more about the subject, find the full quotes, read the research paper. Otherwise, you&#8217;re still uninformed, and probably feeling an emotion about it (see above).<br><br><strong>Rationale:</strong> Same as above, really. You&#8217;re not getting the full picture, just a summary, and summaries tend to be incomplete and biased. You can do better.<br></li>



<li><strong>Determine the general bias of the news source.</strong> Is it left-biased? Right? Center? All the above?<br><br><strong>Cool.</strong> Anyway, go read more articles about the subject.<br><br>Read it from CNN, BBC, Fox News, Huffington Post. Find out what people are saying. Understand the spectrum and the angles. Find the research papers, the professional discussions, the deep dives. Get familiar with the complexity.<br><br><em>Then</em> form your own opinion. “I don’t understand this well enough” is a perfectly valid opinion.<br><br><strong>Rationale:</strong> News are written by individuals (unless they’re written by AI). Regardless of the bias of the publication, you&#8217;re usually dealing with people who may impose their own bias or lack of knowledge on a subject. They, you, and their editors aren&#8217;t going to think the same way everyone else does, and even if they&#8217;re trying to be legit and diverse, they&#8217;re going to miss something.<br><br>If you care at all about the topic, if you want to discuss the topic, go learn about more of the angles and the slants, because that&#8217;s an important part of building a true understanding of any topic.<br></li>
</ol>



<p class="wp-block-paragraph">That’s verbatim from my note I keep handy when I’m sorting out the latest complexities of the world causing my head to spin.</p>



<h2 class="wp-block-heading">So let’s go over the important points</h2>



<p class="wp-block-paragraph"><em>Read</em>.</p>



<p class="wp-block-paragraph">Read the article, not just the headline.</p>



<p class="wp-block-paragraph">Read the research paper.</p>



<p class="wp-block-paragraph">Read what you agree with — and what you don’t.</p>



<p class="wp-block-paragraph">Read books. Long articles. Deep dives.</p>



<p class="wp-block-paragraph">Read a ChatGPT explanation. A Wikipedia article. An opinion piece — with its counterpoint.</p>



<p class="wp-block-paragraph">Think about how much time you’ve spent doom-scrolling, reacting, stressing. Now imagine what you could learn instead.</p>



<p class="wp-block-paragraph">Know that every news story is just a fragment. Approach with eyes open.</p>



<p class="wp-block-paragraph">And when you do read — whatever you read — work to <em>understand</em>. Know you don’t know everything. And if you don’t fully understand it? <strong>Don’t spread it.</strong></p>



<p class="wp-block-paragraph">The world is noisy, and angry. Misinformation is easy. Critical thinking is much harder, but it’s necessary.</p>



<p class="wp-block-paragraph">Dig deeper. Get comfortable with complexity.</p>



<p class="wp-block-paragraph">This is how we engage. How we fight injustice. How we keep from drowning.</p>



<p class="wp-block-paragraph">This is how we do better.</p>



<p class="wp-block-paragraph"><strong>Thanks for reading.</strong></p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://chipx86.blog/2025/02/12/read-dammit/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://chipx86.blog/2025/02/12/read-dammit/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>chipx86</name>
							<uri>http://chipx86.blog</uri>
						</author>

		<title type="html"><![CDATA[Let&#8217;s Preserve Government Data Before It&#8217;s Too Late!]]></title>
		<link rel="alternate" type="text/html" href="https://chipx86.blog/2025/02/05/lets-preserve-government-data-before-its-too-late/" />

		<id>https://chipx86.blog/?p=1153</id>
		<updated>2025-06-29T02:13:43Z</updated>
		<published>2025-02-05T10:10:24Z</published>
		<category scheme="https://chipx86.blog" term="Writings" /><category scheme="https://chipx86.blog" term="Data" /><category scheme="https://chipx86.blog" term="Government" /><category scheme="https://chipx86.blog" term="Projects" /><category scheme="https://chipx86.blog" term="United States" />
		<summary type="html"><![CDATA[This has been one hell of a bumpy month, and I have a lot I could scream talk about, but for the moment, let&#8217;s talk data. The US Government has spoiled us in recent years with the amount of public data and information available. NIH studies, wastewater virus shedding data, COVID-19 impacts, climate trends and [&#8230;]]]></summary>

					<content type="html" xml:base="https://chipx86.blog/2025/02/05/lets-preserve-government-data-before-its-too-late/"><![CDATA[
<p class="wp-block-paragraph">This has been one hell of a bumpy month, and I have a lot I could <s>scream</s> talk about, but for the moment, let&#8217;s talk data.</p>



<p class="wp-block-paragraph">The US Government has spoiled us in recent years with the amount of public data and information available. NIH studies, wastewater virus shedding data, COVID-19 impacts, climate trends and forecasts, all kinds of things. Some of those are things I used for my <a href="https://bc19.live/">COVID reporting</a> over the past four years.</p>



<p class="wp-block-paragraph">And in recent weeks, the US Government has demanded that some of this data be purged or altered to fit the whims of the new White House.</p>



<p class="wp-block-paragraph">A bit too <a href="https://www.sparknotes.com/lit/1984/summary/">1984</a> for my tastes. A bit too <a href="https://www.sparknotes.com/lit/451/summary/">Fahrenheit 451</a>.</p>



<p class="wp-block-paragraph">And this is not the first time data&#8217;s disappeared under a new administration, and it&#8217;s not always one party or another, though what&#8217;s happening now is scary.</p>



<p class="wp-block-paragraph">But as they say, look to the helpers. There are <a href="https://archive.org/details/data-rescue-efforts-2025-02-04/page/n1/mode/2up">several</a> <a href="https://tracker.archiveteam.org/">massive</a> <a href="https://www.reddit.com/r/DataHoarder/">efforts</a> underway to archive as much data as possible so it&#8217;s not lost forever. </p>



<p class="wp-block-paragraph">Today, I set up <a href="https://tracker.archiveteam.org/">Archive Team&#8217;s Warrior</a>, which automates the collaboration around spidering, downloading, processing, and uploading data from governmental sites (and others) to <a href="https://archive.org/">archive.org</a>. All it takes is some bandwidth (okay, a fair amount of bandwidth — looking at 1TB/month right now), some hard drive space, and some CPU cycles, and I can help with this archiving project. It&#8217;s excellent, easy to set up, fun to watch, and requires virtually no work on the user&#8217;s end. They provide VMs and Docker images (I chose Docker), and once installed, it&#8217;s self-managing.</p>



<p class="wp-block-paragraph">I&#8217;m exploring more of what&#8217;s out there for data preservation, and thinking about how I can get involved. There are a few really interesting resources out there, including:</p>



<ul class="wp-block-list">
<li><a href="https://govdiff.com/">GovDiff</a>: See the differences in governmental information and resources before and after this administration began its.. work.</li>



<li><a href="https://www.reddit.com/r/DataHoarder/">/r/DataHoarder</a> on Reddit: A group of people working to collect and archive data of all kinds.</li>



<li><a href="https://eotarchive.org/">End of Term Archive Project</a>: Captures US Government sites after presidential terms end.</li>



<li><a href="https://docs.google.com/document/d/15ZRxHqbhGDHCXo7Hqi_Vcy4Q50ZItLblIFaY3s7LBLw/edit?tab=t.0">Data Rescue Efforts</a> by Lynda M. Kellam (<a href="https://archive.org/details/data-rescue-efforts-2025-02-04/page/n1/mode/2up">archived link</a>): A whole collection of sites worth exploring.</li>



<li><a href="https://archive.org/">Archive.org</a>, which hopefully you know about already, and which must be preserved at all costs.</li>
</ul>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Also of note, <a href="https://archive.org/details/20250128-cdc-datasets">CDC Datasets prior to January 28th, 2025</a> (nearly 100GB worth), which I&#8217;ll be archiving myself.</p>



<p class="wp-block-paragraph">No doubt, lots of data will be lost to time. I can only hope there&#8217;s enough people at these agencies who quietly, discretely backed up and sent off what they could before this all went down. In either case, the fact that so many people can join in on the data archiving effort today is incredible, and I hope anyone out there with the resources to spare will take a moment and set up Archive Team&#8217;s Warrior and contribute to the effort.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://chipx86.blog/2025/02/05/lets-preserve-government-data-before-its-too-late/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://chipx86.blog/2025/02/05/lets-preserve-government-data-before-its-too-late/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>chipx86</name>
							<uri>http://chipx86.blog</uri>
						</author>

		<title type="html"><![CDATA[A New Patching Process for RBTools 5.1]]></title>
		<link rel="alternate" type="text/html" href="https://chipx86.blog/2024/06/10/a-new-patching-process-for-rbtools-5-1/" />

		<id>https://chipx86.blog/?p=1137</id>
		<updated>2025-06-29T02:12:38Z</updated>
		<published>2024-06-10T23:46:17Z</published>
		<category scheme="https://chipx86.blog" term="Beanbag" /><category scheme="https://chipx86.blog" term="Review Board" /><category scheme="https://chipx86.blog" term="Mercurial" /><category scheme="https://chipx86.blog" term="Patch" /><category scheme="https://chipx86.blog" term="Patches" /><category scheme="https://chipx86.blog" term="RBTools" /><category scheme="https://chipx86.blog" term="Review-Board" />
		<summary type="html"><![CDATA[RBTools, our command line tool suite for Review Board, is getting all-new infrastructure for applying patches. This will be available in rbt patch, rbt land, and any custom code that needs to deal with patches. A customer reported that multi-commit review requests won&#8217;t apply on Mercurial. The problem, it turns out, is that Mercurial won&#8217;t [&#8230;]]]></summary>

					<content type="html" xml:base="https://chipx86.blog/2024/06/10/a-new-patching-process-for-rbtools-5-1/"><![CDATA[
<p class="wp-block-paragraph"><a href="https://www.reviewboard.org/docs/rbtools/latest/">RBTools</a>, our command line tool suite for <a href="https://www.reviewboard.org/">Review Board</a>, is getting all-new infrastructure for applying patches. This will be available in <a href="https://www.reviewboard.org/docs/rbtools/latest/rbt/commands/patch/">rbt patch</a>, <a href="https://www.reviewboard.org/docs/rbtools/latest/rbt/commands/land/">rbt land</a>, and any custom code that needs to deal with patches.</p>



<p class="wp-block-paragraph">A customer reported that multi-commit review requests won&#8217;t apply on <a href="https://mercurial-scm.org/">Mercurial</a>. The problem, it turns out, is that Mercurial won&#8217;t let you apply a patch if the working tree isn&#8217;t clean, and that includes if you&#8217;ve applied a prior patch in a series. That&#8217;s pretty annoying, but there are good reasons for it.</p>



<p class="wp-block-paragraph">It <em>can</em>, however, apply multiple patches in one go. Which is nice, but impossible to use with RBTools today.</p>



<p class="wp-block-paragraph">So this weekend, I began working on a new patch application process.</p>



<h3 class="wp-block-heading">How SCMs apply patches</h3>



<p class="wp-block-paragraph">There are a lot of SCMs out there, and they all work a bit differently.</p>



<p class="wp-block-paragraph">When dealing with patches, most are thin wrappers around GNU diff/patch, with a bit of pre/post-processing to deal with SCM-specific metadata in the patches.</p>



<p class="wp-block-paragraph">The process usually looks like this:</p>



<ol class="wp-block-list">
<li><strong>Extract SCM-specific data from the diff.</strong><br><br>The SCM patcher will read through the patch and look for any SCM-specific data to extract. This may include commit IDs/revisions, file modes, symlinks to apply, binary file changes, and commit messages and other metadata. It&#8217;ll usually validate the local checkout and any file modifications, to an extent.</li>



<li><strong>Normalize the diff, if needed.</strong><br><br>This can involve taking parts of the diff that can&#8217;t be passed to GNU patch (such as binary file changes, file/directory metadata changes) and setting those aside to handle specially. It may also split the patch into per-file segments. The results will be something that can be passed to GNU diff.</li>



<li><strong>Invoke the patcher (usually GNU patch or similar)</strong>.<br><br>This often involves calling an external patch program with a patch or an extracted per-file patch, checking the results, and choosing how to handle things like conflicts or staging a file for a commit or applying some metadata to the file.</li>



<li><strong>Invoke any custom patching logic.</strong><br><br>This is where logic specific to the SCM may be applied. Patching a binary file, changing directory metadata, setting up symlinks, etc.</li>
</ol>



<p class="wp-block-paragraph">SCMs can go any route with their patching logic, but that&#8217;s a decent overview of what to expect.</p>



<p class="wp-block-paragraph">Depending on what that logic looks like, and what constraints the SCM imposes, this process may bail early. For instance, some will just let you apply a patch on top of a potentially-dirty working directory, and some will not.</p>



<p class="wp-block-paragraph">Mercurial won&#8217;t, which brings us to this project.</p>



<h3 class="wp-block-heading">Out with the old</h3>



<p class="wp-block-paragraph">RBTools uses an SCM abstraction model, with classes implementing features for specific SCMs. We determine the right backend for a local source tree, get an instance of that backend, and then call methods on it.</p>



<p class="wp-block-paragraph">Patches are run through an <code>apply_patch()</code> method. It looks like this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
def apply_patch(
    self,
    patch_file: str,
    *,
    base_path: str,
    base_dir: str,
    p: Optional&#x5B;str] = None,
    revert: bool = False,
) -&gt; PatchResult:
    ...
</pre></div>


<p class="wp-block-paragraph">This takes in a path to a patch file, some criteria like whether to revert or commit a patch, and a few other things. The SCM can then use the default GNU patch implementation, or it can use something SCM-specific.</p>



<p class="wp-block-paragraph">At the end of this, we get a <code>PatchResult</code>, which the caller can use to determine if the patch applied, if there were conflicts, or if it just outright failed.</p>



<p class="wp-block-paragraph">The problem is, each patch is handled independently. There&#8217;s no way to say &#8220;I have 5 patches. Do what you need to do to apply them.&#8221;</p>



<p class="wp-block-paragraph">So we&#8217;re stuck applying one-by-one in Mercurial, and therefore we&#8217;re doomed to fail.</p>



<p class="wp-block-paragraph">This architecture is old, and we&#8217;re ready to move on from it.</p>



<h3 class="wp-block-heading">In with the new</h3>



<p class="wp-block-paragraph">We&#8217;re introducing new primitives in RBTools, which give SCMs full control over the whole process. Not only are these useful for RBTools commands, but for third-party code built upon RBTools.</p>



<p class="wp-block-paragraph">Patch application is now made up of the following pieces:</p>



<ul class="wp-block-list">
<li><code>Patch</code>: A representation of a patch to apply, with all the information needed to apply it.</li>



<li><code>Patcher</code>: A class responsible for applying and committing patches, built to allow SCM-specific subclasses to communicate patching capabilities and to define patching logic.</li>



<li><code>PatchResult</code>: The result of a patch operation, covering one or more patches.</li>
</ul>



<p class="wp-block-paragraph"><code>Patcher</code> consolidates the roles of both the old <code>apply_patch()</code> and some of the internal logic within our <a href="https://www.reviewboard.org/docs/rbtools/latest/rbt/commands/patch/">rbt patch</a> command. By default, it just feeds patches into GNU patch one-by-one, but SCMs can do whatever they need to here by pointing to a custom <code>Patcher</code> subclass and changing that logic.</p>



<p class="wp-block-paragraph">Callers tell the <code>Patcher</code>, &#8220;Hey, I have these patches, and have these settings to consider (reverting, making commits, etc.)&#8221; and will then get back an object that says what the patcher is capable of doing.</p>



<p class="wp-block-paragraph">They can then say &#8220;Okay, begin patching, and give me each <code>PatchResult</code> as you go.&#8221; The <code>Patcher</code> can apply them one-by-one or in batches (Mercurial will use batches), and send back useful <code>PatchResult</code>s as appropriate.</p>



<p class="wp-block-paragraph">That in turn gives the caller the ability to report progress on the process without assuming anything about what that process looks like.</p>



<h3 class="wp-block-heading">And it Just Works (TM)</h3>



<p class="wp-block-paragraph">This design is very clean to use. Here&#8217;s an example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
review_request = api_root.get_review_request(review_request_id=123)

patcher = scmclient.get_patcher(patches=&#x5B;
    Patch(content=b&#039;...&#039;),
    Patch(content=b&#039;...&#039;),
    Patch(content=b&#039;...&#039;),
])
total_patches = len(patcher.patches)

try:
    if patcher.can_commit:
        print(f&#039;Preparing to commit {total_patches} patches...&#039;)
        patcher.prepare_for_commit(review_request=review_request)
    else:
        print(f&#039;Preparing to apply {total_patches} patches...&#039;)

    for patch_result in patcher.patch():
        print(f&#039;Applied patch {patch_result.patch_num} / {total_patches}&#039;)
except ApplyPatchError as e:
    patch_result = e.failed_patch_result

    if patch_result:
        print(f&#039;Error applying patch {patch_result.patch_num}: {e}&#039;)

        if patch_result.has_conflicts:
            print()
            print(&#039;Conflicts:&#039;)

            for conflict in patch_result.conflicts:
                print(f&#039;* {conflict}&#039;)
    else:
        print(f&#039;Error applying patches: {e}&#039;)
</pre></div>


<p class="wp-block-paragraph">In this example, we:</p>



<ol class="wp-block-list">
<li>Defined three patches to apply</li>



<li>Requested to perform commits if possible, using the review request information to aid in the process.</li>



<li>Displayed progress for any patched files.</li>



<li>Handled any patching errors, and showing any failed patch numbers and any conflicts if that information is available.</li>
</ol>



<p class="wp-block-paragraph">This will work across all SCMs, entirely backed by the SCM&#8217;s own patching logic.</p>



<p class="wp-block-paragraph">This is still very much in progress, but is slated for RBTools 5.1, coming soon.</p>



<p class="wp-block-paragraph">Until then, check out our all-new <a href="https://www.reviewboard.org/news/2024/06/06/review-board-7-its-a-bright-day-for-code-review/">Review Board 7 release</a> and our all-new <a href="https://discord.com/invite/h9XFTBQxKh">Review Board Discord channel</a>, open for all developers and for Review Board users alike.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://chipx86.blog/2024/06/10/a-new-patching-process-for-rbtools-5-1/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://chipx86.blog/2024/06/10/a-new-patching-process-for-rbtools-5-1/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
	</feed>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/

Page Caching using Disk: Enhanced (Page is feed) 
Lazy Loading (feed)
Database Caching 30/51 queries in 0.017 seconds using Memcached

Served from: blog.chipx86.com @ 2026-05-31 11:52:09 by W3 Total Cache
-->