<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Witold Wozniak</title>
    <description>The latest articles on DEV Community by Witold Wozniak (@witoldwozniak).</description>
    <link>https://dev.to/witoldwozniak</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3163164%2Fc555472d-b241-41c5-bf1c-d56c45e11e4f.png</url>
      <title>DEV Community: Witold Wozniak</title>
      <link>https://dev.to/witoldwozniak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/witoldwozniak"/>
    <language>en</language>
    <item>
      <title>Resolving MissingMethodException in Newtonsoft.Json</title>
      <dc:creator>Witold Wozniak</dc:creator>
      <pubDate>Wed, 17 Jun 2026 10:30:49 +0000</pubDate>
      <link>https://dev.to/witoldwozniak/resolving-missingmethodexception-in-newtonsoftjson-41hf</link>
      <guid>https://dev.to/witoldwozniak/resolving-missingmethodexception-in-newtonsoftjson-41hf</guid>
      <description>&lt;p&gt;A test passed in CI and failed on my machine. Same commit, same code, two different outcomes, and the difference turned out to live in the Global Assembly Cache rather than anywhere in the repository. This is a write-up of how a strong-named assembly in the GAC can shadow the copy your application ships, why &lt;code&gt;MissingMethodException&lt;/code&gt; is the symptom, and the one-line change that resolves it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;A unit test failed locally with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System.MissingMethodException: Method not found:
'System.String Newtonsoft.Json.Linq.JToken.ToString(Newtonsoft.Json.Formatting)'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same test passed in Jenkins and GitHub Actions. The code was compiled against a recent Newtonsoft.Json, and the build output folder contained a &lt;code&gt;Newtonsoft.Json.dll&lt;/code&gt; that does expose &lt;code&gt;JToken.ToString(Formatting)&lt;/code&gt;. The method existed in the assembly sitting next to the application. The runtime was not loading that assembly.&lt;/p&gt;

&lt;p&gt;A gap that appears on developer workstations but never in CI points away from the code and toward the environment. The environment, in this case, was the GAC.&lt;/p&gt;

&lt;h2&gt;
  
  
  The explanation
&lt;/h2&gt;

&lt;p&gt;On .NET Framework, the runtime resolves a strong-named assembly by its identity: name, public key token, culture, and assembly version. When an assembly with a matching identity is present in the GAC, the GAC copy is preferred over a copy in the application's own directory.&lt;/p&gt;

&lt;p&gt;Newtonsoft.Json keeps the same strong-name identity across the entire 13.x line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AssemblyVersion&lt;/code&gt; is &lt;code&gt;13.0.0.0&lt;/code&gt; for every 13.0.x release&lt;/li&gt;
&lt;li&gt;the public key token is identical across them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two different builds — say 13.0.1 and a later 13.0.x — are therefore indistinguishable to the runtime's binding logic. They present the same identity. A binding redirect cannot tell them apart, because there is nothing to redirect between. So if any 13.0.x is registered in the GAC, it satisfies the reference, and the runtime loads it in preference to the version you shipped.&lt;/p&gt;

&lt;p&gt;Because all 13.x releases share one identity, an application cannot enforce that it runs against the build it was compiled and tested against. An older release with a known vulnerability, present in the GAC, will be loaded ahead of the patched copy in the application folder, and there is no binding-redirect mechanism to prevent it. The upstream issues linked below raise exactly this point.&lt;/p&gt;




&lt;p&gt;The two assemblies are not byte-identical. Method signatures shifted across 13.x patch releases, including on &lt;code&gt;JToken.ToString&lt;/code&gt; and &lt;code&gt;JToken.WriteTo&lt;/code&gt;. The set of overloads present in 13.0.1 is not exactly the set present in a later 13.0.x.&lt;/p&gt;

&lt;p&gt;That produces the failure. The compiler binds the call against the overload set in the version you reference. At runtime the GAC copy is loaded, its overload set is different, the specific overload the call was compiled against is not there, and you get &lt;code&gt;MissingMethodException&lt;/code&gt;: a method present at compile time and absent at run time, with no code change between the two.&lt;/p&gt;

&lt;p&gt;Continuous integration never reproduces it because the build agents have no such GAC entry. They load the assembly from the application folder, which is the one the build referenced. Green every time.&lt;/p&gt;




&lt;p&gt;The older Newtonsoft.Json in the GAC was not put there by the application, and on a managed corporate machine it cannot simply be removed. It is registered and held open by background agents — endpoint security, device management, and update services — that run for the life of the session and keep file handles on the assembly the whole time.&lt;/p&gt;

&lt;p&gt;Searching the GAC path in Resource Monitor shows the holders directly. On a typical managed Windows laptop they are the usual fleet-management software:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Associated Handles                              Search: Newtonsoft.Json.dll
─────────────────────────────────────────────────────────────────────────────
Image                                PID    Type   Handle Name
─────────────────────────────────────────────────────────────────────────────
OktaVerify.exe                       2137   File   C:\Windows\Microsoft.NET\
Dell.Update.SubAgent.exe             15288  File     assembly\GAC_MSIL\
WorkspaceONEHubHealthMonitoring.exe  7696   File     Newtonsoft.Json\
TaskScheduler.exe                    8372   File     v4.0_13.0.0.0__
ServiceShell.exe                     26004  File     30ad4fe6b2a6aeed\
(and others)                                         Newtonsoft.Json.dll
─────────────────────────────────────────────────────────────────────────────
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The copy is effectively immutable: you do not control the processes pinning it, and you cannot unregister an assembly the operating system reports as in use. Any fix has to assume the GAC copy stays exactly where it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Since the GAC copy cannot be moved, the call has to bind to an overload that exists in both versions. Both &lt;code&gt;ToString&lt;/code&gt; and &lt;code&gt;WriteTo&lt;/code&gt; have a &lt;code&gt;params JsonConverter[]&lt;/code&gt; overload that has been present across the 13.x line. The single-argument overloads are the ones whose presence varies between patch releases. Passing the converter argument explicitly forces binding to the stable overload.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;ToString&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before — binds to ToString(Formatting), absent from the older GAC copy&lt;/span&gt;
&lt;span class="n"&gt;jToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Formatting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// after — binds to ToString(Formatting, params JsonConverter[])&lt;/span&gt;
&lt;span class="n"&gt;jToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Formatting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

&lt;span class="c1"&gt;// in older C# versions&lt;/span&gt;
&lt;span class="n"&gt;jToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Formatting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JsonConverter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;WriteTo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before — binds to WriteTo(JsonWriter)&lt;/span&gt;
&lt;span class="n"&gt;jToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// after — binds to WriteTo(JsonWriter, params JsonConverter[])&lt;/span&gt;
&lt;span class="n"&gt;jToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JsonConverter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The empty converter array means no converters are applied, so the output is byte-for-byte identical to the original call. Nothing about serialization behavior changes. The only thing that changes is which overload the compiler selects, and the selected overload is one the older GAC assembly also has. The maintainer recommends this same workaround on the upstream issue.&lt;/p&gt;

&lt;p&gt;It is a targeted mitigation, not a cure. It resolves the specific calls that break, and it leaves the underlying resolution behavior untouched — any other call site reaching for a newer-only overload will fail the same way until it gets the same treatment.&lt;/p&gt;




&lt;p&gt;There is a second option that avoids the unstable type entirely. Instead of asking the token to render itself, hand it to the static serializer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before — JToken instance method, overload set varies across the GAC copy&lt;/span&gt;
&lt;span class="n"&gt;jToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Formatting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// after — static method, signature stable across 13.x&lt;/span&gt;
&lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Formatting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;JsonConvert.SerializeObject(object, Formatting)&lt;/code&gt; is the relevant difference. It is a static method whose signature has held across the 13.x line, and it is a different member from the one breaking, so the varying &lt;code&gt;JToken.ToString&lt;/code&gt; overloads in the GAC copy never enter resolution. The token is serialized through the normal &lt;code&gt;JsonSerializer&lt;/code&gt; path and the result is the JSON you expect.&lt;/p&gt;

&lt;p&gt;The tradeoff is that this routes through default &lt;code&gt;JsonSerializerSettings&lt;/code&gt; rather than the token's own write path. For a plain &lt;code&gt;JToken&lt;/code&gt; the output matches &lt;code&gt;ToString(Formatting.None)&lt;/code&gt;, but if the application configures global default settings, output is no longer guaranteed byte-identical. Where that matters, prefer the explicit-overload fix, which changes only overload selection and leaves serialization behavior alone.&lt;/p&gt;




&lt;p&gt;Both options are local mitigations. The root cause is the shared assembly version, and that is being addressed upstream. &lt;code&gt;13.0.5-beta1&lt;/code&gt; is out as of December 30, 2025 and is intended to resolve the binding problem permanently; the maintainer has asked people hitting this to try it and report back. Until a stable release lands and reaches the machines registering the GAC copy, the changes above are the reliable way to keep working.&lt;/p&gt;

&lt;p&gt;The narrowing that got me here was mechanical, with help from a senior colleague:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirmed the failure was in the &lt;code&gt;JToken.ToString(Formatting.None)&lt;/code&gt; path.&lt;/li&gt;
&lt;li&gt;Checked the build output folder: a newer &lt;code&gt;Newtonsoft.Json.dll&lt;/code&gt; was present and did contain the member.&lt;/li&gt;
&lt;li&gt;Checked the GAC: an older copy was present.&lt;/li&gt;
&lt;li&gt;Compared against CI, where the test passes, which left local assembly resolution as the only difference between the two environments.&lt;/li&gt;
&lt;li&gt;Matched the behavior against the upstream issues describing the same strong-name and GAC collision.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a build succeeds everywhere except one class of machine, the difference is usually not in the code. On a managed Windows workstation the GAC is shared, writable by other software, and authoritative for strong-named assemblies, which means a dependency you never installed can quietly take precedence over the one you shipped.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/JamesNK/Newtonsoft.Json/issues/3084" rel="noopener noreferrer"&gt;JamesNK/Newtonsoft.Json#3084&lt;/a&gt; — &lt;code&gt;JObject.ToString&lt;/code&gt; MissingMethodException when an app built against a newer 13.0.x loads an older one from the GAC, with the maintainer's overload workaround.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/JamesNK/Newtonsoft.Json/issues/3095" rel="noopener noreferrer"&gt;JamesNK/Newtonsoft.Json#3095&lt;/a&gt; — the same failure on &lt;code&gt;JToken.WriteTo&lt;/code&gt;, framed around the shared assembly version and the binding-redirect and security implications.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.nuget.org/packages/Newtonsoft.Json/13.0.5-beta1" rel="noopener noreferrer"&gt;Newtonsoft.Json 13.0.5-beta1&lt;/a&gt; on NuGet (published 2025-12-30).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/dotnet/framework/app-domains/gac" rel="noopener noreferrer"&gt;Global Assembly Cache&lt;/a&gt; article on Microsoft Learn.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Can Copilot be your AI pair programmer?</title>
      <dc:creator>Witold Wozniak</dc:creator>
      <pubDate>Thu, 11 Jun 2026 18:10:21 +0000</pubDate>
      <link>https://dev.to/witoldwozniak/can-copilot-be-your-ai-pair-programmer-2m65</link>
      <guid>https://dev.to/witoldwozniak/can-copilot-be-your-ai-pair-programmer-2m65</guid>
      <description>&lt;p&gt;My dear teammate decided to ruin my evening yesterday by asking an intriguing question: could you set up Copilot as a real pair programmer? Not the chat box you take turns with, but something that watches your code as you write it and reacts in the moment, the way a person at the next desk would. The short answer is no, not with anything you can install today.&lt;/p&gt;

&lt;h2&gt;
  
  
  What pairing means
&lt;/h2&gt;

&lt;p&gt;GitHub launched Copilot in 2021 as "your AI pair programmer," and the same announcement described it adapting to you as you type. The thing was named for pairing and built for autocomplete, so it is fair to ask whether it can do the thing on the label. None of what follows is news, but I am spelling it out because the rest of the piece holds the product to these exact terms.&lt;/p&gt;

&lt;p&gt;Pair programming is a core practice of Extreme Programming, with a definition that predates Copilot by two decades. The usual articulation gives it two roles. The driver types; the navigator reviews each line as it appears, watches for the mistake before it lands, and thinks a step ahead about design. The navigator works while the driver is mid-line, not after a turn is handed over. The two swap roles every few minutes, and both stay engaged the whole time; a navigator who checks out is not pairing.&lt;/p&gt;

&lt;p&gt;The navigator produces without touching the keyboard. The reviewing, the watching, the thinking ahead: that is the work. The canon is explicit about it — the oldest objection to pairing, that it doubles cost by paying two people for one person's typing, is a named misconception, precisely because programming is not typing. So the navigator, who writes almost nothing, is the harder half to automate: the job is continuous real-time oversight, catching the wrong thing the instant it appears and saying so before it costs anything.&lt;/p&gt;

&lt;p&gt;Hold the product against that definition. A tool that completes your line as you type is doing the driver's job, faster — filling the keyboard, the part the canon just told us was never the point. It is not in the loop with you. It never reacts to its own last suggestion, it never waits for yours, and it never takes the navigator's seat: it cannot watch you work and speak up, only wait to be asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not
&lt;/h2&gt;

&lt;p&gt;From the outside, the model is a function. You hand it the whole context, it returns a complete answer. The only stream is the output, and it is read-only: you watch tokens arrive, but there is no second pipe to push new tokens into the call while it runs. To react to something new, you stop and make another call with the new context folded in. Every reaction is a fresh, full-context request. There is no "while you were typing" — there is only the next turn. (Stateful APIs that "remember" the conversation do not change this. They store the context so you do not have to resend it; the model still consumes it whole, one turn at a time.)&lt;/p&gt;

&lt;p&gt;Voice is the proof of how solid that wall is. OpenAI's Realtime API and Google's Gemini Live feel like the real thing: they run over a live two-way connection, they talk while listening, they stop the instant you cut in. But underneath, they are still turn-based. Interruption is not the model hearing you mid-sentence and adjusting: it is the server detecting your speech, cancelling the response in flight, discarding the audio you had not heard yet, appending what you said, and starting a fresh turn. Often it happens fast enough that you do feel heard. [1] The industry spent real money building the full-duplex pipe for voice, and what it bought was an excellent imitation of mid-stream reaction, not the thing itself. A kind of "optimistic &lt;del&gt;UI&lt;/del&gt; AI", if you will.&lt;/p&gt;

&lt;p&gt;The architecture that genuinely does the thing exists. It is called full duplex, and the clearest example is a research speech model called Moshi, which runs your audio and its own as two parallel streams and conditions on your input every fraction of a second, with no stop and no restart. [2] And because a full-duplex model never stops generating, getting it to shut up when you interrupt is apparently harder, not easier, which is probably why Moshi has not made it into a code editor since its heroic escape from a French AI laboratory.&lt;/p&gt;

&lt;p&gt;It seems that nobody has built full duplex for text because text does not need it. Speech is a continuous signal with no natural end, so the system has to guess when you are done. Text comes with a turn boundary built in. You press Enter, and the turn is over. No guessing which moment counts as done, because you explicitly say "I am done, your turn." This is the same reason I have to say "over" after asking my mom on my walkie-talkie to bring me another bag of Cheetos — an open channel gives you no way to hear that someone has finished, so the boundary has to be a word. The entire problem full duplex solves does not exist when the input already arrives pre-segmented, which is why nowadays I tend to text my mom on Signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  How close you can get
&lt;/h2&gt;

&lt;p&gt;Autocomplete is not pairing. It reacts as you type, but you cannot steer it, and it does not answer to your intent. It completes whatever you are writing regardless of what you meant, which makes it a fast stream of guesses with no interaction and no loop.&lt;/p&gt;

&lt;p&gt;Agent mode is the real near-miss. It reasons about your work and responds to it, which is the engagement autocomplete lacks. But you have to hand it the turn: you write a prompt, send it, and wait for the burst of work to come back. The trigger does not have to be a prompt you type, though. OpenCode lets you wire it to an external event instead, like a file save or a failing test, through a plugin that injects a turn on its own, so the agent reacts to your activity without being explicitly asked.&lt;/p&gt;

&lt;p&gt;Whether a harness can do that comes down to mechanism, not brand. Pretty much every decent harness has hooks, but hooks fire only on the session's own internal lifecycle. Reaching outside the session needs a plugin or extension layer on top, one that can inject a turn. OpenCode has it through plugins, and Copilot CLI through a separate extension system. Claude Code is the exception. It has hooks, but its plugin layer can't inject a turn, so the only external trigger is the SDK, which starts a fresh session instead of continuing the current one.&lt;/p&gt;

&lt;p&gt;Either way it is still bursts, not full-auto. This can look like the AI taking the driver's seat while you navigate, but handing off a task and reviewing what comes back is a swap across a turn boundary, not the fluid trading of seats a shared loop needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth it
&lt;/h2&gt;

&lt;p&gt;Suppose you hacked the agent loop tighter and fired it on every pause instead of every save. You would pay full context on every trigger, eat the latency of a fresh call each time, and drown in suggestions you never asked for. You would approximate continuity badly and noisily, and it would still be bursts. Firing more often does not turn a sequence of turns into a continuous stream. The gap between the near-miss and real pairing is not the cadence of the trigger, so no setting closes it.&lt;/p&gt;

&lt;p&gt;When real pairing does reach a code editor, it will not be a Copilot setting. It will be a different kind of model. Until then, the honest answer is that he is stuck with me as his pairing partner.&lt;/p&gt;




&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;p&gt;[1] That interruption is cancel-and-restart, rather than the model editing a generation already in flight, is an inference from how transformer inference works, not a confirmed internal detail. OpenAI and Google document the protocol — voice activity detection, response cancellation, transcript truncation — but not the decode loop itself, so "abort and re-run" is a reasoned conclusion, not a stated one.&lt;/p&gt;

&lt;p&gt;[2] Moshi. Défossez et al., Kyutai, 2024. The model processes the user stream and its own stream jointly as two autoregressive token streams at a 12.5 Hz frame rate (one 80 ms frame per step), discarding its prediction of the user stream and substituting the real incoming audio, which carries into context for the next step. That is genuine mid-decode conditioning. Measured latency around 200 ms, which is inside the range of a normal human conversational gap. The HuggingFace reference implementation is simplified and not real-time; the live full-duplex behavior is in Kyutai's own implementation. It is an open research model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pair programming, definition and the driver/navigator roles: the Agile Alliance glossary and Williams and Kessler, "Pair Programming Illuminated" (2002). The "doubles cost" objection is recorded there as a misconception that equates programming with typing; the driver/navigator framing postdates XP's original practice and is not universally accepted.&lt;/li&gt;
&lt;li&gt;OpenAI, Realtime API documentation (voice activity detection, response cancellation, &lt;code&gt;conversation.item.truncate&lt;/code&gt;, WebSocket/WebRTC/SIP transports).&lt;/li&gt;
&lt;li&gt;Google, Gemini Live API documentation, ai.google.dev/api/live (&lt;code&gt;BidiGenerateContent&lt;/code&gt;, interruption and turn handling).&lt;/li&gt;
&lt;li&gt;OpenAI, streaming responses documentation (single HTTP request, server-sent events as one-directional output).&lt;/li&gt;
&lt;li&gt;Défossez et al., "Moshi: a speech-text foundation model for real-time dialogue," 2024, arXiv:2410.00037.&lt;/li&gt;
&lt;li&gt;GitHub Copilot CLI hooks reference and extensions documentation, docs.github.com (lifecycle hook events; the separate extension system over JSON-RPC).&lt;/li&gt;
&lt;li&gt;Anthropic, Claude Code hooks documentation (internal lifecycle events) and Agent SDK.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;"From turn-taking to synchronous dialogue: a survey of full-duplex spoken language models," arXiv:2509.14515 — where the line between pseudo-full-duplex and genuine full-duplex systems is drawn.&lt;/li&gt;
&lt;li&gt;Latent Space, "OpenAI Realtime API: the missing manual" — a practitioner walk-through of the event protocol and input-token caching.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>JavaScript-less Mobile Menu with Tailwind</title>
      <dc:creator>Witold Wozniak</dc:creator>
      <pubDate>Mon, 26 May 2025 13:36:17 +0000</pubDate>
      <link>https://dev.to/witoldwozniak/javascript-less-mobile-menu-with-tailwind-l88</link>
      <guid>https://dev.to/witoldwozniak/javascript-less-mobile-menu-with-tailwind-l88</guid>
      <description>&lt;h1&gt;
  
  
  JavaScript-less Mobile Menu with Tailwind
&lt;/h1&gt;




&lt;p&gt;I've recently been working on a simple static website. One of the goals of the project was to challenge myself to improve my knowledge of CSS and Tailwind. I wanted to see how much I can do without a line of JavaScript. I explored different JavaScript-less options for mobile navigation menus, and the &lt;strong&gt;checkbox hack&lt;/strong&gt; caught my attention.&lt;/p&gt;

&lt;p&gt;It's simple, and it can teach you a thing or two about how to think outside of the box when it comes to your stylesheets. It can be made accessible with careful attention to ARIA attributes and focus management, though some advanced accessibility features (which are typically JavaScript-reliant) have limitations as discussed later. And here's a fun fact: you can use this pattern to toggle the visibility of &lt;em&gt;anything&lt;/em&gt; on your page!&lt;/p&gt;

&lt;p&gt;So here we are. Here's a JavaScript-less expandable / collapsible mobile navigation menu implemented with Tailwind. For those of you who may not be familiar with Tailwind CSS, it is a utility-first CSS framework that allows developers to rapidly build custom user interfaces directly in their markup by composing pre-defined classes. Instead of writing custom CSS for every element, you apply small, single-purpose utility classes like &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;pt-4&lt;/code&gt;, or &lt;code&gt;text-center&lt;/code&gt; directly to your HTML. This approach promotes consistency, reduces the need for context switching between HTML and CSS files, and results in highly optimized stylesheets as only the used utilities are bundled.&lt;/p&gt;

&lt;p&gt;Keep in mind the code here is minimal – no fancy styling, no accessibility – you'll likely want to handle those aspects yourself anyway. This is just to show you how to toggle visibility of an element.&lt;/p&gt;

&lt;p&gt;If you're here just for the snippet, I'll save you some time — here's the basic code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;nav&amp;gt;
    &amp;lt;div id="mobile-menu-container" class="md:hidden relative"&amp;gt;
        &amp;lt;input
        id="mobile-menu-toggle"
        type="checkbox"
        class="peer absolute z-10 opacity-0"
        /&amp;gt;
        &amp;lt;span id="kebab"
        class="relative z-0 block peer-checked:hidden"
        &amp;gt;⁝&amp;lt;/span&amp;gt;
        &amp;lt;span
        id="x-mark"
        class="relative z-0 hidden peer-checked:block"
        &amp;gt;✕&amp;lt;/span&amp;gt;
        &amp;lt;ul
        id="mobile-menu"
        class="hidden peer-checked:block"
        &amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="ikea.com"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="buymeacoffee.com/vitcosoft"&amp;gt;Charity&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="medium.com/@vitcosoft"&amp;gt;Reading&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if you're curious, keep on reading for step-by-step instructions with some extras along the way. The pretty version of the menu is revealed along the way!&lt;/p&gt;

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




&lt;h2&gt;
  
  
  The Checkbox Hack Overview
&lt;/h2&gt;

&lt;p&gt;The whole trick is based on three simple steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Place an invisible checkbox &lt;strong&gt;above&lt;/strong&gt; your menu icon;&lt;/li&gt;
&lt;li&gt;Place the menu &lt;strong&gt;outside&lt;/strong&gt; the view;&lt;/li&gt;
&lt;li&gt;Toggle menu visibility using the &lt;strong&gt;subsequent-sibling combinator&lt;/strong&gt; based on the checkbox's state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that is it! Add some criminally smooth animations, AI-powered icons, and you've got yourself a fully functional, JavaScript-less mobile menu. Congratulations! Easier than centering a div, eh?&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation with Tailwind
&lt;/h2&gt;

&lt;p&gt;Let me walk you through the implementation steps, so you can fully grasp the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Markup
&lt;/h3&gt;

&lt;p&gt;Let's set up some basic HTML, so we can start applying the Tailwind magic. We'll add a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; to act as the &lt;code&gt;mobile-menu-container&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;nav&amp;gt;
    &amp;lt;div id="mobile-menu-container"&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to pick the façade (usually a hamburger or kebab icon) that will fool the user by pretending to toggle the menu. For simplicity, let's use some Unicode chars depicting three dots and an x-mark. We also need to introduce the main protagonist of our little trick — the checkbox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;nav&amp;gt;
    &amp;lt;div id="mobile-menu-container"&amp;gt;
        &amp;lt;input id="mobile-menu-toggle" type="checkbox"/&amp;gt;
        &amp;lt;span id="kebab"&amp;gt;⁝&amp;lt;/span&amp;gt;
        &amp;lt;span id="x-mark"&amp;gt;✕&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, add the unordered list that will act as our navigation menu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;nav&amp;gt;
    &amp;lt;div id="mobile-menu-container"&amp;gt;
        &amp;lt;input id="mobile-menu-toggle" type="checkbox"/&amp;gt;
        &amp;lt;span id="kebab"&amp;gt;⁝&amp;lt;/span&amp;gt;
        &amp;lt;span id="x-mark"&amp;gt;✕&amp;lt;/span&amp;gt;
        &amp;lt;ul id="mobile-menu"&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="ikea.com"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="buymeacoffee.com/vitcosoft"&amp;gt;Charity&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="medium.com/@vitcosoft"&amp;gt;Reading&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key consideration here is that &lt;strong&gt;the checkbox, the icons, and the list are all sibling elements&lt;/strong&gt;, marked as peers in Tailwind.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Tailwind styles
&lt;/h3&gt;

&lt;p&gt;Now that we have our markup in place, we can start the styling.&lt;br&gt;
Let's begin by making our container &lt;code&gt;hidden&lt;/code&gt; on larger screens and setting its position to &lt;code&gt;relative&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;nav&amp;gt;
    &amp;lt;div id="mobile-menu-container" class="md:hidden relative"&amp;gt;
        &amp;lt;input id="mobile-menu-toggle" type="checkbox"/&amp;gt;
        &amp;lt;span id="kebab"&amp;gt;⁝&amp;lt;/span&amp;gt;
        &amp;lt;span id="x-mark"&amp;gt;✕&amp;lt;/span&amp;gt;
        &amp;lt;ul id="mobile-menu"&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="ikea.com"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="buymeacoffee.com/vitcosoft"&amp;gt;Charity&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="medium.com/@vitcosoft"&amp;gt;Reading&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why do we want it &lt;code&gt;relative&lt;/code&gt;? Since this trick is dependent on the &lt;code&gt;z-index&lt;/code&gt; property, we want to keep things clean and create a "local" stacking context within our container. Also, &lt;code&gt;z-index&lt;/code&gt; only works with elements positioned otherwise than &lt;code&gt;static&lt;/code&gt;. Let's now add utility classes to the checkbox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;nav&amp;gt;
    &amp;lt;div id="mobile-menu-container" class="md:hidden relative"&amp;gt;
        &amp;lt;input
        id="mobile-menu-toggle"
        type="checkbox"
        class="peer absolute z-10 opacity-0"
        /&amp;gt;
        &amp;lt;span id="kebab"&amp;gt;⁝&amp;lt;/span&amp;gt;
        &amp;lt;span id="x-mark"&amp;gt;✕&amp;lt;/span&amp;gt;
        &amp;lt;ul id="mobile-menu"&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="ikea.com"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="buymeacoffee.com/vitcosoft"&amp;gt;Charity&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="medium.com/@vitcosoft"&amp;gt;Reading&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Here's a breakdown of what happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;opacity-0&lt;/code&gt; will make the checkbox invisible to eyes, while keeping its position and interactivity intact;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;z-10&lt;/code&gt; places the checkbox one level above siblings within &lt;code&gt;mobile-menu-container&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;absolute&lt;/code&gt; places the input element outside the normal flow of the document, so it no longer occupies space where it would normally be placed, which allows us to place it &lt;strong&gt;above&lt;/strong&gt; its siblings;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;peer&lt;/code&gt; is a utility class that allows us to style sibling elements based on the properties and state of this particular element.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the minimal setup for our checkbox. Let's now shift our attention to the icons. The reason we have two icons is simple: they will switch places depending on the state of the checkbox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;nav&amp;gt;
    &amp;lt;div id="mobile-menu-container" class="md:hidden relative"&amp;gt;
        &amp;lt;input
        id="mobile-menu-toggle"
        type="checkbox"
        class="peer absolute z-10 opacity-0"
        /&amp;gt;
        &amp;lt;span id="kebab"
        class="relative z-0 block peer-checked:hidden"
        &amp;gt;⁝&amp;lt;/span&amp;gt;
        &amp;lt;span
        id="x-mark"
        class="relative z-0 hidden peer-checked:block"
        &amp;gt;✕&amp;lt;/span&amp;gt;
        &amp;lt;ul id="mobile-menu"&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="ikea.com"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="buymeacoffee.com/vitcosoft"&amp;gt;Charity&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="medium.com/@vitcosoft"&amp;gt;Reading&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down a bit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;relative&lt;/code&gt;, again, allows elements to use &lt;code&gt;z-index&lt;/code&gt;, but does not move them from their normal document flow;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;z-0&lt;/code&gt; — technically, we do not need to set &lt;code&gt;z-index&lt;/code&gt; here (the input will have already been placed above the icons), but we want to be explicit about our intent, which is placing these particular elements below the checkbox;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block&lt;/code&gt; and &lt;code&gt;hidden&lt;/code&gt; set initial states of the icons — the kebab as visible, and x-mark as initially hidden;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;peer-checked:hidden&lt;/code&gt; and &lt;code&gt;peer-checked:block&lt;/code&gt; hides the &lt;code&gt;kebab&lt;/code&gt; and reveals the &lt;code&gt;x-mark&lt;/code&gt; when the &lt;code&gt;peer&lt;/code&gt; checkbox has state of &lt;code&gt;checked&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strictly speaking, the trick is complete. We manipulate visibility of these elements based on the state of the checkbox. But the title promised you a mobile menu, so let's apply the same logic to the unordered list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;nav&amp;gt;
    &amp;lt;div id="mobile-menu-container" class="md:hidden relative"&amp;gt;
        &amp;lt;input
        id="mobile-menu-toggle"
        type="checkbox"
        class="peer absolute z-10 opacity-0"
        /&amp;gt;
        &amp;lt;span id="kebab"
        class="relative z-0 block peer-checked:hidden"
        &amp;gt;⁝&amp;lt;/span&amp;gt;
        &amp;lt;span
        id="x-mark"
        class="relative z-0 hidden peer-checked:block"
        &amp;gt;✕&amp;lt;/span&amp;gt;
        &amp;lt;ul
        id="mobile-menu"
        class="hidden peer-checked:block"
        &amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="ikea.com"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="buymeacoffee.com/vitcosoft"&amp;gt;Charity&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href="medium.com/@vitcosoft"&amp;gt;Reading&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is basically it! We can now toggle the menu visibility without any JavaScript! This one's for all of the JavaScript haters out there.&lt;/p&gt;




&lt;h3&gt;
  
  
  (bonus) Step 3: Let's make it pretty!
&lt;/h3&gt;

&lt;p&gt;If you made it till the end — below lies the prettier version of this menu. And more accessible, because accessibility is beautiful by definition. For instance, this enhanced version (seen in the linked example and the snippet you're reviewing) bolsters accessibility through several specific improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clearer Control Description:&lt;/strong&gt; The underlying checkbox is often made visually hidden (e.g., using an &lt;code&gt;sr-only&lt;/code&gt; class) but remains accessible to assistive technologies. It's given a descriptive &lt;code&gt;aria-label&lt;/code&gt; like &lt;code&gt;"Open or close navigation menu"&lt;/code&gt; to clearly state its purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programmatic Linking:&lt;/strong&gt; The &lt;code&gt;aria-controls&lt;/code&gt; attribute is added to the checkbox to create a programmatic link with the menu panel (e.g., &lt;code&gt;id="mobile-menu-panel"&lt;/code&gt;) that it shows and hides. This helps assistive technologies understand the relationship between the control and the content it affects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focusable Visual Toggle:&lt;/strong&gt; The visible part that users click or tap (often a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; associated with the checkbox) is made explicitly keyboard focusable (e.g., using &lt;code&gt;tabindex="0"&lt;/code&gt; if it's not an inherently focusable element acting as the label) and receives clear visual focus indicators (like &lt;code&gt;focus:ring-2 focus:ring-white&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Menu Region:&lt;/strong&gt; The menu panel itself is given a &lt;code&gt;role="region"&lt;/code&gt; and an &lt;code&gt;aria-label&lt;/code&gt; (e.g., &lt;code&gt;"Mobile navigation"&lt;/code&gt;) to define it as a distinct landmark region, making it easier for screen reader users to navigate and understand the page structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Focus Visibility:&lt;/strong&gt; All interactive elements within the open menu, such as navigation links and any explicit "Close menu" buttons (which might also be a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; for the same checkbox), are designed with clear and visible focus styles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully, these examples will inspire you to make something even better!&lt;/p&gt;

&lt;p&gt;Check the code out here ==&amp;gt; &lt;a href="https://play.tailwindcss.com/TAv4KHynWM?size=400x720" rel="noopener noreferrer"&gt;play.tailwindcss.com&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Note: In programming, every decision is a tradeoff
&lt;/h3&gt;

&lt;p&gt;One feature of computer programming that makes us feel powerful and powerless all at the same time is the fact that everything can be done in a myriad of ways — which makes every decision a tradeoff. Here are some notable considerations regarding the advantages and drawbacks of this implementation of a mobile navigation menu:&lt;/p&gt;

&lt;h4&gt;
  
  
  Strengths:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript-Free Operation:&lt;/strong&gt; Works reliably without client-side scripting, enhancing robustness and broad compatibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS-Powered Interactivity:&lt;/strong&gt; Leverages performant, native CSS for smooth animations and state management, avoiding JavaScript overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core Accessibility Can Be Achieved:&lt;/strong&gt; The checkbox itself provides basic keyboard navigation. With additions such as ARIA attributes and custom focus indicators (as demonstrated in the linked example), good core accessibility is possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight Implementation:&lt;/strong&gt; Avoids JavaScript file weight and execution, potentially leading to faster component rendering.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Accessibility Gaps:&lt;/strong&gt; Lacks JavaScript-dependent features like true focus trapping within the open menu or dynamic &lt;code&gt;aria-expanded&lt;/code&gt; on a semantic button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Compromise:&lt;/strong&gt; Employs a checkbox for UI toggling, which is a functional workaround rather than the element's primary semantic role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited UX Patterns:&lt;/strong&gt; Cannot implement common user experience enhancements like "click outside to close" or "Escape key" closure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structural Rigidity:&lt;/strong&gt; Requires a specific HTML element order and sibling relationships for the CSS selectors to function, reducing layout flexibility.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's a cool hack, but a hack nonetheless. Thanks for reading!&lt;/p&gt;




&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;MDN Web Docs:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Subsequent-sibling_combinator" rel="noopener noreferrer"&gt;Subsequent-sibling combinator&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index" rel="noopener noreferrer"&gt;Understanding z-index&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Guides" rel="noopener noreferrer"&gt;ARIA guides&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tailwind CSS Docs:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-sibling-state" rel="noopener noreferrer"&gt;Styling based on sibling state&lt;/a&gt;&lt;br&gt;
&lt;a href="https://tailwindcss.com/docs/z-index" rel="noopener noreferrer"&gt;Utilities for controlling the stack order of an element.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSS-Tricks:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://css-tricks.com/three-css-alternatives-to-javascript-navigation/" rel="noopener noreferrer"&gt;Three CSS Alternatives to JavaScript&amp;nbsp;Navigation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://css-tricks.com/responsive-menu-concepts/" rel="noopener noreferrer"&gt;Responsive Menu Concepts&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tailwindcss</category>
      <category>css</category>
    </item>
  </channel>
</rss>
