<?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: Matthew Dean</title>
    <description>The latest articles on DEV Community by Matthew Dean (@matthewdean).</description>
    <link>https://dev.to/matthewdean</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%2F660523%2F265ba873-238c-45bd-b160-b1f0ec0e404c.jpeg</url>
      <title>DEV Community: Matthew Dean</title>
      <link>https://dev.to/matthewdean</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matthewdean"/>
    <language>en</language>
    <item>
      <title>Web components failed. We can fix them.</title>
      <dc:creator>Matthew Dean</dc:creator>
      <pubDate>Wed, 07 Oct 2026 12:48:03 +0000</pubDate>
      <link>https://dev.to/matthewdean/web-components-failed-we-can-fix-them-1o37</link>
      <guid>https://dev.to/matthewdean/web-components-failed-we-can-fix-them-1o37</guid>
      <description>&lt;p&gt;In 2004, the W3C was betting HTML's future on XHTML 2: a stricter, XML-based language that wasn't backward compatible with the web people had actually built. Apple, Mozilla, and Opera disagreed, and formed the WHATWG to keep evolving HTML instead. That work became HTML5.&lt;/p&gt;

&lt;p&gt;And for a while, it was thrilling. HTML5 wasn't just a spec; it was a movement. We got &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; (goodbye, Flash), &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;, new form input types, semantic elements like &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, and a parsing algorithm that finally made every browser read broken markup the same way. There was a logo. HTML had a &lt;em&gt;logo&lt;/em&gt;. There were whole conferences about it. The message was clear: HTML was alive again, and it was going to grow up into an application platform.&lt;/p&gt;

&lt;p&gt;HTML5 became a W3C Recommendation in 2014. And then the energy mostly went somewhere else.&lt;/p&gt;

&lt;p&gt;Look at what happened to the other two languages over the next decade:&lt;/p&gt;

&lt;p&gt;JavaScript went from a language people apologized for to one that ships a new edition every year. ES2015 alone gave us classes, modules, promises, arrow functions, and destructuring. TC39 built a stage process so public that you can watch a feature grow from a napkin sketch into a shipped standard.&lt;/p&gt;

&lt;p&gt;CSS may have had the best decade any language has ever had. Grid. Custom properties. Container queries. &lt;code&gt;:has()&lt;/code&gt;, the parent selector we were told for years was impossible. Native nesting. Cascade layers. &lt;code&gt;@scope&lt;/code&gt;. Describe 2024's CSS to a developer in 2014, and they'd assume you were describing a preprocessor. (I should know. I've been maintaining Less.js for over 10 years.)&lt;/p&gt;

&lt;p&gt;And HTML? HTML got crumbs.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element that took until 2022 to work in every major browser. A &lt;code&gt;popover&lt;/code&gt; attribute. A &lt;code&gt;&amp;lt;search&amp;gt;&lt;/code&gt; element for marking up search forms. Invoker commands, so a button can open a dialog without a line of JavaScript. Every one of these is welcome. But every one of them is a &lt;em&gt;widget&lt;/em&gt;: a single, pre-built behavior handed down by the spec. None of them is a &lt;em&gt;primitive&lt;/em&gt;: something you can build your own things out of.&lt;/p&gt;

&lt;p&gt;HTML is now a "Living Standard," which is a lovely name. But living isn't the same as growing. JavaScript and CSS got new &lt;em&gt;capabilities&lt;/em&gt;; HTML got new &lt;em&gt;nouns&lt;/em&gt;. A decade on, markup still has no way to say "repeat this," "show this only if," "this value comes from over there," or "this is a component, and here's its contract." For any of that, you leave HTML and go write JavaScript.&lt;/p&gt;

&lt;p&gt;Web Components were supposed to fill that gap. And to be fair, they shipped. They work. GitHub, YouTube, and Salesforce all use custom elements in production, and some of them use them well.&lt;/p&gt;

&lt;p&gt;But they were meant to be more than that. They were meant to be &lt;em&gt;the web's component model&lt;/em&gt;: write a component once, use it anywhere, no framework required. More than a decade later, ask HTML for a reusable, typed component with conditionals, loops, and reactive state, and the answer is the same as it was in 2013: "install a framework." That's the sense in which they failed.&lt;/p&gt;

&lt;p&gt;Some context: I've been a professional web developer for over 20 years. I've written plenty of custom elements. I've written a lot more React, Vue, and Svelte components. And I've watched a lot of teams (including mine) try to build a "framework-agnostic" component library on Web Components and quietly give up.&lt;/p&gt;

&lt;p&gt;Here's how that played out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The promise, and the retreat
&lt;/h2&gt;

&lt;p&gt;Web Components were first pitched in 2011, and Google's Polymer library became their flagship: the platform as the framework. Polymer eventually went into maintenance mode in 2018 and was &lt;a href="https://lit.dev/articles/lit-for-polymer-users/" rel="noopener noreferrer"&gt;superseded by Lit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lit is genuinely good. But the transition tells you something. The people &lt;em&gt;most invested&lt;/em&gt; in Web Components concluded that the raw custom-element API needed a rendering library on top of it. That was a reasonable call; it was also an admission that the platform alone wasn't enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework authors looked elsewhere
&lt;/h2&gt;

&lt;p&gt;Meanwhile, the people building component models for a living were reaching their own conclusions. In 2019, Rich Harris wrote &lt;a href="https://dev.to/richharris/why-i-don-t-use-web-components-2cia"&gt;"Why I don't use web components"&lt;/a&gt;. If you haven't read it, the short version:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Shadow DOM pushes your CSS into JavaScript.&lt;/li&gt;
&lt;li&gt;The property/attribute split forces boilerplate.&lt;/li&gt;
&lt;li&gt;Slotted content renders eagerly, whether you want it or not.&lt;/li&gt;
&lt;li&gt;There's one global registry, so naming collisions are your problem.&lt;/li&gt;
&lt;li&gt;The DOM is, in his words, "an awkward interface for building interactive applications."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can debate any one of those points. The outcome is harder to debate: React, Vue, Svelte, and Solid each built their own component model rather than building on this one. The people who design component models for a living chose not to use the platform's.&lt;/p&gt;

&lt;h2&gt;
  
  
  A button you can't extend
&lt;/h2&gt;

&lt;p&gt;Even if you skip the frameworks and go all in on the platform, you hit walls quickly. The one that surprises people most: Say you want a button with some extra behavior. The spec has an answer for this! It's called a customized built-in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;is=&lt;/span&gt;&lt;span class="s"&gt;"fancy-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;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 javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FancyButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLButtonElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// extra behavior here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fancy-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FancyButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! It's still a real &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. It submits forms, it's focusable, it has the right role, Enter and Space work. Ship it!&lt;/p&gt;

&lt;p&gt;Welp, not so fast. That works in Chromium and Firefox. WebKit's &lt;a href="https://github.com/WebKit/standards-positions/issues/97" rel="noopener noreferrer"&gt;formal standards position&lt;/a&gt; is to &lt;em&gt;oppose&lt;/em&gt; customized built-ins and not implement them. So in Safari, you get a plain button that ignores your class entirely.&lt;/p&gt;

&lt;p&gt;"Fine," you say, "I'll use an autonomous custom element."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;fancy-button&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/fancy-button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works. But now you have an element that doesn't submit forms, isn't focusable, has no button role, and doesn't respond to the keyboard. So you add &lt;code&gt;tabindex&lt;/code&gt;, &lt;code&gt;role="button"&lt;/code&gt;, a keydown handler for Enter and Space, &lt;code&gt;ElementInternals&lt;/code&gt; for form association, disabled-state handling...&lt;/p&gt;

&lt;p&gt;At this point you've reimplemented &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, and you'll be maintaining that reimplementation for as long as the component lives. The browser already had a perfectly good one; the platform just didn't give you a way to use it.&lt;/p&gt;

&lt;p&gt;The only spec-sanctioned way to reuse native semantics has no cross-browser path. That's not an edge case; that's the core use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ten years to catch up
&lt;/h2&gt;

&lt;p&gt;Interop was the other half of the promise, and for about a decade, the relationship status between React and custom elements was "it's complicated." React couldn't pass an object or array to one as a property, and couldn't listen to its custom events without manual &lt;code&gt;ref&lt;/code&gt; wiring. The gap was tracked publicly on &lt;a href="https://custom-elements-everywhere.com/" rel="noopener noreferrer"&gt;Custom Elements Everywhere&lt;/a&gt; for years.&lt;/p&gt;

&lt;p&gt;Full support landed in &lt;a href="https://react.dev/blog/2024/12/05/react-19" rel="noopener noreferrer"&gt;React 19, in December 2024&lt;/a&gt;. That's more than ten years after custom elements were proposed. "Use it anywhere" didn't include the most popular UI library for most of that time.&lt;/p&gt;

&lt;p&gt;The platform's own basics took nearly as long:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form participation via &lt;code&gt;ElementInternals&lt;/code&gt; only became stable cross-browser with Safari 16.4, in &lt;a href="https://webkit.org/blog/13711/elementinternals-and-form-associated-custom-elements/" rel="noopener noreferrer"&gt;March 2023&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Declarative Shadow DOM (i.e. server rendering a shadow root without JavaScript) reached Safari in 16.4 and Firefox in 123, &lt;a href="https://webkit.org/blog/13851/declarative-shadow-dom/" rel="noopener noreferrer"&gt;in 2024&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Forms and SSR. The two things every real app needs. Roughly a decade to get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what actually went wrong?
&lt;/h2&gt;

&lt;p&gt;Honestly, I don't think it matters whether Web Components failed by design or just because they were never fully leveraged. If you're picking a component model today, the outcome is the same: in their shipped form, they didn't become the web's component layer.&lt;/p&gt;

&lt;p&gt;But I &lt;em&gt;do&lt;/em&gt; think there's a single root cause, and it's this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web Components solved packaging. They never solved authoring.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They gave us a place to &lt;em&gt;put&lt;/em&gt; a component (a tag name, a lifecycle, a shadow root). They never gave us a way to &lt;em&gt;write&lt;/em&gt; one in markup. Look at what an app author reaches for every day, and what HTML gives you natively:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you need&lt;/th&gt;
&lt;th&gt;What HTML gives you&lt;/th&gt;
&lt;th&gt;What everyone does instead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A reusable component with a typed API&lt;/td&gt;
&lt;td&gt;Custom Elements (untyped, imperative, string-only attributes)&lt;/td&gt;
&lt;td&gt;React / Vue / Svelte components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conditional and repeated markup&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;v-if&lt;/code&gt;, &lt;code&gt;{#each}&lt;/code&gt;, &lt;code&gt;.map()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reactive state&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;hooks, refs, runes, signals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bind an input to state&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;controlled inputs, &lt;code&gt;v-model&lt;/code&gt;, &lt;code&gt;bind:value&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Declare a data dependency&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;fetch-in-effect, loaders, resources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every cell in that last column solves the same problem with different syntax: a loop is &lt;code&gt;v-for&lt;/code&gt; in Vue, &lt;code&gt;{#each}&lt;/code&gt; in Svelte, and &lt;code&gt;.map()&lt;/code&gt; in React. Same idea, three syntaxes, and none of them carry over. That's why your component library gets rewritten every time your company changes frameworks. Web Components didn't fix that, because they never tried to.&lt;/p&gt;

&lt;p&gt;This is the HTML5 story again. JavaScript and CSS each got a sustained, decade-long investment in the &lt;em&gt;language itself&lt;/em&gt;. HTML's component model, by contrast, was built from the JavaScript side: a class you register, with HTML as the place it gets mounted. The markup never learned anything new.&lt;/p&gt;

&lt;p&gt;Shadow DOM added a subtler problem. Most of us wanted &lt;em&gt;scoping&lt;/em&gt;: my component's styles shouldn't leak out. What we got was &lt;em&gt;isolation&lt;/em&gt;: nothing gets in either, so theming, shared design tokens, forms, and focus all have to negotiate with the boundary. We asked for scoping and got isolation along with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  We can fix them
&lt;/h2&gt;

&lt;p&gt;Here's the part where I stop complaining. If you take the record above seriously, the fix has a pretty clear shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write components as markup&lt;/strong&gt;, not as imperative JavaScript classes. Templates, conditionals, loops, bindings: in HTML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A component should lower to a real native element.&lt;/strong&gt; If it's a button, the browser should see a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. No wrapper, no rebuilt semantics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope styles without a shadow boundary.&lt;/strong&gt; CSS &lt;code&gt;@scope&lt;/code&gt; exists now. Encapsulation without isolation; real isolation becomes opt-in, not a tax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reactivity should be a declarative dependency graph&lt;/strong&gt; that tools can read, type, and analyze. No &lt;code&gt;eval&lt;/code&gt;, no &lt;code&gt;new Function&lt;/code&gt;, CSP-clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It should be portable.&lt;/strong&gt; Compile the same source to React, Vue, or Svelte, so it isn't Yet Another Walled Garden.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graduate into the platform one piece at a time.&lt;/strong&gt; That's how Declarative Shadow DOM and invoker commands went from userland shims to shipped syntax.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these ideas are new on their own. Vue and Svelte proved markup-first components and scoped styles. Mitosis proved one source, many targets. The missing piece is putting them together &lt;em&gt;as HTML&lt;/em&gt;, with the explicit goal of getting them into the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter HTML Next
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nextwebwg.org/html-next/" rel="noopener noreferrer"&gt;HTML Next&lt;/a&gt; is a component format, plus open source (MIT) tooling, built on a public &lt;a href="https://nextwebwg.org/declarative-components/" rel="noopener noreferrer"&gt;Declarative HTML Components proposal&lt;/a&gt;. Here's a complete component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;component=&lt;/span&gt;&lt;span class="s"&gt;"x-counter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;defs&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;state&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"count"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/state&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;handler&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"increment"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;set&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"count"&lt;/span&gt; &lt;span class="na"&gt;expr:value=&lt;/span&gt;&lt;span class="s"&gt;"$count + 1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/set&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/handler&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;handler&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"reset"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;set&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"count"&lt;/span&gt; &lt;span class="na"&gt;expr:value=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/set&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/handler&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/defs&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;"increment"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Count: {$count}
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;"reset"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Reset&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Markup, typed state, and event handlers in one HTML file. No class, no &lt;code&gt;connectedCallback&lt;/code&gt;, no &lt;code&gt;attributeChangedCallback&lt;/code&gt;, no shadow root.&lt;/p&gt;

&lt;p&gt;And remember the button problem? Here's what happens to a component invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- You write --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;x-button&lt;/span&gt; &lt;span class="na"&gt;variant=&lt;/span&gt;&lt;span class="s"&gt;"solid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/x-button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Replaced with a real button at runtime --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;data-component=&lt;/span&gt;&lt;span class="s"&gt;"x-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component doesn't pretend to be a button and hope every browser plays along. It gets replaced with an actual &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. No customized built-ins, so no waiting on Safari, from the people who make iPhones (who are famously against buttons). Form submission, focus, role, and keyboard behavior stay the browser's job, where they belong.&lt;/p&gt;

&lt;p&gt;You can use that same source file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;natively&lt;/strong&gt;, with a small browser runtime (or with Vite), or&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;in Vue, React, or Svelte&lt;/strong&gt;, imported alongside your existing components through Vite adapters, or&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;as generated framework source&lt;/strong&gt;, via the converter, if you'd rather ship plain React/Vue/Svelte code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last part is the bit I care most about. A component library shouldn't need a rewrite every time an app changes frameworks. Write it once in HTML, and let the consuming project decide.&lt;/p&gt;

&lt;p&gt;This is a dev.to article, so it's probably not going to surprise you that I'm the editor of this proposal, and you should weigh my enthusiasm accordingly.&lt;/p&gt;

&lt;p&gt;The proposal is at Stage 0. The tooling is early, and syntax and output may change. It is &lt;em&gt;not&lt;/em&gt; a W3C or WHATWG deliverable, and browsers don't understand this syntax on their own; the tools run or compile it for you. The goal is to prove features in userland and then upstream the ones that earn it, feature by feature. Some things a compiler simply can't fix (like native element extension across browsers), and those belong in later proposals to the actual standards bodies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The proposal behind it
&lt;/h2&gt;

&lt;p&gt;The tooling is the part you can use today. The part I'd most like your eyes on is the spec it implements: &lt;a href="https://nextwebwg.org/declarative-components/" rel="noopener noreferrer"&gt;Declarative HTML Components Level 1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's written the way platform specs are written, because the end goal is to become one. It covers templating (&lt;code&gt;$each&lt;/code&gt;, &lt;code&gt;$if&lt;/code&gt;, &lt;code&gt;$match&lt;/code&gt;), a small type system, a pure expression language with &lt;code&gt;Intl&lt;/code&gt; formatting, bindings and events, slots and composition, server-rendered markup that hydrates into the same instance, reactivity and data, validation, style scoping, and a security chapter (no &lt;code&gt;eval&lt;/code&gt;, static resolution).&lt;/p&gt;

&lt;p&gt;A few design decisions I think are worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conformance is observable equivalence.&lt;/strong&gt; A compiler and a browser polyfill are both valid implementations, and they must produce the same native DOM, state, events, accessibility, and errors for the same source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It versions like the platform does.&lt;/strong&gt; Levels like CSS (this is Level 1), stages like TC39 (this is Stage 0), and dated snapshots in between. No big-bang "1.0."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's purely additive.&lt;/strong&gt; A plain HTML file is already valid. You adopt one feature, one tag at a time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It names what it can't fix.&lt;/strong&gt; Native element extension and a better Declarative Shadow DOM are explicitly marked as upstream work for the real standards bodies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also an honest list of open questions: data fetching and caching semantics, SSR serialization, whether expressions should use &lt;code&gt;and&lt;/code&gt;/&lt;code&gt;or&lt;/code&gt; or &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;/&lt;code&gt;||&lt;/code&gt;, scheduling, and parser edge cases like tables and SVG. If you have opinions on any of those (and if you've read this far, you probably do), &lt;a href="https://github.com/nextwebwg/specs/issues" rel="noopener noreferrer"&gt;open an issue on the specs repo&lt;/a&gt;. That feedback shapes what Level 1 becomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving forward
&lt;/h2&gt;

&lt;p&gt;I don't want to bury Web Components. A lot of smart people put a decade into them, and pieces of that work (custom element registration, &lt;code&gt;ElementInternals&lt;/code&gt;, Declarative Shadow DOM) are genuinely useful. I want to &lt;em&gt;finish&lt;/em&gt; them: give the platform the authoring layer it skipped.&lt;/p&gt;

&lt;p&gt;If that sounds interesting, &lt;a href="https://nextwebwg.org/declarative-components/" rel="noopener noreferrer"&gt;read the proposal&lt;/a&gt;, &lt;a href="https://nextwebwg.org/html-next/quick-start" rel="noopener noreferrer"&gt;build your first component&lt;/a&gt;, poke at the &lt;a href="https://github.com/nextwebwg/html-next" rel="noopener noreferrer"&gt;source&lt;/a&gt;, and &lt;a href="https://github.com/nextwebwg/html-next/issues" rel="noopener noreferrer"&gt;file issues&lt;/a&gt; when something breaks. Something will. That's what Stage 0 is for.&lt;/p&gt;

&lt;p&gt;In 2004, a few people at three browser companies decided HTML deserved a better future than the one it was being handed, and they went and built it. The whole web got better for it, quickly. JavaScript and CSS have had that kind of energy for ten years straight. I think it's HTML's turn again, and this time, anyone can help write it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webcomponents</category>
      <category>html</category>
      <category>javascript</category>
    </item>
    <item>
      <title>CSS: the language with no syntax</title>
      <dc:creator>Matthew Dean</dc:creator>
      <pubDate>Fri, 08 Aug 2025 18:13:43 +0000</pubDate>
      <link>https://dev.to/matthewdean/css-the-language-with-no-syntax-oom</link>
      <guid>https://dev.to/matthewdean/css-the-language-with-no-syntax-oom</guid>
      <description>&lt;p&gt;Congratulations! You have fallen prey to my clever click-bait-y title, but before you dismiss this topic out-of-hand ("Because," you say, "of &lt;em&gt;course&lt;/em&gt; CSS has syntax"), or leave a smarmy comment, allow me to walk you through what CSS is, and how it's defined.&lt;/p&gt;

&lt;p&gt;Some context: I've been a maintainer of Less.js (the CSS pre-processor) for about 10 years, and the lead maintainer for most of that time. When I started contributing, I knew very little about not just CSS parsing, but about parsing in general.&lt;/p&gt;

&lt;p&gt;In the last 5 years, I've worked on not just Less but other CSS-related parsing projects, including one that re-imagines both Less and Sass parsing for the modern era (which has yet to be released). I've used a variety of parsing approaches, parsing generators, and parsing libraries.&lt;/p&gt;

&lt;p&gt;In some cases, I had to abandon the use of certain parsers or parsing approaches because they just weren't flexible for CSS, and the reason is that CSS has no single syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do I mean by "single syntax"?
&lt;/h2&gt;

&lt;p&gt;You can think of CSS as kind of a collection of "micro-syntaxes". CSS has kind of a top-level syntax for how things are organized (okay, okay, yes you can say that at the &lt;em&gt;top level&lt;/em&gt;, there is a syntax), and then the way it works is that individual specifications have their own micro-syntax that defines the syntax within that location. On the whole, each micro-syntax has generally tried to mostly look consistent with other micro-syntaxes before it, but that doesn't mean that they actually &lt;em&gt;are&lt;/em&gt; consistent or that the same syntax has the same semantics, as you might find in other languages.&lt;/p&gt;

&lt;p&gt;You might be scratching your head at this, because, if you've never had to parse it, or have that parser actually &lt;em&gt;understand&lt;/em&gt; all its component pieces (as it does in a system like Less), CSS syntax looks the same everywhere.&lt;/p&gt;

&lt;p&gt;So, let's illustrate this with some examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assignment or selector?
&lt;/h2&gt;

&lt;p&gt;What does the colon &lt;code&gt;:&lt;/code&gt; mean in CSS? You might be tempted to say assignment, because of this:&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="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* Look! There's an assignment `:` operator! */&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, then, what does this mean?&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="cm"&gt;/* Look! There's... oh... */&lt;/span&gt;
&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Not a problem!" you say. This is at the start of a ruleset (also called a qualified rule), and we know that pseudo-classes and pseudo-elements start with &lt;code&gt;:&lt;/code&gt; followed by an identifier. Easy peasy.&lt;/p&gt;

&lt;p&gt;Alright, so a pseudo-selector looks like &lt;code&gt;:ident&lt;/code&gt;, cool. Okay, how do we parse this?&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="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* Look There's a pseudo-... oh... */&lt;/span&gt;
  &lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="nd"&gt;:red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, never mind that, we say. We can just have two modes! When we're  at the "top-level" where we're expecting rulesets, we can have &lt;code&gt;:&lt;/code&gt; mean one thing, and then inside it, we can have it mean another.&lt;/p&gt;

&lt;p&gt;Welp, not so fast. One of the reasons Less and Sass were so popular for so long was the convenience of nesting rulesets and at-rules inside each other, and both engines support this:&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="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:hover&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;CSS Nesting has now been adopted by the core CSS language itself, and as of 2024, is &lt;a href="https://caniuse.com/css-nesting" rel="noopener noreferrer"&gt;now supported in browsers&lt;/a&gt;. So this form is valid in CSS now too.&lt;/p&gt;

&lt;p&gt;If you know anything about parsing, you may now start to recognize that we're looking at an example of "infinite lookahead" and backtracking. Some engines either don't support infinite lookahead, or don't support it without major performance penalties. The reason it's "infinite" is because of cases like:&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="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;foo&lt;/span&gt;&lt;span class="nd"&gt;:red&lt;/span&gt; &lt;span class="nt"&gt;blue&lt;/span&gt; &lt;span class="nt"&gt;green&lt;/span&gt; &lt;span class="nt"&gt;yellow&lt;/span&gt; &lt;span class="nt"&gt;purple&lt;/span&gt; &lt;span class="nt"&gt;brown&lt;/span&gt; &lt;span class="nt"&gt;black&lt;/span&gt; &lt;span class="nt"&gt;grey&lt;/span&gt;&lt;span class="nc"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we parse this, we have to ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;is this a property called "foo" with a value of &lt;code&gt;red blue green yellow purple brown black grey&lt;/code&gt; etc? or&lt;/li&gt;
&lt;li&gt;is this a selector selecting an element called "foo" with a pseudo-class of &lt;code&gt;:red&lt;/code&gt; and descendant elements of &lt;code&gt;blue green yellow purple brown black grey&lt;/code&gt; etc?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are ways to avoid infinite lookahead, of course, such as having a list of all known CSS properties, and/or having a list of all known valid HTML elements, but this is already a little too deep into parsing, so let's get back on track to syntax and how CSS is defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  How CSS syntax is actually defined
&lt;/h2&gt;

&lt;p&gt;The way that CSS syntax is defined is that you can think of it as having a basic outer structure with "slots" where syntax definitions can go.&lt;/p&gt;

&lt;p&gt;These "slots" are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An at-rule's "prelude" (everything before outer &lt;code&gt;;&lt;/code&gt; or its &lt;code&gt;{}&lt;/code&gt; block)&lt;/li&gt;
&lt;li&gt;An at-rule's block (&lt;code&gt;{}&lt;/code&gt;) contents&lt;/li&gt;
&lt;li&gt;A property's value&lt;/li&gt;
&lt;li&gt;A pseudo-selector's &lt;code&gt;()&lt;/code&gt; block, if it exists&lt;/li&gt;
&lt;li&gt;A CSS function's &lt;code&gt;()&lt;/code&gt; block&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example 1: at-rules
&lt;/h3&gt;

&lt;p&gt;Instead of having general parsing rules, parsing is specified by the at-rule specification itself. For example: the &lt;code&gt;@supports&lt;/code&gt; at-rule parses (or can parse) property/value pairs, like:&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="k"&gt;@supports&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;transform-origin&lt;/span&gt;&lt;span class="nd"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;5&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;5&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="o"&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;...whereas the &lt;code&gt;@scope&lt;/code&gt; rule expects to be parsing selectors e.g.&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="k"&gt;@scope&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.card&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 2: :nth-child() and dimensions
&lt;/h3&gt;

&lt;p&gt;In general, CSS considers units like &lt;code&gt;-1&lt;/code&gt; to be distinct tokens. If you have this, for example, this will be parsed as two values: &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;-1&lt;/code&gt;:&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="cm"&gt;/* This has a value of 1 -1 */&lt;/span&gt;
&lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, &lt;code&gt;:nth-&lt;/code&gt; functions define their own micro-syntax: &lt;code&gt;An+B&lt;/code&gt;. Instead of the argument to &lt;code&gt;:nth-child(n-1)&lt;/code&gt; being interpreted as two tokens, &lt;code&gt;n&lt;/code&gt; and &lt;code&gt;-1&lt;/code&gt;, it's interpreted as "n minus 1", which, confusingly, is different from the &lt;code&gt;calc()&lt;/code&gt; function because, again, &lt;code&gt;calc()&lt;/code&gt; has its own micro-syntax!&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS: What's a "list"?
&lt;/h2&gt;

&lt;p&gt;One of the most frustrating and inconsistent parts of CSS syntax is that it has no formal definition for "lists". Or, rather, it has a collection of syntaxes which all &lt;em&gt;can&lt;/em&gt; represent lists, but not all of them are consistent in describing what's &lt;em&gt;in&lt;/em&gt; the list and what isn't.&lt;/p&gt;

&lt;p&gt;That said, probably the most consistent right now in CSS syntax and micro-syntaxes is comma-separated and semi-colon-separated lists, which is generally defined like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// List members are ['1', '2', '3']
1, 2, 3

// List members are ['1 1', '2 2', '3 3']
1 1, 2 2, 3 3

// List members are ['a: 1', 'b: 2', 'c: 3']
a: 1; b: 2; c: 3;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is, &lt;em&gt;in general&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each comma splits the value into distinct members of the list i.e. if you have two commas, you have 3 members of the list.&lt;/li&gt;
&lt;li&gt;A semi-colon-separated list "groups" other values that, themselves, can have comma-separated lists.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You may be scratching your head and saying, "What do you mean, 'in general'? Isn't this always true in CSS?"&lt;/p&gt;

&lt;p&gt;Sorry, but nope. Where this concept begins to fall apart is in CSS function calls. The reason it begins to fall apart is because of these ambiguities in CSS syntax:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CSS functions can receive arguments. Those arguments are CSS values.&lt;/li&gt;
&lt;li&gt;CSS function arguments are a comma-separated list, BUT (importantly),&lt;/li&gt;
&lt;li&gt;CSS values can &lt;em&gt;also&lt;/em&gt; be a comma-separated list.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With early CSS functions, this wasn't an issue. All functions received values that didn't themselves have commas. Eventually, though, use cases and CSS proposals emerged for functions that &lt;em&gt;could&lt;/em&gt; receive commas. And not just functions. There emerged use cases where other CSS values, which could be comma-separated, might &lt;strong&gt;&lt;em&gt;themselves&lt;/em&gt;&lt;/strong&gt; need sub-lists as individual parts of the value.&lt;/p&gt;

&lt;p&gt;So, now what were proposal authors to do? If your guess is, "Hopefully, something consistent?" then prepare to be horribly disappointed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Many Failed Attempts at Lists-of-Lists Syntax in CSS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The &lt;code&gt;/&lt;/code&gt; separator
&lt;/h3&gt;

&lt;p&gt;You'll probably recognize this. In some CSS values, the list separator is a slash (&lt;code&gt;/&lt;/code&gt;) character. But it would be a mistake to consider it a list separator like &lt;code&gt;,&lt;/code&gt; or &lt;code&gt;;&lt;/code&gt;. I'll explain.&lt;/p&gt;

&lt;p&gt;Let's take border-radius as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;border-radius: 10px 20px / 30px 40px;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the border-radius syntax, this syntax is used to figure out "radius pairs" for corners. I don't want to get into the minutia of the &lt;code&gt;border-radius&lt;/code&gt; syntax, but suffice to say that the &lt;code&gt;/&lt;/code&gt; separates a set of values preceding it from a set of values following it. In this case, it's a list like: ['10px 20px', '30px 40px'].&lt;/p&gt;

&lt;p&gt;You might be tempted, then, to think of the &lt;code&gt;/&lt;/code&gt; as just another list separator. But, of course, that isn't the case.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;calc()&lt;/code&gt; was introduced, it recognized that almost all programming languages use &lt;code&gt;/&lt;/code&gt; as a "division operator". It wasn't going to re-invent the wheel, so the &lt;code&gt;calc()&lt;/code&gt; micro-syntax uses it for division and not as a list separator:&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="cm"&gt;/** This means "20px divided by 2" and not
 * "a list containing '20px' and '2' */&lt;/span&gt;
&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;20px&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some property sets, like &lt;code&gt;background-*&lt;/code&gt; for example, use comma-separated lists for individual properties, and slash-separated lists for other properties. So, when they get put together, you may get a value like this:&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="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url(a.png)&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt; &lt;span class="nb"&gt;top&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;url(b.png)&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From a syntax perspective, what does this value mean? If you're familiar with the &lt;code&gt;background&lt;/code&gt; property(s), you may instinctively be able to read this unambiguously. But let's look at this from purely a syntax perspective.&lt;/p&gt;

&lt;p&gt;Can you tell me what the "lists" or "lists of lists" are from these CSS values?&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="na"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;three&lt;/span&gt; &lt;span class="n"&gt;four&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="na"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt; &lt;span class="n"&gt;three&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;four&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="na"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt; &lt;span class="n"&gt;three&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;four&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your brain says, "The comma splits the value into list pairs, and the slash further splits THOSE values into list pairs," it's a good guess, but, in CSS, that's just not true. It's entirely dependent on the micro-syntax of the given property. You have to know the property name to know how to understand the syntax that follows.&lt;/p&gt;

&lt;p&gt;In the case of &lt;code&gt;background&lt;/code&gt;, this is how these lists are actually grouped semantically:&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="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url(a.png)&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt; &lt;span class="nb"&gt;top&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;url(b.png)&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
  background: [
    [
      'url(a.png)',
      'left top'
      'cover'
    ],
    [
      'url(b.png)',
      'right',
      'contain'
    ]
  ]
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the &lt;code&gt;/&lt;/code&gt; divided lists consistently across CSS, then the semantic division of this value would be:&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="nl"&gt;background&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="s1"&gt;'url(a.png) left top'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'cover'&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s1"&gt;'url(b.png) right'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'contain'&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;Even &lt;code&gt;url(a.png) left top&lt;/code&gt; requires you to understand the micro-syntax of &lt;code&gt;background&lt;/code&gt; to know that &lt;code&gt;url(a.png)&lt;/code&gt; is the background-image and &lt;code&gt;left top&lt;/code&gt; represents a single property &lt;code&gt;background-position&lt;/code&gt; and not two distinct properties. ✨ Micro-syntaxes! ✨&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Lists of lists in function (or "function-like") calls
&lt;/h3&gt;

&lt;p&gt;One of the first functions that needed a list of lists was &lt;code&gt;var()&lt;/code&gt;. You might think of it as having two values, and it does: the custom property name and the fallback value:&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="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;theme-color&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, CSS authors had to resolve something: what about values that have commas? The solution, in this case? Just treat everything after the first comma as being absorbed into a single value.&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="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;theme-font&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Helvetica'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is the comma consistent here? Again, take the &lt;code&gt;var()&lt;/code&gt; out of this and just think of CSS functions as a general syntax. What does this mean?&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="na"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;new-css-function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;three&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is that a function that is taking 3 value arguments? Or is it taking one comma-separated value argument?&lt;/p&gt;

&lt;p&gt;Well, in the case of &lt;code&gt;var()&lt;/code&gt;, delightfully, it's neither. It's taking 2 arguments, the second of which can be a comma-separated argument.&lt;/p&gt;

&lt;p&gt;From a language syntax perspective, that's kind of insane, but it gets better!&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  A note about Less / Sass's handling of lists of lists
&lt;/h4&gt;

&lt;p&gt;Since both Less and Sass (the SCSS variant) are supersets of CSS, there are cases where each had to solve lists-of-lists from a wholistic syntax perspective since CSS offers no consistent guidance in this regard.&lt;br&gt;
Sass let's you wrap values in parentheses to pass lists to functions and mixins:&lt;/p&gt;


&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sass-function&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Less prefers that you be more explicit, because parentheses may be part of a valid value (think media query syntax), so it asks you to use Less's escape &lt;code&gt;~&lt;/code&gt; character to more clearly denote that you want the final value to be without the parentheses:&lt;/p&gt;


&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;less-function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. The CSS Functions and Mixins Module
&lt;/h3&gt;

&lt;p&gt;Probably the most bizarre and perverse twist on trying to come up with a solution for lists of lists is the new &lt;a href="https://www.w3.org/TR/css-mixins-1/" rel="noopener noreferrer"&gt;CSS functions and mixins module proposal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It uses NONE of the prior solutions, including Sass / Less prior art, to define lists of lists and comes up with yet another. So, if you're counting, CSS and CSS preprocessor syntax now have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;comma-separated lists&lt;/li&gt;
&lt;li&gt;semi-colon separated lists&lt;/li&gt;
&lt;li&gt;slash-separated lists (inconsistent)&lt;/li&gt;
&lt;li&gt;parentheses-grouped lists (Less and Sass)&lt;/li&gt;
&lt;li&gt;...a brand new one defined by this module: curly-brace-grouped lists&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This syntax ends up looking like this:&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="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;--max-plus-x&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nt"&gt;1px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;7px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;2px&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;3px&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me be clear: it's not that I think this syntax is &lt;em&gt;bad&lt;/em&gt;, necessarily; it's just that it's Yet Another Micro-syntax. IMO it has these two flaws:&lt;/p&gt;

&lt;h4&gt;
  
  
  Flaw 1
&lt;/h4&gt;

&lt;p&gt;Until now, curly braces in CSS has actually been a mostly-consistent syntax, denoting a group of rules, like:&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="cm"&gt;/* We thought we understood curly braces in CSS... */&lt;/span&gt;
&lt;span class="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&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 authors of this module proposal have decided to break that syntax convention, entirely, such that one of the few syntax "rules" will now be, again, removed from "reliable" syntax. (Not that CSS ever specified that curly braces should / would always contain rules; it's just been a consistent syntax convention until now.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Flaw 2
&lt;/h4&gt;

&lt;p&gt;Instead of CSS authors saying, "You know what? Maybe we should make a formal 'list of lists' standard in CSS and start using it," the use of this format for this module has no general applicability, and there are no plans to migrate it anywhere else.&lt;/p&gt;

&lt;p&gt;In other words, if CSS &lt;strong&gt;were&lt;/strong&gt; a single syntax, a syntax proposal with this would come with certain expectations to update other syntaxes for function calls or values to be able to use it, such as:&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="cm"&gt;/* Supporting this moving forward: */&lt;/span&gt;
&lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;theme-font&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'Helvetica'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;sans-serif&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;);
/* &lt;/span&gt;&lt;span class="na"&gt;Instead&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;the&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;inconsistent&lt;/span&gt;&lt;span class="err"&gt; `&lt;/span&gt;&lt;span class="na"&gt;rgb&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;100&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;100&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;100&lt;/span&gt;&lt;span class="err"&gt; / &lt;/span&gt;&lt;span class="na"&gt;0&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;5&lt;/span&gt;&lt;span class="err"&gt;)` &lt;/span&gt;&lt;span class="na"&gt;syntax&lt;/span&gt;&lt;span class="err"&gt; */
&lt;/span&gt;&lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nt"&gt;100&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;100&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="nc"&gt;.5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no such proposal here. It's just Yet Another Micro-syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS - not one syntax, but a smorgasbord of micro-syntaxes
&lt;/h2&gt;

&lt;p&gt;Listen: I love CSS! Sure, from a language syntax perspective, it resembles something like Howl's Moving Castle. But, from a beginner perspective, its relatively-lean syntax means that it's easy to write and quite forgiving. In some ways, its weaknesses are also its strengths.&lt;/p&gt;

&lt;p&gt;Just... don't ever try to build a CSS parser. Become a tomato farmer instead.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Addendum: despite knowing better, I've currently building a new CSS parsing &amp;amp; processing framework anyway, to replace Less / Sass / CSS modules / Tailwind / Styled Components / other styling systems, so follow me on here and/or leave a comment if you want to hear about it!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>less</category>
      <category>sass</category>
    </item>
    <item>
      <title>Why, after 20 years as a JavaScript developer, I switched to semicolons</title>
      <dc:creator>Matthew Dean</dc:creator>
      <pubDate>Sun, 13 Jul 2025 20:07:46 +0000</pubDate>
      <link>https://dev.to/matthewdean/why-after-20-years-as-a-javascript-developer-i-switched-to-semicolons-2n1c</link>
      <guid>https://dev.to/matthewdean/why-after-20-years-as-a-javascript-developer-i-switched-to-semicolons-2n1c</guid>
      <description>&lt;p&gt;This is a first of a series I thought I'd start writing (and hopefully continue writing) about my experiences over 36 years as a programmer, and 20+ years as a professional web developer. (Am I old now? I think I'm old.)&lt;/p&gt;

&lt;p&gt;First, what are we talking about? If you're new to JavaScript, you may know that JavaScript is rather forgiving of statement terminations. JavaScript isn't alone in this. Go, Swift, Kotlin, Ruby, Python, Lua... they all make semicolons optional at the end of statements.&lt;/p&gt;

&lt;p&gt;In JavaScript, the spec calls this Automatic Semicolon Insertion or ASI. The simplified explanation is that if JS sees what &lt;em&gt;would&lt;/em&gt; be a syntax error, and it &lt;em&gt;thinks&lt;/em&gt; a semicolon might fix it, it inserts one automatically.&lt;/p&gt;

&lt;p&gt;Here's an example:&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;let&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;two&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Early in the culture of JavaScript development, almost all code examples you would find online, though, always had semicolon terminators, like:&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;let&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;two&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The semicolon-less era
&lt;/h2&gt;

&lt;p&gt;In the early 2010s, this started to shift. Web development, as a culture, was getting more and more sophisticated, which led to more subject-matter experts with in-depth knowledge, including of esoteric concepts like ASI.&lt;/p&gt;

&lt;p&gt;Many JavaScript developers began to say, "Look, semicolons are optional 99% of the time. Why not just omit them, as a standard?" For many, because semicolons seemed unnecessary, then they were just a form of visual noise.&lt;/p&gt;

&lt;p&gt;Around the same time, teams were promoting coding with the same style, for easier maintenance and for making fewer decisions. So this new "standard" began to be incorporated in linting tools so that semicolon terminators were now not just optional, they were actually forbidden in a given codebase.&lt;/p&gt;

&lt;p&gt;Anyway, this is a lot of probably unnecessary history, which many readers may already know. The point is, why did I, after adopting semicolon-less JavaScript for over ten years, decide to abandon this as of this year (2025)?&lt;/p&gt;

&lt;h2&gt;
  
  
  99% is not 100%
&lt;/h2&gt;

&lt;p&gt;JavaScript doesn't require semicolons &lt;em&gt;most&lt;/em&gt; of the time, but when it does, errors can suddenly become very unintuitive. Let's take this code example (demonstrating variable swapping via array destructuring), which I borrowed from &lt;a href="https://medium.com/@tolulope-malomo/the-javascript-bug-from-hell-01bb1670d7ae" rel="noopener noreferrer"&gt;this Medium post&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;varOne&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;varTwo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;varOne&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;varTwo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;varTwo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;varOne&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript will actually parse that second statement as:&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;let&lt;/span&gt; &lt;span class="nx"&gt;varTwo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;varOne&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;varTwo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;varTwo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;varOne&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I paste the above code example in VSCode, and let TypeScript try to explain the error to me, I don't get something like "semicolon missing between line two..." blah blah blah, instead I get these errors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;'varTwo' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Type 'any[]' is not assignable to type 'string'&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Index signature in type 'String' only permits reading.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Left side of comma operator is unused and has no side effects.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sure, if you know ASI rules, after a bit of head-scratching, you may eventually realize a semicolon is necessary here. The reason is that JavaScript will &lt;em&gt;not&lt;/em&gt; insert a semicolon before &lt;code&gt;(&lt;/code&gt; or &lt;code&gt;[&lt;/code&gt; at the start of a line,&lt;/p&gt;

&lt;p&gt;You can, of course, just add in one of two ways: either the end of the second statement or the start of the third, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Option 1&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;varOne&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;varTwo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;varOne&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;varTwo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;varTwo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;varOne&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// Option 2&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;varOne&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;varTwo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="p"&gt;;[&lt;/span&gt;&lt;span class="nx"&gt;varOne&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;varTwo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;varTwo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;varOne&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interestingly most "Ditch your Semicolon" advocates have recommended Option 2, because you're not altering the &lt;em&gt;second&lt;/em&gt; statement in order to resolve the &lt;em&gt;third&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code can start to look weird fast
&lt;/h2&gt;

&lt;p&gt;Despite many advocates insisting that the above example was rare, as I said, it can make errors unintuitive, but, also, if you're working in a team where someone needs to review your code and isn't as familiar with ASI quirks, the above solutions can easily look like a typo.&lt;/p&gt;

&lt;p&gt;Additionally, in the 2010s, destructuring patterns, including re-assignment via destructuring, were not as prevalent, as it only began to be supported in middle of 2016.&lt;/p&gt;

&lt;p&gt;For these reasons, over time, I found myself needing to pepper in semicolons, because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I began to use destructuring more in my own code, including re-assignment via destructuring.&lt;/li&gt;
&lt;li&gt;I began to use TypeScript almost exclusively.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  TypeScript: adding additional problems for ASI
&lt;/h2&gt;

&lt;p&gt;Why would TypeScript make this issue worse? Let's take the following code, in JS:&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;let&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input.url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works fine! But let's take this into TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input.url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// - errors: 'input' is possibly null&lt;/span&gt;
&lt;span class="c1"&gt;//           Property 'focus' does not exist on type 'Element'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, let's fix that by casting this to what we know this element actually is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input.url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of the sudden, &lt;em&gt;both&lt;/em&gt; these lines are reporting errors, even though we only changed the second line. Why? Because TypeScript imports all the same ASI quirks as JavaScript, meaning a line that starts with &lt;code&gt;(&lt;/code&gt; continues the statement.&lt;/p&gt;

&lt;p&gt;This began to increasingly happen to me. I would fix a type problem, which would then require me to wrap something in parentheses, which then would create new errors, which then I'd grumble and insert an awkward-looking semicolon there at the start of the statement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was the issue with semicolons in the first place?
&lt;/h2&gt;

&lt;p&gt;Eventually, I began to wonder what I was really gaining by leaning on ASI. As software developers, we're &lt;em&gt;used&lt;/em&gt; to looking at semicolons at the end of statements. In fact, if you're a web developer, you're used to putting them at the end of CSS declarations. They just aren't a big deal.&lt;/p&gt;

&lt;p&gt;And as to visual noise? Because they are so ubiquitous, they start to visually disappear anyway.&lt;/p&gt;

&lt;p&gt;They don't even arguably require more &lt;em&gt;typing&lt;/em&gt;. I have both ESLint and "Fix on save" enabled in VSCode, meaning every save will just quietly fix any missing semicolons.&lt;/p&gt;

&lt;p&gt;As a result, I've discovered that I actually think about semicolons &lt;em&gt;less by using them than by not using them&lt;/em&gt;. There's just less cognitive overhead. Statements end with semicolons, full stop. Same-line statements are separated with semicolons. (Almost) all statements have semicolon companions. I no longer have to think about semicolons &lt;em&gt;while&lt;/em&gt; destructuring or fixing type problems. My code is no longer confusing my C# colleagues who may be reviewing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving forward;
&lt;/h2&gt;

&lt;p&gt;Anyway, for me, while this is arguably not the largest issue or problem to have, I thought it would be an interesting dive into code style decisions. And I would imagine some of the same logic may apply to other code that uses optional semicolons, and I'd be interested to hear about that!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@shantk18?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Sudharshan TK&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/text-mM9vVJ2oDeI?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>codestyle</category>
    </item>
    <item>
      <title>Simplicity as a Feature</title>
      <dc:creator>Matthew Dean</dc:creator>
      <pubDate>Fri, 26 Nov 2021 23:55:06 +0000</pubDate>
      <link>https://dev.to/matthewdean/simplicity-as-a-feature-14be</link>
      <guid>https://dev.to/matthewdean/simplicity-as-a-feature-14be</guid>
      <description>&lt;p&gt;I recently came across the article "&lt;a href="https://www.infoworld.com/article/3639050/complexity-is-killing-software-developers.html" rel="noopener noreferrer"&gt;Complexity is killing software developers&lt;/a&gt;," and I've been thinking about it ever since.&lt;/p&gt;

&lt;p&gt;The article covers a broad spectrum around software development in general, but I've been thinking specifically around the domain of front-end web development, and the multiple layers of abstraction we often buy into as being zero-cost--essentially: all feature benefits, with no significant drawbacks.&lt;/p&gt;

&lt;p&gt;We write our components in JavaScript. Except it isn't. It's TypeScript. We write a function call to update the DOM. Except it isn't a function call. It's JSX (TSX). It'll be a function call later. The JSX contains markup of what HTML elements it will render. Except it doesn't. They're "styled components", all of which are abstracted from both the elements they render and the CSS that will eventually be read by a browser. We include this component in our HTML document, except we don't. Someone wrote a 200-line Webpack config file that will magically split this component and others, along with other assets and resources, and render a linked document, based on an abstracted template. Maybe Webpack itself is abstracted, behind a tool like Next.js or Vite.&lt;/p&gt;

&lt;p&gt;At my company, I started working on a site and just mentally tallying the layers of abstraction: styled components, TypeScript, React, Redux, route strings as typed enums, content as JSON...and I started to wonder: was all this necessary? Is it worth it?&lt;/p&gt;

&lt;p&gt;Or another way to ask it: what is this costing us?&lt;/p&gt;

&lt;p&gt;First of all, I have to acknowledge that this question is ironic coming from someone who's been one of the maintainers of Less (the CSS pre-processor) for the last few years. So, it should be said that I'm not &lt;em&gt;against&lt;/em&gt; any of these abstractions on their face. I may have strong opinions about this or that tool, but there's no question (to me) that the rise of reactive libraries were a net benefit to web development, or the adoption of component-based design. I've often advocated strongly for tools like TypeScript.&lt;/p&gt;

&lt;p&gt;That said, in the past little while, I've started to feel differently, and I've been thinking about this problem from a different perspective. What I've started to see is that, where we used to add these tools to solve a particular pain point where we had before, as a culture, we in web development have acclimated to complexity regardless of the costs. Why wouldn't we use Webpack? We used it before. Why wouldn't we use TypeScript? It's what we're comfortable with.&lt;/p&gt;

&lt;p&gt;I think what we've failed to recognize--and I will include myself in this--is that for each benefit it may provide, there is a maintenance and cognitive overhead cost (among other costs) in each additional abstraction that we add into our workflow.&lt;/p&gt;

&lt;p&gt;Our monorepo takes forever to run &lt;code&gt;yarn install&lt;/code&gt;, and no one knows exactly why. The complex nest of dependencies is not something we've been able to really dig into yet, as we're plowing through each sprint. Each app takes mountains of time to compile, and to run tests, and bundles seem unnecessarily large, but to decipher that just takes more time with each layer of abstraction.&lt;/p&gt;

&lt;p&gt;Recently, I switched our dev build compiler of some of our shared packages to &lt;a href="https://swc.rs/" rel="noopener noreferrer"&gt;SWC&lt;/a&gt; from TypeScript (along with removing other abstractions), which, on the one hand, is great! You definitely should explore doing that.&lt;/p&gt;

&lt;p&gt;But... on the other hand, I was solving a problem that we ourselves had created, and this sacrilegious thought also occurred to me: what if we weren't compiling / transpiling our code at all? How much faster would that be?&lt;/p&gt;

&lt;p&gt;Immediately after I had that thought, I looked around me to make sure the web development gods were not about to smite me. After all, we've lived in a world of, if not TypeScript, surely at least Babel-ified JavaScript, but there's a question if that's needed anymore.&lt;/p&gt;

&lt;p&gt;I'm not the first one to have this thought. Rich Harris, a prominent figure in web development, having developed Ractive, Rollup, and Svelte, had this to say about moving from TypeScript back to JavaScript (with JSDoc type annotations): "Among other things, the resulting code is generally smaller than transpiled code. Building, testing etc all become much less finicky. And .d.ts files are still generated from source code."&lt;/p&gt;

&lt;p&gt;I don't want to make this entirely about TypeScript; for many teams, TypeScript may be the best option! But I think there's an overlooked value in sincerely asking the question of just how complex any project needs to be, and recognizing that each layer of abstraction is &lt;em&gt;not&lt;/em&gt; zero-cost. It may increase development time, even as it decreases it in other areas. It may increase build time or deploy time. It may increase tech debt. It can increase cognitive overhead, or the time to onboard a new developer.&lt;/p&gt;

&lt;p&gt;Do you need React, or will Svelte do? Or maybe something even lighter?&lt;/p&gt;

&lt;p&gt;Do you need Less / Sass, or styled components, or is your design system simple enough that regular ol' CSS will work?&lt;/p&gt;

&lt;p&gt;Do you need Webpack? Or is there something simpler, maybe with fewer options, but with less cognitive overhead?&lt;/p&gt;

&lt;p&gt;Do you need Redux, or can you use Zustand? Do you even need a global state library?&lt;/p&gt;

&lt;p&gt;Do you need JSX? Do you need TypeScript?&lt;/p&gt;

&lt;p&gt;I've begun to think about this as Simplicity as a Feature. Just in the way that we may optimize for performance, and build for Performance as a Feature, I'm starting to think we should optimize our tools and codebases for simplicity. Not to use the &lt;em&gt;simplest&lt;/em&gt; tools but to simply use &lt;em&gt;only&lt;/em&gt; the tools with &lt;em&gt;only&lt;/em&gt; the features that we really need. And if we start to need that next abstraction, that's okay! Sometimes there are things that are trade-offs for performance, just like there are things that are trade-offs for simplicity.&lt;/p&gt;

&lt;p&gt;But the leaner you keep your tools and your code, in theory, the faster you and your team can iterate, build, and deploy.&lt;/p&gt;

&lt;p&gt;So stop using giant boilerplate-y project templates with every conceivable feature and tool you might ever need. If you use TypeScript, it's okay to not use it on some things! Heck, it's okay to manually write an &lt;code&gt;.html&lt;/code&gt; file! It doesn't make you a bad developer, I promise!&lt;/p&gt;

&lt;p&gt;And if you're new to web development, don't buy into web sites and apps needing to be necessarily complex, and you needing to learn and use myriad layers of abstraction.&lt;/p&gt;

&lt;p&gt;It's okay for things to be simple. That might even be best.&lt;/p&gt;

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