<?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: Stéphane LaFlèche</title>
    <description>The latest articles on DEV Community by Stéphane LaFlèche (@slafleche).</description>
    <link>https://dev.clauneck.workers.dev/slafleche</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%2F3945110%2F9f2496df-2161-4a89-b93d-066f91ce5e75.jpeg</url>
      <title>DEV Community: Stéphane LaFlèche</title>
      <link>https://dev.clauneck.workers.dev/slafleche</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.clauneck.workers.dev/feed/slafleche"/>
    <language>en</language>
    <item>
      <title>We're making the Dreamweaver mistake again, on purpose this time</title>
      <dc:creator>Stéphane LaFlèche</dc:creator>
      <pubDate>Wed, 24 Jun 2026 20:37:51 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/slafleche/were-making-the-dreamweaver-mistake-again-on-purpose-this-time-ema</link>
      <guid>https://dev.clauneck.workers.dev/slafleche/were-making-the-dreamweaver-mistake-again-on-purpose-this-time-ema</guid>
      <description>&lt;p&gt;&lt;em&gt;We spent twenty years getting designers out of the code. AI just put the design back in charge of it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The pendulum swings back
&lt;/h2&gt;

&lt;p&gt;Designers used to be in the code. In the Dreamweaver and WYSIWYG era, you laid out a page in a visual tool and it wrote the markup for you. The industry then spent the better part of twenty years undoing that. We drew a line: designers design, developers build, and a human translates between the two.&lt;/p&gt;

&lt;p&gt;AI is swinging the pendulum back. Not by putting designers back in the code, but by handing their design to a machine that writes it. Point the model at the file, get components out. The design drives the code again.&lt;/p&gt;

&lt;p&gt;Here is the part that should give you pause. This is not a clean return to the Dreamweaver days. Back then, however messy the generated markup was, a person still sat in the translation and owned it. Now the design goes straight to code with no one in the driver's seat. That seat is where quality used to get enforced.&lt;/p&gt;

&lt;p&gt;Two quick things before I go further, because this argument gets misread in two predictable ways.&lt;/p&gt;

&lt;p&gt;First, this is not a knock on designers. Designers do &lt;strong&gt;great work&lt;/strong&gt;. The point is a distinction. What makes a great design file is not what makes a great design system, or what makes good code. A file is judged on the design. A system is judged on reuse, on states, on durability. The AI workflow blurs that line, and the blur is the whole subject.&lt;/p&gt;

&lt;p&gt;Second, if you are building a simple static site, pointing AI at the design is genuinely fine. The snapshot is the thing. Ship it. The problem I am describing only bites when the design is meant to become a reusable system: a design system, a custom CMS, a dynamic UI. Keep that scope in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dream is real
&lt;/h2&gt;

&lt;p&gt;I want to be fair to the dream, because it is seductive and it is partly true. Point AI at the design, get working code, one step.&lt;/p&gt;

&lt;p&gt;AI is genuinely valuable. I use it. It has let me do things I could not have done before, and finish projects under tight deadlines in codebases I did not know well. I am not here to tell you the tool is bad.&lt;/p&gt;

&lt;p&gt;But notice where that value lands. It lands at the snapshot level. Real value is not the same thing as a foundation. That gap is the rest of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  A design is not a foundation
&lt;/h2&gt;

&lt;p&gt;Here is the concrete failure, and it is not hypothetical. Teams build entire token pipelines around the variable names a designer chose in Figma. Naming is a design decision, but auto-translation quietly promotes it to a load-bearing contract. Rename one variable in the design file and the pipeline snaps three steps downstream. The design is doing a foundation's job, and breaking, because it was never built for it.&lt;/p&gt;

&lt;p&gt;You can see it in a single pair of names. &lt;code&gt;--blue-500&lt;/code&gt; is a value; &lt;code&gt;--color-primary&lt;/code&gt; is an intent. A system should depend on the intent, and AI cannot reliably tell which one the design meant.&lt;/p&gt;

&lt;p&gt;The obvious fix is to train designers to name things the system-correct way. But that points the effort at the wrong person. I love working with designers, but the answer is not to turn them into systems engineers, negotiating every token name. Translating their vision into a durable system is my job, not theirs. That translation wants to be a pipeline: map the design tokens onto a universal standard, then watch for drift. I will come back to what that standard is.&lt;/p&gt;

&lt;p&gt;Naming is only the first failure mode. Set it aside and a deeper one remains. A design is a static snapshot. It shows one state of one screen. It does not encode reusable versus one-off, the empty, loading, and error states, what is content-driven versus fixed, or how a CMS feeds it. That systemic context lives in the developer's head and in the code. It is not in the file.&lt;/p&gt;

&lt;p&gt;The industry has noticed the input problem, and it is moving on it. Google recently open-sourced DESIGN.md, a format for handing a structured design system to a coding agent instead of a raw file, and a catalogue of them is already forming.&lt;sup id="fnref1"&gt;1&lt;/sup&gt; That is a real step, and it tells you the input was a weak link all along. Hold that thought, because where DESIGN.md stops is exactly the point.&lt;/p&gt;

&lt;p&gt;The deeper limit is not the input format. It is that the design, in any form, is the wrong source for the decisions. The model's best case is the guts of the design file, an artifact optimized for none of the things a foundation needs: not design-system structure, not coding best practices, not the developer or agent experience. Its worst case is a screenshot, pixels with no structure at all. Even a flawless extraction cannot supply the architectural decisions a human makes to accommodate a design.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; The deciding stays human.&lt;/p&gt;

&lt;p&gt;So the verdict. A design is not a foundation. Auto-translating it into code silently promotes it to one, and it was never built for the role.&lt;/p&gt;

&lt;h2&gt;
  
  
  People are working on this
&lt;/h2&gt;

&lt;p&gt;This gap is real, and a lot of people are working on it right now, from the biggest players to solo builders. Google's DESIGN.md is one move. An ecosystem has formed around it almost overnight, and it is already being put to the test in production. My friend Amrutha Kollu is one of the independent builders coming at it. Her tool, Fixel, validates your code against Figma in continuous integration and catches design-system drift before it merges. When it finds drift, it posts the finding back onto the Figma canvas as a comment, so the designer sees it in the place they already work.&lt;sup id="fnref3"&gt;3&lt;/sup&gt; It is promising work on a real problem.&lt;/p&gt;

&lt;p&gt;Where all of this lands is genuinely open. A lot of smart people are coming at the same problem from different angles at once. That open question is the honest state of things, and it is a big part of why I am writing this.&lt;/p&gt;

&lt;p&gt;I want to be precise about where it sits, because the boundaries are the interesting part. Fixel is on the code side, and it does not claim to have solved the whole thing. The limit worth naming, and I mean it as architecture, not criticism: it reads directly from the design tool, so any tool-coupled approach risks going obsolete unless it targets the open standard underneath. That standard is the W3C Design Tokens format, or DTCG, the vendor-neutral spec the major tools are converging on, and it is the durable anchor here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The missing middle
&lt;/h2&gt;

&lt;p&gt;You might think the newest tools already close this. They have gone well past reading a screenshot. Claude Design imports your design system straight from your codebase and checks its output against it before you see it.&lt;sup id="fnref4"&gt;4&lt;/sup&gt; Google's Stitch reads your tokens before it draws a screen. That is real, and it is the convert step getting genuinely good.&lt;/p&gt;

&lt;p&gt;But two things none of it does. It assumes the system already exists and is clean enough to import, which means someone built that foundation and still owns it. It also checks only once, at generation. The harder problem is what happens after the code merges, when the design and the system keep moving and quietly drift apart.&lt;/p&gt;

&lt;p&gt;So that is where this leaves us. Not with the design driving the code, and not with a person hand-translating every screen either. It leaves us with a middle that someone has to own.&lt;/p&gt;

&lt;p&gt;Here is the bet I would make on what the missing middle looks like. I think it starts with &lt;a href="https://dev.clauneck.workers.dev/slafleche/the-case-for-compiled-typed-css-blame-ai-8m8"&gt;typed CSS inputs at build time&lt;/a&gt;: a foundation where a value carries a type and a meaning, not just a string the model can get wrong. On top of that, the AI does what it is good at. It pulls the variables out of the design and proposes how they map onto the system you already have. Then the UX engineer does what only they can. They shape that proposal into the system they actually want, decide what each value means, and set the behaviour and the success criteria the result has to meet. Typed inputs and automated checks keep the AI's suggestions honest.&lt;/p&gt;

&lt;p&gt;Those decisions do not live in someone's head, or get re-derived on every run. They become an artifact: a committed, versioned record of how this design maps onto the system, owned by the engineer. Fixel already does a version of this. Its overrides registry commits each intentional deviation to git with a reason and a timestamp, an audit trail instead of a silent guess. This is the part DESIGN.md cannot reach. Its prose "why" is re-read and re-interpreted by the model on every run, where a committed artifact is a decision someone made and signed.&lt;/p&gt;

&lt;p&gt;Notice what did not happen there. The design never became the system on its own. Clean variables out of Figma are a real step, but they are not the finish line, because the same tokens can be implemented a hundred ways. Choosing which one is the judgment, and the judgment is the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake, named
&lt;/h2&gt;

&lt;p&gt;Here is the irony to leave on. AI makes designers more responsible for code quality than they were back when we actually wanted them writing it. The design becomes the code, and no one is left gatekeeping the translation.&lt;/p&gt;

&lt;p&gt;So the real mistake is not that we use AI to get there. It is that we are writing the UX engineer out of the loop, right when that judgment most needs an owner. The role is not going away. It is changing into the person who owns this middle: the system the design feeds into, the mapping, the contract that keeps a design from quietly becoming the foundation.&lt;/p&gt;

&lt;p&gt;The durable anchor under all of it is a standard with the mapping in your hands, and that standard is where I am headed in my next article.&lt;/p&gt;

&lt;p&gt;If you are building this middle, I would genuinely like to hear how you draw the line between what the AI proposes and what stays yours to decide. That is the conversation I am trying to start.&lt;/p&gt;







&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;DESIGN.md is a Markdown file that describes a design system to an AI coding agent in a structured way: machine-readable tokens plus human-readable prose. Google Labs open-sourced it in April 2026. See the Google Labs announcement (blog.google, "Stitch DESIGN.md") and the spec repo (github.com/google-labs-code/design.md). A community catalogue at getdesign.md (maintained by VoltAgent) already lists 75 DESIGN.md files built on the spec (as of June 2026).&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;This limit shows up in practice. When Atlassian benchmarked DESIGN.md against their own design-system tooling, they reported it was more likely to re-create existing components than to import the ones already in their system. A faithful description of how a component looks does not carry the decision to reuse the component that already exists. See Atlassian, "Atlassian's DESIGN.md is here: what we learned testing portable design context in practice" (atlassian.com, June 2026).&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Amrutha Kollu's writing on this gap: &lt;a href="https://dev.clauneck.workers.dev/akollu72/how-we-shipped-60-design-system-components-in-5-weeks-using-figma-as-the-single-source-of-truth-1lkc"&gt;How I shipped 60+ design system components in 5 weeks using Figma as the single source of truth&lt;/a&gt;, &lt;a href="https://dev.clauneck.workers.dev/akollu72/why-ai-keeps-generating-the-wrong-design-tokens-and-how-i-fixed-it-with-figmas-api-17o4"&gt;Why AI keeps generating the wrong design tokens and how I fixed it with Figma's API&lt;/a&gt;, and &lt;a href="https://dev.clauneck.workers.dev/akollu72/check-designs-validates-your-figma-what-validates-your-code-38c2"&gt;Check Designs validates your Figma. What validates your code?&lt;/a&gt;. Fixel validates code against Figma in CI to catch design-system drift.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;Claude Design, Anthropic Labs (anthropic.com/news/claude-design-anthropic-labs). A June 2026 update added importing a design system from a repository or files and checking generated output against it.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>design</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Most of the web is touch. We still don't save handedness.</title>
      <dc:creator>Stéphane LaFlèche</dc:creator>
      <pubDate>Fri, 19 Jun 2026 01:02:51 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/slafleche/most-of-the-web-is-touch-we-still-dont-save-handedness-5ea0</link>
      <guid>https://dev.clauneck.workers.dev/slafleche/most-of-the-web-is-touch-we-still-dont-save-handedness-5ea0</guid>
      <description>&lt;p&gt;How is there still no OS setting to save your handedness preference?&lt;/p&gt;

&lt;p&gt;We can detect a surprising amount about how someone wants to use a page. Dark mode, reduced motion, higher contrast, reduced data: the &lt;code&gt;prefers-*&lt;/code&gt; media features expose all of it. There is one obvious preference the platform never exposes: which hand you are using.&lt;/p&gt;

&lt;p&gt;On a touchscreen, your hand is the interface. It is the cursor. Yet a designer building for that screen has no way to know whether the thumb in play is on the left or the right. So I &lt;a href="https://github.com/w3c/csswg-drafts/issues/13215" rel="noopener noreferrer"&gt;opened a proposal with the CSS Working Group&lt;/a&gt; for a new media feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-handedness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* layout tuned for a left thumb */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-handedness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* layout tuned for a right thumb */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like the rest of the family, this is a &lt;strong&gt;declared preference&lt;/strong&gt;, not a guess about your body: the user sets it the way they set dark mode. And because it would live at the OS level, flipping it could be a one-tap control, the same quick toggle we already use for things like Bluetooth. The web already reflows an entire layout when you rotate the screen, so a left-to-right flip is a smaller version of a shift the platform has handled for years.&lt;/p&gt;

&lt;p&gt;Here is why I think it earns a place in the family.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Most of the web is touch now
&lt;/h3&gt;

&lt;p&gt;Touch is no longer the secondary surface. As of Statcounter's May 2026 figures&lt;sup id="fnref1"&gt;1&lt;/sup&gt;, mobile sits at 50.29% of global page views and tablets add another 1.48%. Phones alone have edged past desktop. The majority of the web now happens on a screen you hold in one hand and drive with the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  And about 1 in 10 of those users are left-handed
&lt;/h3&gt;

&lt;p&gt;The best estimate from the largest meta-analysis of handedness&lt;sup id="fnref2"&gt;2&lt;/sup&gt;, 200 studies and over 2.3 million people, is that roughly 10% of people are left-handed. Stack that on top of a touch-majority web and you get a real population, not an edge case: a tenth of the dominant surface, using it the mirror image of how it was likely designed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apps already solve it, one at a time
&lt;/h3&gt;

&lt;p&gt;This is not hypothetical. Creative apps already ship handedness settings by hand. Procreate has a Right-hand interface preference&lt;sup id="fnref3"&gt;3&lt;/sup&gt; that flips its sidebar, because the default sits under the left hand while you paint with the right. SketchUp for iPad has a left-handed mode. Note apps tune palm rejection around which hand is resting on the glass. The mechanic differs (occlusion on a tablet, reach on a phone), but the missing signal is the same, and any of these apps could take its default straight from the OS instead of asking again.&lt;/p&gt;

&lt;p&gt;Every one of these reinvented the same toggle, because the platform exposes no shared signal. That is the argument for standardizing one: a user should set this once and have everything honour it, not hunt for the same preference in every app. It is the same reason dark mode lives at the OS level instead of in a hundred separate settings screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's a UX choice, and it goes beyond placement
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Not just flipping the menu
&lt;/h3&gt;

&lt;p&gt;To be clear, this is not "put the menu on the left." Designers already place navigation on either side freely, and both are fine. Which hand is holding the device is a different thing: a deliberate decision about what sits where, and that map depends on the hand. On a phone it is reach: a primary action belongs under the resting thumb, and a destructive action is often placed out of easy reach on purpose so it is hard to hit by accident. On a tablet it is occlusion: the dominant hand and forearm rest over the screen, so a control on the wrong side ends up under the very hand that is covering it. Without a handedness signal, that intent silently inverts for left-handers: the action you tucked away becomes the easiest one to hit, and the content you meant to keep clear sits under their hand. You never know it happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gestures and interaction, not just layout
&lt;/h3&gt;

&lt;p&gt;It also reaches past static layout. &lt;code&gt;prefers-*&lt;/code&gt; features are readable from JavaScript through &lt;code&gt;window.matchMedia&lt;/code&gt;, so handedness would drive interaction, not only styling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;leftHanded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(prefers-handedness: left)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That opens up things CSS alone cannot reach: inverting a swipe or gesture direction, choosing which edge a swipe action lives on, deciding where a context menu opens so the hand does not cover it. This is an interaction signal, not a styling toggle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The objections
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "Too niche, too much work"
&lt;/h3&gt;

&lt;p&gt;The feature itself is trivial: a single value a stylesheet or a script can read. The family already includes &lt;code&gt;prefers-reduced-motion&lt;/code&gt;, which is more involved both to define and to honour, and it shipped. Dark mode is the cleaner precedent. &lt;code&gt;prefers-color-scheme&lt;/code&gt; went from nothing to shipping across every major browser by early 2020&lt;sup id="fnref4"&gt;4&lt;/sup&gt;, fast, and it is arguably a more frivolous preference than how you physically hold the device. It is low-hanging fruit: easy to implement, and a real improvement for a substantial share of users. Hard to beat that return on effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  "It needs platform buy-in"
&lt;/h3&gt;

&lt;p&gt;True, and that is the point of proposing it as a standard. The signal would be an OS setting, the same origin as dark mode. Both &lt;code&gt;prefers-color-scheme&lt;/code&gt; and &lt;code&gt;prefers-reduced-motion&lt;/code&gt; needed operating systems to expose a setting, and both got it. Handedness only differs in that the toggle does not exist yet. The honest part: that is a heavier ask than a normal CSS feature, because it needs device makers, not just browsers. But the incentive is there. Handedness support is a real selling point on phones and tablets, and the stylus world already tracks it for palm rejection. The people who would benefit from shipping it are the same people who would ship it.&lt;/p&gt;

&lt;h3&gt;
  
  
  "It's a fingerprinting vector"
&lt;/h3&gt;

&lt;p&gt;This is the serious objection, and it was the first thing raised on the proposal. The answer is to scope it. Handedness is meaningless on desktop, where the pointer is decoupled from grip, so the query can carry a value only in touch contexts and add no entropy where it would be useless anyway. And because it is a declared preference that ships off by default, like the rest of the family, it is a single low-entropy bit the user controls, not something inferred behind their back. It can also be restricted to first-party contexts, so the page you are visiting can read it but embedded third-party scripts cannot. That closes the cross-site tracking path without an opt-out checkbox or a permission prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weigh in
&lt;/h2&gt;

&lt;p&gt;The proposal is live and open. If you have a use case, a concern, or a good argument against it, that is exactly what the thread is for: &lt;a href="https://github.com/w3c/csswg-drafts/issues/13215" rel="noopener noreferrer"&gt;csswg-drafts #13215&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you think this should exist, say so there. A thumbs-up or a short comment with your own use case is the kind of signal that helps a working group tell a real need from a passing idea.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Statcounter Global Stats, Desktop vs Mobile vs Tablet Market Share Worldwide, May 2026: &lt;a href="https://gs.statcounter.com/platform-market-share/desktop-mobile-tablet" rel="noopener noreferrer"&gt;https://gs.statcounter.com/platform-market-share/desktop-mobile-tablet&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Papadatou-Pastou et al. (2020), Human handedness: A meta-analysis, Psychological Bulletin: &lt;a href="https://doi.org/10.1037/bul0000229" rel="noopener noreferrer"&gt;https://doi.org/10.1037/bul0000229&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Procreate Handbook, Interface (Right-hand interface preference): &lt;a href="https://help.procreate.com/procreate/handbook/interface-gestures/interface" rel="noopener noreferrer"&gt;https://help.procreate.com/procreate/handbook/interface-gestures/interface&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;MDN, prefers-color-scheme (Baseline since early 2020): &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>css</category>
      <category>discuss</category>
      <category>mobile</category>
      <category>ux</category>
    </item>
    <item>
      <title>The case for compiled, typed CSS (blame AI)</title>
      <dc:creator>Stéphane LaFlèche</dc:creator>
      <pubDate>Wed, 27 May 2026 10:38:44 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/slafleche/the-case-for-compiled-typed-css-blame-ai-8m8</link>
      <guid>https://dev.clauneck.workers.dev/slafleche/the-case-for-compiled-typed-css-blame-ai-8m8</guid>
      <description>&lt;p&gt;We spent years getting TypeScript to where it is. It already checks your APIs, your components, your state. Your CSS values are still strings that nothing compiles. Why wait for a new tool when the one you have can do this today?&lt;/p&gt;

&lt;p&gt;A 2025 academic study &lt;sup&gt;[1]&lt;/sup&gt; found that 94% of LLM-generated compilation errors were type-check failures. GitHub's Octoverse report &lt;sup&gt;[2]&lt;/sup&gt; cited the same stat to explain TypeScript's rise to the most-used language on the platform. In typed languages, the compiler catches most of what AI gets wrong. CSS has no compiler.&lt;/p&gt;

&lt;p&gt;Two claims follow, and they're separable. First: CSS lacks a verification layer, and AI makes that gap expensive. Second: build-time typed styles are the fix I've landed on. TypeScript is already in the stack. Anything else is another dependency and another source of drift. You can accept the first claim and argue with the second.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI fails at CSS
&lt;/h2&gt;

&lt;p&gt;Write &lt;code&gt;var(--spacign-md)&lt;/code&gt; and nothing fails. The browser silently falls back. Write &lt;code&gt;padding: 12px&lt;/code&gt; when your design token says 16px. It renders. Ship it.&lt;/p&gt;

&lt;p&gt;AI has generated &lt;code&gt;width: fit-parent&lt;/code&gt; &lt;sup&gt;[3]&lt;/sup&gt;, a value that does not exist (the real one is &lt;code&gt;fit-content&lt;/code&gt;). It writes &lt;code&gt;padding: 12px&lt;/code&gt; when the design token is &lt;code&gt;spacing-md&lt;/code&gt; at 16px &lt;sup&gt;[4]&lt;/sup&gt;, because it doesn't check your token file, it approximates. It applies Tailwind v3 logic to v4 projects &lt;sup&gt;[5]&lt;/sup&gt;, importing deprecated packages and breaking styles. As one writer put it: "AI didn't create this problem. It scaled it." &lt;sup&gt;[4]&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Human developers hesitate when uncertain. AI does not. It generates code with the same confidence whether implementing a known pattern or hallucinating something that has never existed &lt;sup&gt;[3]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Linters like PostCSS and Stylelint catch syntax errors, not semantic ones. They verify grammar. What's missing is something that verifies meaning. And yes, AI writes better plain CSS than CSS-in-JS: more training data. But better unchecked CSS is still unchecked.&lt;/p&gt;

&lt;h2&gt;
  
  
  "AI will just get better"
&lt;/h2&gt;

&lt;p&gt;Maybe. But we still need to ship code today.&lt;/p&gt;

&lt;p&gt;Web-Bench &lt;sup&gt;[6]&lt;/sup&gt; (ByteDance, 2025), a benchmark of real-world web development tasks, showed the then-leading model (Claude 3.7 Sonnet) at 25.1% first-pass accuracy. GitClear's 2025 analysis &lt;sup&gt;[7]&lt;/sup&gt; of 211 million changed lines (2020-2024) found copy-pasted code rose from 8.3% to 12.3% while refactoring collapsed from 25% to under 10%. The benchmark scores go up. The real-world quality metrics go the other direction.&lt;/p&gt;

&lt;p&gt;Simon Willison argued &lt;sup&gt;[8]&lt;/sup&gt; that code hallucinations are the least dangerous kind, because compilers catch them. He's right, except CSS has no compiler. His optimism has a CSS-shaped hole.&lt;/p&gt;

&lt;p&gt;"AI will get better" is a bet. A type system is a guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Tailwind stands
&lt;/h2&gt;

&lt;p&gt;Tailwind is dominant for new projects at ~12 million weekly downloads &lt;sup&gt;[9]&lt;/sup&gt;. Its LSP flags invalid classes, ESLint plugins enforce scale usage.&lt;/p&gt;

&lt;p&gt;But Tailwind v4 moved configuration from JavaScript (&lt;code&gt;tailwind.config.ts&lt;/code&gt;) to native CSS (&lt;code&gt;@theme {}&lt;/code&gt; blocks). Simpler, yes. But the default path now authors tokens directly as CSS strings, outside any type system. You can still generate &lt;code&gt;@theme&lt;/code&gt; from a typed source, that is exactly the move I'll argue for below, but nothing in v4 pushes you there. The path of least resistance lost its types. If AI writes &lt;code&gt;@theme { --color-brand: #3b82f6 }&lt;/code&gt; when the designer specified &lt;code&gt;#2563eb&lt;/code&gt;, the CSS build passes fine. Both are valid hex. The problem isn't syntax, it's that there's no contract between your styles and your TypeScript components. Meanwhile, Tailwind Labs itself is under AI pressure: revenue down 80%, three engineers laid off &lt;sup&gt;[10]&lt;/sup&gt;. The framework thrives while the company scrambles. AI is reshaping even the most popular CSS framework's ecosystem in ways nobody planned for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build-time typed styles
&lt;/h2&gt;

&lt;p&gt;Not runtime CSS-in-JS. styled-components, Emotion, runtime style injection: that's dead for good reasons &lt;sup&gt;[11]&lt;/sup&gt;, and it should stay dead.&lt;/p&gt;

&lt;p&gt;This is something different. Values live in TypeScript, get checked by the compiler, and emit static CSS. Zero runtime. The output is plain CSS. The verification happens before it reaches the browser. The AI has to satisfy the TypeScript spec to produce output at all.&lt;/p&gt;

&lt;p&gt;You can write custom validation on top: assert that a color pair meets contrast requirements, that a measurement doesn't exceed a bound. If your values already live in TypeScript, why maintain a parallel set in Sass?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why maintain two sets of values?
&lt;/h2&gt;

&lt;p&gt;TypeScript is the most-used language on GitHub &lt;sup&gt;[2]&lt;/sup&gt; as of August 2025. Your spacing scale, your breakpoints, your theme config: they're already in &lt;code&gt;.ts&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;The question isn't "should I put CSS in JavaScript?" It's "should I type the values that are already there?"&lt;/p&gt;

&lt;p&gt;If you maintain CSS variables separately from your TypeScript tokens, you have two sets of values to keep in sync. That's where drift creeps in. Define your tokens once in TS, output to CSS variables, a Tailwind theme, responsive helpers, whatever your project needs. One source, no sync problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS variables are great. They're not a contract.
&lt;/h2&gt;

&lt;p&gt;I used to dismiss CSS variables. Then I found a real use for them: responsive values from Figma design tokens that replaced a React context for window sizes. Huge simplification. I'm strict about keeping usage limited to where they genuinely earn it.&lt;/p&gt;

&lt;p&gt;But CSS variables have specific weaknesses with AI. &lt;code&gt;var(--spacign-md)&lt;/code&gt; is valid syntax that silently fails. When a variable is set at the root, overridden in a layout component, overridden again in a card, and consumed in a button several layers deep, AI has no way to reason about which level set it. &lt;code&gt;@property&lt;/code&gt; adds native type checking, but it validates at render time, not build time. The wrong value still ships. Going the other direction, reading CSS vars in JavaScript with &lt;code&gt;getComputedStyle&lt;/code&gt; is runtime string parsing. No type safety going in, no type safety coming out.&lt;/p&gt;

&lt;p&gt;And native CSS features always lag behind in browser support. When your values live in TypeScript and compile to static CSS, you control the output. Browser compatibility becomes a build concern, not an architecture concern.&lt;/p&gt;

&lt;p&gt;You can still output CSS variables from your typed tokens. That's not a tradeoff, it's the point. One typed source, visible to both CSS at runtime and TypeScript at build time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification, not authoring
&lt;/h2&gt;

&lt;p&gt;The argument is about verification, not authoring. AI made authoring effortless: it produces CSS as fast as you can ask for it. Producing correct CSS is a different problem, and nobody solved the checking part. "Design systems solve the vocabulary problem. They do not solve the verification problem." &lt;sup&gt;[4]&lt;/sup&gt; Types are the verification layer.&lt;/p&gt;

&lt;p&gt;The strongest counter is "just give AI better context." It's partially right. Sachin Patel's team &lt;sup&gt;[12]&lt;/sup&gt; aligned Figma tokens with CSS variables and got reliable output. I use context engineering myself: I've written &lt;a href="https://lafleche.dev/" rel="noopener noreferrer"&gt;Claude Code skills and Cursor rules&lt;/a&gt; to keep AI aligned with codebase standards. These approaches work.&lt;/p&gt;

&lt;p&gt;But context is a conversation. The next developer, or the next AI session without the rules loaded, can ignore it. Types are a contract. You can't compile past them. Or as a Builder.io blog post &lt;sup&gt;[13]&lt;/sup&gt; framed it: "Without types, the AI is guessing. With types, it's reading a spec."&lt;/p&gt;

&lt;p&gt;Off-scale is fine, as long as it's visible. An explicit &lt;a href="https://www.npmjs.com/package/css-calipers" rel="noopener noreferrer"&gt;&lt;code&gt;m(17)&lt;/code&gt;&lt;/a&gt; is different from a magic &lt;code&gt;p-[17px]&lt;/code&gt; buried in a Tailwind class string. And because it's structurally distinct, you can lint for it. A CI rule that flags off-scale density is trivial when escape hatches have their own syntax. When off-scale and on-scale look identical, that rule is impossible.&lt;/p&gt;

&lt;p&gt;In practice, visual regression testing, human review, checking layouts across viewports: that work exists whether you use typed styles or not. What typed styles change is what you spend your review time on. Without them, reviewers catch both mechanical errors and visual ones. With them, the mechanical layer is handled before the code reaches review. What's left is the judgment work. There's a taxonomist quality to writing good CSS: classifying values, deciding where things belong. That part stays human. Types clear the noise so you can focus on it.&lt;/p&gt;

&lt;p&gt;I've been building the typed-measurement piece of this with &lt;a href="https://www.npmjs.com/package/css-calipers" rel="noopener noreferrer"&gt;CSS-Calipers&lt;/a&gt;. That layer is solid: compile-time unit safety, immutable values, off-scale values that are explicit rather than invisible. The broader framework around it is not solved, and I'm not claiming it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use what your project needs
&lt;/h2&gt;

&lt;p&gt;This is not an all-or-nothing proposition. Nobody is asking you to rewrite your project in CSS-in-JS. Maybe you type your entire spacing scale. Maybe you type one mission-critical token that keeps breaking. Maybe you don't need any of this. You know your project best.&lt;/p&gt;

&lt;p&gt;I'm resistant to change myself. I had a visceral reaction to CSS-in-JS when I first encountered it. Had the same reaction to CSS variables. Both times, a specific use case changed my mind. Not hype. A real problem the tool solved better than the alternatives.&lt;/p&gt;

&lt;p&gt;The industry was right to leave runtime CSS-in-JS. Native CSS is more powerful than ever. Tailwind dominates for real reasons. The point isn't to replace any of that. It's that a verification layer should exist for the parts that matter, and you should know the tradeoff when you skip it.&lt;/p&gt;

&lt;p&gt;The output is still the full CSS spec. Nothing is restricted. What changes is how many guardrails you put between your values and that output. One project might need strict token enforcement across every component. Another might need a single typed measurement in a critical layout. The point is you choose the constraint level, not the framework.&lt;/p&gt;

&lt;p&gt;CSS should fail silently in the browser. That's a feature. It should fail loudly in the build. That's what's missing. I wrote about &lt;a href="https://dev.clauneck.workers.dev/slafleche/we-still-dont-have-proper-css-frameworks-18dk"&gt;what a real CSS framework could look like&lt;/a&gt; if you want the longer version of that argument.&lt;/p&gt;




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

&lt;p&gt;[1] Mündler et al., "Type-Constrained Code Generation with Language Models" (2025) - &lt;a href="https://arxiv.org/abs/2504.09246" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2504.09246&lt;/a&gt;&lt;br&gt;
[2] GitHub Octoverse 2025 - &lt;a href="https://github.blog/news-insights/octoverse/octoverse-a-new-developer-joins-github-every-second-as-ai-leads-typescript-to-1/" rel="noopener noreferrer"&gt;https://github.blog/news-insights/octoverse/octoverse-a-new-developer-joins-github-every-second-as-ai-leads-typescript-to-1/&lt;/a&gt;&lt;br&gt;
[3] Shahid Pattani, "Design-to-Code AI Is Not Magic" - &lt;a href="https://medium.com/aimonks/design-to-code-ai-is-not-magic-heres-why-it-fails-sometimes-7740051f3580" rel="noopener noreferrer"&gt;https://medium.com/aimonks/design-to-code-ai-is-not-magic-heres-why-it-fails-sometimes-7740051f3580&lt;/a&gt;&lt;br&gt;
[4] Emilia BiblioKit, "3 Design System Bugs That Survive Every Code Review" - &lt;a href="https://medium.com/design-bootcamp/3-design-system-bugs-that-survive-every-code-review-and-why-ai-makes-them-worse-55272372ee6a" rel="noopener noreferrer"&gt;https://medium.com/design-bootcamp/3-design-system-bugs-that-survive-every-code-review-and-why-ai-makes-them-worse-55272372ee6a&lt;/a&gt;&lt;br&gt;
[5] Prathit, "AI Models Still Can't Configure Tailwind Correctly" - &lt;a href="https://prathit.vercel.app/blog/ai-models-still-can%27t-configure-tailwind" rel="noopener noreferrer"&gt;https://prathit.vercel.app/blog/ai-models-still-can%27t-configure-tailwind&lt;/a&gt;&lt;br&gt;
[6] Web-Bench (ByteDance, 2025) - &lt;a href="https://arxiv.org/html/2505.07473v1" rel="noopener noreferrer"&gt;https://arxiv.org/html/2505.07473v1&lt;/a&gt;&lt;br&gt;
[7] GitClear, "AI Code Quality 2025" - &lt;a href="https://www.gitclear.com/ai_assistant_code_quality_2025_research" rel="noopener noreferrer"&gt;https://www.gitclear.com/ai_assistant_code_quality_2025_research&lt;/a&gt;&lt;br&gt;
[8] Simon Willison, "Hallucinations in code are the least dangerous form of LLM mistakes" - &lt;a href="https://simonwillison.net/2025/Mar/2/hallucinations-in-code/" rel="noopener noreferrer"&gt;https://simonwillison.net/2025/Mar/2/hallucinations-in-code/&lt;/a&gt;&lt;br&gt;
[9] PkgPulse, "The State of CSS-in-JS in 2026" - &lt;a href="https://www.pkgpulse.com/guides/state-of-css-in-js-2026" rel="noopener noreferrer"&gt;https://www.pkgpulse.com/guides/state-of-css-in-js-2026&lt;/a&gt;&lt;br&gt;
[10] devclass, "Tailwind Labs layoffs" - &lt;a href="https://devclass.com/2026/01/08/tailwind-labs-lays-off-75-percent-of-its-engineers-thanks-to-brutal-impact-of-ai/" rel="noopener noreferrer"&gt;https://devclass.com/2026/01/08/tailwind-labs-lays-off-75-percent-of-its-engineers-thanks-to-brutal-impact-of-ai/&lt;/a&gt;&lt;br&gt;
[11] React 18 Working Group Discussion #110 (Sebastian Markbage) - &lt;a href="https://github.com/reactwg/react-18/discussions/110" rel="noopener noreferrer"&gt;https://github.com/reactwg/react-18/discussions/110&lt;/a&gt;&lt;br&gt;
[12] Sachin Patel, "Cursor Isn't the Problem. Your Design Tokens Are." - &lt;a href="https://medium.com/@sachin88/how-we-fixed-design-tokens-to-make-cursor-generate-reliable-ui-code-74d699e72e38" rel="noopener noreferrer"&gt;https://medium.com/@sachin88/how-we-fixed-design-tokens-to-make-cursor-generate-reliable-ui-code-74d699e72e38&lt;/a&gt;&lt;br&gt;
[13] Builder.io, "TypeScript vs JavaScript: Why AI Coding Tools Work Better with TypeScript" - &lt;a href="https://www.builder.io/blog/typescript-vs-javascript" rel="noopener noreferrer"&gt;https://www.builder.io/blog/typescript-vs-javascript&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>css</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>We still don't have proper CSS frameworks</title>
      <dc:creator>Stéphane LaFlèche</dc:creator>
      <pubDate>Fri, 22 May 2026 12:58:55 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/slafleche/we-still-dont-have-proper-css-frameworks-18dk</link>
      <guid>https://dev.clauneck.workers.dev/slafleche/we-still-dont-have-proper-css-frameworks-18dk</guid>
      <description>&lt;p&gt;We have utility libraries, class-naming conventions, and methodologies dressed up in framework branding. What we don't have is a CSS layer that behaves like a framework does in any other ecosystem: typed input, defined output, a contract a compiler can actually enforce.&lt;/p&gt;

&lt;p&gt;This is the long version of a complaint I've been making in private for years. It also names what I think a real CSS framework would do, and shows the working sketch I've been building in that direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailwind is useful, but it's not really a framework
&lt;/h2&gt;

&lt;p&gt;The current heavyweight is Tailwind, with &lt;a href="https://www.npmjs.com/package/tailwindcss" rel="noopener noreferrer"&gt;over 400 million npm downloads a month as of May 2026&lt;/a&gt;, so let's start with what it actually is. Strip the build step away and the utility-first methodology isn't new. Object-Oriented CSS, popularized by &lt;a href="https://github.com/stubbornella/oocss" rel="noopener noreferrer"&gt;Nicole Sullivan&lt;/a&gt; in 2008, was already making the same case: small, single-purpose classes you compose at the markup layer.&lt;/p&gt;

&lt;p&gt;Before Tailwind existed, plenty of teams generated their own utility scales using SCSS loops. You can rebuild a meaningful slice of Tailwind's spacing utilities in a few lines of SCSS that would've worked in 2012:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Define the design system tokens&lt;/span&gt;
&lt;span class="nv"&gt;$spacing-map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.25rem&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;
  &lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.5rem&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;
  &lt;span class="s2"&gt;"4"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;    &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;
  &lt;span class="s2"&gt;"8"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;     &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Loop to generate padding and margin utilities&lt;/span&gt;
&lt;span class="k"&gt;@each&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$spacing-map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.p-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.pt-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.m-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.mt-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Compiles to:&lt;/span&gt;
&lt;span class="c1"&gt;// .p-1  { padding: 0.25rem; }&lt;/span&gt;
&lt;span class="c1"&gt;// .pt-1 { padding-top: 0.25rem; } ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What Tailwind genuinely adds is the build step. The JIT compiler scans your codebase and emits only the utility classes you actually use, with arbitrary-value support (&lt;code&gt;p-[17px]&lt;/code&gt;) for one-offs. That's a real ergonomic win. Bundle sizes shrank and authoring speed went up.&lt;/p&gt;

&lt;p&gt;The downstream cost is harder to talk about, because it shows up in maintenance, not authoring. In a Tailwind codebase, when something looks off on a page and you go searching for what styled it, you often can't. The class you'd search for (&lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;text-center&lt;/code&gt;, &lt;code&gt;p-4&lt;/code&gt;) appears a thousand times across the project. Layout decisions become string fragments scattered across every component file, not declarations you can grep, jump-to, or rename in one place. The tools that normally help you navigate a codebase mostly don't.&lt;/p&gt;

&lt;p&gt;What we have, then: utility classes that were doable in SCSS in 2012, a build step that makes them ergonomic, and a search-and-rename problem that gets worse the bigger the project gets. Is that really what we want from a framework?&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this breaks
&lt;/h2&gt;

&lt;p&gt;AI didn't break Tailwind, even though &lt;a href="https://lobste.rs/s/dzcugr/tailwind_labs_loses_80_revenue_75" rel="noopener noreferrer"&gt;January's layoffs at Tailwind Labs&lt;/a&gt; (the "AI killed Tailwind" framing) read that way in headlines. It exposed a contract Tailwind never had.&lt;/p&gt;

&lt;p&gt;For most of the Tailwind era, humans wrote the class names. There was an implicit social contract: you stayed mostly on the spacing scale, you didn't use arbitrary values without a reason, you noticed when a string was getting absurd and broke it into a component. The framework didn't enforce any of that. It just trusted you.&lt;/p&gt;

&lt;p&gt;The trust assumption is breaking. AI now writes a meaningful share of production CSS, and AI has no implicit social contract. It generates plausible-looking utility strings that pass a glance review and then break across a viewport, or duplicate themselves, or quietly drift off the scale. The class attribute runs to thirty utilities, half of them redundant (&lt;code&gt;p-4 pt-4 pb-4 px-4 mt-2 mb-2&lt;/code&gt; for what should have been &lt;code&gt;p-4 my-2&lt;/code&gt;), and nothing in the stack catches the drift. Reviewing the output by hand becomes the bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The arbitrary-value escape hatch.&lt;/strong&gt; &lt;code&gt;p-[17px]&lt;/code&gt; was tolerable when a senior dev would push back in review. Now it's a default behavior of the code generator, and there's no contract for anyone to point at. "Use the scale" is a vibe, not a rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The systematic-looking string.&lt;/strong&gt; &lt;code&gt;p-[var(--spacing-3)]&lt;/code&gt; looks systematic. There's a token name in there, the convention seems followed, reviewers wave it through. But the framework's scanning step doesn't actually understand it. The var lives inside a string fragment, not in the helper API. You can typo the variable name and ship it. Nothing catches that. Hardcoding is honest dishonesty. &lt;strong&gt;Token-wrapped strings are dishonest dishonesty.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tailwind users will point to the fixes: &lt;code&gt;@apply&lt;/code&gt; and component extraction for the grep problem, ESLint plugins and a theme-only config for the off-scale problem. These help, but they are external constraints layered on top, not a contract the type system enforces. A linter you can disable per line is a convention with tooling, not a compiler that refuses to emit invalid output. That distinction is the whole argument.&lt;/p&gt;

&lt;p&gt;The fix isn't to ban AI from CSS, or to ban arbitrary values. The fix is to give the codebase a contract the AI can actually run inside: typed inputs that the build can verify, helpers that emit valid CSS, off-scale values visible rather than indistinguishable from on-scale ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a framework should mean for CSS
&lt;/h2&gt;

&lt;p&gt;In any other ecosystem, a framework gives you a contract. You hand it inputs in a defined shape, it does the work inside, you get a defined output. Rails takes route definitions and gives you a request lifecycle. React takes components and props and gives you a reconciled tree. The framework owns the implementation; you own the configuration and composition.&lt;/p&gt;

&lt;p&gt;A CSS framework, by that standard, would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Take typed design tokens in and emit valid CSS out.&lt;/strong&gt; Every value (length, color, string) becomes data the build can see, not strings to scan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail at compile time on mismatched units.&lt;/strong&gt; Off-scale values stay possible, but visibly off-scale, not laundered into the same syntax as everything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cover the full CSS spec, not a subset.&lt;/strong&gt; New properties land in browsers all the time (&lt;code&gt;@container&lt;/code&gt;, &lt;code&gt;view-timeline&lt;/code&gt;, &lt;code&gt;field-sizing&lt;/code&gt;); a framework that gates which features you can use restricts the work to whatever its authors had time to model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stay opinionated at the edges, loose in the middle.&lt;/strong&gt; Strict on typed input and valid emission. Composition, file organization, helper depth: all yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offer opt-in helpers, never mandatory ones.&lt;/strong&gt; A team could write a &lt;code&gt;borders&lt;/code&gt; helper that groups width, color, and radius the way designers think about them; another team could skip that layer entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That shape (strict edges, loose middle, full spec) is the &lt;strong&gt;inverse&lt;/strong&gt; of how most CSS frameworks work. Tailwind owns the middle (every class is theirs); the edges stay loose (any string can land in a class attribute). CSS-in-JS libraries own a template syntax in the middle, with values inside the template still untyped. Typed CSS-in-JS libraries like vanilla-extract type the output but not the input. A framework with strict edges in both directions, and a loose middle, is rare in CSS, but it's how typed systems work everywhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  A working sketch: CSS-Calipers
&lt;/h2&gt;

&lt;p&gt;This is the principle in code. &lt;a href="https://www.npmjs.com/package/css-calipers" rel="noopener noreferrer"&gt;CSS-Calipers&lt;/a&gt; is a small TypeScript library I wrote last year. The ideas behind it go back to my Vanilla Forums days. It covers the measurement-and-math piece of the framework I keep wanting. &lt;strong&gt;Tokens in, typed CSS out.&lt;/strong&gt; The helpers are the contract. Best to use at build time, but occasional runtime possible.&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%2Fyjjlw8agszlu20005ien.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%2Fyjjlw8agszlu20005ien.png" alt="CSS-Calipers code example showing the m() constructor, typed math via .add() and .multiply(), a deliberate unit-mismatch error (px + deg), and the final .css() emission step" width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The measurements stay opaque through composition. Nothing emits a string until you call &lt;code&gt;.css()&lt;/code&gt; at the boundary. The math is checked at every step, not just at the end.&lt;/p&gt;

&lt;p&gt;Mismatched units fail fast. As the snippet above shows, &lt;code&gt;paddingBase.add(rotation)&lt;/code&gt; throws a clear error with px vs. deg named in the message. You don't find out in production that you added pixels to degrees.&lt;/p&gt;

&lt;p&gt;The measurement core is the foundation. On top of it I've built a helpers layer in my &lt;a href="https://lafleche.dev/" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;: borders, paddings, margins, shadows. Each helper consumes measurements and emits typed style objects. Here's the borders helper in actual use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use defaults from the token layer&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardBase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;borders&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Override specific values inline&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardEmphasis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;borders&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;m&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;south&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;m&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// compass-style: south = bottom&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Or pass a full token config&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardThemed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;borders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardBorders&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three calling shapes, all valid: defaults, inline overrides, full token configs. The third one is where this starts to feel like a framework. Imagine you import the token from a tokens file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tokens/cardBorders.ts — today&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardBorders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;m&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/Card.styles.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cardBorders&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/tokens/cardBorders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardStyles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;borders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cardBorders&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now design wants a thicker accent top and rounded bottom corners. You edit only the tokens file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tokens/cardBorders.ts — after the design tweak&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardBorders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;m&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;m&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accent&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;south&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;m&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component file is &lt;strong&gt;unchanged&lt;/strong&gt;. The helper accepts the new token shape and emits more CSS; if you remove keys later, it emits less. Adding a &lt;code&gt;borderTopWidth&lt;/code&gt; to the design means a new key in the token object, not a new class in your markup. &lt;strong&gt;The call site is invariant; the design tokens are where the change happens.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spacing helpers work the same way: &lt;code&gt;paddings(m(16))&lt;/code&gt; for uniform, &lt;code&gt;paddings({ vertical: m(8), horizontal: m(16) })&lt;/code&gt; for axis-intent, &lt;code&gt;paddings(theme.cardPadding)&lt;/code&gt; to delegate the shape entirely to tokens. Same calling pattern across the layer.&lt;/p&gt;

&lt;p&gt;Helper names are deliberately the plural of the CSS property they emit: &lt;code&gt;paddings&lt;/code&gt;, &lt;code&gt;borders&lt;/code&gt;, &lt;code&gt;margins&lt;/code&gt;, &lt;code&gt;shadows&lt;/code&gt;. Grep-able, visually distinct from a raw &lt;code&gt;padding&lt;/code&gt; property in a style object, consistent across the layer.&lt;/p&gt;

&lt;p&gt;The same pattern extends through the rest of the helpers layer: &lt;code&gt;@supports&lt;/code&gt; fallbacks as typed objects, accessibility variants typed against the actual CSS feature set, color manipulation through typed methods, typed media-query factories, typed &lt;code&gt;clamp&lt;/code&gt; and other math primitives. CSS values get type-checking in places they almost never do in normal CSS-in-JS work.&lt;/p&gt;

&lt;p&gt;You can opt in or out at any boundary. Use measurements for high-stakes spacing math; write raw Tailwind classes for layout primitives where utility-first wins; drop into a CSS Module or styled-components for component-scoped work. CSS-Calipers itself is compiler-agnostic: it produces typed CSS strings that work with any of those, or with bare style objects, or wherever else you want valid CSS to land. The library doesn't try to own the middle. The contract is the function signatures, not the entire styling story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Missing Framework Layer
&lt;/h2&gt;

&lt;p&gt;We still don't have proper CSS frameworks. The pieces exist in different places: typed CSS-in-TS, design tokens, scanning compilers, agent-aware tooling. None of them are wired together into a single thing that takes typed input from your token layer, emits valid CSS to the spec, and stays out of the way in between.&lt;/p&gt;

&lt;p&gt;Getting there isn't a single project. It's a shift in how the category is defined: what we call "frameworks" today are vocabularies and methodologies. What a real one would be is a contract.&lt;/p&gt;

&lt;p&gt;It's hard. The CSS spec is complex: edge cases, shorthand properties, overrides, the cascade itself. A real framework can't simplify those away by giving you a subset; it has to absorb the mess and stay faithful to it. CSS is the spec, and the spec is messy.&lt;/p&gt;

&lt;p&gt;A real CSS framework is opinionated at the edges where types matter, loose in the middle where you compose, and covers the whole CSS spec rather than a subset. We don't have one yet. We could.&lt;/p&gt;

</description>
      <category>development</category>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
