<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.0">Jekyll</generator><link href="https://elisehe.in/feed.xml" rel="self" type="application/atom+xml" /><link href="https://elisehe.in/" rel="alternate" type="text/html" /><updated>2025-05-26T17:03:01+00:00</updated><id>https://elisehe.in/feed.xml</id><title type="html">Elise Hein’s personal site</title><subtitle>Fuzzy opinions on design and development, infrequently and in long-form.</subtitle><author><name>Elise Hein</name></author><entry><title type="html">Edges and intersections</title><link href="https://elisehe.in/2025/05/08/edges-and-intersections.html" rel="alternate" type="text/html" title="Edges and intersections" /><published>2025-05-08T00:00:00+00:00</published><updated>2025-05-08T00:00:00+00:00</updated><id>https://elisehe.in/2025/05/08/edges-and-intersections</id><content type="html" xml:base="https://elisehe.in/2025/05/08/edges-and-intersections.html"><![CDATA[<p class="no-drop-caps">I love building for the web.</p>

<p>The fact that a single set of standards — HTML, CSS, and JavaScript — has been refined over decades to grow impressively more inclusive, to support so many new contexts, all while being entirely backwards compatible, is remarkable. A feature can take endless forms when it reaches the end user, and trying to work with that unpredictability, <a href="https://bigmedium.com/ideas/design-system-pace-layers-slow-fast.html">along the grain</a>, is what I like about my work.</p>

<p>This perspective is something I've grown into over time. Early in my career, I was more interested in ambitious prototypes and funky experiments (“look what I made!”) but these days I prefer using the platform as it was designed, leveraging its strengths. There’s often a straightforward, well-supported way to solve a problem, and I enjoy finding it. It rewards curiosity, precision, and care.</p>

<p>Allowing that curiosity to take the lead, I’ve spent the last decade working at the intersection of design and engineering. To make something resilient, accessible, and fast, you have to think in systems and care about details. And to shape those systems meaningfully, you have to understand users, flows, and interface patterns.</p>

<p>My official titles have leaned towards engineering, but much of my day-to-day work has lived near the edge of product design. At <a href="https://fora.health">Fora Health</a>, working with vulnerable user groups taught me to notice the emotional impact of my design choices. Designing in problem spaces like clinical depression and dementia, I learned firsthand how even a single misplaced word can have a dramatic effect on how someone feels walking away from an interaction — a lesson in empathy that I've found applicable in every design problem I've faced since.</p>

<p>At <a href="https://griffin.com">Griffin</a>, I led the design and development of a design system powering complex banking apps, marketing sites and internal tools. But in parallel, I continued designing on a higher level for product areas like 2FA, billing and payment operations. I was constantly zooming in and back out, across the <a href="https://bigmedium.com/ideas/design-system-pace-layers-slow-fast.html">pace layers of process</a>, from the outer orbits of innovation to the quality of individual interactions at the core.</p>

<p>Changing contexts like this daily helped shape my philosophy towards design engineering as a discipline. Providing teams with a shared UI language is only the baseline. At a higher level, I've come to see it as firmly rooted in <strong>empowerment</strong> and <strong>enablement</strong> — lowering the barrier to quality for everyone involved in building the product, through education, inclusion, and making the right thing the easiest thing to do.</p>

<p>Design systems are one tool — a robust UI library will scale quality. But alongside that, accurate developer documentation will foster adoption, higher-level design guidelines will create trust, working with product designers and PMs spurs innovation, and fixing papercuts will demonstrate care, while setting the bar high. As one founder explained his vision to me, it's as much about guiding designers towards excellent code as it is about guiding other engineers towards UI excellence — and there is so much more to that than design system ownership.</p>

<p>In practice, this kind of work can take many forms. It could mean maintaining a <a href="https://sophieaguado.medium.com/articulating-design-decisions-0b81fbe030c6">record of design decisions</a>. Sometimes it's providing an alternative declarative API in an otherwise compound component<sup id="fnref:ashby" role="doc-noteref"><a href="#fn:ashby" class="footnote">1</a></sup>. Or <a href="https://www.smashingmagazine.com/2024/05/decision-trees-ui-components/">formatting design guidelines as flowcharts</a> to better support complex decision-making. Adding automated accessibility checks to the testing suite. <a href="https://papercuts.gitlab.com/">Publicly celebrating papercuts fixed</a>. Refining the build pipeline to better support theming. Other times it's pairing with other engineers or joining RFC discussions to keep design infrastructure grounded in the realities of how teams actually work day to day.  And calling it <em>infrastructure</em> matters — it's not the layer on top, it's what gets built with.</p>

<p>This kind of <a href="https://www.noidea.dog/glue">glue work</a> helps make quality the default outcome in teams without enough design-focused engineers to go around. And for a solo UI engineer in a mostly backend-leaning team — as I've found myself in many past roles — the need for this connective tissue ends up shaping much of the work.</p>

<p>But I still get enormous pleasure from the craft itself, and I’ve missed working closely with likeminded engineers who enjoy refining the details that make a UI feel solid. I've recently been going through a preview of <a href="https://rauno.me">Rauno Freiberg's</a> upcoming <a href="https://devouringdetails.com">Devouring Details</a> interaction design course, and it's been a welcome reminder of how much joy there is in chasing quality at the level of individual interactions<sup id="fnref:websites" role="doc-noteref"><a href="#fn:websites" class="footnote">2</a></sup> — not just for the polish, but for the confidence this instils that the same quality is also present in the parts of the product that the user <em>can't</em> see.</p>

<p>Paco Coursey captures it perfectly:</p>

<blockquote>
  <p>Animate those icons. Brand that scrollbar. Polish that <code class="language-plaintext highlighter-rouge">:active</code> state. Load a single typeface glyph to render the world’s best ampersand. Make it fast. Make it accessible. Add keyboard shortcuts. Add tooltips. Animate the transition between two open tooltips. Manually measure text and avoid line wrapping widows. Design favicons that reflect app state. Convert internal URLs to rich previews. […] Design a whole new stylesheet for printing. Design a whole new page for mobile. Dynamically generate video thumbnails and use them in a custom video player. Optimize your re-renders.</p>
  <footer><p><cite><a href="https://ui.land/interviews/paco-coursey#what-are-you-currently-excited-about?">Paco Coursey</a></cite></p></footer>
</blockquote>

<p>It’s the sort of work where aesthetics, accessibility, motion, and psychology meet frontend performance and CSS craft, and where I slip into flow most easily.</p>

<hr />

<p>Working with thoughtful, principled engineers and talented designers over the years, I've picked up enough from both to navigate the intersection, acting as glue when needed. The pull between disciplines often feels like a tension — a choice on which edge of the split I want to fall on. But the balancing act itself is also what keeps me attentive, and the work rewarding.</p>

<p>I'm taking this mindset to Plain, and I'm curious to see how the lines continue shifting in a new context — especially now, as AI is redefining in real time how we design and build for the web. Methods change, but the results will depend on the same core principles. There's plenty still to work out!</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:ashby" role="doc-endnote">
      <p>I spoke to several companies when looking for my next role, and <a href="https://ashbyhq.com">Ashby</a> stood out for how directly they probed for UI engineering instinct. One take-home involved designing a Combobox API. It captured the essence of bridging disciplines: creating a <em>language</em> that promotes quality, feels intuitive to developers, and flexible for designers. <a href="#fnref:ashby" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:websites" role="doc-endnote">
      <p>It came at just the right moment — as a good friend put it during a spell of jadedness, <em>it's just websites in the end</em>, and I'd found it hard to disagree. Freiberg's care in every 150ms transition was a good nudge that I'm still hungry for the details. <a href="#fnref:websites" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Elise Hein</name></author><summary type="html"><![CDATA[I love building for the web.]]></summary></entry><entry><title type="html">Visual design work examples from Elise</title><link href="https://elisehe.in/plain" rel="alternate" type="text/html" title="Visual design work examples from Elise" /><published>2025-03-10T00:00:00+00:00</published><updated>2025-03-10T00:00:00+00:00</updated><id>https://elisehe.in/plain</id><content type="html" xml:base="https://elisehe.in/plain"><![CDATA[<p class="no-drop-caps">Hello, Plain 👋🏻 Here's a collection of past projects that showcase the more visual side of my work.</p>

<h4 id="griffin-ui-design"><a href="/griffin-ui-design">Griffin UI Design</a></h4>

<p>A bird's-eye view of the visual design work I produced for the family of API-first banking products while at <a href="https://griffin.com">Griffin</a></p>

<h4 id="design-guidelines-for-a-table-component"><a href="https://design.griffin.com/components/table">Design guidelines for a Table component</a></h4>

<p>A set of interactive design guidelines that I wrote for the <code class="language-plaintext highlighter-rouge">Table</code> component in Griffin's design system</p>

<h4 id="fora-health-ui-design"><a href="/fora-health-ui-design">Fora Health UI Design</a></h4>

<p>A bird's-eye view of the visual UI design I produced for the Fora Health app during my time at <a href="https://ctrl-group.com">Ctrl Group</a></p>

<h4 id="schematics-a-love-story"><a href="https://schematics.elisehe.in">Schematics: A Love Story</a></h4>

<p>An animated a collection of diagrams from a visual poetry book by Julian Hibbard</p>

<h4 id="tenfold"><a href="https://elisehe.in/2016/09/30/tenfold">Tenfold</a></h4>

<p>A number puzzle game for iOS
 </p>]]></content><author><name>Elise Hein</name></author><summary type="html"><![CDATA[Hello, Plain 👋🏻 Here's a collection of past projects that showcase the more visual side of my work.]]></summary></entry><entry><title type="html">Griffin UI design</title><link href="https://elisehe.in/griffin-ui-design/" rel="alternate" type="text/html" title="Griffin UI design" /><published>2025-02-28T00:00:00+00:00</published><updated>2025-02-28T00:00:00+00:00</updated><id>https://elisehe.in/griffin-ui-design</id><content type="html" xml:base="https://elisehe.in/griffin-ui-design/"><![CDATA[<div class="article-body">

  <p>At Griffin, I led the design and build of a multi-theme design system powering two complex banking apps and several websites.</p>

  <p>As a generalist designer/developer, my design work spanned from higher-level product design (working on features such as billing, Open Banking, and API security), low-level UI polish, producing public-facing marketing material, and writing interactive design guidelines.<sup id="fnref:designdocs" role="doc-noteref"><a href="#fn:designdocs" class="footnote">1</a></sup></p>

  <p>In most of the below examples, I produced both the design and the implementation. All but a few made their way to production.</p>

</div>

<div class="article-body">
<small class="note">This is a bird's-eye view focusing on visual design; more detailed prototypes and examples are available on request.
</small>
<small class="note"><strong>For best experience, view on desktop.</strong>
</small>
</div>

<div class="article-body">

  <h2 id="app-and-ui-design">App and UI design</h2>

</div>

<div class="griffin-gallery griffin-gallery--app">
  <div>
    <img src="/assets/post-assets/griffin/open-banking-mobile.svg" alt="" />
  </div>
  <div>
    <div>
      <video width="100" autoplay="" loop="" muted="" class="no-shadow">
        <source src="/assets/post-assets/griffin/payment-status.mp4" type="video/mp4" />
      </video>
    </div>
  </div>
  <div>
    <img src="/assets/post-assets/griffin/delete-public-key.svg" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/approve-redirect.svg" class="no-shadow" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/timeline-payment-returning.png" class="no-shadow" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/confirm-payment-dialog.png" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/combobox.png" class="no-shadow" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/confirm-fee-changes.png" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/invoice.png" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/alerts.png" class="no-shadow" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/signatures.svg" alt="" />
  </div>
  <!-- <div>
    <img src="/assets/post-assets/griffin/signatures-empty.svg" alt="" />
  </div> -->
  <div>
    <img src="/assets/post-assets/griffin/cop-no-match.png" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/update-fees.gif" alt="" />
  </div>
</div>

<div class="article-body">

  <h2 id="website-design-and-marketing-assets">Website design and marketing assets</h2>

</div>

<div class="griffin-gallery griffin-gallery--web">
  <div>
    <img src="/assets/post-assets/griffin/four-tiles.svg" class="no-shadow" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/verify.png" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/faq-dialog.png" alt="" />
  </div>
  <div>
    <img src="/assets/post-assets/griffin/pricing-mobile.png" class="no-shadow" alt="" />
  </div>
  <div>
    <video width="300" autoplay="" loop="" muted="" class="no-shadow">
      <source src="/assets/post-assets/griffin/funds-flow.mp4" type="video/mp4" />
    </video>
  </div>
</div>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:designdocs" role="doc-endnote">
      <p>Some examples include guidelines for building <a href="https://design.griffin.com/components/table">accessible and consistent tables</a>, and how not to abuse the humble <a href="https://design.griffin.com/components/link">anchor link</a>. <a href="#fnref:designdocs" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Elise Hein</name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Fighting inter-component HTML bloat</title><link href="https://elisehe.in/2023/03/27/minimal-html-in-design-systems.html" rel="alternate" type="text/html" title="Fighting inter-component HTML bloat" /><published>2023-03-27T00:00:00+00:00</published><updated>2023-03-27T00:00:00+00:00</updated><id>https://elisehe.in/2023/03/27/minimal-html-in-design-systems</id><content type="html" xml:base="https://elisehe.in/2023/03/27/minimal-html-in-design-systems.html"><![CDATA[<p>When we decide what qualifies as a component in a component library or design system, we aim for a separation of concerns: we decouple something that creates whitespace from something that creates frames from something that styles type so that each can be reused in different contexts. But when each UI characteristic is represented by its own DOM tree, this decoupling can — by design — result in HTML bloat.</p>

<p>Let's look at where this comes from, why redundant HTML should be avoided in the first place, and how to do so without sacrificing the neat separation of concerns that helps to create the shared language between design and development.</p>

<p><em>Note: The examples in this post will be React-specific, but the core idea extends to most JS-based component libraries.<sup id="fnref:classbasedjsbased" role="doc-noteref"><a href="#fn:classbasedjsbased" class="footnote">1</a></sup></em></p>

<p>Consider a <code class="language-plaintext highlighter-rouge">Card</code> component with a title and optional description:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Card</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">Stack</span> <span class="na">spacing</span><span class="p">=</span><span class="s">"medium"</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">Heading</span><span class="p">&gt;</span>Card title<span class="p">&lt;/</span><span class="nc">Heading</span><span class="p">&gt;</span>

    <span class="si">{</span><span class="nx">description</span> <span class="o">&amp;&amp;</span>
      <span class="p">&lt;</span><span class="nc">Text</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">description</span><span class="si">}</span><span class="p">&lt;/</span><span class="nc">Text</span><span class="p">&gt;</span><span class="si">}</span>
  <span class="p">&lt;/</span><span class="nc">Stack</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nc">Card</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>From the component library design point of view, this makes sense:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Card</code> is responsible for visually framing the component.</li>
  <li><code class="language-plaintext highlighter-rouge">Stack</code> is responsible for the spacing between the heading and description.</li>
</ul>

<p>Let's look at the HTML this produces. A simplified version might be something like this:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;style </span><span class="na">type=</span><span class="s">"text/css"</span><span class="nt">&gt;</span>
  <span class="nc">.card</span> <span class="p">{</span>
    <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="no">silver</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.stack</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
    <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">column</span><span class="p">;</span>
    <span class="py">gap</span><span class="p">:</span> <span class="m">1rem</span><span class="p">;</span>
  <span class="p">}</span>
<span class="nt">&lt;/style&gt;</span>

<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"card"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"stack"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;h2&gt;</span>Card title<span class="nt">&lt;/h2&gt;</span>
    <span class="nt">&lt;p&gt;</span>Description<span class="nt">&lt;/p&gt;</span>
  <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<p>Strictly speaking, you don't need two wrapper <code class="language-plaintext highlighter-rouge">div</code>s to achieve the intended look. If you'd never been “tainted” with the design system mindset and instead built this bit of UI using plain HTML and CSS, I'd be willing to bet you'd instinctively combine the styles for the card frame and spacing between children into a single class<sup id="fnref:codegolfing" role="doc-noteref"><a href="#fn:codegolfing" class="footnote">2</a></sup>. The card description could be included or omitted without any impact on the markup:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;style </span><span class="na">type=</span><span class="s">"text/css"</span><span class="nt">&gt;</span>
  <span class="nc">.card</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
    <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">column</span><span class="p">;</span>
    <span class="py">gap</span><span class="p">:</span> <span class="m">1rem</span><span class="p">;</span>
    <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="no">silver</span><span class="p">;</span>
  <span class="p">}</span>
<span class="nt">&lt;/style&gt;</span>

<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"card"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;h2&gt;</span>Card title<span class="nt">&lt;/h2&gt;</span>
  <span class="nt">&lt;p&gt;</span>Description<span class="nt">&lt;/p&gt;</span>
<span class="nt">&lt;/div&gt;</span>

<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"card"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;h1&gt;</span>Card title<span class="nt">&lt;/h1&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<p>We have lost the redundant wrapper. But we have also lost the neatly separated areas of responsibility for the card frame and element spacing. The importance of neither should be overlooked, but satisfying both requires us to be deliberate in how we design our component APIs.</p>

<p>When you're the one authoring a component, you have a level of control over the markup and styling that lets you keep bloat to a minimum. The onus cannot be on the <em>consumer</em> of your library to make these same optimisations at the inter-component level, because they probably won't. Why not?</p>
<ul>
  <li>Semantics and accessibility are usually handled for you in design system components, so it can be easy to forget that this doesn't extend to page-level (inter-component) code.</li>
  <li>While minimising markup bloat isn't particularly tricky, it can result in a fair amount of boilerplate.</li>
</ul>

<p>Creating context-aware components in a way that consumers can ignore inter-component markup altogether is unrealistic. For now, we cannot bundle good page-level markup hygiene free into our design systems, but we should at the least make working towards it convenient.</p>

<p>I'd like to cover a few ways that component APIs can be extended to make the underlying markup more flexible. But before that, a sidebar on why bloated HTML should sound alarm bells in the first place. Should we be worried about div soup at all in 2023?</p>

<h2 id="why-avoid-extra-wrapper-elements">Why avoid extra wrapper elements?</h2>

<h3 id="1-bloated-html-hurts-performance">1. Bloated HTML hurts performance</h3>

<p>Redundant wrapper elements are a form of bloated HTML, and bloated HTML can <a href="https://developer.chrome.com/en/docs/lighthouse/performance/dom-size/">hurt performance</a>, both during page load (resource size) and runtime (costly layout reflows and element querying).</p>

<p>We obsessively optimise when it comes to scripts, styles, and other resources; interestingly, that doesn't always apply to HTML. In the case of single-page apps, it may feel like you stand to gain more in performance if you focus on JavaScript. In the case of server-rendered, non-interactive sites, <a href="https://blog.jim-nielsen.com/2021/thoughts-on-avoiding-an-excessive-dom-size/">some people doubt</a> that the performance impact of a large DOM is noticeable.</p>

<p>We also lack the required tooling: according to Jens Meiert, not enough HTML minifiers <a href="https://meiert.com/en/blog/html-performance/">prune optional HTML tags and default attributes</a><sup id="fnref:optionalhtml" role="doc-noteref"><a href="#fn:optionalhtml" class="footnote">3</a></sup>. When it comes specifically to unneeded wrapper elements, tooling wouldn't help us anyway (you would need a pretty sophisticated analysis of the associated styling to determine if an element can really be omitted).</p>

<h3 id="2-redundant-elements-can-create-problems-with-accessibility">2. Redundant elements can create problems with accessibility</h3>

<p>Our markup needs to be semantic; an extra div here and there doesn't take anything away from that. But a misplaced extra element can make your carefully selected semantic tags useless.</p>

<p>This happens mainly with elements that have strict rules about containment or placement. For example:</p>

<ul>
  <li>A <code class="language-plaintext highlighter-rouge">legend</code> must appear as the first child of <code class="language-plaintext highlighter-rouge">fieldset</code>.</li>
  <li>A <code class="language-plaintext highlighter-rouge">figcaption</code> must appear as the first or last child of <code class="language-plaintext highlighter-rouge">figure</code>. </li>
  <li>A <code class="language-plaintext highlighter-rouge">li</code> must appear as a direct child of either <code class="language-plaintext highlighter-rouge">ul</code>, <code class="language-plaintext highlighter-rouge">ol</code> or <code class="language-plaintext highlighter-rouge">menu</code>.</li>
  <li><del><code class="language-plaintext highlighter-rouge">dt</code> and <code class="language-plaintext highlighter-rouge">dd</code> must both appear as direct children of <code class="language-plaintext highlighter-rouge">dl</code></del><sup id="fnref:definitionlistsspec" role="doc-noteref"><a href="#fn:definitionlistsspec" class="footnote">4</a></sup></li>
</ul>

<p>Design system components with a strict separation of concerns can easily break this<sup id="fnref:whyfragments" role="doc-noteref"><a href="#fn:whyfragments" class="footnote">5</a></sup>:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">Columns</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">Column</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">li</span><span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nc">Column</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">Column</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">li</span><span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nc">Column</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">Column</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">li</span><span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nc">Column</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nc">Columns</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>Out of the top 100 most visited websites, <a href="https://meiert.com/en/blog/valid-html-2022/">not a single one used valid HTML in 2022</a>. Misplaced wrappers may play only a small role in this, but if we can author design system components that make it easier to avoid, we should.</p>

<h3 id="3-redundant-elements-can-break-styling">3. Redundant elements can break styling</h3>

<p>Just as the extra element is sometimes legitimately needed for specific styling needs, in other situations it can break your layout. When using CSS flexbox and grid, each element in the flex or grid layout must be a direct child of the parent with <code class="language-plaintext highlighter-rouge">display: grid</code> or <code class="language-plaintext highlighter-rouge">display: flex</code>.</p>

<h3 id="4-deeply-nested-dom-trees-are-annoying-to-work-with">4. Deeply nested DOM trees are annoying to work with</h3>

<p>Finally, working with deeply nested DOM trees just slows you down.</p>

<p>When making sense of a bit of HTML, I scan it for landmarks and other elements that hint at the structure of the content. This becomes harder to do if the number of meaningful elements is eclipsed by those that are meaningless. Debugging becomes harder, tracking down nodes in the inspector becomes more tedious, and the joy of the craft is diminished.</p>

<hr />

<h2 id="making-component-markup-flexible">Making component markup flexible</h2>

<p>Here's a non-complete list of techniques I use to make the underlying markup in my components more flexible. Many of these primarily serve some other need but come with the bonus of reducing redundant wrappers.</p>

<h3 id="1-always-accept-inline-styles-and-classes">1. Always accept inline styles and classes</h3>

<p>Component libraries often come with <code class="language-plaintext highlighter-rouge">Box</code> and other generic container components. These primitives are encouraged, among other use cases, when you need custom styling.</p>

<p>Why not just use a <code class="language-plaintext highlighter-rouge">div</code>? In some design systems, this comes down to the use of CSS-in-JS and/or theming libraries: going via <code class="language-plaintext highlighter-rouge">Box</code> or an equivalent primitive usually comes with direct access to the design system's tokens. If the source of truth for your tokens is in JavaScript, a <code class="language-plaintext highlighter-rouge">div</code> styled with plain old CSS just won't cut it. Neither will tacking a class or inline styles onto an existing component<sup id="fnref:customstyleprop" role="doc-noteref"><a href="#fn:customstyleprop" class="footnote">6</a></sup>.</p>

<p><code class="language-plaintext highlighter-rouge">Box</code> is a great primitive to expose, but should not be seen as the sole tool for style overrides. When a component disallows style overrides or limits them to a subset of CSS properties, you end up with a tree of boxes when your styling adjustments could easily have been made on the nodes already present in the DOM.</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="cm">/* Rigid style overrides */</span><span class="p">}</span>
<span class="p">&lt;</span><span class="nc">Box</span>
  <span class="na">borderRadius</span><span class="p">=</span><span class="s">"borderRadius20"</span>
  <span class="na">borderStyle</span><span class="p">=</span><span class="s">"solid"</span>
  <span class="na">borderWidth</span><span class="p">=</span><span class="s">"borderWidth10"</span>
  <span class="na">borderColor</span><span class="p">=</span><span class="s">"colorBorderPrimaryWeak"</span>
<span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">FancyComponentWithoutABorder</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">Heading</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">Box</span> <span class="na">fontWeight</span><span class="p">=</span><span class="s">"bold"</span><span class="p">&gt;</span>Heading<span class="p">&lt;/</span><span class="nc">Box</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nc">Heading</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nc">FancyComponentWithoutABorder</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nc">Box</span><span class="p">&gt;</span>

<span class="p">{</span><span class="cm">/* Flexible style overrides */</span><span class="p">}</span>
<span class="p">&lt;</span><span class="nc">FancyComponentWithoutABorder</span>
  <span class="na">borderRadius</span><span class="p">=</span><span class="s">"borderRadius20"</span>
  <span class="na">borderStyle</span><span class="p">=</span><span class="s">"solid"</span>
  <span class="na">borderWidth</span><span class="p">=</span><span class="s">"borderWidth10"</span>
  <span class="na">borderColor</span><span class="p">=</span><span class="s">"colorBorderPrimaryWeak"</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">Heading</span> <span class="na">fontWeight</span><span class="p">=</span><span class="s">"bold"</span><span class="p">&gt;</span>Heading<span class="p">&lt;/</span><span class="nc">Heading</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nc">FancyComponentWithoutABorder</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>So — whether you're limited in the use of vanilla CSS because of design tokens or you just want to formalise <em>how</em> styles are overridden, make sure you expose <em>a</em> way of doing so on every component. If people need to adjust component styling, they will. You may as well make targeting the right node as easy as possible.</p>

<h3 id="2-make-the-root-node-tag-customisable">2. Make the root node tag customisable</h3>

<p>Overriding the root node tag of a component is usually exposed via an <code class="language-plaintext highlighter-rouge">as</code> prop like so: <code class="language-plaintext highlighter-rouge">&lt;Box as="header"&gt;</code>.</p>

<p>It's uncommon for the <code class="language-plaintext highlighter-rouge">as</code> prop or equivalent <em>not</em> to be provided these days. Just to explain its importance, here's one way you end up with extra DOM nodes when you're not allowed to modify the rendered HTML element. Say you want to render a list of cards:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Stack</span> <span class="na">spacing</span><span class="p">=</span><span class="s">"small"</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">Card</span><span class="p">&gt;&lt;/</span><span class="nc">Card</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">Card</span><span class="p">&gt;&lt;/</span><span class="nc">Card</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">Card</span><span class="p">&gt;&lt;/</span><span class="nc">Card</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nc">Stack</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>Using <code class="language-plaintext highlighter-rouge">as</code>, this could be rewritten as follows:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Stack</span> <span class="na">as</span><span class="p">=</span><span class="s">"ul"</span> <span class="na">spacing</span><span class="p">=</span><span class="s">"small"</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">Card</span> <span class="na">as</span><span class="p">=</span><span class="s">"li"</span> <span class="p">/&gt;</span>
  <span class="p">&lt;</span><span class="nc">Card</span> <span class="na">as</span><span class="p">=</span><span class="s">"li"</span> <span class="p">/&gt;</span>
  <span class="p">&lt;</span><span class="nc">Card</span> <span class="na">as</span><span class="p">=</span><span class="s">"li"</span> <span class="p">/&gt;</span>
<span class="p">&lt;/</span><span class="nc">Stack</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>Even worse than a component that doesn't provide an <code class="language-plaintext highlighter-rouge">as</code> prop is one that doesn't provide an <code class="language-plaintext highlighter-rouge">as</code> prop <em>and</em> renders something other than a generic <code class="language-plaintext highlighter-rouge">div</code><sup id="fnref:aspropexceptions" role="doc-noteref"><a href="#fn:aspropexceptions" class="footnote">7</a></sup>. A good example is something like <code class="language-plaintext highlighter-rouge">Heading</code>. It may be tempting to enforce one of <code class="language-plaintext highlighter-rouge">h1</code>–<code class="language-plaintext highlighter-rouge">h6</code> elements under the hood. But (as much as you discourage it in your documentation) that would be making the assumption that your <code class="language-plaintext highlighter-rouge">Heading</code> component will never be used to show arbitrary text that just needs to look prominent.</p>

<h3 id="3-consider-if-your-component-needs-to-output-its-own-root-node">3. Consider if your component needs to output its own root node</h3>

<p>Similarly to <code class="language-plaintext highlighter-rouge">as</code>, I would like to see much more of <code class="language-plaintext highlighter-rouge">asChild</code> or an equivalent in the wild.</p>

<p>Popularised by <a href="https://www.radix-ui.com/docs/primitives/overview/styling#changing-the-rendered-element">Radix UI</a>, <code class="language-plaintext highlighter-rouge">asChild</code> is a way to merge a single-node component's props onto its own single child node. The node itself will be omitted, but its props and functionality will be copied into its child. This works great in highly composable components, or when one component provides behaviour while the other provides styling, as illustrated by Radix UI's own examples:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">Dialog</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@radix-ui/react-dialog</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">MyButton</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./my-button</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nc">Dialog</span><span class="p">.</span><span class="nc">Root</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">Dialog</span><span class="p">.</span><span class="nc">Trigger</span> <span class="na">asChild</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nc">MyButton</span><span class="p">&gt;</span>Open dialog<span class="p">&lt;/</span><span class="nc">MyButton</span><span class="p">&gt;</span>
      <span class="p">&lt;/</span><span class="nc">Dialog</span><span class="p">.</span><span class="nc">Trigger</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">Dialog</span><span class="p">.</span><span class="nc">Content</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nc">Dialog</span><span class="p">.</span><span class="nc">Content</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nc">Dialog</span><span class="p">.</span><span class="nc">Root</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">DialogTrigger</code> might by default return a <code class="language-plaintext highlighter-rouge">&lt;button&gt;</code>, but we want to use our own <code class="language-plaintext highlighter-rouge">MyButton</code> as the trigger instead. Without <code class="language-plaintext highlighter-rouge">asChild</code>, we may end up with <code class="language-plaintext highlighter-rouge">&lt;button&gt;&lt;button&gt;&lt;/button&gt;&lt;/button&gt;</code>, where the extra element is both invalid and unnecessary. With <code class="language-plaintext highlighter-rouge">asChild</code>, the output is a single <code class="language-plaintext highlighter-rouge">&lt;button/&gt;</code> with styling from <code class="language-plaintext highlighter-rouge">&lt;MyButton&gt;</code> and dialog trigger semantics from <code class="language-plaintext highlighter-rouge">&lt;Dialog.Trigger&gt;</code>.</p>

<p>Support for <code class="language-plaintext highlighter-rouge">asChild</code> is especially critical in libraries that provide highly composable components (of which Radix UI is a great example). Compound components <a href="https://epicreact.dev/soul-crushing-components/">come with a range of benefits</a>, but they are also more prone to outputting bloated HTML unless you specifically build in safeguards to avoid it.</p>

<p>If you want to build in <code class="language-plaintext highlighter-rouge">asChild</code> support into your own components, Radix UI provides the nifty <a href="https://www.radix-ui.com/docs/primitives/utilities/slot">Slot</a> utility to handle merging of event handlers and other props for you.</p>

<p>In more specialised components, it may not always be appropriate to support a full merge of props à la <code class="language-plaintext highlighter-rouge">asChild</code>. But the basic idea still stands: just because we use a HTML-like entity as the vessel for a component doesn't mean it needs to output an actual HTML node. Some examples:</p>
<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">passHref</code> in <a href="https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-custom-component-that-wraps-an-a-tag"><code class="language-plaintext highlighter-rouge">Link</code> from Next.js</a></strong>: <code class="language-plaintext highlighter-rouge">&lt;Link href="/foo" passHref&gt;&lt;Text&gt;Link&lt;/Text&gt;&lt;/Link&gt;</code> outputs a single <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> tag.</li>
  <li><strong>Enhancing the behaviour of child nodes</strong>: If a component is responsible purely for attaching attributes to its <em>child</em> node, why introduce a wrapper? A good example is the <code class="language-plaintext highlighter-rouge">VisuallyHidden</code> component, which usually just <a href="https://github.com/radix-ui/primitives/blob/main/packages/react/visually-hidden/src/VisuallyHidden.tsx">adds a set of inline styles</a> to its content. If its content consists of multiple sibling nodes, the wrapper is justified. But if it wraps a single element, the set of styles can just as well be attached to that one element, and the wrapper omitted.</li>
  <li><strong>Wrappers that operate on multiple children</strong>: If a wrapper node only has an effect when it has more than one child, why not double-check the number of children before applying that effect? Standard components such as <code class="language-plaintext highlighter-rouge">Stack</code> or <code class="language-plaintext highlighter-rouge">Inline</code> are great examples of this, because they're responsible for laying out multiple children along an axis. Single child → no gaps or axes to work with → why the wrapper? <sup id="fnref:omitforsinglechild" role="doc-noteref"><a href="#fn:omitforsinglechild" class="footnote">8</a></sup></li>
</ul>

<h3 id="4-make-your-stacks-smarter">4. Make your stacks smarter</h3>

<p>This one is a bit special, as it concerns a specific component: the ubiquitous <code class="language-plaintext highlighter-rouge">Stack</code> (sometimes differentiated as <code class="language-plaintext highlighter-rouge">Stack</code> and <code class="language-plaintext highlighter-rouge">Inline</code> or <code class="language-plaintext highlighter-rouge">Flex</code>, depending on the implementation).</p>

<p>Let's go back to the <code class="language-plaintext highlighter-rouge">Card</code> example from the start of this post and extend it to support a subtitle:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Card</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">Stack</span> <span class="na">spacing</span><span class="p">=</span><span class="s">"medium"</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">Heading</span> <span class="na">level</span><span class="p">=</span><span class="si">{</span><span class="mi">2</span><span class="si">}</span><span class="p">&gt;</span>Card title<span class="p">&lt;/</span><span class="nc">Heading</span><span class="p">&gt;</span>
    <span class="si">{</span><span class="nx">subtitle</span> <span class="o">&amp;&amp;</span>
        <span class="p">&lt;</span><span class="nc">Heading</span> <span class="na">level</span><span class="p">=</span><span class="si">{</span><span class="mi">3</span><span class="si">}</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">subtitle</span><span class="si">}</span><span class="p">&lt;/</span><span class="nc">Heading</span><span class="p">&gt;</span><span class="si">}</span>

    <span class="si">{</span><span class="nx">description</span> <span class="o">&amp;&amp;</span>
      <span class="p">&lt;</span><span class="nc">Text</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">description</span><span class="si">}</span><span class="p">&lt;/</span><span class="nc">Text</span><span class="p">&gt;</span><span class="si">}</span>
  <span class="p">&lt;/</span><span class="nc">Stack</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nc">Card</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>The optional subtitle happens to sit closer to the title than it does to the description, which makes the wrapper <code class="language-plaintext highlighter-rouge">Stack</code> with medium spacing insufficient. Along comes a second <code class="language-plaintext highlighter-rouge">Stack</code>, this time with <code class="language-plaintext highlighter-rouge">small</code> spacing:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Card</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">Stack</span> <span class="na">spacing</span><span class="p">=</span><span class="s">"medium"</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">Stack</span> <span class="na">spacing</span><span class="p">=</span><span class="s">"small"</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">Heading</span> <span class="na">level</span><span class="p">=</span><span class="si">{</span><span class="mi">2</span><span class="si">}</span><span class="p">&gt;</span>Card title<span class="p">&lt;/</span><span class="nc">Heading</span><span class="p">&gt;</span>
      <span class="si">{</span><span class="nx">subtitle</span> <span class="o">&amp;&amp;</span>
          <span class="p">&lt;</span><span class="nc">Heading</span> <span class="na">level</span><span class="p">=</span><span class="si">{</span><span class="mi">3</span><span class="si">}</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">subtitle</span><span class="si">}</span><span class="p">&lt;/</span><span class="nc">Heading</span><span class="p">&gt;</span><span class="si">}</span>
    <span class="p">&lt;/</span><span class="nc">Stack</span><span class="p">&gt;</span>

    <span class="si">{</span><span class="nx">description</span> <span class="o">&amp;&amp;</span>
      <span class="p">&lt;</span><span class="nc">Text</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">description</span><span class="si">}</span><span class="p">&lt;/</span><span class="nc">Text</span><span class="p">&gt;</span><span class="si">}</span>
  <span class="p">&lt;/</span><span class="nc">Stack</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nc">Card</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>We are well on our way into div soup territory.</p>

<p>Why can't stack-like components support variable spacing between children? Neither flex nor grid layouts currently support variable gaps, so at the moment the implementation for something like this would necessarily rely on <code class="language-plaintext highlighter-rouge">margin</code> on each child rather than <code class="language-plaintext highlighter-rouge">gap</code> on the wrapper.</p>

<p>This would violate a core tenet of design systems — that elements should never be responsible for layout outside of their own bounds.</p>

<p>I think this violation is justified. Unless you're working with straight-up lists of repeated elements, it's uncommon for a well-designed UI to feature more than 2–3 consequtive elements with equal spacing anyway. The result is deeply nested stacks upon stacks upon stacks when really we should be dealing with a flat list of siblings — unnecessary wrappers at their most obvious.</p>

<p>I encourage supporting something like <code class="language-plaintext highlighter-rouge">&lt;Stack spacing={["small", "small", "medium"]}&gt;</code> alongside the standard <code class="language-plaintext highlighter-rouge">&lt;Stack spacing="medium"&gt;</code>. Because stack-like components are so ubiquitous, the impact on redundant HTML will be noticeable.</p>

<h3 id="5-raise-awareness-in-documentation">5. Raise awareness in documentation</h3>

<p>Markup manipulation should be normalised in your documentation.</p>

<p>As mentioned, the <code class="language-plaintext highlighter-rouge">as</code> prop is quite a common feature in component libraries, but it's fair to assume it's not used as often as it could be<sup id="fnref:elementdiversity" role="doc-noteref"><a href="#fn:elementdiversity" class="footnote">9</a></sup>. Could insufficient documentation and awareness be one of the causes?</p>

<p>I'd argue that it's rare to achieve great markup hygiene at the page level without frequent use of one of the described techniques (style overrides and the <code class="language-plaintext highlighter-rouge">as</code> prop being the most critical). As such, their use should be illustrated in all documented code examples to make them more realistic.</p>

<p>Instead of illustrating the use of the <code class="language-plaintext highlighter-rouge">Columns</code> component with a bare-bones example like this:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Columns</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">Column</span><span class="p">&gt;</span>A column<span class="p">&lt;/</span><span class="nc">Column</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">Column</span><span class="p">&gt;</span>A column<span class="p">&lt;/</span><span class="nc">Column</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nc">Columns</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>…consider adding (at least somewhat) realistic semantics into your code snippets:</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Columns</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span> <span class="p">{</span> <span class="na">height</span><span class="p">:</span> <span class="dl">"</span><span class="s2">100vh</span><span class="dl">"</span> <span class="p">}</span> <span class="si">}</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">Column</span> <span class="na">as</span><span class="p">=</span><span class="s">"nav"</span><span class="p">&gt;</span>Navigation<span class="p">&lt;/</span><span class="nc">Column</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">Column</span> <span class="na">as</span><span class="p">=</span><span class="s">"main"</span><span class="p">&gt;</span>Main content<span class="p">&lt;/</span><span class="nc">Column</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nc">Columns</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>It also helps to mention the default root HTML element that a component outputs in its description (someone consuming your docs will be forced to take the extra second to consider whether that element needs overriding).</p>

<p>Finally, it goes without saying that it helps to frequently <em>look</em> at and analyse the HTML your components produce. Storybook addons like <a href="https://storybook.js.org/addons/@whitespace/storybook-addon-html">HTML preview</a> and <a href="https://storybook.js.org/addons/@storybook/addon-a11y">Accessibility</a> are as much (or more) about raising awareness as they are about functionality.</p>

<hr />

<p>When viewed in the context of a single component, redundant HTML can seem inconsequential. And given the current zeitgeist around AI, obsessing about an extra <code class="language-plaintext highlighter-rouge">div</code> may feel a bit 🤡</p>

<p>But when viewed collectively and across products, the importance of lean markup is compounded. In her article, <a href="https://amyhupe.co.uk/articles/building-conscious-design-systems/"><em>Building conscious design systems</em></a>, Amy Hupe wrote:</p>

<blockquote>
  <p>If we can use our design systems to speed up meaningful work, standardise things to a high quality, and scale the things we actually want to reproduce — then the reverse is also true.  It means that we can also use our design systems to speed up problematic work, standardise things to a poor quality, and scale things we don't want to reproduce.</p>
</blockquote>

<p>Bloat scales. Let's make it easier for component consumers to avoid it.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:classbasedjsbased" role="doc-endnote">
      <p>In JavaScript-based component libraries, the “vessel” for a single component is the HTML node: <code class="language-plaintext highlighter-rouge">&lt;MyComponent&gt;</code> in libraries like React or Vue, <code class="language-plaintext highlighter-rouge">&lt;my-component&gt;</code> in native web components. Contrast this to CSS-based libraries such as Salesforce's <a href="https://www.lightningdesignsystem.com/guidelines/markup-and-style/">Lightning</a> where the consumer is responsible for attaching predefined classes to their own HTML structure. HTML bloat as a biproduct occurs mostly in the former. <a href="#fnref:classbasedjsbased" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:codegolfing" role="doc-endnote">
      <p>How few HTML elements can you get away with while keeping the intended styling? If you're into <a href="https://en.wikipedia.org/wiki/Code_golf">code golfing</a>, you could look at the number of characters of readable text to characters of code as a success metric. For inspiration, Tim Berners-Lee's <a href="http://info.cern.ch/hypertext/WWW/Summary.html"><em>World Wide Web Summary</em></a> from 1991 boasts a ratio of around 0.9 (<a href="https://mondaynote.com/bloated-html-the-best-and-the-worse-cac6eb06496d">less than 4200 characters of readable text for less than 4600 characters of code</a>). <a href="#fnref:codegolfing" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:optionalhtml" role="doc-endnote">
      <p>Note that “optional” here refers not to extra wrappers, but to tags that are <a href="https://meiert.com/en/blog/optional-html/">optional syntactically</a>. <a href="#fnref:optionalhtml" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:definitionlistsspec" role="doc-endnote">
      <p>Thank you <a href="https://twitter.com/sebdedeyne/status/1640594363481767936">@sebdedeyne</a> for pointing out that you're actually <a href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-dl-element">allowed to wrap <code class="language-plaintext highlighter-rouge">&lt;dt&gt;&lt;/dt&gt;&lt;dd&gt;&lt;/dd&gt;</code> inside a <code class="language-plaintext highlighter-rouge">&lt;div&gt;</code></a>. <a href="#fnref:definitionlistsspec" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:whyfragments" role="doc-endnote">
      <p>Speaking specifically of React, sometimes these situations arise because a component rendering multiple children must wrap them in a single parent node; <a href="https://reactjs.org/docs/fragments.html#motivation">fragments</a> were added precisely to keep HTML valid when this happens. <a href="#fnref:whyfragments" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:customstyleprop" role="doc-endnote">
      <p>See, for example, the <code class="language-plaintext highlighter-rouge">css</code> prop in <a href="https://stitches.dev/docs/overriding-styles#the-css-prop">Stitches</a>, the <code class="language-plaintext highlighter-rouge">sx</code> prop in <a href="https://theme-ui.com/sx-prop">Theme UI</a>, a more constrained <code class="language-plaintext highlighter-rouge">style</code> prop in <a href="https://nulogy.design/guides/style-props/">Nulogy</a>. Other design systems expose an individual prop per styling property, for example, <a href="https://paste.twilio.design/primitives/box">Paste</a> or <a href="https://seek-oss.github.io/braid-design-system/components/Box#css-utilities">Braid</a>. <a href="#fnref:customstyleprop" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:aspropexceptions" role="doc-endnote">
      <p>Of course, highly specific exceptions apply, such as a <code class="language-plaintext highlighter-rouge">Link</code> having to render <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> in all instances. <a href="#fnref:aspropexceptions" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:omitforsinglechild" role="doc-endnote">
      <p>When you allow any component to receive inline styles or classes, having that component's root node be omitted when it has only a single child may come as a surprise to people who expected to see other, arbitrary attributes attached to the wrapper. For this reason, I sometimes use a boolean flag such as <code class="language-plaintext highlighter-rouge">omitForSingleChild</code> so that consumers can explicitly omit the wrapper node depending on context (avoiding the boilerplate involved with doing the conditional rendering inline). <a href="#fnref:omitforsinglechild" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:elementdiversity" role="doc-endnote">
      <p>Based on the <a href="https://almanac.httparchive.org/en/2020/markup#element-diversity">analysis of 7.5 million webpages in 2020</a>, element diversity was poor: out of the 112 available elements in HTML, the 90th percentile website only used 42. The most popular HTML element in 2020 continued to be <code class="language-plaintext highlighter-rouge">div</code>. <a href="#fnref:elementdiversity" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Elise Hein</name></author><summary type="html"><![CDATA[When we decide what qualifies as a component in a component library or design system, we aim for a separation of concerns: we decouple something that creates whitespace from something that creates frames from something that styles type so that each can be reused in different contexts. But when each UI characteristic is represented by its own DOM tree, this decoupling can — by design — result in HTML bloat.]]></summary></entry><entry><title type="html">The wasted potential of CSS attribute selectors</title><link href="https://elisehe.in/2022/10/16/attribute-selectors.html" rel="alternate" type="text/html" title="The wasted potential of CSS attribute selectors" /><published>2022-10-16T00:00:00+00:00</published><updated>2022-10-16T00:00:00+00:00</updated><id>https://elisehe.in/2022/10/16/attribute-selectors</id><content type="html" xml:base="https://elisehe.in/2022/10/16/attribute-selectors.html"><![CDATA[<p>I'm a long-time <a href="https://getbem.com/introduction/">BEM</a> user. The double underscore creeps into my code even when selector collision isn't an issue. I don't think it's BEM, specifically, that I'm drawn to (it just happened to be the methodology I first learned about and grew used to). It's the fact that a methodology, any methodology, adds structure and semantics to HTML class naming where inherently there isn't one.</p>

<p>It's why I always feel a bit lost when using CSS modules or CSS-in-JS libraries. What do you <em>mean</em> I can name my classes or styled components whatever I want? It's not unheard of for people to <a href="https://medium.com/trabe/using-bem-conventions-in-css-modules-leveraging-custom-webpack-loaders-fd985f72bcb2">use BEM <em>with</em> CSS modules</a>. This might seem silly  —  they solve the same problem, after all. But perhaps it shows that rigid class naming conventions are attractive beyond their power to avoid collisions. For better or worse, they allow us to gauge the role of each selector from the stylesheet alone.</p>

<p>In our preoccupation with classes, it's easy to think of them as the default selector. Historically, when people have talked about targeting elements by their attributes, it's been in the context of neat tricks or one liners. You make a mental note to use <code class="language-plaintext highlighter-rouge">a[href^=https://specific-domain.com]</code> the next time you want to use pink underlines for each link to one specific domain, and promptly forget about it forever. Or, to give a more practical example, attribute selectors have been recommended as a kind of linter or debugger for invalid HTML (<code class="language-plaintext highlighter-rouge">img:not([alt]) { border: 1rem solid red; }</code>).</p>

<p>More recently, the idea to treat attribute selectors on par with classes as first-class citizens has been proposed more widely. We're no longer talking about edge cases, but challenging the very defaultness of classes, all while not giving up that sense of structure that many of us look for in CSS naming conventions.</p>

<p>The two articles that have stood out for me are:</p>

<ul>
  <li><a href="https://www.aleksandrhovhannisyan.com/blog/represent-state-with-html-attributes-not-class-names/"><em>Represent state with HTML attributes not class names</em></a> by Aleksandr Hovhannisyan</li>
  <li><a href="https://www.keithcirkel.co.uk/css-classes-considered-harmful/"><em>CSS classes considered harmful</em></a> by Keith Cirkel</li>
</ul>

<p>Aleksandr and Keith both advocate for attribute selectors instead of class names, but while Aleksandr describes the more conservative approach of preferring attribute selectors where one already exists on the HTML element, Keith goes a step further and proposes adding your own <code class="language-plaintext highlighter-rouge">data-*</code> attribute instead of another class when you need something to hook into.</p>

<p>Let's look at both more closely.</p>

<h2 id="representing-nativestate">Representing native state</h2>

<p>Aleksandr Hovhannisyan makes the case that classes are too often used to style different states or variants of an element, where that state might  — or, critically, <em>should</em> —  already be semantically represented by other properties such as ARIA attributes or pseudo selectors.</p>

<p>No-one objects that you should prefer <code class="language-plaintext highlighter-rouge">input[disabled]</code> over <code class="language-plaintext highlighter-rouge">input.is-disabled</code>, because most of the time it's unnecessary to add that class name manually when you already have an attribute selector to hook into. But we tend to forget that there are plenty of other native ways to represent state that we should be using anyway  — namely ARIA attributes. (Though we don't get them for free as we do with pseudo classes like <code class="language-plaintext highlighter-rouge">:hover</code>, <code class="language-plaintext highlighter-rouge">:checked</code> or <code class="language-plaintext highlighter-rouge">:focus</code>.)</p>

<p>Compare the following. Using modifier classes:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"/page2/"</span> <span class="na">class=</span><span class="s">"nav-link nav-link--is-active"</span><span class="nt">&gt;</span>Page 2<span class="nt">&lt;/a&gt;</span>

<span class="nt">&lt;style&gt;</span>
<span class="nc">.nav-link.nav-link--is-active</span> <span class="p">{</span> <span class="p">}</span>
<span class="nt">&lt;/style&gt;</span>
</code></pre></div></div>

<p>Using ARIA attributes:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"/page2/"</span> <span class="na">class=</span><span class="s">"nav-link"</span> <span class="na">aria-current=</span><span class="s">"page"</span><span class="nt">&gt;</span>Page 2<span class="nt">&lt;/a&gt;</span>

<span class="nt">&lt;style&gt;</span>
<span class="nc">.nav-link</span><span class="o">[</span><span class="nt">aria-current</span><span class="o">=</span><span class="s1">"page"</span><span class="o">]</span> <span class="p">{</span> <span class="p">}</span>
<span class="nt">&lt;/style&gt;</span>
</code></pre></div></div>

<p>The crucial point here is that <code class="language-plaintext highlighter-rouge">aria-current="page"</code> should be included regardless of the styling needs of an active link, and I can't think of a reason why we should ever add an extra class where the state can already be selected for in a different way. Worst case scenario, this would mean you're implementing state management twice.</p>

<p>This promotes an a11y-first mindset — if there is no attribute or pseudo selector available to represent the state we wish to style, should we add one? Are we using the right HTML element? We are forced to go through a mental flow chart of native, semantic HTML and CSS features we could tap into before resorting to classes.</p>

<p>I'm reminded of <a href="https://testing-library.com/docs/queries/bytestid/">Testing Library's guiding principle</a> to only ever reach for the <code class="language-plaintext highlighter-rouge">testId</code> element selector when you've ruled out all other, more semantic querying options. What if we treated classes a bit like test IDs, appreciating them as a robust, generic fallback option that we can reach for when there are no other appropriate, semantic attributes available on the element?</p>

<p>Other people have covered thoroughly the many examples of using ARIA attributes and other semantic selectors for styling, so I won't cover any more here. Do have a read if you're interested:</p>

<ul>
  <li><a href="https://benmyers.dev/blog/semantic-selectors/"><em>Style with stateful, semantic selectors</em></a> by Ben Meyers</li>
  <li><a href="https://css-tricks.com/user-facing-state/"><em>User facing state</em></a> by Scott O'Hara</li>
  <li><a href="https://adrianroselli.com/2021/06/using-css-to-enforce-accessibility.html"><em>Using CSS to enforce accessibility</em></a> by Adrian Roselli</li>
</ul>

<h2 id="representing-customstate">Representing custom state</h2>

<p>Aleksandr Hovhannisyan explains well how the use of attribute selectors can result in better UX. I find it a satisfying coincidence that Keith Cirkel reaches the same conclusions coming from a place of developer experience. He highlights the DX issue with classes:</p>

<blockquote>
  <p>Classes, being a list of arbitrary strings, have no key-values, no private state, no complex types (which also means IDE support is quite limited) and rely on custom DSLs like BEM just to make them slightly more usable. We keep trying to implement parameters into a <code class="language-plaintext highlighter-rouge">Set&lt;string&gt;</code> when what we want is a <code class="language-plaintext highlighter-rouge">Map&lt;string, T&gt;</code>.</p>
  <footer><p><cite><a href="https://www.keithcirkel.co.uk/css-classes-considered-harmful">Keith Cirkel</a></cite></p></footer>
</blockquote>

<p>There's a well-known fundamental principle in software engineering: <a href="https://kentcdodds.com/blog/make-impossible-states-impossible"><strong>Make impossible states impossible</strong></a><sup id="fnref:impossiblestates" role="doc-noteref"><a href="#fn:impossiblestates" class="footnote">1</a></sup>.</p>

<p>My impression from Keith's article is that, at its core, this is the principle that class selectors violate. An element's classes are never guaranteed to reflect their state (nothing prevents you from adding the class <code class="language-plaintext highlighter-rouge">button--loading</code> to a button that isn't, in fact, loading), and classes that are used to represent one of many mutually exclusive variants are never guaranteed to be mutually exclusive (nothing prevents you from adding <code class="language-plaintext highlighter-rouge">button--primary</code> and <code class="language-plaintext highlighter-rouge">button--secondary</code> to the same button).</p>

<p>I love how Keith frames this as a type issue. As an example, we can look at the naming of some of <a href="https://tailwindcss.com/docs/responsive-design">Tailwind's utility classes</a>: using a colon to group related sets of classes could be taken as an attempt to signal mutual exclusivity. <code class="language-plaintext highlighter-rouge">&lt;div class="sm:w-80 sm:w-96"&gt;</code> should sound the alarm bells because the offending logic is immediately visible in the naming.</p>

<p>Keith reminds us that attribute selectors can be some of the most versatile in our CSS toolbelt  — <code class="language-plaintext highlighter-rouge">data-*</code> attributes, combined with the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors">rich variation in CSS attribute selectors</a>, could be used to capture strings as well as lists — and he encourages us to embrace them. Here lies the difference between his and Aleksandr's suggestions: it's not about merely using the native attributes already present on the element, it's about adding custom ones — instead of classes — when they're not.</p>

<p>An example from Keith's article:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.Card</span> <span class="p">{</span> <span class="c">/* ... */</span> <span class="p">}</span>
<span class="nc">.Card</span><span class="o">[</span><span class="nt">data-size</span><span class="o">=</span><span class="nt">big</span><span class="o">]</span> <span class="p">{</span> <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.Card</span><span class="o">[</span><span class="nt">data-size</span><span class="o">=</span><span class="nt">medium</span><span class="o">]</span> <span class="p">{</span> <span class="nl">width</span><span class="p">:</span> <span class="m">50%</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.Card</span><span class="o">[</span><span class="nt">data-size</span><span class="o">=</span><span class="nt">small</span><span class="o">]</span> <span class="p">{</span> <span class="nl">width</span><span class="p">:</span> <span class="m">25%</span><span class="p">;</span> <span class="p">}</span>

<span class="nc">.Card</span><span class="o">[</span><span class="nt">data-align</span><span class="o">=</span><span class="nt">left</span><span class="o">]</span> <span class="p">{</span> <span class="nl">text-align</span><span class="p">:</span> <span class="nb">left</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.Card</span><span class="o">[</span><span class="nt">data-align</span><span class="o">=</span><span class="nt">right</span><span class="o">]</span> <span class="p">{</span> <span class="nl">text-align</span><span class="p">:</span> <span class="nb">right</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.Card</span><span class="o">[</span><span class="nt">data-align</span><span class="o">=</span><span class="nt">center</span><span class="o">]</span> <span class="p">{</span> <span class="nl">text-align</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.Card</span><span class="o">[</span><span class="nt">data-border-collapse</span><span class="o">~=</span><span class="s1">"top"</span><span class="o">]</span> <span class="p">{</span> <span class="nl">border-top</span><span class="p">:</span> <span class="m">0</span> <span class="p">}</span>
<span class="nc">.Card</span><span class="o">[</span><span class="nt">data-border-collapse</span><span class="o">~=</span><span class="s1">"right"</span><span class="o">]</span> <span class="p">{</span> <span class="nl">border-right</span><span class="p">:</span> <span class="m">0</span> <span class="p">}</span>
<span class="nc">.Card</span><span class="o">[</span><span class="nt">data-border-collapse</span><span class="o">~=</span><span class="s1">"bottom"</span><span class="o">]</span> <span class="p">{</span> <span class="nl">border-bottom</span><span class="p">:</span> <span class="m">0</span> <span class="p">}</span>
<span class="nc">.Card</span><span class="o">[</span><span class="nt">data-border-collapse</span><span class="o">~=</span><span class="s1">"left"</span><span class="o">]</span> <span class="p">{</span> <span class="nl">border-left</span><span class="p">:</span> <span class="m">0</span> <span class="p">}</span>
</code></pre></div></div>

<p>It's much easier to avoid impossible states when you have different “slots” to fill in with the different groups of variants for your element, rather than shoving all of them in a single cobbled up string in <code class="language-plaintext highlighter-rouge">class</code>, however disciplined your naming may be.</p>

<p>And there's a reason why <code class="language-plaintext highlighter-rouge">&lt;article class="card" data-align="left" data-size="small" /&gt;</code> looks attractive — it's mirroring the APIs we're used to seeing in design systems and component libraries, but bringing it to vanilla HTML and CSS. Indeed, it's a small step from data attribute selectors to custom pseudo selectors or prop-based selectors when using Web Components (think <code class="language-plaintext highlighter-rouge">&lt;my-card align="left" size="small" /&gt;</code>).</p>

<p>Still, for now, if you're not yet using custom elements, there's a big difference between</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;article</span>
  <span class="na">class=</span><span class="s">"card"</span>
  <span class="na">data-loading=</span><span class="s">"true"</span>
  <span class="na">data-variant=</span><span class="s">"primary"</span>
  <span class="na">data-size=</span><span class="s">"large"</span>
  <span class="na">data-border=</span><span class="s">"top right"</span>
  <span class="na">data-elevation=</span><span class="s">"high"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>and</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;button</span>
  <span class="na">aria-label=</span><span class="s">"Toggle menu"</span>
  <span class="na">aria-controls=</span><span class="s">"navbar-menu"</span>
  <span class="na">aria-expanded=</span><span class="s">"true"</span>
  <span class="na">disabled</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>While I wholeheartedly agree with using attributes over classes when they're already present, adding your own may be a harder pill to swallow. Tapping into native attribute selectors doesn't conflict with any existing CSS methodology you may be using, but adding custom ones may require a bigger commitment and change in the overall naming conventions in your codebase. It may also make it more difficult to gauge from the HTML alone which attributes are for behaviour versus just styling concerns. And, ultimately, there's that nagging feeling that you're going against the grain by repurposing data attributes for styling — after all, selecting elements is what classes are <em>for</em>, and what browsers optimise for in selector performance.</p>

<p>Even so, it's an enticing proposal and I'm excited to see how people continue to shape it. <a href="https://twitter.com/devongovett/status/1576635415024390144">Devon Govett</a> endorses a split where he uses a class for the component itself, but signals all internal variants and state with <code class="language-plaintext highlighter-rouge">data-*</code> or <code class="language-plaintext highlighter-rouge">aria-*</code> attributes. There are some great insights in this thread for those who would like to dig deeper.</p>

<blockquote class="embedded-tweet">
  <blockquote class="twitter-tweet"><p lang="en" dir="ltr">New favorite styling method: class names for components, data/aria attributes for states.<br /><br />Attributes are so much better than classes for states. No need to mess with string concatenation of conditional class names, you can have values instead of only booleans, etc. 👍 <a href="https://t.co/eNs9xYjiPO">pic.twitter.com/eNs9xYjiPO</a></p>&mdash; Devon Govett (@devongovett) <a href="https://twitter.com/devongovett/status/1576635415024390144?ref_src=twsrc%5Etfw">October 2, 2022</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</blockquote>

<hr />

<p>While I'm mostly working on codebases using CSS-in-JS these days, where little of this is of much relevance, I can't wait to rewire my brain to reach for attributes more often. Styling based on ARIA attributes encourages more accessible markup, and styling based on custom data attributes makes it more robust and readable — a better experience for users and developers alike.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:impossiblestates" role="doc-endnote">
      <p>I highly recommend the article and linked talk. This may be the principle I rely on the most when authoring code, and the main reason why I lament the lack of native enums in JavaScript. <a href="#fnref:impossiblestates" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Elise Hein</name></author><summary type="html"><![CDATA[I'm a long-time BEM user. The double underscore creeps into my code even when selector collision isn't an issue. I don't think it's BEM, specifically, that I'm drawn to (it just happened to be the methodology I first learned about and grew used to). It's the fact that a methodology, any methodology, adds structure and semantics to HTML class naming where inherently there isn't one.]]></summary></entry><entry><title type="html">Fora Health UI design</title><link href="https://elisehe.in/fora-health-ui-design/" rel="alternate" type="text/html" title="Fora Health UI design" /><published>2022-09-01T00:00:00+00:00</published><updated>2022-09-01T00:00:00+00:00</updated><id>https://elisehe.in/fora-health</id><content type="html" xml:base="https://elisehe.in/fora-health-ui-design/"><![CDATA[<div class="fora-health-gallery">
  <img src="/assets/post-assets/fora-health/task-status-box.svg" class="no-shadow" style="grid-column: 1 / -1" />
</div>

<div class="article-body drop-caps">
  <p>In 2020, about halfway through my tenure at Ctrl Group, I transitioned from a purely software engineering role into a developer/designer hybrid position.</p>

  <p>One of my first design tasks was to give the iOS and Android apps a visual
refresh and establish a design system that worked across mobile and Web.</p>

  <p>Starting from a deep-dive into colours and typography, I designed a new
component library for the iOS and Android apps in Figma, ensuring it served all
existing use cases. I extended the library over the years as we worked on new
features.</p>

  <p>The UI consists largely of in-chat widgets like jump-off points to modal content and complex inputs for collecting different types of data.</p>

  <p><small class="note note--figma">
  Links to more detailed Figma projects and prototypes available on request.
</small></p>

</div>

<div class="fora-health-gallery fora-health-gallery--mobile">

  <div>
    <img src="/assets/post-assets/fora-health/credentials.svg" />
    <img src="/assets/post-assets/fora-health/reports.svg" />
    <img src="/assets/post-assets/fora-health/date-switchers.svg" />
    <img src="/assets/post-assets/fora-health/empty-day.svg" />
    <img src="/assets/post-assets/fora-health/topic-menu.svg" />

    <!-- Last two only shown on mobile to make up for lack of final column -->
    <img src="/assets/post-assets/fora-health/goal-progress-chat.svg" />
    <img src="/assets/post-assets/fora-health/category-modal.svg" />
  </div>

  <div>
    <img src="/assets/post-assets/fora-health/med-adherence.svg" />
    <img src="/assets/post-assets/fora-health/side-effects-input-2.svg" />
    <img class="no-shadow" src="/assets/post-assets/fora-health/editable-card-buttons-treatment-prefs.svg" />
    <img src="/assets/post-assets/fora-health/guide-chat.svg" />
    <img src="/assets/post-assets/fora-health/guides-library.svg" />
    <img src="/assets/post-assets/fora-health/guide.svg" />

    <!-- Last two only shown on mobile to make up for lack of final column -->
    <img src="/assets/post-assets/fora-health/activity-modal.svg" />
    <img src="/assets/post-assets/fora-health/goal-milestones-input.svg" />
  </div>

  <div>
    <img src="/assets/post-assets/fora-health/phq9-card.svg" class="no-shadow" />
    <img class="no-shadow" src="/assets/post-assets/fora-health/goal-progress-card.svg" />
    <img class="no-shadow" src="/assets/post-assets/fora-health/side-effects-card.svg" />
    <img class="no-shadow" src="/assets/post-assets/fora-health/cognition-card.svg" />
    <img src="/assets/post-assets/fora-health/treatment-opts-card.svg" class="no-shadow" />
    <img src="/assets/post-assets/fora-health/option-review.svg" />
    <img src="/assets/post-assets/fora-health/interest-levels-modal.svg" />
    <img src="/assets/post-assets/fora-health/subject-change-title.svg" />
    <img src="/assets/post-assets/fora-health/visit-prep-chat.svg" />

    <!-- Last two only shown on mobile to make up for lack of final column -->
    <img src="/assets/post-assets/fora-health/goal-milestones.svg" />
    <img src="/assets/post-assets/fora-health/importance-modal.svg" />
  </div>

  <div>
    <img src="/assets/post-assets/fora-health/goal-progress-chat.svg" />
    <img src="/assets/post-assets/fora-health/category-modal.svg" />
    <img src="/assets/post-assets/fora-health/activity-modal.svg" />
    <img src="/assets/post-assets/fora-health/goal-milestones-input.svg" />
    <img src="/assets/post-assets/fora-health/goal-milestones.svg" />
    <img src="/assets/post-assets/fora-health/importance-modal.svg" />
  </div>
</div>]]></content><author><name>Elise Hein</name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Where's the fun in accessibility?</title><link href="https://elisehe.in/2022/07/19/the-fun-in-accessibility.html" rel="alternate" type="text/html" title="Where's the fun in accessibility?" /><published>2022-07-19T00:00:00+00:00</published><updated>2022-07-19T00:00:00+00:00</updated><id>https://elisehe.in/2022/07/19/the-fun-in-accessibility</id><content type="html" xml:base="https://elisehe.in/2022/07/19/the-fun-in-accessibility.html"><![CDATA[<p class="no-drop-caps">Consider this question posted on the UX community on Stack Exchange:</p>

<blockquote>
  <p><a href="https://ux.stackexchange.com/questions/138340/does-including-a-konami-code-triggered-easter-egg-negatively-impact-keyboard-acc">Does including a Konami-code triggered Easter Egg negatively impact keyboard accessibility?</a></p>
</blockquote>

<p>(<a href="https://en.wikipedia.org/wiki/Konami_Code">Konami code</a>, a sequence of specific keypresses, is a cheat code originating from video games. It's sometimes used as a way to trigger easter eggs on websites.)</p>

<p>Much of discussion focuses on how likely it is that the key sequence could be triggered accidentally. The implication is that by minimising any chances of encountering a feature, you can avoid putting in the work to make it accessible. This may be true for Konami code, but it made me wonder — instead of keeping easter eggs and bonus content neatly tucked away so that they don't break when assistive technology is involved, how often are these elements added with inclusivity in mind from the start?</p>

<p>We've been taught that UX is not only about function, but about delight, whimsy, wit, and beauty. But when it comes to accessibility, we tend to take the term literally: can this be accessed? Articles on accessibility focus on functionality, and the small, non-essential moments of delight are reserved mostly for mouse or touchpad users with good eyesight. What's the equivalent of a playful design for people who don't necessarily experience the visual, high-speed or animated version of it?</p>

<p>Any visual, hearing, motor, vestibular or other impairments I face are mostly situational, so I can't draw on experience here. I can spot creative touches that don't work well for users of assistive technologies (I'll share a few below). It's a little harder to find those that are added specifically with that audience in mind: by definition, they would likely be lost on me. But when I asked local leading accessibility consultant <a href="https://twitter.com/jakobrosin">Jakob Rosin</a> for his input, his examples, too, were mostly about <em>functional</em> alternatives, not the playful ones.</p>

<p>I'll cover both here. Training myself to notice these alternative features, while not strictly fun in the sense that I was going for, still helps me move away from thinking of one type of design as the default. Different representations should not be a mere means of accessing some one true default — they're opportunities to add features and creative touches that shine only in a particular medium.</p>

<h2 id="playful-design-as-a-progressive-enhancement">Playful design as a progressive enhancement</h2>

<p>Let's start by looking at how common playful touches can leave out users of assistive technologies.</p>

<p>The simplest example is anything triggered by a pointing device. Think interactive gradient backgrounds that change colour as you hover across a page, particle animations that respond to pointer speed and direction, or any non-essential hover state that doesn't also respond to a focus event. When not tied to critical functionality, these features are clearly a progressive enhancement, sometimes even intentionally a bit hidden. And you should not feel guilty for getting creative with pointer interactions! But consider the equivalent for a screen reader user — is there anything in your design that elicits the same “huh, that's neat” reaction from someone who can‘t see it?</p>

<p>To give an example, check out the interactive particle animation presented front and center on <a href="https://www.madebyon.com/">this agency landing page</a>:</p>

<figure class="image">
  <img src="/assets/post-assets/fun-a11y/particle-animation2.gif" alt="A screen capture of madebyon.com that shows how the agency's logo is transformed into animated particles that respond to the user's pointer movement in a satisfying way." />
  <figcaption>
    The way this particle animation on madebyon.com responds to pointer events is mesmerising.
  </figcaption>
</figure>

<p>I do love the slow, satisfying inertia that ON has fine-tuned to perfection. It invites play. There's a competitive advantage to features like these, especially in agency land (it's a bit of a flex, isn't it). But in many cases the interaction cannot be triggered by keyboard, and there are no alternative, accessible labels — never mind that many people prefer not to encounter motion at all when browsing the web. <a href="https://www.theverge.com/users/s_e_smith">s.e. smith</a> writes in <a href="https://www.theverge.com/23191768/animation-accessibility-neurodivergence"><em>My war on animation</em></a>:</p>

<blockquote>
  <p>I'm not the only one who struggles with parallax scrolling […]. It can also be tough for screen readers to work with, particularly when it's being used for something like a graphics-heavy display of data. Other people just find it annoying, which seems fair. Could there be an alternative plain or clean version of the same data, presented with the same care? […] <strong>Rather than viewing access as an imposition that narrows your options, think of it as an invitation to think outside the box.</strong></p>
  <footer><p><cite><a href="https://www.theverge.com/23191768/animation-accessibility-neurodivergence">s.e. smith</a></cite>, emphasis mine</p></footer>
</blockquote>

<p>If a touch of whimsy suits only one particular medium, could we find ways to compensate in others?</p>

<p>Another example is Google's long-standing design for their pagination where an <em>o</em> from the stretched out <em>Gooooooogle</em> accompanies each number in the list of results, as if to vocalise how <em>loooooong</em> it is. It's cute, in an Old Web kind of way. (If anyone knows where I could learn more about the history behind this bit of design, please share!) When you look at the implementation though, the play on… erm, word, is lost inside a neat list of links: <code class="language-plaintext highlighter-rouge">Page 1</code>, <code class="language-plaintext highlighter-rouge">Page 2</code>, <code class="language-plaintext highlighter-rouge">Page 3</code>… Functional, but boring compared to the visual.</p>

<figure class="image">
  <img src="/assets/post-assets/fun-a11y/google-pagination.png" alt="A screenshot of the “Goooooooogle” graphic in Google's pagination, side by side with its accessibility tree showing a simple list of links to pages." />
  <figcaption>
    The accessibility tree shows a list titled “Page navigation” that consists of links to all pages. Functionally, this is perfect. But the playfulness of the repeated <em>o</em>'s, added as a background image to each link with CSS, is lost.
  </figcaption>
</figure>

<p>Let's look at error pages, a quintessential outlet for playfulness in design. When you interrupt your users with an error, you probably want to do so with friendly humility. Any chuckle you can elicit with a creative illustration will soften the message more. It's curious, then, to see suggestions like the following in a guide on UX copywriting:</p>

<blockquote>
  <p>This is a 500 error page, so instead of the image saying something like: “Drawing of a woman screwing in a lightbulb”, it makes more sense to use “500 internal server error” as the alt text.<sup id="fnref:screwing" role="doc-noteref"><a href="#fn:screwing" class="footnote">1</a></sup></p>
  <footer><p><cite><a href="https://medium.com/docusign-design/a-ux-copywriters-guide-to-accessibility-79dbea85798d">Robert Peiffle</a></cite></p></footer>
</blockquote>

<figure class="image">
  <img src="/assets/post-assets/fun-a11y/docusign-error.png" alt="A screenshot of DocuSign's error page showing a bit of copy and a playful illustration to go with it. The text says “We're fixing it”, and the illustration shows someone switching out a light bulb on a giant lamp." />
  <figcaption>
    A screenshot of DocusSign's error page, via <a href="https://medium.com/docusign-design/a-ux-copywriters-guide-to-accessibility-79dbea85798d">Robert Peiffle</a>
  </figcaption>
</figure>

<p>I'm not going to argue against this specific use case, as alt text is notoriously difficult to get right and heavily dependent on context. And when it comes to clever illustrations, it may just not pack the same punch. If the copywriting does much of the heavy lifting to deliver the quip, you can probably consider the illustration a bonus. But I would love to learn of examples where the overall message is just as playful for people seeing it, hearing it, viewing it zoomed in or zoomed out, and with or without images or motion.</p>

<figure class="image">
  <img src="/assets/post-assets/fun-a11y/github-error.png" alt="A screenshot of GitHub's 404 error page showing an illustration with a parallax effect on the left and the accessibility tree on the right." />
  <figcaption>
    GitHub's error page features a Star Wars themed illustration of Octocat with a parallax effect when you hover your mouse over it. The only text exposed is “404 This is not the web page you are looking for”.
  </figcaption>
</figure>

<p>I'd like to look at one more type of non-essential information presented visually, and that's look and feel. Serif or sans serif fonts, rounded or pointy corners, bright or pastel colours… there's a lot of tacit information that these small design choices collectively convey. But it is precisely the small design choices that I often see ignored — classed as “decorative” — when reading about accessibility. This leaves the entire burden of branding on voice &amp; tone alone.</p>

<p>In <a href="https://www.joshwcomeau.com/css/surprising-truth-about-pixels-and-accessibility/"><em>The surprising truth about pixels and accessibility</em></a> (an excellent overview of the long-standing debate), Josh Comeau writes:</p>

<blockquote>
  <p>Similarly, how about border widths? It doesn't really make sense for a border to become thicker as the user scales up their preferred text size, does it?</p>
  <footer><p><cite><a href="https://www.joshwcomeau.com/css/surprising-truth-about-pixels-and-accessibility/">Josh Comeau</a></cite></p></footer>
</blockquote>

<p>This is subtle, but worth challenging — why <em>not</em>?</p>

<p>Yes, we run into thorny issues around whether the browser zoom function should apply to the full page or just text<sup id="fnref:textonlyzoomdebate" role="doc-noteref"><a href="#fn:textonlyzoomdebate" class="footnote">2</a></sup>. Your unit strategy also makes little difference for users who only zoom marginally for reading comfort. For someone who routinely browses the web at 250% zoom level though, odds are they cannot make out 1px borders at all. Why shouldn't we take any opportunity to keep the nonverbal cues in our designs intact for everyone?</p>

<p>Consider these different uses of borders on <a href="https://bbc.com">bbc.com</a>. Some scale with the text, some don't.</p>

<figure class="image">
  <img src="/assets/post-assets/fun-a11y/bbc-border-comparison.png" alt="A screen capture of a news item from bbc.com comparing 100% zoom level and 250% zoom level, showing the size of a decorative border decreasing relatively to the text size until it is barely visible." />
  <figcaption>
    bbc.com uses 1px for the horizontal separators between titles and authors in their travel section. It's therefore considered decorative enough to risk not being spotted.
  </figcaption>
</figure>

<figure class="image">
  <img src="/assets/post-assets/fun-a11y/meaningful-borders-500.png" width="550px" alt="A screen capture of news items from The BBC's homepage where both relative and absolute sizing have been used for borders." />
  <figcaption>
    Here, bbc.com uses 1px for the horizontal separators between items, and 0.125rem for the coloured bar next to each item category, likely because the colour conveys meaning.
  </figcaption>
</figure>

<p>The differences are so subtle, but they provide a window into how designers reason about accessibility when it comes to non-essential content.</p>

<hr />

<p>It's fair to say there is a disproportionate focus on visuals, illustrations and motion when it comes to creativity and playfulness. Without the lived experience, it's tricky for me to imagine equivalents in other representations. But I'd like to share a couple of examples where it has been done beautifully. Please share if you've come across others!</p>

<h2 id="playing-with-broken-images">Playing with broken images</h2>

<p>In <a href="https://medium.com/r?url=https%3A%2F%2Fjilt.com%2Fblog%2Falt-text%2F"><em>How to get the most out of alt text in your marketing emails</em></a>, Chris Donald talks about fun ways of leveraging alt text for people who have images disabled, or whose network connection is too slow for them to load (certainly a valid audience to consider when it comes to inclusivity).</p>

<p>First, it's perhaps not widely known that alt text inherits styling just like other elements. You simply don't see it unless your images fail to load and the alt text is displayed as a fallback. You can target it directly by associating the styles with the <code class="language-plaintext highlighter-rouge">&lt;img&gt;</code> element:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;img</span> <span class="na">class=</span><span class="s">"alt-text-fallback"</span><span class="nt">&gt;</span>

<span class="nt">&lt;style&gt;</span>
  <span class="nc">.alt-text-fallback</span> <span class="p">{</span>
    <span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--color-text-accent</span><span class="p">);</span>
    <span class="nl">font-size</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--font-size-xxl</span><span class="p">);</span>
    <span class="nl">font-family</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--font-family-display</span><span class="p">);</span>
  <span class="p">}</span>
<span class="nt">&lt;/style&gt;</span></code></pre></figure>

<figcaption><p>Any CSS declarations controlling text styling such as colour, font size or family will be applied to the alt text displayed as a fallback if the image fails to load.</p>
</figcaption>

<p>This could be neat for text-based images such as logos: the fallback alt text could be styled as a close enough replica of the logo, and the actual image becomes almost a progressive enhancement (remember though that the alt text should remain first and foremost a signal for screen reader users, so if your logo is a link to your homepage with the alt text “Home”, you should probably keep it that way).</p>

<p>Taking this idea further — and this is where we get to the easter egg ! — Chris Donald describes how Sony PlayStation sliced their hero image into separate blocks with different background colours to create fallback pixel art out of those blocks if the images fail to load:</p>

<figure class="image">
  <img src="/assets/post-assets/fun-a11y/autobots.png" alt="A screenshot comparing the same marketing email side by side where one has the images loaded correctly, and the other is missing images and the background colour used for each slice of the image reveals a pixel art style Autobots logo." />
  <figcaption>
    “The email's hero image has been sliced and styled to reveal the Transformers' Autobots logo when images are disabled.” (screenshot and quote via <a href="https://jilt.com/blog/alt-text/">Chris Donald</a>)
  </figcaption>
</figure>

<p>This example really captures the essence of deliberately adding alternative versions of playful touches where the standard use case (assuming images will load) doesn't apply.</p>

<blockquote>
  <p>And while those efforts might not be seen (nor appreciated) by the overwhelming majority of your customers, the ones who do pick up on what you've done just might appreciate it even more.</p>
  <footer><p><cite><a href="https://jilt.com/blog/alt-text/">Chris Donald</a></cite></p></footer>
</blockquote>

<h2 id="creatures-biting-wetly"><em>[creatures biting wetly]</em></h2>

<p>I can't help but draw a parallel with closed captioning here. People with hearing loss are by far not the only ones who benefit from closed captions. Just as people on a slow connection who usually want to see images would appreciate surprise pixel art, hearing people who can't rely on audio just then will welcome well-written captions. (As a parent of young kids, I can't remember the last time I dared turning the volume up during movie night after the kids' bedtime).</p>

<blockquote class="embedded-tweet">
  <blockquote class="twitter-tweet"><p lang="en" dir="ltr">“tentacles wetly squelching”<br /><br />Meet the wordsmiths who gave us those brilliant subtitles in Stranger Things 4 <a href="https://t.co/aFMC4c3tSe">https://t.co/aFMC4c3tSe</a> <a href="https://t.co/qrCNrt4EI0">pic.twitter.com/qrCNrt4EI0</a></p>&mdash; Netflix (@netflix) <a href="https://twitter.com/netflix/status/1546632039763365888?ref_src=twsrc%5Etfw">July 11, 2022</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</blockquote>

<h2 id="alternative-function">Alternative function</h2>

<p>I mentioned earlier the distinction between alternative fun and alternative function. I've been trying my best to find IRL examples of the former, but most of what I covered here requires some imagination to fill in the gaps.</p>

<p>Going back to some of the examples <a href="https://twitter.com/jakobrosin">Jakob Rosin</a> mentioned, he focused on products that provide different  functionality specifically suited to each medium. While not quite the easter egg, almost non-essential type features I was looking for, his examples highlight an interesting area of inclusive design: instead of making a feature merely accessible via different mediums, consider whether it's actually useful at all in those mediums, and provide alternatives or configuration options where appropriate.</p>

<p>Jakob mentions the various <a href="https://www.apple.com/ios/ios-16-preview/features/">customisation options introduced for the lock screen on iOS 16</a>: fonts, colour, photos, layout, and other visuals. This is fun for sighted people, possibly essential for the partially sighted, but doesn't make much sense for people who can't see the screen at all. What are the equivalent configuration options for other senses? The specific sounds your phone makes or haptic feedback it gives when locking and unlocking, for example, are not currently configurable (bar the ability to switch them off).</p>

<figure style="text-align: center">
  <iframe width="560" height="315" src="https://www.youtube.com/embed/RH9cezXEFS0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
  <figcaption>
    Apple introduced the new lock screen for iOS 16 at WWDC 2022.
  </figcaption>
</figure>

<p>An example Jakob gave of a product that gets it right is <a href="https://slack.com/help/articles/360000411963-Use-Slack-with-a-screen-reader">Slack</a>. In its onboarding flow, Slack asks screen reader users which order they would like to hear content presented in. For example, should the time of a message be announced before its body or vice versa? This would never make sense for people perceiving the UI visually, and that's what makes it stand out — it's a different feature, added with a different user group in mind.</p>

<p>We can flip this idea around. Indeed, many of the problems we face when designing for a sighted audience are not problems at all for screen reader users. For example, instead of requiring an extra action to reveal truncated labels (in tables, say), just present them in full. Screen space is not an issue here, and reading the full label is no doubt faster for the sped up synthetic voice of a screen reader than explicitly revealing a hidden portion. If you're controlling truncation in CSS with <code class="language-plaintext highlighter-rouge">text-overflow: ellipsis</code>, screen reader users <em>already</em> hear the full content as given in the source. I love this example because it makes the <em>tooltip</em> the alternative experience — not the ARIA label or alt text or focus state.</p>

<figure class="image">
  <img src="/assets/post-assets/fun-a11y/airtable-tooltip.gif" alt="A screen recording showing how hovering over a truncated table column heading in Airtable (“Ignor…”) reveals a tooltip with the full heading label (“Ignore in total cost”)." />
  <figcaption>
    Airtable truncates table headings with CSS. The tooltip here is an accessibility improvement for sighted users 🙂
  </figcaption>
</figure>

<hr />

<p>It might be premature to talk about playful touches when the accessibility lobby is still trying their best to get the basics down. If you're familiar with the idea of the adjacent possible, the thought that we have not only the luxury, but even the <em>possibility</em> to focus on fun in non-visual mediums may be ridiculous. You can't get creative with what isn't there!</p>

<p>Content and function obviously take priority. Still, I hope these examples sparked an idea or two. As we learn to stop viewing the visual, high-definition, high-speed experience as the default, we'll become better at noticing gaps in functionality. The fun should soon follow!</p>

<hr />

<p><a name="update"></a></p>

<h3 id="update-21st-september-2022">Update: 21st September, 2022</h3>

<p>This article has been making the rounds for a while so I thought I'd share some more interesting examples of creative touches in accessible design that people have brought up as feedback. It's been encouraging to see that the landscape isn't as devoid of playfulness as I initially thought!</p>

<ul>
  <li>
    <p><a href="https://twitter.com/fraktalisman/status/1557760281199132673?s=21">@franktalisman</a> shared a presentation by Vasilis van Gemert on voicing animations by making screen readers sound a bit unexpected and silly.</p>

    <p>See: <a href="https://beyondtellerrand.com/events/dusseldorf-2022/speakers/vasilis-van-gemert"><em>Exclusive Design</em></a></p>
  </li>
  <li>
    <p><a href="https://twitter.com/joshwcomeau/status/1550592023618527233?s=21">@JoshWComeau</a> shared his generative art tool, <a href="https://tinkersynth.com/">Tinkersynth</a>, which gives keyboard users <em>more</em> control over sliders than mouse users.</p>

    <p>See: <a href="https://tinkersynth.com/">Tinkersynth</a></p>
  </li>
  <li>
    <p>As a less encouraging example, <a href="https://twitter.com/ckundo/status/1550618716081119232?s=21">@ckundo</a> brought up <em>stramps</em> — ramps that blend into staircases. You can see how the intention might have been to bring an artistic touch to something perhaps a little boring, but the result is a dangerous loss in functionality.</p>

    <p>See: <a href="https://incl.ca/the-problems-with-ramps-blended-into-stairs/"><em>The problems with ramps blended into stairs</em></a></p>
  </li>
  <li>
    <p><a href="https://twitter.com/jakobrosin">Jakob Rosin</a> mentioned a clever way of bringing more expressiveness to <em>team</em> or <em>about</em> pages on marketing sites by adding audio clips of people's voices next to their photos.</p>

    <p>See: <a href="https://voxmate.com/about">Voxmate</a></p>
  </li>
  <li>
    <p><a href="https://twitter.com/jakobrosin">Jakob Rosin</a> also brought up audio logos — adding an alternative audio representation to a visual logo. Sonic branding is a concept traditionally more common in advertising and broadcasting, but, indeed, there is no reason why we couldn't use elements of it in digital products and services, too.</p>
  </li>
  <li>
    <p>[2nd October, 2022] I was listening to <a href="https://thecsspodcast.libsyn.com/049-accessibility"><em>Episode 049: Accessibility</em> of <em>The CSS Podcast</em></a>, where <a href="https://twitter.com/argyleink">Adam Argyle</a> describes how he animates <code class="language-plaintext highlighter-rouge">outline-offset</code> on focus states from a larger focus ring to a smaller one, creating a cool zoom effect when using the keyboard to tab through elements.</p>
  </li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:screwing" role="doc-endnote">
      <p>Thank you <a href="https://lobste.rs/u/teiresias">@teiresias</a> for <a href="https://lobste.rs/s/v6tuv7/where_s_fun_accessibility#c_xmsah3">pointing out</a> the ambiguousness of the chosen alt text. “Woman replacing light bulb” is probably less loaded, no matter how playful the intent :) <a href="#fnref:screwing" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:textonlyzoomdebate" role="doc-endnote">
      <p>I find that most sites nowadays use a mix of text only and full page zoom, but check out <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=401322">this 15-year old discussion on Bugzilla</a> for a fun look into platform development history. <a href="#fnref:textonlyzoomdebate" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Elise Hein</name></author><summary type="html"><![CDATA[Consider this question posted on the UX community on Stack Exchange:]]></summary></entry><entry><title type="html">Using the platform</title><link href="https://elisehe.in/2021/08/22/using-the-platform.html" rel="alternate" type="text/html" title="Using the platform" /><published>2021-08-22T00:00:00+00:00</published><updated>2021-08-22T00:00:00+00:00</updated><id>https://elisehe.in/2021/08/22/using-the-platform</id><content type="html" xml:base="https://elisehe.in/2021/08/22/using-the-platform.html"><![CDATA[<p>I recently came across a series of articles by Daniel Kehoe where he introduces <a href="https://tutorials.yax.com/articles/build-websites-the-yax-way/quicktakes/what-is-the-yax-way.html"><em>The Stackless Way</em></a>, an optimistic take on web development that proposes we “use the platform” (modern features built into the language) instead of frameworks and build tools that keep getting replaced every few years.</p>

<p>It was good timing. While I'm a front-end developer at heart, I've rarely had the luxury of focusing on it full time. I've been dipping in and out of JavaScript, never fully caught up, always trying to navigate the ecosystem all over again each time a project came up. And framework fatigue is real!</p>

<p>So, instead of finally getting into Rollup to replace an ancient Browserify build on an old codebase (which could also really use that upgrade from Polymer to LitElement…), I decided to go “stackless”. I took a long-time idea for a motion design project and built it using nothing but features native to the browser: vanilla JS, ES6 modules and web components.</p>

<p>Working on a codebase with no dependencies has been a way of rediscovering exactly what I get for free in 2021, and what value I'm adding by bringing frameworks, transpilers and bundlers to the mix. I'd like to share what I learned (and what I needed to unlearn) in the process.</p>

<h3 id="the-project">The project</h3>
<p>The project itself, <a href="https://schematics.elisehe.in"><em>Schematics: A Love Story</em></a>, is a collection of animated diagrams from a visual poetry book by the same name. The animations dotted throughout this write-up are examples from the project: SVG created programmatically with modular, vanilla JavaScript, encapsulated in a web component, sitting inside a framework-free codebase.</p>

<figure class="image">
	<picture>
		<source srcset="/assets/post-assets/stackless/fig14-desktop.gif" media="(min-width: 750px)" />
    <img src="/assets/post-assets/stackless/fig14-mobile.gif" alt="Figure 14: A line spiralling upwards along a the time axis in a 3-dimensional coordinate system." />
	</picture>
  <figcaption>
		A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard's</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br /><a href="https://schematics.elisehe.in">See full collection</a>
  </figcaption>
</figure>

<h2 id="the-stackless-way">The Stackless Way</h2>

<p>Daniel Kehoe is not alone in his push-back on the complexity of the modern web. Frank Chimero's <a href="https://frankchimero.com/blog/2018/everything-easy/"><em>Everything Easy is Hard Again</em></a> gets to me every time, and, more recently, I  enjoyed <a href="https://medium.com/codex/youre-missing-out-on-vanilla-js-91aceec917d6">this lighthearted rant</a> about the lack of appreciation for vanilla JS.</p>

<p>Appeals for external JS dependencies to be added with thoughtful intent have become common, but some schools of thought question the concept of single-page apps as a whole: server-side rendering is still a thing, after all. Pages can also be pre-built into a fully static site and served from a CDN (see <a href="https://jamstack.org/what-is-jamstack/">Jamstack</a>). These approaches recognise that we can move some of the complexity currently managed by front-end frameworks elsewhere on the stack.</p>

<p>But Kehoe's series of articles on going stackless feels a little different. It's not just about JavaScript — it's a wholehearted devotion to the native features of the web as a platform (Routing? Just make sure every URL matches a .html file!)</p>

<p>There are limits to this kind of purism, of course. The Stackless Way, to me, is less of a realistic approach to building production web apps and more of a learning and introspection tool, a way to take a step back and fall in love with the platform again.</p>

<p>Being all about motion design, my own project relied on many a <code class="language-plaintext highlighter-rouge">setTimeout</code> and page transitions — a single-page app, really… just handcrafted. The two main technologies that made it possible are ES6 modules and web components, which, along with module CDNs, form the pillars of The Stackless Way.</p>

<h2 id="es6-modules">ES6 modules</h2>
<p>If you've been developing for the web for some time, you might remember doing this:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;head&gt;</span>
  <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"/js/vendor/jquery.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
  <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"/js/main.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
  <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"/js/subscribe.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
  <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"/js/gallery.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
  <span class="c">&lt;!-- Many more script tags whose specific ordering was a source of bugs and frustration --&gt;</span>
<span class="nt">&lt;/head&gt;</span>
</code></pre></div></div>

<p>Listing all required scripts separately in the document <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> was all we could do given the lack of support for a native import/export mechanism in JavaScript. This was, of course, suboptimal:</p>

<ul>
  <li>each script tag initiates a HTTP request;</li>
  <li>there is no namespacing (all scripts exist in the global scope);</li>
  <li>the order of execution is linear, and maps directly onto the ordering of the script tags.</li>
</ul>

<p>To combat this, approaches to modularity in JavaScript have proliferated over the years (AMD, UMD, CommonJS), and along with them, build tools and bundlers to convert the modular code into something that the browser understands.</p>

<p>ECMAScript Modules (also referred to as ESM or ES6 Modules) is the first <em>native</em> standard for modules in JavaScript. I emphasise <em>native</em> here because that means we can ditch the build tools and bundlers in favour of <code class="language-plaintext highlighter-rouge">&lt;script type="module"&gt;</code> in HTML and <code class="language-plaintext highlighter-rouge">import</code>/<code class="language-plaintext highlighter-rouge">export</code> in JS:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;head&gt;</span>
  <span class="c">&lt;!-- Single entry point for a dependency graph of any depth --&gt;</span>
  <span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">src=</span><span class="s">"main.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;/head&gt;</span>
</code></pre></div></div>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// main.js</span>
<span class="k">import</span> <span class="nx">Gallery</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Gallery.js</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">SubscriptionForm</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./SubscriptionForm.js</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// Gallery.js</span>
<span class="k">import</span> <span class="nx">Swipe</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./helpers/gestures.js</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">Gallery</span> <span class="p">{}</span>
</code></pre></div></div>

<p><strong>Native support for modularity is the most important step towards a build-free codebase</strong>. If I had access to only one <a href="http://es6-features.org/">ES6 feature</a> for the rest of my life, I'm confident that modules would take me most of the way there when it comes to well-structured native JavaScript.</p>

<p>And not having to build your JS app is magical. Setting up the directory structure for <em>Schematics</em>, I was giddy with excitement skipping <code class="language-plaintext highlighter-rouge">npm run build</code> and seeing my source files mirrored — as is! — in the browser. It reminded me of when I first began building websites and double-clicking <code class="language-plaintext highlighter-rouge">index.html</code> was enough to see your work in the browser.</p>

<p>You do need a local server if you're using ES6 modules (so double-clicking an HTML file might be a thing of the past for good). But there's no lag between editing your code and seeing changes in the browser, and the source code you see in the inspector is exactly what you typed into your editor (no sourcemaps!).</p>

<p>Nevermind the faster edit-compile-debug cycle — this speaks directly to Chimero's concern about the <a href="https://frankchimero.com/blog/2018/everything-easy/">lack of legibility in today's codebases as an obstacle to learning the craft</a>:</p>

<blockquote>
  <p>Before, the websites could explain themselves; now, someone needs to walk you through it. Illegibility comes from complexity without clarity. I believe that the legibility of the source is one of the most important properties of the web. […] the best way to help someone <em>write</em> markup is to make sure they can <em>read</em> markup.</p>
  <footer><p><cite><a href="https://frankchimero.com/blog/2018/everything-easy">Frank Chimero</a></cite></p></footer>
</blockquote>

<figure class="image">
	<picture>
		<source srcset="/assets/post-assets/stackless/fig18-desktop.gif" media="(min-width: 750px)" />
    <img src="/assets/post-assets/stackless/fig18-mobile.gif" alt="Figure 18: A flow diagram for love. Good? Yes. More? Yes. Do it." />
	</picture>
  <figcaption>
		A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard's</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br /><a href="https://schematics.elisehe.in">See full collection</a>
  </figcaption>
</figure>

<p>I recommend reading Kehoe's <a href="https://tutorials.yax.com/articles/javascript/import/index.html"><em>Javascript import explained</em></a> or Ayodeji's <a href="https://medium.com/backticks-tildes/introduction-to-es6-modules-49956f580da"><em>Introduction to ES6 modules</em></a> for a historic overview of approaches to modularity, how we got to ESM, and how to write your own modules.</p>

<h3 id="the-future-of-app-bundles">The future of app bundles</h3>

<p>Just because you don't strictly need a build tool to <em>run</em> your code, doesn't mean you shouldn't use one for production builds to optimise performance. I'm referring here not to transpilation, but to minification, mangling, tree-shaking, etc.</p>

<p>I've been bundling JavaScript into a single <code class="language-plaintext highlighter-rouge">app.js</code> file for such a long time that my first instinct was to concatenate everything into a single script on <em>Schematics</em>, too. This, of course, will not work with ES6 modules — the concept of files is what creates boundaries between different modules, and there is no way to specify more than one module per file.</p>

<p>Luckily, you don't need to bundle your ES6 modules into a single file to improve performance. Let me repeat: <strong>you don't need to bundle your ES6 modules into a single file to improve performance</strong>.</p>

<p>Here's why:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">&lt;script type="module"&gt;</code> requests are deferred by default: they won't block document parsing.</li>
  <li>Serving all modules from separate source files is great for caching, as the modules unaffected by some change can continue being retrieved from the cache. When you serve a single bundle, any one change will invalidate the whole bundle.</li>
  <li>You can lazy load parts of your code by making use of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports">dynamic imports</a> — importing modules during runtime at the point where you actually need them.</li>
  <li>You can preload critical modules with <a href="https://developers.google.com/web/updates/2017/12/modulepreload"><code class="language-plaintext highlighter-rouge">modulepreload</code></a>. This starts parsing and compiling the linked modules immediately, off the main thread.</li>
  <li>Finally, HTTP2 may in the future allow us to resolve the entire dependency graph from the first <code class="language-plaintext highlighter-rouge">&lt;script type="module"&gt;</code> request, and send all required files back in a single response. For the time being, this requires bespoke logic written on the server side.</li>
</ol>

<p>And don't forget about bundle size:</p>

<blockquote>
  <p>If you inspect the output code generated by most popular bundlers, you'll find a lot of boilerplate whose only purpose is to dynamically load other code and manage dependencies, but none of that would be needed if we just used modules with import and export statements!</p>
  <footer><p><cite><a href="https://frankchimero.com/blog/2018/everything-easy">Philip Walton</a></cite></p></footer>
</blockquote>

<p>I still have some work to do to go against my first instinct to bundle. It's not uncommon for a SPA to have hundreds of JavaScript files, and the prospect of serving them all separately seems counterintuitive. Even having read up on all the reasons why I don't need to bundle my modules, I occasionally hesitated before separating some logic into its own file to avoid the extra request.</p>

<figure class="image">
	<picture>
		<source srcset="/assets/post-assets/stackless/fig20-desktop.gif" media="(min-width: 750px)" />
    <img src="/assets/post-assets/stackless/fig20-mobile.gif" alt="Figure 20: An illustration of the propagation of sound waves across arrays of vertical lines." />
	</picture>
  <figcaption>
		A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard's</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br /><a href="https://schematics.elisehe.in">See full collection</a>
  </figcaption>
</figure>

<p>For more details and interesting discussion around ES6 module load performance and app bundles, see <a href="https://esdiscuss.org/topic/fwd-are-es6-modules-in-browsers-going-to-get-loaded-level-by-level">this thread on the ECMAScript Discussion Archives</a>. The spec is not set in stone yet, and there are plenty of ideas floating around, some more daring than others (my personal favourite is the proposal to serve the entire module dependency graph as a .zip file).</p>

<blockquote>
  <p>There's plenty of ongoing module work happening in Chrome, though, so we're getting closer to giving bundlers their well-earned rest!</p>
  <footer><p><cite><a href="https://developers.google.com/web/updates/2017/12/modulepreload">Sérgio Gomes</a></cite></p></footer>
</blockquote>

<p>I also recommend reading <a href="https://philipwalton.com/articles/using-native-javascript-modules-in-production-today"><em>Using Native JavaScript Modules in Production Today</em></a> by Philip Walton. It explains how to serve optimised module files alongside a standard fallback single-file bundle for older browsers.</p>

<p>Finally, if you're looking for an alternative to webpack that supports native modules, check out <a href="https://vitejs.dev">Vite</a> or <a href="https://rollupjs.org/">Rollup</a>.</p>

<h2 id="web-components">Web components</h2>

<p><a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Components</a> is an umbrella term for several native technologies (Custom Elements, Shadow DOM and HTML templates) that let us bundle the markup and dynamic behaviour of an element into a reusable piece of code, on a high level no different to the React or Vue component. As of May 2021, <a href="https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/"><em>All the Ways to Make a Web Component</em></a> lists 55 variants of a hypothetical <code class="language-plaintext highlighter-rouge">&lt;my-counter&gt;</code> component for comparison across bundle size, coding style and performance. Native web components, of course, stand out because they have no external dependencies (and, consequently, an unmatched tiny bundle size).</p>

<blockquote class="embedded-tweet">
	<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Congrats, you&#39;re now ready to go 🎉</p>&mdash; Web Component Bytes (@wcbytes) <a href="https://twitter.com/wcbytes/status/1428446347015991297?ref_src=twsrc%5Etfw">August 19, 2021</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</blockquote>

<p>Here are some of my own impressions of web components as compared to external frameworks when it comes to DX and architecture.</p>

<h3 id="shadow-dom-and-template">Shadow DOM and <code class="language-plaintext highlighter-rouge">&lt;template&gt;</code></h3>

<p>The shadow DOM, a way of keeping the internals of a component inaccessible to the main document, is a neat feature in theory, but a natural use case for it didn't really arise when working on <em>Schematics</em>. I've played around with the shadow DOM in the past as part of a Polymer (now LitElement) project, and I mostly remember it creating more problems than it solved, especially when it came to styling. I can see the shadow DOM being useful in cases where a component is reused across many sites and in unpredictable contexts (such as elements in a UI library); for your standard <code class="language-plaintext highlighter-rouge">&lt;site-specific-header-dropdown&gt;</code>, it doesn't add much value.</p>

<p>The <code class="language-plaintext highlighter-rouge">&lt;template&gt;</code> and <code class="language-plaintext highlighter-rouge">&lt;slot&gt;</code> elements (the latter only useful if you're using the shadow DOM) are presented as a means of adding hidden markup inside JS, but they feel clunky if you're used to something like JSX. Additionally, the <code class="language-plaintext highlighter-rouge">&lt;template&gt;</code> tag <a href="https://stackoverflow.com/a/53317973">seems counter-inuitive if the aim is to create portable components</a> that can be included in an app with a single import. When declared in the HTML, any JavaScript making use of the <code class="language-plaintext highlighter-rouge">&lt;template&gt;</code> makes assumptions about what's available to it in the main document. Indeed, at times the <code class="language-plaintext highlighter-rouge">index.html</code> in Schematics felt like a dumping ground for various <code class="language-plaintext highlighter-rouge">&lt;template&gt;</code>s, rather than a neat overview of the page structure.</p>

<p><a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/HTML_Imports">HTML Imports</a> was a proposal that might have allowed us to package JS and HTML together into a single bundle, making <code class="language-plaintext highlighter-rouge">&lt;template&gt;</code> the star of the show. Unfortunately, the feature never took off.</p>

<p>Another <a href="https://github.com/whatwg/html/issues/2254">proposed feature</a> is a means to add variables, simple statements and event handlers to nodes in a <code class="language-plaintext highlighter-rouge">&lt;template&gt;</code> tag. The gist of it is similar to what you'd currently see in front-end frameworks:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;template</span> <span class="na">id=</span><span class="s">"card"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;card&gt;</span>
    <span class="nt">&lt;h2&gt;</span>{{title}}<span class="nt">&lt;/h2&gt;</span>
    <span class="nt">&lt;div&gt;</span>{{description}}<span class="nt">&lt;/div&gt;</span>
    <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"/card/{{id}}"</span><span class="nt">&gt;</span>Read more<span class="nt">&lt;/a&gt;</span>
    <span class="nt">&lt;button</span> <span class="na">handler=</span><span class="s">"onEdit"</span><span class="nt">&gt;</span>Edit<span class="nt">&lt;/button&gt;</span>
  <span class="nt">&lt;/card&gt;</span>
<span class="nt">&lt;/temlate&gt;</span>
</code></pre></div></div>

<p>Being accustomed to built-in data-binding in frameworks, until the variables above are automatically updated, the feature feels half-baked. As it stands, it's difficult to gauge the full, declarative markup of a component in one place, as update logic is scattered throughout the class with query selectors, <code class="language-plaintext highlighter-rouge">innerHTML</code>, document fragments and the like.</p>

<p>If you'd like variable and event handler support in <code class="language-plaintext highlighter-rouge">&lt;template&gt;</code> now, GitHub, who <a href="https://github.blog/2021-05-04-how-we-use-web-components-at-github/">use vanilla web components internally</a>, provide <a href="https://github.com/github/template-parts">a polyfill</a> for the minimum viable bits of the proposal. As for data binding, Danny Moerkerke describes one approach to implementing it yourself: <a href="https://medium.com/swlh/https-medium-com-drmoerkerke-data-binding-for-web-components-in-just-a-few-lines-of-code-33f0a46943b3"><em>Data binding for Web Components in just a few lines of code</em></a>.</p>

<figure class="image">
	<picture>
		<source srcset="/assets/post-assets/stackless/fig36-desktop.gif" media="(min-width: 750px)" />
    <img src="/assets/post-assets/stackless/fig36-mobile.gif" alt="Figure 36: A swinging pendulum." />
	</picture>
  <figcaption>
		A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard's</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br /><a href="https://schematics.elisehe.in">See full collection</a>
  </figcaption>
</figure>

<h3 id="extending-native-elements">Extending native elements</h3>

<p>The web components feature I was most excited about, and one that a third-party framework inherently cannot provide, is <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#customized_built-in_elements">the ability to extend native HTML elements</a>. This allows you to inherit any built-in behaviour for that element in your component, including accessibility-specific properties. Unfortunatley, <a href="https://dev.to/lkraav/comment/ad06">Apple have decided not to support customizable elements in Safari</a>.</p>

<p>This is disappointing. Using front-end frameworks, we often implement our own, more snazzy versions of native HTML elements. Input elements especially, such as dropdowns and radio buttons, are tricky to style using CSS and often end up reproduced with JS. But reproducing the required ARIA attributes, keyboard navigation and the like requires extra effort — something we would get for free with customizable elements.</p>

<h3 id="components-all-the-way-down">Components all the way down?</h3>

<p>While ES modules sparked excitement for the future, I was left underwhelmed by the prospect of native web components replacing traditional front-end frameworks. In hindsight, weighing up web components against JS frameworks was missing the point of the exercise. It's not about making more of your JavaScript vanilla; it's spotting opportunities to forego JavaScript in the first place.</p>

<p>Being a React user, attaching my <code class="language-plaintext highlighter-rouge">App.js</code> root component to a <code class="language-plaintext highlighter-rouge">&lt;div id="app"&gt;</code> root node and working down the tree, in smaller components, is how I'm used to thinking about SPAs. Though you <em>can</em> attach your component onto individual DOM nodes (and you could easily mix and match frameworks, too), the more standard approach is to let the framework drive the whole page.</p>

<p>This will quickly fall apart when using web components. At the level of the atomic UI element, they shine; for the business logic and user flows, you should defer to a different part of your stack. As for static content — why render it with JavaScript if raw markup does the job perfectly?</p>

<p>And so, I caught my mindset shifting from an app that's just one big component to <em>not</em> using components by default, breaking the link between component and framework in the process.</p>

<p>In the case of <em>Schematics</em>, I ended up with just a couple of main custom elements sitting neatly in the middle of native HTML tags. A simplified illustration of the idea:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;body&gt;</span>
  <span class="nt">&lt;header&gt;&lt;/header&gt;</span>
  <span class="nt">&lt;aside&gt;&lt;/aside&gt;</span>
  <span class="nt">&lt;main&gt;</span>
    <span class="nt">&lt;schematics-figure&gt;&lt;/schematics-figure&gt;</span>
    <span class="nt">&lt;schematics-figure-toolbar&gt;&lt;/schematics-figure-toolbar&gt;</span>
  <span class="nt">&lt;/main&gt;</span>
  <span class="nt">&lt;footer&gt;&lt;/footer&gt;</span>
<span class="nt">&lt;/body&gt;</span>
</code></pre></div></div>

<p>It seems obvious in hindsight that a component on a website should encapsulate the behaviour and appearance of some bit of UI. But, as <a href="https://dev.to/redbar0n/what-happened-to-components-being-just-a-visual-thing-22hc">this article nicely puts it</a>, with the proliferation of JS frameworks, “we've landed over in the ditch on the other side of the road: putting all sorts of behaviour into the same JSX-denoted structure as Components”.</p>

<blockquote>
  <p>“React renders your UI and responds to events” was <a href="https://youtu.be/x7cQ3mrcKaY?t=79">how it was introduced</a>. Not: “React is a way to transform and execute all your logic in a hierarchy of declarative markup”, as it has devolved into in many cases.</p>
  <footer><p><cite><a href="https://dev.to/redbar0n/what-happened-to-components-being-just-a-visual-thing-22hc">Magne</a></cite></p></footer>
</blockquote>

<figure class="image">
	<picture>
		<source srcset="/assets/post-assets/stackless/fig43-desktop.gif" media="(min-width: 750px)" />
    <img src="/assets/post-assets/stackless/fig43-mobile.gif" alt="Figure 43: A 3D cube erratically rotating and changing size." />
	</picture>
  <figcaption>
		A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard's</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br /><a href="https://schematics.elisehe.in">See full collection</a>
  </figcaption>
</figure>

<hr />

<p>The above thoughts are mostly based on first impressions. If you're interested in a more detailed review of the state of the web components spec, I recommend <a href="https://webreflection.medium.com/about-web-components-cc3e8b4035b0">this excellent article by Andrea Giammarchi</a> which takes a more critical look of the feature in the context of the 30-year history of the web.</p>

<h2 id="the-future-of-stackless">The future of stackless</h2>

<p><em>Schematics</em> was a simple project as far as architecture goes, and web components and ES6 modules took me most of the way there. I can't see myself building a complex app without a framework. Fully static sites, too, are simpler to build using a generator like Jekyll: something like markup reuse across pages is impossible when you only have the browser and text editor to work with (the humble <code class="language-plaintext highlighter-rouge">&lt;iframe&gt;</code> might disagree with me here…).</p>

<p>Technical limitations aside, an issue I became aware of during this exercise was a lack of standards in vanilla JavaScript. Because we're so used to the framework dictating how to structure our codebase, there aren't many established guidelines should you decide to handcraft your JavaScript. Never mind the high-level architecture — even the way you define a class, use static variables, or implement composition all have a range of approaches to them. Web components fill in the gap when it comes to the UI; perhaps if more developers dared to go framework-free (when appropriate!) discussions around best practices would be more productive.</p>

<h2 id="know-the-platform">Know the platform</h2>

<p>Developing web apps without frameworks or build tools is not an end goal in and of itself. As Daniel Kehoe put it in the stackless newsletter:</p>

<blockquote>
  <p>I don't think we'll be talking about “stackless” in a few years. It's just going to be part of any web developer's professional bag of tricks.</p>
</blockquote>

<p>I'd love to see that happen. I have a special disdain for beginner JavaScript tutorials that have you run <code class="language-plaintext highlighter-rouge">create-react-app</code> as the first step, and this exercise has only strengthened my conviction that every beginner programmer should get to grips with HTML, CSS and vanilla JS before delving into frameworks. Features native to the web are what all frameworks share, and knowing the platform makes for a stronger foundation in the face of change.</p>

<hr />

<h3 id="postscriptum-stackless-stylesheets">Postscriptum: Stackless stylesheets?</h3>

<p>I'm so used to writing LESS or SCSS, or, more recently, setting up a set of PostCSS plugins, that <em>not</em> having a preprocessor didn't cross my mind at first.</p>

<p>Indeed, Kehoe encourages the use of CSS frameworks and/or preprocessors <a href="https://tutorials.yax.com/articles/the-yax-way/2.html">“until libraries of custom UI elements are more broadly available”</a>.</p>

<p>But such a big part of the appeal of stackless for me was running <em>all</em> source code in the browser as is — having a preprocessing step for CSS would ruin that. So, in the spirit of the exercise I limited myself to vanilla CSS.</p>

<p>I persisted with this right up until the first media query came along, and things began to look nasty. Yes, modern CSS does get you 90% there; custom properties are a real workhorse (don't forget, no preprocessor can give you properties that update during runtime, or that you can set/get via JavaScript). But the lack of native support for nested selectors (along with the parent selector) I cannot live without, and that extra 10% makes a world's difference.</p>

<p>Stackless CSS isn't there yet. Use a preprocessor.</p>

<hr />

<h3 id="update-21st-may-2025">Update: 21st May, 2025</h3>

<p>I've been pleased to see a lot of resources pop up that take a vanilla-first approach to teaching and discussing building for the web:</p>

<ul>
  <li><a href="https://htmlforpeople.com/">HTML for people</a> teaches anybody, with or without any prior experience coding, to make and publish a website using HTML.</li>
  <li><a href="https://rosswintle.uk/2024/02/a-manifesto-for-small-static-web-apps/">A manifesto for small, static websites</a></li>
  <li><a href="https://plainvanillaweb.com/index.html">Plain Vanilla</a> — <em>An explainer for doing web development using only vanilla techniques. No tools, no frameworks — just HTML, CSS, and JavaScript.</em></li>
</ul>]]></content><author><name>Elise Hein</name></author><summary type="html"><![CDATA[I recently came across a series of articles by Daniel Kehoe where he introduces The Stackless Way, an optimistic take on web development that proposes we “use the platform” (modern features built into the language) instead of frameworks and build tools that keep getting replaced every few years.]]></summary></entry><entry><title type="html">Schematics: A Love Story</title><link href="https://elisehe.in/2021/06/25/schematics.html" rel="alternate" type="text/html" title="Schematics: A Love Story" /><published>2021-06-25T00:00:00+00:00</published><updated>2021-06-25T00:00:00+00:00</updated><id>https://elisehe.in/2021/06/25/schematics</id><content type="html" xml:base="https://elisehe.in/2021/06/25/schematics.html"><![CDATA[]]></content><author><name>Elise Hein</name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Why should type be fluid, anyway?</title><link href="https://elisehe.in/2021/03/13/fluid-type.html" rel="alternate" type="text/html" title="Why should type be fluid, anyway?" /><published>2021-03-13T00:00:00+00:00</published><updated>2021-03-13T00:00:00+00:00</updated><id>https://elisehe.in/2021/03/13/fluid-type</id><content type="html" xml:base="https://elisehe.in/2021/03/13/fluid-type.html"><![CDATA[<p>We call type “fluid” when the text on a web page scales
<span style="white-space: nowrap; line-height: 0"><!--
--><span style="display: inline-block; font-size: 0.8em">s</span><!--
--><span style="display: inline-block; font-size: 0.9em">m</span><!--
--><span style="display: inline-block; font-size: 1em">o</span><!--
--><span style="display: inline-block; font-size: 1.05em">o</span><!--
--><span style="display: inline-block; font-size: 1.1em">t</span><!--
--><span style="display: inline-block; font-size: 1.15em">h</span><!--
--><span style="display: inline-block; font-size: 1.2em">l</span><!--
--><span style="display: inline-block; font-size: 1.25em">y</span></span> in relation to screen size without any breakpoints. If, like me, you've just had a marathon catch-up with all things web typography in 2021, the most thrilling part about this concept would seem to be the immense gains in code maintainability.</p>

<p>Articles on fluid type often introduce the topic by explaining how it can remove complexity in styling<sup id="fnref:developergains" role="doc-noteref"><a href="#fn:developergains" class="footnote">1</a></sup>. One example:</p>

<blockquote>
  <p>I wanted to reduce the number of media-queries, but maintain responsiveness. I wanted to reduce the CSS footprint of the website. But above all, I wanted to make my CSS more maintainable. Adding fluidity for font-sizes, element sizes, and spacing contributed to achieving this goal.</p>
  <footer><p><cite><a href="https://vycke.dev/blog/fluid-interfaces-using-css">Kevin Pennekamp</a></cite>, 2020</p></footer>
</blockquote>

<p>A better developer experience is valuable in its own right. But—compared to a well-executed breakpoint-based design—what exactly is the UX impact of fully fluid type?</p>

<p>Nothing much, is the short answer. A thoughtfully implemented fluid type system won't affect users in any meaningful way—positive or negative. Maybe that's why discussions on fluid type often just gloss over readability. Plain and simple, it really is mostly about the technical optimisations it affords.</p>

<p>Still, even if subtle, I can't help but wonder about how fluidity in design impacts people, and how it matches our expectations of using the web. So, here are my thoughts and findings—some concrete, stemming from visual design and typographic theory, and some of my own (wild?) speculation.</p>

<h2 id="fluid-sizing-vs-breakpoint-based-sizing">Fluid sizing vs breakpoint-based sizing</h2>

<p>Let's start by getting size—and why it's not what matters here—out of the way.</p>

<p>First, the obvious: fluid typography is less about the outcome, and more about doing the same thing in a slightly different way. Imagine a breakpoint-based website that shows 14px text on mobile and 22px text on desktop. After you ditch the media queries and scale the text fluidly from 14px to 22px, a lot of people will still go on seeing roughly 14px text on mobile and 22px text on desktop.</p>

<p>It's the in-between screen sizes where fluidity makes more of a difference. But here, I think everyone can agree that text set at 17.6px on a 540px screen and scaled down to 15.2px on a 414px screen is not fundamentally better than showing 16px text on both. We must remember that screen width is only a convenient proxy to a much richer and unpredictable viewing context, which, at this level of tiny adjustments, will likely undo any efforts to make sizing perfect.</p>

<blockquote>
  <p>Can we count on context? Can we assume that everyone holds a phone or tablet at the same distance, or that desktop browsers are all hunched over a desk? […] Until computers sense a user's reading distance and adjust page scale proportionally (sounds like a nightmare, right?) we'll have to make these sensible generalizations.</p>
  <footer><p><cite><a href="https://trentwalton.com/2012/06/19/fluid-type">Trent Walton</a></cite>, 2012</p></footer>
</blockquote>

<p>Looking at where fluid font sizing can go wrong, the danger is tying it too intimately to the viewport, resulting in text that becomes too small or too big too fast. In 2021, though, this is mostly a non-issue. With the help of <a href="https://css-tricks.com/simplified-fluid-typography/">new CSS features</a>, <a href="https://zellwk.com/blog/viewport-based-typography/">smart implementations</a> avoid extreme scaling by only “sprinkling” the calculation with a tiny viewport multiple, and clamping it to a reasonable minimum and maximum—all the while <a href="https://ntgard.medium.com/accessible-fluid-typography-875c4aac8056">keeping everything accessible</a>!</p>

<figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nt">body</span> <span class="p">{</span>
  <span class="nl">font-size</span><span class="p">:</span> <span class="n">clamp</span><span class="p">(</span><span class="m">1rem</span><span class="p">,</span> <span class="m">0.5rem</span> <span class="err">+</span> <span class="m">2vw</span><span class="p">,</span> <span class="m">1.625rem</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<figcaption><p>Type that scales fluidly from 1rem to 1.625rem (16px to 26px in the default case). Clamped, subtle, and accessible. The technique using <code>clamp</code> is originally from <a href="https://css-tricks.com/simplified-fluid-typography/">Simplified Fluid Typography</a>; to make it accessible, Nick Gard added relative units in <a href="https://ntgard.medium.com/accessible-fluid-typography-875c4aac8056">Accessible Fluid Typography</a>.</p>
</figcaption>

<p>So—fluid typography, if carefully implemented, won't harm readability. But it also doesn't have much of an edge over a handful of well-chosen breakpoints and font sizes.</p>

<h2 id="display-text-and-line-length">Display text and line length</h2>

<p>I tried and failed to find many specific areas where viewport-relative typography <em>does</em> outperform breakpoint-based sizing in terms of readability. Here are two: setting display text and maintaining consistent measure.</p>

<ul>
  <li>
    <h4>Display text</h4>
    <p>Large textual elements such as titles or bold visual statements look more striking when they are tied to the viewport size. Richart Rutter <a href="https://24ways.org/2016/responsive-display-text/">puts it nicely</a> when he says that “display text, as defined by its purpose and relative size, is text to be <em>seen</em> first, and <em>read</em> second”.</p>

    <blockquote>
      <p>In other words a <em>picture</em> of text. When it comes to pictures, you are likely to scale all scene-setting imagery – cover photos, hero images, and so on – relative to the viewport. Take the same approach with display text: lock the size and shape of the text to the screen or browser window.</p>
    </blockquote>
  </li>
  <li>
    <h4>Consistent measure (line length)</h4>
    <p>It's widely known that there exists an optimal line length (measured in words or characters) for comfortable reading. Line length can be unpredictable at different screen widths for text whose size is static.</p>
  </li>
</ul>

<p>Viewport-relative sizing easily addresses both of these concerns<sup id="fnref:displayandmeasure" role="doc-noteref"><a href="#fn:displayandmeasure" class="footnote">2</a></sup>. It's worth pointing out, though, that while it's probably the obvious solution for setting display text (unless you're happy with JavaScript), it's by far not the only alternative for achieving consistent line length. In addition to good old media queries to control text container sizing<sup id="fnref:stepbystepmq-nudge-6" role="doc-noteref"><a href="#fn:stepbystepmq-nudge-6" class="footnote">3</a></sup>, you can also <a href="https://caniuse.com/ch-unit">use the <code class="language-plaintext highlighter-rouge">ch</code> unit</a> to specify a <code class="language-plaintext highlighter-rouge">max-length</code> for a block of text. Ultimately, don't forget that <a href="https://www.smashingmagazine.com/2014/09/balancing-line-length-font-size-responsive-web-design/">on screens, there is a relatively wide range on line length where reading comfort isn't compromised</a>, and it's not necessarily tied to text size.</p>

<blockquote>
  <p>When designing a responsive website, start with a comfortable font size and an ideal measure to help determine break points. But when the time comes (as it always does), let the ideal measure go.</p>
  <footer><p><cite><a href="https://www.smashingmagazine.com/2014/09/balancing-line-length-font-size-responsive-web-design/">Smashing Magazine</a></cite>, 2014</p></footer>
</blockquote>

<h2 id="a-note-on-accessibility">A note on accessibility</h2>

<p>Allowing people to retain control over the font size displayed in their browser is one of the most basic of accessibility requirements. Matej Latin thoroughly compared the pros and cons of different fluid type approaches in <a href="https://betterwebtype.com/articles/2019/05/14/the-state-of-fluid-web-typography/">The State of Fluid Web Typography</a>, and many of them feature issues of accessibility. As mentioned above, it <em>is</em> possible to make fluid typography accessible by combining viewport units with <code class="language-plaintext highlighter-rouge">rem</code> or <code class="language-plaintext highlighter-rouge">em</code> (preferably <code class="language-plaintext highlighter-rouge">rem</code>)—this ensures text zooming is functional and that the browser's default font size is respected.</p>

<p>Still, Latin's line of thinking resonates with me:</p>

<blockquote>
  <p>We shouldn't be focusing on making our font sizes scale nicely for every screen width. We should make our texts easy and enjoyable to read to each and every one of our users. This means leaving some of the control over web typography in their hands. At this moment, fluid web typography interferes too much with that.</p>
</blockquote>

<p>This rings true even for fluid type that ticks the accessibility boxes. It's especially relevant when laptop/desktop users resize their browser—more on that later.</p>

<hr />

<p>I'd be very interested to hear of more readability-related cases for or against fluid text sizing. As it stands, I'm not convinced there is a strong argument.</p>

<p>Next, let me shift focus from the text itself to the interface as a whole. Can the extent to which fluidity is applied create unexpected usability issues?</p>

<h2 id="fluid-type-or-fluid-interface">Fluid type or fluid interface?</h2>

<p>Similarly to readability, <em>how much</em> of the content on your site should scale fluidly is something I rarely find discussed. Perhaps you consider fluidity important only for display text, as suggested above; maybe it's all the body copy so that you can maintain consistent line lengths. There's the option of making type fluid only on handheld devices, where there's less room for drastic layout changes across sizes<sup id="fnref:mobileonly" role="doc-noteref"><a href="#fn:mobileonly" class="footnote">4</a></sup>.</p>

<p>I'm keen to hear if there are best practices here that I've missed, but it seems to me that applying fluidity anywhere can easily trickle down to the entire interface (especially if the aim is to have fewer breakpoints). Having scaled the font size, you need to scale line heights and spacing around elements. But most interface elements contain some text; you go around adding a <code class="language-plaintext highlighter-rouge">vw</code> here, a <code class="language-plaintext highlighter-rouge">vmin</code> there, and suddenly the majority of your interface is fluid.</p>

<p>At the extreme, this can result in a design that scales fully proportionally and maintains its aspect ratios everywhere. It's as if you're looking at a picture of a website, not a live interface.</p>

<p>Common sense says that most uses of fluidity will fall somewhere in the middle of that spectrum. Still, it's interesting to speculate on how full fluidity might end up harming usability.</p>

<h3 id="resizing-windows">Resizing windows</h3>

<p>Everyone resizes their windows. Your site's visitors are probably not doing it to scrutinise your responsive design, but there may be other, very practical reasons at play.</p>

<p>I make a window bigger when I want to see <em>more</em> information, not <em>the same</em> information presented in a larger size. A reason for making windows smaller might be wanting to fit multiple windows next to each other and work with all of them simultaneously (think task-based workflows such as editing in one window and viewing references in another). I have no data to back this up, but my intuition says that it's the amount of available space, not the size of visible content, that people are playing with when they resize windows.</p>

<p>When I imagine a fully fluid interface, I'm reminded of how Frank Chimero describes <a href="https://frankchimero.com/blog/2015/the-webs-grain/">edgelessness</a> as a core characteristic of designing for the web. A fully fluid interface will stubbornly stick to its edges, choosing to scale its content instead of adjusting its layout. To me, this feels annoying in the same way that scrolljacking feels annoying. To use Chimero's terms, it goes against the grain.</p>

<p>Taking a step back—as mentioned before, using viewport-relative units for just your text and scaling the whole design proportionally are very different things. But in all cases it's worth considering for a moment how and whether people expect content to scale during resizing. Browsing a blog? It probably doesn't matter. Referencing a dense data table while working in a separate window? Maybe don't go crazy with fluidity.</p>

<h3 id="one-fluid-size-fits-all">One (fluid) size fits all</h3>

<p>I'm verging on the hypothetical now, but for the sake of argument, here's a final thought on how excessive fluidity in interfaces can negatively impact UX: the loss of pressure on developers to adapt the design.</p>

<p>If things scale proportionally and line breaks stay where they ought to, everything technically looks great on a wide array of screen sizes. At the extreme, you might get away with separate mobile and desktop layouts, with everything in between relying on fluidity.</p>

<p>But this goes back to the previous point about people wanting to see more content, not the same content bigger. The extra space afforded by larger screens should be seen as an opportunity to adjust the layout so the viewer can take full advantage of their screen real estate. Conversely, on small screens, with a fluid interface you can <em>juuuuust</em> push at the boundary of what's acceptable, because everything fits, after all.</p>

<blockquote>
  <p>How large should images be? When does the site look awkward &amp; disjointed? When do things get too large? Is it appropriate to slide this layout's columns out into additional sidebars […]?</p>
  <footer><p><cite><a href="https://trentwalton.com/2012/06/19/fluid-type/">Trent Walton</a></cite>, 2012</p></footer>
</blockquote>

<p>It's not that anyone would purposefully neglect responsive design. But it does become easier for things to count as passable. At the very least, subtly adjusting designs for many screen sizes (“tweak till you're cross-eyed”, in Walton's words<sup id="fnref:stepbystepmq-nudge-6:1" role="doc-noteref"><a href="#fn:stepbystepmq-nudge-6" class="footnote">3</a></sup>) is an outlet for creativity and craftsmanship, and it would be a shame to start seeing less of that.</p>

<hr />

<p>To recap: I explained why fluid font sizing isn't inherently better or worse for UX, aside from a couple of minor, specific areas. I also speculated on how excessive fluidity can become problematic. Many of the issues raised are subtle, but I wanted to raise them nonetheless and add to what otherwise feels like a developer-centric discussion.</p>

<p>Personally, I'm happy to stay with responsive approaches for now. I do want to make a point of endorsing fluid type and its simplicity: it's a clever technique that when thoughtfully implemented can create beautiful typography that scales exactly where you want it to, challenging the view that dimensions on the web should be fixed. But as it stands, I'm not convinced the value added is worthwhile.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:developergains" role="doc-endnote">
      <p><a href="https://vycke.dev/blog/fluid-interfaces-using-css/">Fluid interfaces using CSS</a> by Kevin Pennekamp, 2020<br />
<a href="https://pixelgrade.com/upstairs/creating-true-fluid-web-typography/">Creating true fluid web typography to improve our processes</a> by Razvan Onofrei, 2020<br />
<a href="https://zellwk.com/blog/changing-modular-scale/">Changing Modular Scale Ratio at Different Breakpoints</a> by Zell Liew, 2016 <a href="#fnref:developergains" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:displayandmeasure" role="doc-endnote">
      <p><a href="https://css-tricks.com/viewport-sized-typography/">Viewport Sized Typography</a> by Chris Coyer, 2012<br /><a href="https://24ways.org/2016/responsive-display-text/">Get the Balance Right: Responsive Display Text</a> by Richard Rutter, 2016 <a href="#fnref:displayandmeasure" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:stepbystepmq-nudge-6" role="doc-endnote">
      <p>I cite Trent Walton's 2012 article, <a href="https://trentwalton.com/2012/06/19/fluid-type/">Fluid Type</a>, a few times here. Among other things, I think it includes a lovely, step-by-step description of the traditional responsive, breakpoint-based, approach to getting line lengths perfect. His thoroughness shows a real respect to viewers, which is a part of why it's somewhat bittersweet to see more pragmatic, one-size-fits-all solutions becoming popular. <a href="#fnref:stepbystepmq-nudge-6" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:stepbystepmq-nudge-6:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:mobileonly" role="doc-endnote">
      <p>The design is usually the same across mobile phones, so making the type proportional to other elements leaves less room for surprising edge cases. I haven't seen this idea of fluidity only on smaller screens toyed with, but I personally think it's something worth exploring (<a href="https://www.munnelly.com/webdev/responsive-web-design-fluid-typography.html">this article</a> cites laptop sizes as “popular choices” for the maximum screen widths where fluidity is applied, but it's unclear whether that means fluidity on larger screens is <em>unpopular</em>; Razvan Onofrei <a href="https://pixelgrade.com/upstairs/creating-true-fluid-web-typography/">also hints at</a> laptop screen sizes being the boundary for fluidity, but, again, it's not made explicit). <a href="#fnref:mobileonly" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Elise Hein</name></author><summary type="html"><![CDATA[We call type “fluid” when the text on a web page scales smoothly in relation to screen size without any breakpoints. If, like me, you've just had a marathon catch-up with all things web typography in 2021, the most thrilling part about this concept would seem to be the immense gains in code maintainability.]]></summary></entry></feed>