<?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.to/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.to/slafleche</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/slafleche"/>
    <language>en</language>
    <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.to/slafleche/most-of-the-web-is-touch-we-still-dont-save-handedness-5ea0</link>
      <guid>https://dev.to/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.to/slafleche/the-case-for-compiled-typed-css-blame-ai-8m8</link>
      <guid>https://dev.to/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.to/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.to/slafleche/we-still-dont-have-proper-css-frameworks-18dk</link>
      <guid>https://dev.to/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>
