<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>Blog | Navillus</title><description>We build websites and internal tools for small businesses and non-profits. Boring tech, no lock-in, no surprises five years from now.</description><link>https://navillus.dev/</link><language>en-us</language><item><title>The case for boring tech, five years later</title><link>https://navillus.dev/blog/boring-tech-five-years-later</link><guid isPermaLink="true">https://navillus.dev/blog/boring-tech-five-years-later</guid><description>Five years ago we wrote that the boring tools make your life easier in the long run. Here&apos;s what held up, what didn&apos;t, and why it matters more now.</description><pubDate>Mon, 04 May 2026 12:00:00 GMT</pubDate><id>/blog/boring-tech-five-years-later</id><content> &lt;p&gt;Almost five years ago we published &lt;a href=&quot;/blog/power-of-simplicity&quot;&gt;The power of simplicity&lt;/a&gt;, arguing that &amp;quot;the boring tools make your life easier in the long run.&amp;quot; We&amp;#39;d just rebuilt the site on &lt;a href=&quot;https://astro.build&quot;&gt;Astro&lt;/a&gt; and &lt;a href=&quot;https://forestry.io&quot;&gt;Forestry CMS&lt;/a&gt; and were feeling pleased with ourselves about it.&lt;/p&gt;
&lt;p&gt;Forestry got wound down by its own team. Half the JavaScript ecosystem we name-checked is on a different version, deprecated, or quietly forgotten. The &amp;quot;Jamstack&amp;quot; branding has folded back into the rest of the web.&lt;/p&gt;
&lt;p&gt;So how did the thesis hold up?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt; Better than we expected, and for reasons we mostly didn&amp;#39;t predict. The boring stuff outlasted the shiny stuff. We were also wrong about who the post was for.&lt;/p&gt;
&lt;h2&gt;What aged well&lt;/h2&gt;
&lt;p&gt;The list of &amp;quot;old, boring tools&amp;quot; we cited (JSON, git, markdown, HTML, CSS, JavaScript) is exactly the same list we&amp;#39;d cite today, with the same versions still in widespread use. Nobody has had to relearn how to write a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;
&lt;p&gt;A few additions to the boring-tech canon since:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Postgres&lt;/strong&gt; is more entrenched than ever. The five-year-old startup using it is still using it. The hot new managed-Postgres service that launched in 2022 has since been acquired by another managed-Postgres service that launched in 2024.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLite&lt;/strong&gt; quietly took over half the small things we build. It runs at the edge, it ships inside the binary, it backs single-user apps and multi-tenant ones, and it&amp;#39;s still a single file you can copy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Astro&lt;/strong&gt;, which we called &amp;quot;shiny new&amp;quot; in the original post, is now five years old and behaves like the boring tools: backwards-compatible upgrades, slow API churn, content collections that haven&amp;#39;t broken twice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pattern is the same one we noticed in 2021: tools that don&amp;#39;t make you relearn them every year tend to compound. Tools that do, don&amp;#39;t.&lt;/p&gt;
&lt;h2&gt;What didn&amp;#39;t survive&lt;/h2&gt;
&lt;p&gt;The Forestry team rebuilt their product as TinaCMS, which is tied to React, and wound the original CMS down. Snipcart got acquired by Duda back in 2021 and isn&amp;#39;t the standalone story it used to be. Gatsby went from one of three default React choices to a footnote. The serverless-backend startup we evaluated in 2021 is gone; the one that replaced it is being acquired; the one that&amp;#39;s replacing &lt;em&gt;that&lt;/em&gt; is making the same architectural promises in a slightly different colour scheme.&lt;/p&gt;
&lt;p&gt;We&amp;#39;re not gloating. We picked Forestry. We were wrong. The lesson isn&amp;#39;t &amp;quot;we predicted this.&amp;quot; It&amp;#39;s that the cost of being wrong about a vendor was much higher than the cost of being wrong about a framework. Migrating from Astro to Eleventy is a weekend&amp;#39;s work. Migrating off a deprecated CMS is a re-platform.&lt;/p&gt;
&lt;h2&gt;What we got wrong&lt;/h2&gt;
&lt;p&gt;Two things, mainly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We wrote the post for developers.&lt;/strong&gt; That made sense in 2021. We were in a Twitter thread loop with other devs about Jamstack architecture. Five years on, the argument matters even more for the people who &lt;em&gt;aren&amp;#39;t&lt;/em&gt; developers: the small business owner whose site keeps breaking after WordPress updates, the nonprofit director whose previous developer disappeared and took the deployment credentials with her. They don&amp;#39;t have the option of &amp;quot;I&amp;#39;ll just learn the new framework.&amp;quot; For them, boring tech isn&amp;#39;t an aesthetic preference. It&amp;#39;s the only honest answer to &amp;quot;will this still be running in five years.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We undersold the human side.&lt;/strong&gt; The original post celebrated long-lived tech without addressing the social problem: who maintains it after you leave? &amp;quot;Boring&amp;quot; isn&amp;#39;t valuable because it&amp;#39;s old. It&amp;#39;s valuable because the next person can pick it up. Postgres is boring because millions of developers know it. HTML is boring because every browser implements it. A weird stack is &amp;quot;interesting&amp;quot; right up until the day you need to hand it off, at which point it&amp;#39;s a liability.&lt;/p&gt;
&lt;h2&gt;What changed since&lt;/h2&gt;
&lt;p&gt;A few things genuinely shifted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI writes a lot of code now.&lt;/strong&gt; We use it. It doesn&amp;#39;t change the boring-tech argument. If anything, it sharpens it. AI is excellent at reading and writing well-trodden patterns: plain JavaScript, Postgres queries, a basic Astro page. It struggles with bespoke architectures, custom DSLs, and the tangled middleware of a 15-plugin WordPress install. The cheaper AI gets at maintaining your code, the more you should write code AI can read.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The web platform caught up.&lt;/strong&gt; A lot of what required a framework in 2021 now ships in the browser: view transitions, the popover API, native dialogs, container queries. The ratio of &amp;quot;HTML you write&amp;quot; to &amp;quot;framework you wrap it in&amp;quot; keeps tilting toward HTML. Good.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edge and serverless settled.&lt;/strong&gt; The &amp;quot;should I deploy to 27 regions or 3?&amp;quot; debate quieted down. Most small sites and most internal tools want one good region, sometimes a CDN, sometimes a worker at the edge for one specific job. Almost nobody actually needs the architectural complexity that was being sold as table stakes.&lt;/p&gt;
&lt;h2&gt;Why this matters more for our clients&lt;/h2&gt;
&lt;p&gt;Five years ago we were writing for ourselves. Today we build websites and internal tools for &lt;a href=&quot;/&quot;&gt;small businesses and non-profits&lt;/a&gt;, and the stakes are different.&lt;/p&gt;
&lt;p&gt;A nonprofit running on a $30/month hosting bill cannot afford a year of refactoring to chase a framework deprecation. A community center whose donor data lives in a Retool dashboard does not need a microservices migration. The local business whose website does seven things does not benefit from a stack that could theoretically scale to seven million.&lt;/p&gt;
&lt;p&gt;For them, &amp;quot;boring&amp;quot; isn&amp;#39;t conservative. It&amp;#39;s competent. It&amp;#39;s the difference between a website that costs $0 to leave running and one that quietly accumulates a $400/month SaaS bill nobody can explain.&lt;/p&gt;
&lt;h2&gt;What still holds up&lt;/h2&gt;
&lt;p&gt;The closing line of the original post was: &lt;em&gt;&amp;quot;There&amp;#39;s always a new tool to try or pattern to learn, but at the end of the day it&amp;#39;s much more satisfying to ship a product rather than trying out a new tool.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That still stands. We&amp;#39;d add one thing: ship a product &lt;em&gt;that the next person can pick up without you.&lt;/em&gt; Not because they will, but because if they have to, the project survives.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s what boring tech actually buys.&lt;/p&gt;
 </content></item><item><title>How to use Web Components in Astro</title><link>https://navillus.dev/blog/web-components-in-astro</link><guid isPermaLink="true">https://navillus.dev/blog/web-components-in-astro</guid><description>Building custom elements in plain old JavaScript + trying out the new Astro.resolve() API!</description><pubDate>Mon, 23 Aug 2021 17:51:19 GMT</pubDate><id>/blog/web-components-in-astro</id><content> &lt;p&gt;Web components have had a bit of a rocky past, to put it lightly. The API design has gone through multiple iterations, a few unexpected &lt;a href=&quot;https://thenewobjective.com/web-development/a-criticism-of-web-components&quot;&gt;rough edges&lt;/a&gt; really hindered their usefulness. And if it wasn&amp;#39;t already confusing enough, the level of excitement around the vision of custom elements has led to &lt;a href=&quot;https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/&quot;&gt;over &lt;strong&gt;50&lt;/strong&gt;&lt;/a&gt; different patterns and frameworks to get the job done.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve honestly not given custom elements much of a chance since the early iterations of the spec, and its about time I give it a proper chance again. Like many web developers I really love the vision of having a standard for building native custom components without reaching for the &lt;a href=&quot;https://reactjs.org&quot;&gt;usual&lt;/a&gt; &lt;a href=&quot;https://svelte.dev&quot;&gt;JavaScript&lt;/a&gt; &lt;a href=&quot;https://vuejs.org&quot;&gt;frameworks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt; Web components aren&amp;#39;t the magic bullet I&amp;#39;d hoped for, but they&amp;#39;ve come a long way in the last couple years. When paired with Astro&amp;#39;s new &lt;a href=&quot;https://docs.astro.build/reference/api-reference#astroresolve&quot;&gt;resolve&lt;/a&gt; API you end up with a dead simple way to quickly author simple pure JavaScript web components, bundle them for production, and hydrate them on the client. Check out a &lt;a href=&quot;https://demo-astro-web-components.netlify.app/&quot;&gt;live demo&lt;/a&gt; or jump right into the source code on &lt;a href=&quot;https://github.com/navillus-bv/demo-astro-web-components&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Do web components require a framework?&lt;/h2&gt;
&lt;p&gt;No! There are great options if you&amp;#39;re ready to go all-in on frameworks though, I strongly recommend you checkout &lt;a href=&quot;https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/&quot;&gt;webcomponent.dev&amp;#39;s&lt;/a&gt; detailed breakdown of all the different ways it can be done.&lt;/p&gt;
&lt;p&gt;At the end of the day though, the frameworks are just going to compile down to JS (I haven&amp;#39;t seen any WASM implementations yet). Frameworks like &lt;a href=&quot;https://lit.dev/&quot;&gt;Lit&lt;/a&gt; let you skip the boilerplate and can help avoid some of the gotchas along the way, but what&amp;#39;s a basic web component even look like?&lt;/p&gt;
&lt;h3&gt;Less complicated than it sounds&lt;/h3&gt;
&lt;p&gt;Web components can be daunting - shadow roots, &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;s, and &lt;code&gt;extend HTMLElement&lt;/code&gt; aren&amp;#39;t exactly old hack for most web developers. Let&amp;#39;s break down the basic structure first, then jump into an full example.&lt;/p&gt;
&lt;h2&gt;Web component basics&lt;/h2&gt;
&lt;p&gt;Fair warning, I&amp;#39;m by no means an expert on web components - please hit me up on &lt;a href=&quot;https://twitter.com/navillus_dev&quot;&gt;Twitter&lt;/a&gt; if I misrepresent something here!&lt;/p&gt;
&lt;h3&gt;Shadow roots&lt;/h3&gt;
&lt;p&gt;One of the more contentious parts of the spec, and the cause of many of the limitations, is the shadow DOM. The idea is to encapsulate each custom element from the rest of the DOM - if you&amp;#39;ve ever worked with &lt;code&gt;iframe&lt;/code&gt;s this will sound familiar.&lt;/p&gt;
&lt;p&gt;The key here is that code outside of the custom element can reach down into the shadow DOM and change things, styling for example. It doesn&amp;#39;t work the other way around though, elements and styles inside the shadow DOM can&amp;#39;t reach outside and affect the outside world.&lt;/p&gt;
&lt;p&gt;Sounds great, until you want a web component to change it&amp;#39;s style based on the content around it - theming can be tricky and force you to jump through hoops.&lt;/p&gt;
&lt;p&gt;Thankfully, the shadow DOM is actually opt-in and you can extend &lt;code&gt;HTMLElement&lt;/code&gt; without losing access to the rest of the DOM.&lt;/p&gt;
&lt;h3&gt;HTML &lt;code&gt;template&lt;/code&gt;s&lt;/h3&gt;
&lt;p&gt;Web components are meant to be reusable, and for that to be possible you need to be able to define a template with the element&amp;#39;s initial HTML elements and styles.&lt;/p&gt;
&lt;p&gt;This can be done a few different ways, but the most common way is to use template literals right in your web component&amp;#39;s JS file. I&amp;#39;ll be using one of the excellent examples from &lt;a href=&quot;https://webcomponents.dev/edit/ZCUsvyx06Au5j0yZzgG7?pm=1&quot;&gt;webcomponents.dev&lt;/a&gt; as a starting point.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const template = document.createElement(&amp;#39;template&amp;#39;)
template.innerHTML = `
  &amp;lt;style&amp;gt;
    /* your styles */
  &amp;lt;/style&amp;gt;
  &amp;lt;span id=&amp;quot;count&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;
`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Feels a little weird writing HTML in a template literal, right? It gets the job done though, and in my opinion plain JS web components really shine with small components so this shouldn&amp;#39;t get too crazy to maintain.&lt;/p&gt;
&lt;p&gt;All this really does is create a new &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; tag, just like if you directly included it in your &lt;code&gt;index.html&lt;/code&gt;. The template contains all the initial styling and HTML used to initialize the component.&lt;/p&gt;
&lt;h3&gt;extending HTMLElement&lt;/h3&gt;
&lt;p&gt;This is where it gets really interesting. Ever wonder why you can&amp;#39;t make your own &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; elements? Well now you can (kind of)! I wouldn&amp;#39;t recommend trying to actually replace existing HTML tags - I don&amp;#39;t know if that would even work and it sounds like a nightmare for accessibility tools.&lt;/p&gt;
&lt;p&gt;But you can make your own &lt;code&gt;&amp;lt;my-counter&amp;gt;&lt;/code&gt; component, that&amp;#39;s definitely not part of the HTML specs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;class MyCounter extends HTMLElement {
  constructor() {
    super()
    this.count = 0
    // open mode keeps all elements accessible to the outside world
    this.attachShadow({ mode: &amp;#39;open&amp;#39; })
  }
  // ...
}

// tell the browser to use this class for all `&amp;lt;my-counter&amp;gt;` elements
customElements.define(&amp;#39;my-counter&amp;#39;, MyCounter)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the &lt;code&gt;open&lt;/code&gt; mode there? I mentioned earlier that you can avoid the one-way encapsulation of the shadow DOM, that&amp;#39;s all it takes. It&amp;#39;s a shame having to turn off one of the key features of custom elements, but theming and styling really can be a big problem for real world apps!&lt;/p&gt;
&lt;h3&gt;Filling in the details&lt;/h3&gt;
&lt;p&gt;I&amp;#39;ll leave it up to you to check out the full source code on &lt;a href=&quot;https://github.com/navillus-bv/demo-astro-web-components&quot;&gt;GitHub&lt;/a&gt;. I also recommend checking out the examples from &lt;a href=&quot;https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/&quot;&gt;webcomponents.dev&lt;/a&gt; as well to see what all I had to change. Spoiler: not much!&lt;/p&gt;
&lt;h2&gt;How does this work in Astro?&lt;/h2&gt;
&lt;p&gt;One huge benefit of Astro is the heavy focus on minimizing, or even completely avoiding, the amount of JavaScript used on a site. I&amp;#39;ve written &lt;a href=&quot;/blog/keeping-it-simple-with-astro&quot;&gt;before&lt;/a&gt; about how important simplicity is in web development, so I&amp;#39;ll spare you the rant here.&lt;/p&gt;
&lt;p&gt;For me, the big promise of web components is the ability to easily share basic elements across multiple projects without being tied to one specific framework. I&amp;#39;m not ready to build an entire PWA in web components, but when it comes to the base-level building blocks for a site I&amp;#39;d love to share a single &lt;code&gt;&amp;lt;nv-button&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;nv-spinner&amp;gt;&lt;/code&gt; etc.&lt;/p&gt;
&lt;p&gt;Maybe one of these days I&amp;#39;ll find the time to build a full &lt;a href=&quot;https://openui5.org/&quot;&gt;OpenUI&lt;/a&gt; toolkit to use for all of our client projects...&lt;/p&gt;
&lt;h3&gt;Oops, our web component breaks SSR!&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const template = document.createElement(&amp;#39;template&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well that didn&amp;#39;t take long, literally the first line of code breaks our Astro build 🤣&lt;/p&gt;
&lt;p&gt;Astro is a static site generator, the entire build runs in Node.js. That means we can&amp;#39;t actually touch the browser-only &lt;code&gt;document&lt;/code&gt; object.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Just create a shared string here, no more document reference
const template = `
  &amp;lt;style&amp;gt;
    /* your styles */
  &amp;lt;/style&amp;gt;
  &amp;lt;span id=&amp;quot;count&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;
`

class MyCounter extends HTMLElement {
  constructor() {
    super()

    const elem = document.createElement(&amp;#39;template&amp;#39;)
    elem.innerHTML = template

    this.count = 0
    this.attachShadow({ mode: &amp;#39;open&amp;#39; }).appendChild(
      elem.content.cloneNode(true)
    )
  }
}

customElements.define(&amp;#39;my-counter&amp;#39;, MyCounter)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There we go! Don&amp;#39;t touch the document element at all until the constructor is called. Note that this really could/should be cleaned up to move &lt;code&gt;elem&lt;/code&gt; outside the class and only initialize it once, but for the sake of this demo I kept the code easier to follow.&lt;/p&gt;
&lt;h3&gt;Great, but how do we actually load the script?&lt;/h3&gt;
&lt;p&gt;Astro just recently released &lt;a href=&quot;https://astro.build/blog/astro-019&quot;&gt;version 0.19&lt;/a&gt;, one of the cool new features is the &lt;code&gt;Astro.resolve()&lt;/code&gt; API. With it, you can take a relative URL to another file in your &lt;code&gt;src/&lt;/code&gt; directory and resolve it to the built file path.&lt;/p&gt;
&lt;p&gt;This is handy for images, &lt;code&gt;Astro.resolve(&amp;#39;../images/penguin.png&amp;#39;)&lt;/code&gt;, but we&amp;#39;re going to take it a step further and use this new API to pull in our web component&amp;#39;s JS file.&lt;/p&gt;
&lt;p&gt;In the demo project, the web component is defined in &lt;code&gt;src/components/my-counter.js&lt;/code&gt;. Inside the homepage at &lt;code&gt;src/pages/index.astro&lt;/code&gt;,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Welcome to Astro&amp;lt;/title&amp;gt;

  &amp;lt;script type=&amp;quot;module&amp;quot; src={Astro.resolve(&amp;#39;../components/my-counter.js&amp;#39;)}
  &amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
  &amp;lt;my-counter&amp;gt;&amp;lt;/my-counter&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&amp;#39;s all there is to it! From there Astro will be aware of the JS file, bundle it during production builds, and replace the &lt;code&gt;Astro.resolve&lt;/code&gt; call with the URL needed to load in the component.&lt;/p&gt;
&lt;h4&gt;Future reading&lt;/h4&gt;
&lt;p&gt;Follow us on &lt;a href=&quot;https://twitter.com/navillus_dev&quot;&gt;Twitter&lt;/a&gt; or subscribe to our &lt;a href=&quot;https://navillus.dev/feed/blog.xml&quot;&gt;RSS feed&lt;/a&gt; so you don&amp;#39;t miss a future post covering more complex web components written with &lt;a href=&quot;https://lit.dev/&quot;&gt;Lit&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Web components aren&amp;#39;t a magic bullet, but I found this experience &lt;strong&gt;much&lt;/strong&gt; less frustrating than the last time I tried it out. To be fair, that was probably back in 2017 when the spec was still an early work in progress.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m still not sure that I&amp;#39;d go through the effort to build an entire site in custom web components just yet, but I won&amp;#39;t actually be surprised if that&amp;#39;s a great option in the not too distant future.&lt;/p&gt;
&lt;p&gt;Until then, browser support is &lt;a href=&quot;https://caniuse.com/?search=web%20components&quot;&gt;surprisingly good&lt;/a&gt; and web components can be a great solution to reusable base components. Whether you&amp;#39;re managing multiple projects or just preparing for the next big shakeup in frontend frameworks, it&amp;#39;s worth giving native custom elements a second look in 2021.&lt;/p&gt;
 </content></item><item><title>The power of simplicity</title><link>https://navillus.dev/blog/power-of-simplicity</link><guid isPermaLink="true">https://navillus.dev/blog/power-of-simplicity</guid><description>It&apos;s easy to get swept up in the latest libraries and platforms, but don&apos;t underestimate the time savings of &quot;boring&quot; tech.</description><pubDate>Sat, 07 Aug 2021 17:42:35 GMT</pubDate><id>/blog/power-of-simplicity</id><content> &lt;p&gt;Our last post highlighted the benefits of using &lt;a href=&quot;https://astro.build&quot;&gt;Astro&lt;/a&gt; and &lt;a href=&quot;https://forestry.io&quot;&gt;Forestry CMS&lt;/a&gt; (catch up &lt;a href=&quot;/blog/astro-plus-forestry-revisited&quot;&gt;here&lt;/a&gt; in case you missed it). Forestry actually shared our post with a really important point that I thought was worth digging into with a proper blog post.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;The beauty of embracing open formats like Markdown and JSON on top of Git is that a CMS you designed more than 5 years ago, works perfectly with the latest modern web tooling.&lt;br&gt;&lt;br&gt;Now go check &lt;a href=&quot;https://twitter.com/astrodotbuild?ref_src=twsrc%5Etfw&quot;&gt;@astrodotbuild&lt;/a&gt; to ship faster websites 🚀 &lt;a href=&quot;https://t.co/0o6OgCfkgx&quot;&gt;https://t.co/0o6OgCfkgx&lt;/a&gt;&lt;/p&gt;&amp;mdash; Forestry CMS (@forestryio) &lt;a href=&quot;https://twitter.com/forestryio/status/1423251438063521792?ref_src=twsrc%5Etfw&quot;&gt;August 5, 2021&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;&lt;p&gt;Follow &lt;a href=&quot;https://twitter.com/forestryio&quot;&gt;@forestryio&lt;/a&gt; on Twitter if you don&amp;#39;t already! Their git-based CMS tools are a huge win for any Jamstack project and should you have any questions or issues, their team is extremely responsive and helpful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt; The web development landscape is constantly in flux. It&amp;#39;s easy to see the latest tools, languages, and frameworks get hyped on your favorite blogs and podcasts, but at the end of the day you probably need to build quality sites that can be easily maintained for years. More often than not, the boring tools make your life easier in the long run!&lt;/p&gt;
&lt;h2&gt;A bit of history&lt;/h2&gt;
&lt;p&gt;It wasn&amp;#39;t so long ago that sites were edited by handed - good old fashioned HTML,CSS, and (maybe) JS. A site contained more &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt;s than pages, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/marquee&quot;&gt;RIP marquee!&lt;/a&gt;, and the &lt;code&gt;querySelector&lt;/code&gt; wasn&amp;#39;t a thing.&lt;/p&gt;
&lt;p&gt;Browsers are still fundamentally the same today. Yes they are much bigger and feature packed, but at the end of the day a browser takes markup in the form of HTML, styles in the form of CSS, and JavaScript for interactivity. We have more APIs to play with, and more caching problems to manage, but at the end of the day it&amp;#39;s the development workflow that has really complicated.&lt;/p&gt;
&lt;h3&gt;Modern web development&lt;/h3&gt;
&lt;p&gt;Jumping ahead a few years (or 20) and now its not uncommon to start a project by deciding on the 5+ tools and platforms you will be bolting together. Do you want static site generation (SSG), server-side rendering (SSR), or client-side rendering(CSR)? Are you standing up and maintaining a backend server, or jumping on the serverless backend? Will plain CSS do the trick, or is SASS/SCSS/LESS your preferred approach? What about this CSS in JS and styled components madness?!?&lt;/p&gt;
&lt;p&gt;Oh wait, you also need to host it somewhere. Does the project need continuous integration (CI) with automated testing, automated deployments, staging environments, etc? Better schedule another few days to decide on those tools, setup accounts, and bootstrap the basics together before you start coding!&lt;/p&gt;
&lt;h2&gt;Does it have to be so complicated?&lt;/h2&gt;
&lt;p&gt;No! Sure, all these tools and processes were created for good reason. CI can save a huge amount of time. Transpilers, build tools, and linters can help catch common mistakes and enforce team coding standards. Frontend frameworks can really be a lifesaver, especially if you need to build interfaces with complex interactivity and state management.&lt;/p&gt;
&lt;p&gt;But all these advancements can turn into a real nightmare, too. Ever tried onboarding a new hire that is an experienced web developer but not familiar with the latest React trends like styled components or hooks? Is the time spent ramping up on these abstractions really worth the effort for a simple marketing site or statically hosted e-commerce store?&lt;/p&gt;
&lt;h3&gt;Long-term value&lt;/h3&gt;
&lt;p&gt;The tech stack you chose can be a much larger commitment than it first seems to be. I would be very surprised if react was no longer used a couple years down the road, but what about the 15 libraries you pull in to that react app?&lt;/p&gt;
&lt;p&gt;If a project is built on an &lt;em&gt;everything but the kitchen sink™&lt;/em&gt; app platform like &lt;a href=&quot;https://nuxtjs.org/&quot;&gt;Nuxt.js&lt;/a&gt;, &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;, or &lt;a href=&quot;https://kit.svelte.dev&quot;&gt;SvelteKit&lt;/a&gt; can really let you hit the ground running, but will they still be supported a few years down the road? Will developers still be excited to work with them, or will it be the butt of too many developers&amp;#39; jokes like WordPress?&lt;/p&gt;
&lt;h2&gt;Keep it simple, stupid&lt;/h2&gt;
&lt;p&gt;Back to the entire reason we&amp;#39;re here, sticking with &amp;quot;boring&amp;quot; tech may not be such a bad idea. The &lt;a href=&quot;https://en.wikipedia.org/wiki/JSON&quot;&gt;JSON&lt;/a&gt; spec is around 20 years old, &lt;a href=&quot;https://en.wikipedia.org/wiki/Git&quot;&gt;git&lt;/a&gt; is 16 years old, and &lt;a href=&quot;https://en.wikipedia.org/wiki/Markdown&quot;&gt;markdown&lt;/a&gt; is 17 years old. Though the HTML/CSS/JS standards are a constant work in progress they go back as far as &lt;a href=&quot;https://en.wikipedia.org/wiki/HTML&quot;&gt;1993&lt;/a&gt; (ok, 1995 for &lt;a href=&quot;https://en.wikipedia.org/wiki/JavaScript&quot;&gt;JavaScript&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Even better, these are all open standards that may evolve but will never go away.&lt;/p&gt;
&lt;h3&gt;What about this site though?&lt;/h3&gt;
&lt;p&gt;This very site is built with a shiny new SSG, &lt;a href=&quot;https://astro.build&quot;&gt;Astro&lt;/a&gt;, isn&amp;#39;t that a bit hypocritical? I don&amp;#39;t think so, but hear me out!&lt;/p&gt;
&lt;p&gt;Take a look at the &lt;a href=&quot;https://github.com/navillus-bv/navillus-dev&quot;&gt;sourcecode&lt;/a&gt; for this project and you&amp;#39;ll see that the real meat of the site is based entirely on the standards above. SCSS is used for styling, but honestly the only reason we even used that was because CSS nesting isn&amp;#39;t supported yet.&lt;/p&gt;
&lt;p&gt;Most of the site&amp;#39;s pages are authored in markdown and various bits of data are stored in JSON.&lt;/p&gt;
&lt;p&gt;Page templates and components are in &lt;code&gt;.astro&lt;/code&gt; files that really just consists of JavaScript in the form of &lt;a href=&quot;https://docs.astro.build/core-concepts/astro-components#frontmatter-script&quot;&gt;frontmatter&lt;/a&gt;, styles are either in global &lt;code&gt;.css&lt;/code&gt; files or component-level &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; blocks, and markup is mostly HTML with a bit of JSX-like javascript inline for conditionals and for-each loops.&lt;/p&gt;
&lt;p&gt;It would take a matter of a few hours to migrate the entire site to a different SSG, or even an entirely different platform like &lt;a href=&quot;https://nextjs.org&quot;&gt;Next.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So yes, we&amp;#39;re technically using a few new build tools to wire templates together, but at the end of the day it&amp;#39;s easy to use Astro and forget that you aren&amp;#39;t using the basic, web-native HTML/CSS/JS stack.&lt;/p&gt;
&lt;h2&gt;Innovation is amazing, just don&amp;#39;t get stuck&lt;/h2&gt;
&lt;p&gt;It&amp;#39;s a very common mistake, especially for developers newer to web development, to get stuck on the hamster wheel chasing the latest toy. Don&amp;#39;t avoid innovation, just be strategic and selective in where you invest your time and efforts!&lt;/p&gt;
&lt;p&gt;There&amp;#39;s always a new tool to try or pattern to learn, but at the end of the day it&amp;#39;s much more satisfying to ship a product rather than trying out a new tool with sample projects that never sees the light of day!&lt;/p&gt;
&lt;p&gt;I feel like I bashed on react here - don&amp;#39;t get me wrong, react is an excellent and extremely powerful tool. React still has a huge lead in usage compared to the other frontend frameworks and, especially if you&amp;#39;re in the job market, it&amp;#39;s much easier to find job listing that require react experience than listings that don&amp;#39;t mention react at all. But if you find yourself running in circles, getting frustrated with a build pipeline that&amp;#39;s fighting you or magic hooks that you don&amp;#39;t understand, take a step back and learn the basic (boring) 20 year old tools. Ship fast, don&amp;#39;t break the build!&lt;/p&gt;
 </content></item><item><title>Keeping it simple with Astro</title><link>https://navillus.dev/blog/keeping-it-simple-with-astro</link><guid isPermaLink="true">https://navillus.dev/blog/keeping-it-simple-with-astro</guid><description>Do you really need all that JavaScript?</description><pubDate>Mon, 14 Jun 2021 17:41:41 GMT</pubDate><id>/blog/keeping-it-simple-with-astro</id><updated>Fri Jun 25 2021 00:00:00 GMT+0000 (Coordinated Universal Time)</updated><content> &lt;p&gt;Frontend frameworks have taken over much of the web, but the question remains - do we really need all that JavaScript in the browser?&lt;/p&gt;
&lt;h2&gt;Keeping it simple&lt;/h2&gt;
&lt;p&gt;Client-side rendering can be an extremely powerful tool, and in some cases can solve problems that are nearly impossible to solve strictly with server rendering. A vast majority of the web is pretty simple though - mostly static content with a bit of interactivity sprinkled in.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://navillus.dev&quot;&gt;Navillus&lt;/a&gt; is a perfect example. It has a mobile menu taking advantage of Svelte&amp;#39;s excellent &lt;a href=&quot;https://svelte.dev/docs#svelte_transition&quot;&gt;built-in transition&lt;/a&gt;, and we recently added in a dark mode toggle. That&amp;#39;s it for interactions on our site though, the rest is entirely static.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/assets/2021-06-14-navillus-dev-tools.png&quot; alt=&quot;Screenshot of Navillus devtools&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s a screenshot of devtools with our homepage loaded. A grand total of ~78KB transferred, including all the HTML, CSS, JavaScript, images, and fonts. Don&amp;#39;t get me wrong, there&amp;#39;s more to web design and development than just minimizing bundle size, but performance matters and small wins can add up quickly.&lt;/p&gt;
&lt;h3&gt;What&amp;#39;s the trick?&lt;/h3&gt;
&lt;p&gt;When designing our site we had performance in mind from the beginning. You may notice that we purposely don&amp;#39;t have any images on our homepage, and the only icons used are actually taking advantage of the &lt;a href=&quot;https://css-tricks.com/svg-sprites-use-better-icon-fonts/&quot;&gt;SVG sprites&lt;/a&gt; technique to load all the icons in a single file.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Just finished rebuilding &lt;a href=&quot;https://t.co/UcT54hdIY5&quot;&gt;https://t.co/UcT54hdIY5&lt;/a&gt; with &lt;a href=&quot;https://twitter.com/astrodotbuild?ref_src=twsrc%5Etfw&quot;&gt;@astrodotbuild&lt;/a&gt;&lt;br&gt;&lt;br&gt;Progressively enhanced with &lt;a href=&quot;https://twitter.com/sveltejs?ref_src=twsrc%5Etfw&quot;&gt;@sveltejs&lt;/a&gt; and clocking in at a whopping 12.5KB of total JS. Really digging how easy its becoming to build tiny sites these days&lt;/p&gt;&amp;mdash; Navillus (@navillus_dev) &lt;a href=&quot;https://twitter.com/navillus_dev/status/1403481307657691139?ref_src=twsrc%5Etfw&quot;&gt;June 11, 2021&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;&lt;p&gt;As of last week, though, we officially swapped our site over to &lt;a href=&quot;https://astro.build&quot;&gt;Astro&lt;/a&gt;. Why go through all that effort to trust our site with an early beta of a totally new project? Well, why not!&lt;/p&gt;
&lt;p&gt;We didn&amp;#39;t actually plan to go live with an Astro version of our site, expecting to kick the tires and keep an eye on the project. The build was surprisingly straightforward though, and the results don&amp;#39;t lie.&lt;/p&gt;
&lt;h2&gt;Shipping just enough JavaScript&lt;/h2&gt;
&lt;p&gt;The main bit of JavaScript on our site is the mobile menu - we hadn&amp;#39;t actually added the dark theme toggle yet when first starting the Astro project. We already posted about &lt;a href=&quot;/blog/progressive-enhancement&quot;&gt;how we built&lt;/a&gt; our progressively enhanced menu, but the key is that we wanted to use Svelte to easily work with or without JavaScript. If the JS loads, we pull in Svelte&amp;#39;s &lt;code&gt;slide&lt;/code&gt; transition to nicely animate the menu in and out.&lt;/p&gt;
&lt;p&gt;With Astro, we can build 98% of our site with little more than HTML templates and markdown while adding in a tiny amount of JavaScript to improve the experience. No client side routing, no frontend framework dynamically rendering the page, no hydration issues or content flickers.&lt;/p&gt;
&lt;p&gt;Note: Astro isn&amp;#39;t opinionated about what component library you prefer. They shipped with Vue, React, Svelte, and Preact out of the box and more are being added as you read this.&lt;/p&gt;
&lt;h2&gt;Speaking of hydration&lt;/h2&gt;
&lt;p&gt;One of the most interesting ideas about Astro is &lt;a href=&quot;https://github.com/snowpackjs/astro#-partial-hydration&quot;&gt;partial hydration&lt;/a&gt;. By default a component&amp;#39;s JavaScript won&amp;#39;t even load in the browser, it will only be used on the server. That&amp;#39;s not always enough though, and Astro gives you a few options for how to load it on the client.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;Component:load /&amp;gt;
&amp;lt;Component:idle /&amp;gt;
&amp;lt;Component:visible /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Say it&amp;#39;s a component that really needs to be available as soon as possible, just tack &lt;code&gt;:load&lt;/code&gt; onto the component and it will be loaded on the client when the page loads.&lt;/p&gt;
&lt;p&gt;Maybe the component is common but not needed immediately, adding &lt;code&gt;:idle&lt;/code&gt; instead will tell Astro to wait for the browser to initially load the page and go idle before pulling down the component, boosting performance on initial load.&lt;/p&gt;
&lt;p&gt;They have you covered for components further down the page too, &lt;code&gt;:visible&lt;/code&gt; will take advantage of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API&quot;&gt;IntersectionObserver API&lt;/a&gt; to only load the component if a user scrolls down to it. This might be useful for a contact form at the bottom of a marketing page where you need custom JS validation logic but don&amp;#39;t want to risk any performance hiccups when every conversion matters.&lt;/p&gt;
&lt;h3&gt;Coming soon to an Astro near you&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;Component:media =&amp;quot;(min-width: 40em)&amp;quot;&amp;gt;&amp;lt;/Component:media&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is only a proposed feature still open on &lt;a href=&quot;https://github.com/snowpackjs/astro/issues/396&quot;&gt;Github&lt;/a&gt; and the syntax may very likely change, but the concept here will be a huge win.&lt;/p&gt;
&lt;p&gt;Take our mobile menu again. We&amp;#39;re using &lt;code&gt;:idle&lt;/code&gt; to allow our whole page to load before enhancing the menu, but on desktop that menu is never even used. With &lt;code&gt;:media&lt;/code&gt; or similar we could make sure that the menu&amp;#39;s JS is only loaded on mobile viewports, why even both loading it?&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Keep your eye on &lt;a href=&quot;https://astro.build&quot;&gt;Astro&lt;/a&gt;, follow their &lt;a href=&quot;https://github.com/snowpackjs/astro&quot;&gt;Github repo&lt;/a&gt; (or star it if you prefer), and check out the community Discord linked on their homepage to give feedback and share your ideas for the future of Astro!&lt;/p&gt;
 </content></item><item><title>The power of Svelte actions</title><link>https://navillus.dev/blog/power-of-svelte-actions</link><guid isPermaLink="true">https://navillus.dev/blog/power-of-svelte-actions</guid><description>When you should `use:` actions in Svelte.</description><pubDate>Thu, 03 Jun 2021 17:48:11 GMT</pubDate><id>/blog/power-of-svelte-actions</id><updated>Sun Jun 05 2022 00:00:00 GMT+0000 (Coordinated Universal Time)</updated><content> &lt;p&gt;Svelte does an excellent job of blurring the lines between frontend frameworks and plain old HTML/JS. Leaning on Svelte&amp;#39;s &lt;a href=&quot;https://svelte.dev/docs#2_Assignments_are_reactive&quot;&gt;reactivity model&lt;/a&gt;, &lt;a href=&quot;https://svelte.dev/docs#bind_element_property&quot;&gt;property binding&lt;/a&gt;, and &lt;a href=&quot;https://svelte.dev/docs#on_element_event&quot;&gt;event directives&lt;/a&gt; will get you 99% of the way to a finished app, but every once in a while you just really need to work directly with the &lt;code&gt;HTMLElement&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This can be done in a one-off way with the &lt;code&gt;bind:this&lt;/code&gt; approach to getting a local reference to a DOM element, but there&amp;#39;s a better way. The &lt;code&gt;use:&lt;/code&gt; directive allows for reusable logic to be pulled out of the component itself.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s take a look at a really common use for this - listening for clicks/taps outside an area of the UI. I used this recently for &lt;a href=&quot;https://kamfly.io&quot;&gt;Kamfly&amp;#39;s&lt;/a&gt; modals, instead of adding a one-off click listener in the &lt;code&gt;Modal&lt;/code&gt; component I created a &lt;code&gt;clickOutside&lt;/code&gt; action that can be reused elsewhere. This can also help make testing a whole lot easier, testing the async nature of a full blown Modal component can be tricky, testing just the action itself is much less complicated!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt; Check out the final example in this &lt;a href=&quot;https://svelte.dev/repl/c44a10b2b200447ab733da39a448111d?version=3.38.2&quot;&gt;Live REPL demo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Svelte actions&lt;/h2&gt;
&lt;p&gt;Svelte&amp;#39;s official tutorial has an excellent walk through of &lt;a href=&quot;https://svelte.dev/tutorial/actions&quot;&gt;what actions are&lt;/a&gt;, but basically you can think of actions as lifecycle hooks for DOM elements themselves. An action works much like &lt;code&gt;onMount&lt;/code&gt; or &lt;code&gt;onDestroy&lt;/code&gt;, but actions are tied to DOM elements themselves rather than entire Svelte components.&lt;/p&gt;
&lt;h3&gt;Why bother with actions?&lt;/h3&gt;
&lt;p&gt;It&amp;#39;s easier to reach for &lt;code&gt;bind:this&lt;/code&gt; when the component you&amp;#39;re working on needs to interact with a DOM element. I won&amp;#39;t jump into the DRY design principles debate, instead just look at the testing challenges.&lt;/p&gt;
&lt;p&gt;Many projects end up needing a modal at some point, and they&amp;#39;re almost always ignored in automated testing. When the component itself is responsible for hiding/showing the modal, managing a stack of multiple open modals, etc. the logic gets tricky fast.&lt;/p&gt;
&lt;p&gt;Testing asynchronous behavior like animations or modals hiding/showing based on user input can be a chore. What if you could move some of that logic out to a reusable function?&lt;/p&gt;
&lt;h3&gt;Anatomy of an action&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;action = (node: HTMLElement, parameters: any) =&amp;gt; {
	update?: (parameters: any) =&amp;gt; void,
	destroy?: () =&amp;gt; void
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&amp;#39;s really all there is to it! An action in Svelte takes in the DOM element and (optionally) user defined parameters. The &lt;code&gt;action&lt;/code&gt; function can run initialization logic on the element before returning &lt;code&gt;update&lt;/code&gt; and &lt;code&gt;destroy&lt;/code&gt; handlers (both optional).&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;use:&lt;/code&gt; clickOutside&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s look at a really common UX pattern - a modal is open on screen and you want it to hide when the user clicks/taps outside the modal (often on some kind of grayed out background).&lt;/p&gt;
&lt;p&gt;This isn&amp;#39;t a big deal to do in the Modal component itself - add an extra DOM element for the grayed out background and add a click handler to it. But what if you want a similar interaction for your mobile menu? That component is almost certainly different, if you copy the code over now you have duplicated JS in your site &lt;strong&gt;and&lt;/strong&gt; you need complicated test coverage testing the close functionality for both components.&lt;/p&gt;
&lt;h3&gt;The clickOutside action&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export default (node, _options = {}) =&amp;gt; {
  const options = { onClickOutside: () =&amp;gt; {}, ..._options }

  function detect({ target }) {
    if (!node.contains(target)) {
      options.onClickOutside()
    }
  }
  document.addEventListener(&amp;#39;click&amp;#39;, detect, { passive: true, capture: true })

  return {
    destroy() {
      document.removeEventListener(&amp;#39;click&amp;#39;, detect)
    },
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&amp;#39;s a few things going on here, let&amp;#39;s break that down.&lt;/p&gt;
&lt;p&gt;First, the action is taking in optional parameters (&lt;code&gt;_options&lt;/code&gt;). The action expects &lt;code&gt;options.onClickOutside&lt;/code&gt; to be a callback function, if one wasn&amp;#39;t provided it&amp;#39;s defaulted to a noop function.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;detect&lt;/code&gt; function does the real work of checking to see if a click event on the page was inside the original DOM element. The event listener is using &lt;code&gt;passive&lt;/code&gt; to avoid scrolling performance issues, and &lt;code&gt;capture&lt;/code&gt; to make sure that all clicks bubble up to the listener.&lt;/p&gt;
&lt;p&gt;Finally, the action returns a &lt;code&gt;destroy&lt;/code&gt; callback that will clean up the event listener when the element is being removed from the DOM.&lt;/p&gt;
&lt;h3&gt;Allow components to whitelist elements to ignore&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export default (node, _options = {}) =&amp;gt; {
  const options = { include: [], onClickOutside: () =&amp;gt; {}, ..._options }

  function detect({ target }) {
    if (
      !node.contains(target) ||
      options.include.some((i) =&amp;gt; target.isSameNode(i))
    ) {
      options.onClickOutside()
    }
  }

  /** Same as above */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A little extra functionality here allows components to pass in other DOM elements that should be considered as &amp;quot;inside&amp;quot; the action (rather than &amp;quot;outside&amp;quot;).&lt;/p&gt;
&lt;h2&gt;How to test it&lt;/h2&gt;
&lt;p&gt;UI testing will be a topic for another day, but you can see how much easier this will be to test. Instead of having to write tests that can reliably wait for a Modal component to animate into view before testing the click away scenario, tests could work more closely to the REPL demo (below). DOM elements for a test wouldn&amp;#39;t need to animate at all, they just need to accept a &lt;code&gt;click&lt;/code&gt; event so the test can count how many times the &lt;code&gt;onClickOutside&lt;/code&gt; handler was called!&lt;/p&gt;
&lt;h2&gt;Live REPL demo&lt;/h2&gt;
&lt;p&gt;That&amp;#39;s all, folks! Check out the &lt;a href=&quot;https://svelte.dev/repl/c44a10b2b200447ab733da39a448111d?version=3.38.2&quot;&gt;Live REPL demo&lt;/a&gt; for a working example.&lt;/p&gt;
 </content></item><item><title>Introducing chisel.css</title><link>https://navillus.dev/blog/introducing-chisel</link><guid isPermaLink="true">https://navillus.dev/blog/introducing-chisel</guid><description>A (work-in-progress) minimalist CSS framework.</description><pubDate>Wed, 02 Jun 2021 17:37:30 GMT</pubDate><id>/blog/introducing-chisel</id><updated>Sat Jun 05 2021 00:00:00 GMT+0000 (Coordinated Universal Time)</updated><content> &lt;p&gt;Have you ever really tried rendering some HTML without any CSS? It&amp;#39;s a hot mess! Modern browsers are &lt;a href=&quot;https://www.google.com/chromebook/chrome-os/&quot;&gt;operating systems&lt;/a&gt; unto themselves these days - I don&amp;#39;t envy anyone who has to sift through the complexity to add even a basic feature to a browser these days. But damn do I wish they would add spend a couple days to ship some decent looking default styles for HTML5 elements!&lt;/p&gt;
&lt;h2&gt;Enter chisel.css&lt;/h2&gt;
&lt;p&gt;Yes, it&amp;#39;s another CSS framework. No, we don&amp;#39;t want to see it being used for the next 10 years. If we have our way, &lt;code&gt;chisel.css&lt;/code&gt; will make clear just how easy it could be to have modern designs with out-of-the-box HTML elements.&lt;/p&gt;
&lt;p&gt;We&amp;#39;ve included the most common browser resets, similar to &lt;a href=&quot;https://github.com/necolas/normalize.css&quot;&gt;normalize.css&lt;/a&gt; or &lt;a href=&quot;https://csstools.github.io/sanitize.css/&quot;&gt;sanitize.css&lt;/a&gt;, along with modern styling based on &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties&quot;&gt;CSS custom properties&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Why the name?&lt;/h2&gt;
&lt;p&gt;Ask any woodworker that&amp;#39;s dabbled in hand tools and fine joinery what tool they&amp;#39;d keep if they could have only one, it&amp;#39;d be the chisel. Most people never even think about it, but when it comes down to it almost every tool used on wood is basically just a chisel anyway.&lt;/p&gt;
&lt;p&gt;Take a close look at a saw (be careful!). See all those tiny chisels we call saw teeth? Now look at a drill bit - yep, it&amp;#39;s pretty much a chisel blade twisted around a stick.&lt;/p&gt;
&lt;p&gt;We realized we kept starting with the same basic CSS resets and element styles for virtually every Navillus project, after pasting it a half dozen times we figured it was time to standardize. The project quickly took aim at building one CSS framework that can be a complete reset for both browser vendor issues and horribly styled HTML elements.&lt;/p&gt;
&lt;p&gt;Change a few variables here and there, but the goal is for chisel to help every site look good without immediately, ready for your throw in your custom CSS (or wrap a chisel around a stick if you still can&amp;#39;t find that damn 3/8&amp;quot; drill bit).&lt;/p&gt;
&lt;h2&gt;How to use it&lt;/h2&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://navillus-bv.github.io/chisel/&quot;&gt;docs&lt;/a&gt; for full details, but it really is as simple as&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i -s chisel.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or from a CDN&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link href=&amp;quot;https://unpkg.com/chisel.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Minified and compressed the entire bundle is ~3.2KB, with full support for dark mode and basic app theming.&lt;/p&gt;
&lt;h2&gt;Eating our own dog food&lt;/h2&gt;
&lt;p&gt;We recently updated &lt;a href=&quot;https://navillus.dev&quot;&gt;navillus.dev&lt;/a&gt; to use &lt;code&gt;chisel&lt;/code&gt; as a basis for styling, you can see the complete &lt;a href=&quot;https://github.com/Navillus-BV/navillus-dev/pull/11/files?file-filters%5B%5D=.postcss&amp;file-filters%5B%5D=.svelte&quot;&gt;pull request&lt;/a&gt; on GitHub. I&amp;#39;m always a fan of seeing a pull request full of red, gotta love code cleanup!&lt;/p&gt;
&lt;p&gt;Granted we&amp;#39;re a bit biased here, most of the common CSS we copy into every Navillus sites made it&amp;#39;s way into chisel. The power of CSS variables lets you make some pretty drastic changes though, with very little effort.&lt;/p&gt;
&lt;p&gt;Want a different primary color? No problem, change &lt;code&gt;--chisel-primary&lt;/code&gt; and you&amp;#39;re all set. Prefer a different &lt;a href=&quot;https://type-scale.com/&quot;&gt;type scale&lt;/a&gt;? That&amp;#39;s simple too, check the docs for our variables like &lt;code&gt;--chisel-h1&lt;/code&gt; or &lt;code&gt;--chisel-p&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Dark theme&lt;/h2&gt;
&lt;p&gt;Dark theme support is one that gave us more headaches than expected. We wanted chisel to support both browser&amp;#39;s native &lt;code&gt;prefers-color-scheme&lt;/code&gt; as well as a fallback option. We landed on a custom HTML data property, &lt;code&gt;data-chisel-theme&lt;/code&gt;. In the future we plan to ship predefined color palettes that will also take advantage of the HTML &lt;code&gt;dataset&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;So what went wrong? If you take a look at v0.4.0, we added custom CSS properties for component-level styling similar to &lt;code&gt;--chisel-button-bg&lt;/code&gt;. This worked great for making it simple to custom style buttons, say if you want a button variant like &lt;code&gt;.button-hollow&lt;/code&gt;. Each component property defaulted to one of the main color palette styles, a la &lt;code&gt;--chisel-button-bg: var(--chisel-primary)&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;There in lies the problem.&lt;/h3&gt;
&lt;p&gt;At least on some browsers, CSS variable scope isn&amp;#39;t always what you&amp;#39;d expect.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;[data-chisel-theme=&amp;#39;dark&amp;#39;] {
  ---chisel-primary: #002244;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&amp;#39;d think this would also update any CSS properties referencing this variable. Nope! If &lt;code&gt;--chisel-button-bg&lt;/code&gt; is only defined on the &lt;code&gt;:root&lt;/code&gt; scope it isn&amp;#39;t necessarily going to be updated to the new color code. This seems like a bug to me, and I have it on my list to dig through the spec to see if that&amp;#39;s actually expected behavior, but in the meantime we will avoid it.&lt;/p&gt;
&lt;p&gt;Yes, we could use component variables if we redefined &lt;code&gt;--chisel-button-bg&lt;/code&gt; in the &lt;code&gt;dark&lt;/code&gt; theme selector as well. That&amp;#39;d lead to way to much bloat and extra CSS though, it wasn&amp;#39;t worth the extra KB.&lt;/p&gt;
&lt;h2&gt;WIP&lt;/h2&gt;
&lt;p&gt;Chisel is still very much a work in progress. Head over to the &lt;a href=&quot;http://github.com/navillus-bv/chisel&quot;&gt;GitHub repo&lt;/a&gt; to file bugs, request new features, and star the project to follow the latest updates!&lt;/p&gt;
 </content></item><item><title>Including json+ld structured data in Svelte</title><link>https://navillus.dev/blog/json-ld-in-sveltekit</link><guid isPermaLink="true">https://navillus.dev/blog/json-ld-in-sveltekit</guid><description>Easily add json+ld structured data to pages build in Svelte, with TypeScript type checking!</description><pubDate>Mon, 17 May 2021 17:40:47 GMT</pubDate><id>/blog/json-ld-in-sveltekit</id><updated>Sat Jun 05 2021 00:00:00 GMT+0000 (Coordinated Universal Time)</updated><content> &lt;p&gt;Ever wonder how Google search results include shopping results for products, or location results for local businesses? Their search bot is constantly being improved to better find that kind of content on your site, but ultimately if you care at all about SEO on your site it&amp;#39;s important to include &lt;a href=&quot;https://schema.org/&quot;&gt;structured data&lt;/a&gt; on the page.&lt;/p&gt;
&lt;p&gt;Structured data allows you to give search bots scrubbing your page a helping hand by adding metadata that describes your &lt;a href=&quot;https://schema.org/Organization&quot;&gt;business&lt;/a&gt;, &lt;a href=&quot;https://schema.org/Product&quot;&gt;product&lt;/a&gt;, or even your &lt;a href=&quot;https://schema.org/BlogPosting&quot;&gt;blog posts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://json-ld.org/&quot;&gt;JSON+LD&lt;/a&gt; is a lightweight specification for including structured data as a simple JSON object, something we&amp;#39;re all comfortable with in the frontend world.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt; Check out this &lt;a href=&quot;https://svelte.dev/repl/3382db29fc864d60b0a4ca47b3707a95?version=3.38.2&quot;&gt;Svelte REPL&lt;/a&gt; example for a working example in JS. Keep reading to get full TypeScript validation for your Schema objects!&lt;/p&gt;
&lt;h2&gt;JSON+LD by example&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s take a look at a basic example first. I pulled this straight from the &lt;a href=&quot;https://json-ld.org/playground/&quot;&gt;JSON-LD Playground&lt;/a&gt; - it&amp;#39;s an excellent reference for quickly messing around with the different schema and data types.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;@context&amp;quot;: &amp;quot;http://schema.org/&amp;quot;,
  &amp;quot;@type&amp;quot;: &amp;quot;Person&amp;quot;,
  &amp;quot;name&amp;quot;: &amp;quot;Jane Doe&amp;quot;,
  &amp;quot;jobTitle&amp;quot;: &amp;quot;Professor&amp;quot;,
  &amp;quot;telephone&amp;quot;: &amp;quot;(425) 123-4567&amp;quot;,
  &amp;quot;url&amp;quot;: &amp;quot;http://www.janedoe.com&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing crazy going on there (yet). When a search bot comes by to index a page with this JSON, it&amp;#39;s guaranteed to find Professor Jane Doe&amp;#39;s contact information. This contact information is probably included elsewhere on the page, say in a sidebar or page footer, but chances are the way the HTML is setup could prevent the search from connecting the link from a phone number to the person&amp;#39;s name.&lt;/p&gt;
&lt;h3&gt;How do you include it on a page?&lt;/h3&gt;
&lt;p&gt;This is where JSON+LD really stands out compared to other structured data formats that require special HTML attributes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&amp;lt;script type=&amp;quot;application/ld+json&amp;quot;&amp;gt;
	{
		&amp;quot;@context&amp;quot;: &amp;quot;http://schema.org/&amp;quot;,
		&amp;quot;@type&amp;quot;: &amp;quot;Person&amp;quot;,
		&amp;quot;name&amp;quot;: &amp;quot;Jane Doe&amp;quot;,
		&amp;quot;jobTitle&amp;quot;: &amp;quot;Professor&amp;quot;,
		&amp;quot;telephone&amp;quot;: &amp;quot;(425) 123-4567&amp;quot;,
		&amp;quot;url&amp;quot;: &amp;quot;http://www.janedoe.com&amp;quot;
	}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yep, that&amp;#39;s really all there is to it. Wrap the JSON object in a &lt;code&gt;script&lt;/code&gt; tag, mark it as a type of &lt;code&gt;application/ld+json&lt;/code&gt; and you&amp;#39;re all set. There&amp;#39;s way more to the different kinds of schemas you may want to include - check out the &lt;a href=&quot;https://json-ld.org/learn.html&quot;&gt;JSON-LD docs&lt;/a&gt; for a much better walk through of all the details than I would ever fit into a single blog post.&lt;/p&gt;
&lt;h2&gt;JSON+LD in Svelte&lt;/h2&gt;
&lt;p&gt;The beauty of &lt;a href=&quot;https://svelte.dev&quot;&gt;Svelte&lt;/a&gt; is how closely it follows standard web technologies. Components are written in a single file with a &lt;code&gt;script&lt;/code&gt; tag for all the JavaScript, &lt;code&gt;style&lt;/code&gt; tag for the component&amp;#39;s CSS, and one or more HTML elements for the actual DOM elements.&lt;/p&gt;
&lt;p&gt;While that makes authoring Svelte components a nearly frictionless experience for web developers, it can throw a wrench in the JSON+LD department. Tooling built for Svelte like the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode&quot;&gt;VS Code plugin&lt;/a&gt;, and even the Svelte compiler itself, can cause headaches if you try to write an &lt;code&gt;application/ld+json&lt;/code&gt; script in your components.&lt;/p&gt;
&lt;p&gt;This may very well get cleaned up in the future, but for now you&amp;#39;ll likely run into warnings and errors related to having more than one &lt;code&gt;script&lt;/code&gt; tag in a component. A Google search will lead you down a rabbit hole of different combinations of Svelte&amp;#39;s &lt;a href=&quot;https://svelte.dev/docs#html&quot;&gt;@html&lt;/a&gt; expressions and string literals to get it to compile.&lt;/p&gt;
&lt;p&gt;There&amp;#39;s a much better way to do this though, and even better it can be combined with TypeScript to add extra type validations for your JSON schema objects.&lt;/p&gt;
&lt;h3&gt;Serialize your JSON+LD to a script&lt;/h3&gt;
&lt;p&gt;The first part of the magic trick is getting around issues related to parsing extra &lt;code&gt;script&lt;/code&gt; tags in your Svelte component. This is where many will reach for some combination of &lt;code&gt;{$html}&lt;/code&gt; and string literals.&lt;/p&gt;
&lt;p&gt;I prefer to move this out of the Svelte component all together. With the logic in a plain old JavaScript file it can be unit tested and will never run into issues with tools trying to parse &lt;code&gt;.svelte&lt;/code&gt; files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export function serializeSchema(thing) {
  return `&amp;lt;script type=&amp;quot;application/ld+json&amp;quot;&amp;gt;${JSON.stringify(thing)}&amp;lt;/script&amp;gt;`
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&amp;#39;s not much magic going on here, the function just takes in a JavaScript object and spits out the &lt;code&gt;ld+json&lt;/code&gt; script tag for it.&lt;/p&gt;
&lt;h3&gt;Type checking with TypeScript&lt;/h3&gt;
&lt;p&gt;We&amp;#39;ll lean on the &lt;a href=&quot;https://github.com/google/schema-dts&quot;&gt;schema-dts&lt;/a&gt; package for full &lt;a href=&quot;https://schema.org/&quot;&gt;Schema.org&lt;/a&gt; type definitions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import type { Thing, WithContext } from &amp;#39;schema-dts&amp;#39;

export type Schema = Thing | WithContext&amp;lt;Thing&amp;gt;

export function serializeSchema(thing: Schema) {
  return `&amp;lt;script type=&amp;quot;application/ld+json&amp;quot;&amp;gt;${JSON.stringify(
    thing,
    null,
    2
  )}&amp;lt;/script&amp;gt;`
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;schema-dts&lt;/code&gt; defines types for all of the different schema objects, see below for a more detailed example with an &lt;code&gt;Organization&lt;/code&gt; object. This is a huge win, it&amp;#39;s easy to accidentally structure the JSON wrong or have a typo in one of the property names. Setting it up to use TypeScript definitions we can make sure that our JSON objects are validated at build.&lt;/p&gt;
&lt;h3&gt;Defining our Schema.org objects&lt;/h3&gt;
&lt;p&gt;Here&amp;#39;s the &lt;code&gt;Organization&lt;/code&gt; object used by this very site.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import site from `$data/site.json`;

export const organizationSchema: WithContext&amp;lt;Organization&amp;gt; = {
  &amp;quot;@context&amp;quot;: &amp;quot;https://schema.org&amp;quot;,
  &amp;quot;@type&amp;quot;: &amp;quot;Organization&amp;quot;,
  &amp;quot;@id&amp;quot;: `${site.url}#organization`,
  url: site.url,
  name: site.company.name,
  description: site.description,
  sameAs: [`https://twitter.com/${site.social.twitter}`],
  logo: `${site.url}/favicon.svg`,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Data specific to our site is pulled in from a local JSON file. This could be data exposed through a Git-based CMS like &lt;a href=&quot;https://forestry.io&quot;&gt;Forestry&lt;/a&gt; or pulled down from a headless CMS like &lt;a href=&quot;https://sanity.io&quot;&gt;Sanity&lt;/a&gt;. The important thing here is that our &lt;code&gt;Organization&lt;/code&gt; object is defined in TypeScript, verified at build-time, and can be unit tested if you want to make sure the &lt;code&gt;site.json&lt;/code&gt; data is hooked up properly.&lt;/p&gt;
&lt;p&gt;What is &lt;code&gt;WithContext&lt;/code&gt; doing? That&amp;#39;s a clever setup from &lt;code&gt;schema-dts&lt;/code&gt;, the top-level JSON+LD object should have a &lt;code&gt;@context&lt;/code&gt; property. You can nest objects though, and they even have support for using an &lt;a href=&quot;https://json-ld.org/spec/latest/json-ld/#dfn-graph-objects&quot;&gt;object graph&lt;/a&gt; format for multiple objects. Any nested objects, or every object in the graph, doesn&amp;#39;t need &lt;code&gt;@context&lt;/code&gt;. &lt;code&gt;WithContext&lt;/code&gt; is a TypeScript wrapper for another type, in the example above I could have removed &lt;code&gt;@context&lt;/code&gt; from the object and it would be a valid &lt;code&gt;Organization&lt;/code&gt; type.&lt;/p&gt;
&lt;h3&gt;Importing into Svelte&lt;/h3&gt;
&lt;p&gt;Finally, let&amp;#39;s add the JSON+LD into the DOM. Most of our projects end up with a &lt;code&gt;LDTag.svelte&lt;/code&gt; component similar to&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script lang=&amp;quot;ts&amp;quot;&amp;gt;
  import { serializeSchema } from &amp;#39;$utils/json-ld&amp;#39;
  import type { Schema } from &amp;#39;$utils/json-ld&amp;#39;

  export let schema: Schema
&amp;lt;/script&amp;gt;

&amp;lt;svelte:head&amp;gt;
  {@html serializeSchema(schema)}
&amp;lt;/svelte:head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Multiple Svelte components on a page can inject their own &lt;code&gt;application/ld+json&lt;/code&gt; script into the document&amp;#39;s head. This works great with &lt;a href=&quot;https://kit.svelte.dev&quot;&gt;SvelteKit&lt;/a&gt; too, a &lt;a href=&quot;https://kit.svelte.dev/docs#layouts&quot;&gt;layout&lt;/a&gt; or &lt;a href=&quot;https://kit.svelte.dev/docs#routing-pages&quot;&gt;route&lt;/a&gt; component could inject page-level schemas.&lt;/p&gt;
&lt;h2&gt;Working REPL example&lt;/h2&gt;
&lt;p&gt;Take a look at our &lt;a href=&quot;https://svelte.dev/repl/3382db29fc864d60b0a4ca47b3707a95?version=3.38.2&quot;&gt;Svelte REPL&lt;/a&gt; example to see a full working JavaScript example.&lt;/p&gt;
 </content></item><item><title>Web Accessibility: Hidden links make all the difference</title><link>https://navillus.dev/blog/accessibility-skip-to-content</link><guid isPermaLink="true">https://navillus.dev/blog/accessibility-skip-to-content</guid><description>For visitors that use a keyboard or screen reader to navigate websites, hidden quick links can make a site much more enjoyable.</description><pubDate>Mon, 10 May 2021 17:26:48 GMT</pubDate><id>/blog/accessibility-skip-to-content</id><updated>Wed Jul 21 2021 17:26:48 GMT+0000 (Coordinated Universal Time)</updated><content> &lt;p&gt;Accessibility is a bit of a polarizing topic when it comes to web technologies, but it doesn&amp;#39;t have to be. Accessibility isn&amp;#39;t an all or nothing game and more often then not there are small, simple changes that can make a huge difference in both keyboard and screen reader support. Let&amp;#39;s take a look at an accessibility easter egg that &lt;a href=&quot;https://webaim.org/techniques/skipnav/&quot;&gt;may not be new&lt;/a&gt; but is still sorely lacking on many of even the most popular sites.&lt;/p&gt;
&lt;h2&gt;&amp;quot;Skip navigation&amp;quot; links&lt;/h2&gt;
&lt;p&gt;Every web developer should try &lt;a href=&quot;https://www.codecademy.com/articles/how-to-setup-screen-reader&quot;&gt;using a screen reader&lt;/a&gt; at some point. If you&amp;#39;ve ever tried tabbing through a website you&amp;#39;ll get the general idea, the screen reader (or your keyboard&amp;#39;s focus) move one by one through every element on the page. We can modify that behavior with &lt;a href=&quot;https://www.w3.org/TR/html-aria/&quot;&gt;aria&lt;/a&gt; tags, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex&quot;&gt;tabindex&lt;/a&gt;, and certain CSS properties, but one not so obvious solution is to actually hide links on the page that only show up once they are given focus by the keyboard or screen reader.&lt;/p&gt;
&lt;p&gt;Curious what that looks like? Go over to &lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt;&amp;#39;s main site and logout if you are currently signed in (or use a private tab). Click in the address bar to put focus just above the page&amp;#39;s content, then start tabbing down. You&amp;#39;ll likely tab through some of the browser&amp;#39;s chrome first, but as soon as focus gets to the site&amp;#39;s content you&amp;#39;ll see a big blue &amp;quot;Skip to content&amp;quot; button appear in the top left corner.&lt;/p&gt;
&lt;h3&gt;What&amp;#39;s it for?&lt;/h3&gt;
&lt;p&gt;Even on a small site the navigation header likely has 6 to 10 interactive elements (brand logos, page links, login/logout buttons). Visually that isn&amp;#39;t a big deal, navigation headers are almost always designed to pack all that content into a relatively small portion of the screen. Functionally, though, that&amp;#39;s 6 or 10 different elements the user would need to walk through before even hearing the main title in the hero section. That&amp;#39;s a huge barrier to entry!&lt;/p&gt;
&lt;p&gt;Back to the GitHub example, when &amp;quot;Skip to content&amp;quot; has focus and is visible on screen you can still tab past it and walk through every link in their navigation. The real trick is what happens if you hit &lt;code&gt;Enter&lt;/code&gt; on that hidden link - the window&amp;#39;s focus skips past the navigation header and puts focus into the hero section. In the case of GitHub, after you click &amp;quot;Skip to content&amp;quot; the next &lt;code&gt;Tab&lt;/code&gt; will focus on the email signup form rather than the logo in their navigation header.&lt;/p&gt;
&lt;h3&gt;How it works&lt;/h3&gt;
&lt;p&gt;The trick here requires two elements on the page. The very first focusable element on the page will be a new &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; that is the hidden link itself. We want this to work whether JavaScript is enabled or not, so let&amp;#39;s make sure to &lt;a href=&quot;/blog/progressive-enhancement&quot;&gt;progressively enhance&lt;/a&gt; this feature.&lt;/p&gt;
&lt;h2&gt;Time to code it&lt;/h2&gt;
&lt;p&gt;Actually implementing this hidden button can be a bit confusing at first, Let&amp;#39;s take it one step at a time.&lt;/p&gt;
&lt;h3&gt;Start with the HTML&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
  &amp;lt;a href=&amp;quot;#start-of-content&amp;quot; class=&amp;quot;sr-only sr-only-focusable&amp;quot;&amp;gt;
    Skip to content
  &amp;lt;/a&amp;gt;

  &amp;lt;header&amp;gt;
    &amp;lt;!-- Your header content goes here --&amp;gt;
  &amp;lt;/header&amp;gt;

  &amp;lt;div id=&amp;quot;start-of-content&amp;quot; class=&amp;quot;sr-only&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

  &amp;lt;section&amp;gt;
    &amp;lt;!-- Your awesome hero section --&amp;gt;
  &amp;lt;/section&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing to crazy here. You&amp;#39;re exact implementation might look different, but the key is adding a new anchor tag at the top of the page and a new &lt;code&gt;div&lt;/code&gt; immediately after the header.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; doesn&amp;#39;t necessarily have to be the first child of the page&amp;#39;s &lt;code&gt;body&lt;/code&gt;, but it&amp;#39;s important that it is the first focusable element in the DOM.&lt;/p&gt;
&lt;h3&gt;Add a little CSS&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.sr-only {
  border: 0;
  padding: 0;
  margin: 0;
  position: absolute !important;
  height: 1px;
  width: 1px;
  overflow: hidden;
  /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
  clip: rect(1px 1px 1px 1px);
  /* maybe deprecated but we need to support legacy browsers */
  clip: rect(1px, 1px, 1px, 1px);
  /* modern browsers, clip-path works inwards from each corner */
  clip-path: inset(50%);
  /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
  white-space: nowrap;
}

.sr-only.sr-only-focusable:focus {
  width: auto;
  height: auto;
  padding: 0;
  margin: 0;
  overflow: visible;
  clip: auto;
  clip-path: initial;
  white-space: normal;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Update: &lt;code&gt;.sr-only&lt;/code&gt; class updated based on some excellent feedback from Inclusivity Hub on &lt;a href=&quot;https://dev.to/navillusbv/web-accessibility-hidden-links-make-all-the-difference-27a7&quot;&gt;dev.to&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You have likely seen &lt;code&gt;sr-only&lt;/code&gt; helper classes elsewhere, most CSS frameworks like &lt;a href=&quot;https://getbootstrap.com/&quot;&gt;Bootstrap&lt;/a&gt; and &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind&lt;/a&gt; include them out of the box.&lt;/p&gt;
&lt;p&gt;If you haven&amp;#39;t seen &lt;code&gt;sr-only-focusable&lt;/code&gt; before, that&amp;#39;s just an extra helper class that allows the element to be visible &lt;em&gt;only&lt;/em&gt; when it has &lt;code&gt;:focus&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Enhance it with JavaScript&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Update: Looking for a no-JS solution? Skip the JS logic and point your &amp;quot;Skip to Content&amp;quot; link straight to your &lt;code&gt;&amp;lt;main id=&amp;quot;main&amp;quot;&amp;gt;&lt;/code&gt; block!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This feature already works as-is, no JavaScript at all. There is a catch though, clicking anchor tags with with &lt;code&gt;href=&amp;quot;#start-of-content&amp;quot;&lt;/code&gt; will update the URL in the address bar. That&amp;#39;s not always ideal and could even break your page if you are using hash routing.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function onSkipToContent(event) {
  // Stop the event&amp;#39;s default behavior
  // In this case, don&amp;#39;t let it actually change the page&amp;#39;s URL
  event.preventDefault()

  // Find the hidden target div
  const target = document.getElementById(&amp;#39;start-of-content&amp;#39;)

  if (!target) {
    return
  }

  // Find the next element in the DOM
  const content = target.nextElementSibling

  if (content instanceof HTMLElement) {
    // Make sure the content div can&amp;#39;t be tabbed to again, the give it focus
    content.setAttribute(&amp;#39;tabindex&amp;#39;, &amp;#39;-1&amp;#39;)
    content.focus()
  }
}

// Find the hidden &amp;quot;Skip to content&amp;quot; link and hook up tje click event
const link = document.querySelector(&amp;#39;a[href=&amp;quot;#start-of-content&amp;quot;]&amp;#39;)
if (link) {
  link.addEventListener(&amp;#39;click&amp;#39;, onSkipToContent())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will look a little different depending on how your frontend JS is setup and whether you are using a framework like &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt; or &lt;a href=&quot;https://svelte.dev/&quot;&gt;Svelte&lt;/a&gt;. The concept should be easy to transfer though, you just want to take over the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; element&amp;#39;s click event and manually send the window&amp;#39;s focus down the page.&lt;/p&gt;
&lt;p&gt;Why bother with &lt;code&gt;nextElementSibling&lt;/code&gt;? Well it may not be necessary, but we added that &lt;code&gt;div&lt;/code&gt; as a bookmark and don&amp;#39;t want it to actually take focus in the window. Instead, we want to find that bookmark and give focus to whatever comes next in the DOM.&lt;/p&gt;
&lt;h2&gt;That&amp;#39;s all, folks!&lt;/h2&gt;
&lt;p&gt;Adding two elements and a bit of CSS gives us a fully functional &amp;quot;Skip to content&amp;quot; link for keyboard and screen reader users. Another dozen or so lines of JavaScript and we progressively enhanced the link to work as expected with any JavaScript framework or routing scheme the site might be using.&lt;/p&gt;
&lt;p&gt;Stay tuned for more accessibility posts in the future!&lt;/p&gt;
 </content></item><item><title>Progressively enhancing Svelte with JavaScript</title><link>https://navillus.dev/blog/progressive-enhancement</link><guid isPermaLink="true">https://navillus.dev/blog/progressive-enhancement</guid><description>That&apos;s right! Your site built with a JS framework with client-side code can still support users with JS disabled.</description><pubDate>Sat, 08 May 2021 17:49:14 GMT</pubDate><id>/blog/progressive-enhancement</id><updated>Sat Jun 05 2021 00:00:00 GMT+0000 (Coordinated Universal Time)</updated><content> &lt;p&gt;We&amp;#39;ve all heard about &amp;quot;mobile first&amp;quot; designs, but what about JavaScript last? It&amp;#39;ll need a catchier name to really stick, but many of us ignore how important it is that basic site functionality works even if JavaScript is disabled or has failed. Browsing the web with JavaScript disabled can be a real pain but it happens more then you&amp;#39;d expect.&lt;/p&gt;
&lt;p&gt;My trusty &lt;a href=&quot;https://us.kobobooks.com/products/kobo-aura-h2o-edition-2&quot;&gt;Kobo Aura H20&lt;/a&gt; has a browser in it, but JavaScript is disabled and frankly there&amp;#39;s no way the hardware could handle modern JS-heavy sites. Ride a subway in New York and you&amp;#39;ll quickly realize how spotty your cell signal can be, and how broken a site will be if the markup loaded but the 1.3MB of JavaScript didn&amp;#39;t finish before your connection dropped out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt; &lt;a href=&quot;https://svelte.dev/repl/267acb68b79647849f0532774d62c594?version=3.38.2&quot;&gt;Click here&lt;/a&gt; for a working example in the &lt;a href=&quot;https://svelte.dev/repl/&quot;&gt;Svelte REPL&lt;/a&gt;. Or try out the menu on this site - disable your Javascript and shrink the window down to get the mobile view, the menu still works!&lt;/p&gt;
&lt;h2&gt;Don&amp;#39;t break your mobile menu&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Hamburger_button&quot;&gt;hamburger menu&lt;/a&gt; is still king of mobile navigation designs, for better or worse. With a hidden menu, the last thing you want is for a visitor to not even be able to move around your site because they chose to disable JS. Even worse, if your site hits an unhandled exception that might break your JavaScript entirely.&lt;/p&gt;
&lt;p&gt;The menu is almost certainly going to be interacted with, making design and animations a high priority there. The key is to build your menu so that it works with HTML and CSS only. Your JavaScript should enhance the menu with things like notification badges or animations that can&amp;#39;t be done well in CSS.&lt;/p&gt;
&lt;h2&gt;Learn by example&lt;/h2&gt;
&lt;p&gt;We&amp;#39;ll take a look at how this can be done in &lt;a href=&quot;https://svelte.dev&quot;&gt;Svelte&lt;/a&gt;, but the same concepts can be applied to any framework.&lt;/p&gt;
&lt;p&gt;Go ahead and try a working example right here, this site&amp;#39;s menu works similarly. Once scripts are loaded the menu takes advantage of Svelte&amp;#39;s built in &lt;a href=&quot;https://svelte.dev/docs#slide&quot;&gt;slide&lt;/a&gt; transition. Disable JS in your browser though and our menu still works - it doesn&amp;#39;t animate in but visitors can open and navigate around no problem.&lt;/p&gt;
&lt;h3&gt;A basic header in Svelte&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;&amp;lt;script&amp;gt;
  import { slide } from &amp;#39;svelte/transition&amp;#39;

  let menuOpen = false
&amp;lt;/script&amp;gt;

&amp;lt;header&amp;gt;
  &amp;lt;div class=&amp;quot;header__top&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;Navillus&amp;lt;/h1&amp;gt;
    &amp;lt;button on:click={() =&amp;gt; menuOpen = !menuOpen}&amp;gt;
      &amp;amp;#9776;
    &amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;

  {#if menuOpen}
  &amp;lt;nav transition:slide&amp;gt;
    &amp;lt;!-- NAV LINKS GO HERE --&amp;gt;
  &amp;lt;/nav&amp;gt;
&amp;lt;/header&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing too crazy going on here yet. If you aren&amp;#39;t familiar with &lt;a href=&quot;https://svelte.dev&quot;&gt;Svelte&lt;/a&gt;, &lt;code&gt;transition:slide&lt;/code&gt; adds an entry and exit animation to the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;h3&gt;Hide/show a menu without JS&lt;/h3&gt;
&lt;p&gt;HTML checkboxes have a pseudo selector for &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:checked&quot;&gt;:checked&lt;/a&gt;, let&amp;#39;s take advantage of that to show/hide our menu from CSS.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;&amp;lt;header&amp;gt;
  &amp;lt;div class=&amp;quot;header__top&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;Navillus&amp;lt;/h1&amp;gt;
    &amp;lt;label for=&amp;quot;toggle&amp;quot;&amp;gt;&amp;amp;#9776;&amp;lt;/label&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;input id=&amp;quot;toggle&amp;quot; type=&amp;quot;checkbox&amp;quot; class=&amp;quot;sr-only&amp;quot;/&amp;gt;

  &amp;lt;nav&amp;gt;
    &amp;lt;!-- NAV LINKS GO HERE --&amp;gt;
  &amp;lt;/nav&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;style&amp;gt;
  #toggle ~ nav {
    display: none;
  }

  #toggle:checked ~ nav {
    display: block;
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What&amp;#39;s going on there exactly? Instead of a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; there&amp;#39;s a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; tied to the checkbox. In CSS, the &lt;code&gt;nav&lt;/code&gt; element is hidden by default and only shown when the checkbox is &lt;code&gt;:checked&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;The best of both worlds&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;&amp;lt;script&amp;gt;
  import { onMount } from &amp;#39;svelte&amp;#39;
  import { slide } from &amp;#39;svelte/transition&amp;#39;

  let menuOpen = true
  let mounted = false

  onMount(() =&amp;gt; {
    menuOpen = false
    mounted = true
  })
&amp;lt;/script&amp;gt;

&amp;lt;header&amp;gt;
  &amp;lt;div class=&amp;quot;header__top&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;Navillus&amp;lt;/h1&amp;gt;
    &amp;lt;label for=&amp;quot;toggle&amp;quot;&amp;gt;&amp;amp;#9776;&amp;lt;/label&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;input id=&amp;quot;toggle&amp;quot; type=&amp;quot;checkbox&amp;quot; class=&amp;quot;sr-only&amp;quot; class:js={mounted} bind:checked={menuOpen} /&amp;gt;

  {#if menuOpen}
  &amp;lt;nav&amp;gt;
    &amp;lt;!-- NAV LINKS GO HERE --&amp;gt;
  &amp;lt;/nav&amp;gt;
  {/if}
&amp;lt;/header&amp;gt;

&amp;lt;style&amp;gt;
  #toggle:not(.js) ~ nav {
    display: none;
  }

  #toggle:not(.js):checked ~ nav {
      display: block;
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Wait, what?&lt;/h4&gt;
&lt;p&gt;Let&amp;#39;s break that one down step by step.&lt;/p&gt;
&lt;p&gt;First, &lt;code&gt;menuOpen&lt;/code&gt; is set to true initially and reset to false in Svelte&amp;#39;s &lt;a href=&quot;https://svelte.dev/docs#onMount&quot;&gt;onMount&lt;/a&gt; lifecycle hook. If onMount is called we have JavaScript support and can enhance the menu, until then we stick to the HTML/CSS approach.&lt;/p&gt;
&lt;p&gt;We also added in a second boolean flag for &lt;code&gt;mounted&lt;/code&gt;, and with &lt;code&gt;class:js={mounted}&lt;/code&gt; we&amp;#39;re telling Svelte to add the &lt;code&gt;js&lt;/code&gt; class to our checkbox once the component&amp;#39;s scripts have mounted.&lt;/p&gt;
&lt;p&gt;Finally, the CSS has been updated to change the menu&amp;#39;s display only as long as the checkbox doesn&amp;#39;t have the &lt;code&gt;js&lt;/code&gt; class. That&amp;#39;s the real magic, let CSS handle the show/hide functionality &lt;em&gt;until&lt;/em&gt; JavaScript has mounted and Svelte&amp;#39;s &lt;code&gt;slide&lt;/code&gt; transition is ready to animate the menu.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Oops! It&amp;#39;s very easy to inadvertently create accessibility bugs. When manually testing I realized the hidden checkbox wasn&amp;#39;t binding to our &lt;code&gt;menuOpen&lt;/code&gt; state in svelte. The code block above was updated June 2, 2021 to include &lt;code&gt;bind:checked={menuOpen}&lt;/code&gt; to make sure keyboard users can toggle the menu after Svelte hydrates.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Bonus points&lt;/h2&gt;
&lt;p&gt;Svelte&amp;#39;s &lt;a href=&quot;https://svelte.dev/docs#use_action&quot;&gt;use:action&lt;/a&gt; feature can be used to make this a bit more reusable. Actions deserve their own full blog post, but in short an &lt;code&gt;action&lt;/code&gt; in Svelte is function you can add in markup that will get called with the HTML node once it is created.&lt;/p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://svelte.dev/repl/267acb68b79647849f0532774d62c594?version=3.38.2&quot;&gt;REPL example&lt;/a&gt; for a working example a custom &lt;code&gt;enhance&lt;/code&gt; action replacing the &lt;code&gt;mounted&lt;/code&gt; and &lt;code&gt;onMount()&lt;/code&gt; logic above.&lt;/p&gt;
 </content></item></channel></rss>