<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cole Peters</title>
    <description>The latest articles on DEV Community by Cole Peters (@colepeters).</description>
    <link>https://dev.to/colepeters</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002536%2Fb0465ec4-1b17-4f3a-8dc5-3e20808b5178.jpg</url>
      <title>DEV Community: Cole Peters</title>
      <link>https://dev.to/colepeters</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/colepeters"/>
    <language>en</language>
    <item>
      <title>Introducing Enhance Image</title>
      <dc:creator>Cole Peters</dc:creator>
      <pubDate>Tue, 12 Mar 2024 16:26:59 +0000</pubDate>
      <link>https://dev.to/begin/introducing-enhance-image-hh8</link>
      <guid>https://dev.to/begin/introducing-enhance-image-hh8</guid>
      <description>&lt;p&gt;Although we in web development (and certainly those of us at Begin) do a lot of talking about the cost of JavaScript, there’s another type of content on the web that has a huge bearing on performance: images.&lt;/p&gt;

&lt;p&gt;In fact, images are by far &lt;a href="https://almanac.httparchive.org/en/2022/page-weight#fig-8"&gt;the largest contributor to page weight&lt;/a&gt;, while also being in close competition with JavaScript for &lt;a href="https://almanac.httparchive.org/en/2022/page-weight#fig-3"&gt;the highest number of resource requests per page&lt;/a&gt;. Images have a significant impact on multiple aspects of performance, including &lt;a href="https://web.dev/articles/cls"&gt;Cumulative Layout Shift&lt;/a&gt; and &lt;a href="https://web.dev/articles/lcp"&gt;Largest Contentful Paint&lt;/a&gt;. Furthermore, with &lt;a href="https://almanac.httparchive.org/en/2022/page-weight#fig-13"&gt;median image payloads hovering around 1MB&lt;/a&gt;, images can also cost end users a lot of data and money to access (and considering that &lt;a href="https://www.ofcom.org.uk/phones-telecoms-and-internet/advice-for-consumers/advice/pay-as-you-go-mobile-use-it-or-lose-it"&gt;just over 1 in 5 people in the UK still use Pay As You Go&lt;/a&gt; — and &lt;a href="https://www.statista.com/statistics/460086/total-number-of-prepaid-mobile-subscribers-canada/"&gt;as of 2020, 2.45 million people in Canada did, too&lt;/a&gt; — lowering data access requirements is decidedly not just a problem for emerging cellular markets). This makes images an obvious and important target for performance optimization.&lt;/p&gt;

&lt;p&gt;In 2024, we’re fortunate to have more options available to us than ever to reduce the negative impacts of using images on the web. Both the Image and Picture elements now have APIs in place for &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images"&gt;implementing responsive images&lt;/a&gt;, which allow web developers to send the most appropriately sized or formatted image to a particular viewport or device. Using these techniques can greatly reduce the overhead of sending oversized images to a multitude of displays (and users).&lt;/p&gt;

&lt;p&gt;Unfortunately, the APIs for implementing responsive images can be difficult to wrap your head around. While these techniques are effective, they’re not exactly a breeze to internalize — as evidenced by &lt;a href="https://cloudfour.com/thinks/responsive-images-101-definitions/"&gt;this (very good) &lt;em&gt;ten part&lt;/em&gt; series of articles on responsive images&lt;/a&gt;. Meanwhile, preparing multiple variants (based on size, quality, and image format) of every single image on a website can be time consuming at best, and certainly designers and engineers could imagine spending their time on more enjoyable tasks.&lt;/p&gt;

&lt;p&gt;With all this in mind, one of the first side projects I spun up for myself after joining Begin in 2022 was to investigate how we could make responsive images easier for users to author in their &lt;a href="https://enhance.dev"&gt;Enhance&lt;/a&gt; projects. While by no means revolutionary, the core concept was to make a configurable, standards based, single file component available to users, which would simplify the implementation of responsive images in addition to eliminating the need to generate arbitrary image variants by hand. I (and my colleagues at Begin) went through multiple iterations and proposals for this project, weighing everything from the pros, cons, and most compelling use cases of the Image and Picture elements, to different component signatures, options, and patterns for configuration.&lt;/p&gt;

&lt;p&gt;Today, we’re excited to announce that the results of this project are now available in the form of our beta release of &lt;a href="https://enhance.dev/docs/enhance-ui/image"&gt;Enhance Image&lt;/a&gt;, the first in a suite of standards based UI components we’re calling Enhance UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simplified authoring
&lt;/h2&gt;

&lt;p&gt;Generally speaking, responsive images solve one of two problems: resolution switching and art direction. ‘Resolution switching’ refers to providing &lt;em&gt;different resolutions&lt;/em&gt; of a given image to different viewports (for example: sending small images to mobile devices, and large images to external monitors), while ‘art direction’ refers to providing &lt;em&gt;different aesthetic variants&lt;/em&gt; of an image to different viewports (for example: rendering a landscape crop of an image on a shorter, wider screen, and a portrait crop of an image to a taller, narrower screen).&lt;/p&gt;

&lt;p&gt;Enhance Image is built for the former use case: providing different resolutions of the same image to different viewports.&lt;/p&gt;

&lt;p&gt;This use case is supported primarily via two attributes of the native Image element — those being the &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; attributes. In short, the &lt;code&gt;srcset&lt;/code&gt; attribute allows an author to declare URLs to multiple different variants of an image, each of which must have its width declared with a width descriptor (effectively the image’s width in pixels followed by the letter &lt;code&gt;w&lt;/code&gt;). Meanwhile, the &lt;code&gt;sizes&lt;/code&gt; attribute allows an author to specify various media conditions for the viewport and the width of the content area that the image should occupy under that condition. With this information from the &lt;code&gt;sizes&lt;/code&gt; attribute, the browser will examine the list of images in the &lt;code&gt;srcset&lt;/code&gt; attribute and select the image it determines to be the best available option.&lt;/p&gt;

&lt;p&gt;Let’s review an example of how this works in practice before demonstrating what Enhance Image brings to the table.&lt;/p&gt;

&lt;p&gt;Let’s say we’ve got a nice big hero image we want to render at the top of a web page. We have an asset for this image from our designer all ready to go in our Enhance project. But hero images can be pretty large, and we’d like to render smaller versions of this image on smaller viewports, so as not to send those users more data than needed.&lt;/p&gt;

&lt;p&gt;Before implementing this image responsively, our code for our hero section might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/_public/hero.jpg"&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Axol the axolotl galavanting through the world of Enhance"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To implement this image responsively, we could have our designer carve out multiple variations of this image in different sizes, and then use the &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; attributes to provide the browser with the ability to serve the best option to a given display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/_public/hero.jpg"&lt;/span&gt;
    &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/_public/hero-large.jpg 2400w, /_public/hero-medium.jpg 1200w, /_public/hero-small.jpg 800w"&lt;/span&gt;
    &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"100vw"&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Axol the axolotl galavanting through the world of Enhance"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things are going on in the example above: we’re using the &lt;code&gt;srcset&lt;/code&gt; attribute to declare multiple variants of our image, along with a width descriptor for each; we’re leaving the &lt;code&gt;src&lt;/code&gt; attribute in place as a fallback for browsers that may not have the &lt;code&gt;srcset&lt;/code&gt; attribute available; and we’re setting the &lt;code&gt;sizes&lt;/code&gt; attribute to &lt;code&gt;100vw&lt;/code&gt; to tell the browser that it should select an image from the options in &lt;code&gt;srcset&lt;/code&gt; that will be best suited for filling up 100% of the current viewport’s width. Note that the browser will make this determination by factoring in not just the pixel width of the viewport and content areas, but also the display’s pixel density and zoom level, and possibly other factors such as network conditions (these factors can vary between browser vendors).&lt;/p&gt;

&lt;p&gt;This implementation works great, but the code is admittedly not necessarily the easiest to follow. If we want to provide a larger number of variants in the &lt;code&gt;srscset&lt;/code&gt; attribute, things can get gnarly pretty fast (both for authors writing the code and designers creating multiple variants). This is also just a single image on our site — as previously noted, most sites have many more than a single image per page, thus multiplying the amount of work required.&lt;/p&gt;

&lt;p&gt;This is where Enhance Image comes in. First, it provides a familiar but simplified component signature — for example, the equivalent of the above code using Enhance Image would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;enhance-image&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/_public/hero.jpg"&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Axol the axolotl galavanting through the world of Enhance"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/enhance-image&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where have our &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; attributes gone? Well, I’ve been a bit sneaky and previously specified a list of images formatted to Enhance Image’s defaults — that is, Enhance Image will make a 2400px, 1200px, and 800px wide variant available on demand for each source image it’s provided. We also default to &lt;code&gt;100vw&lt;/code&gt; for the &lt;code&gt;sizes&lt;/code&gt; attribute, and it can thus be omitted. (The same is true for the native &lt;code&gt;sizes&lt;/code&gt; attribute, but it was shown previously for clarity).&lt;/p&gt;

&lt;p&gt;Thus, using just the default configuration, every image in your Enhance project can now be delivered responsively by swapping out the &lt;code&gt;img&lt;/code&gt; tag for the &lt;code&gt;enhance-image&lt;/code&gt; custom element tag.&lt;/p&gt;

&lt;p&gt;In the above example, we don’t just deliver the generated variants of your source image in different sizes. By default, we also provide further optimizations by delivering these variants in &lt;a href="https://developers.google.com/speed/webp"&gt;&lt;code&gt;webp&lt;/code&gt; format&lt;/a&gt;, at an 80% quality setting. And — like all other Enhance elements — Enhance Image renders its content as HTML on the server, with no client side JavaScript required.&lt;/p&gt;

&lt;p&gt;Of course, the defaults that ship with Enhance Image won’t work for everyone — in fact, we strongly encourage you to experiment with these values, which is why we’ve made configuring Enhance Image a breeze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration options
&lt;/h2&gt;

&lt;p&gt;Enhance Image’s single file component provides a simple but powerful component primitive for authors, but this is only half the story. Enhance Image itself is powered by a versatile, fully configurable, on demand image transformation service that powers the creation of your source image variants. Here, all credit goes to the brains behind this service, fellow Beginner &lt;a href="https://indieweb.social/@ryanbethel"&gt;Ryan Bethel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our image transformation service allows authors to request generated variants of a given image using three different configuration options, which need to be set in your project’s &lt;a href="https://enhance.dev/docs/conventions/preflight"&gt;Preflight file&lt;/a&gt;:&lt;/p&gt;

&lt;dl&gt;

&lt;dt&gt;
&lt;code&gt;widths&lt;/code&gt; (optional)&lt;/dt&gt;
&lt;dd&gt;

The `widths` option takes an array of pixel widths, specified as unitless integers. A variant of your source image will be generated for every width specified, with a height corresponding to the source image's intrinsic aspect ratio. The default widths are 2400, 1200, and 800.

&lt;/dd&gt;

&lt;dt&gt;
&lt;code&gt;format&lt;/code&gt; (optional)&lt;/dt&gt;
&lt;dd&gt;

The format option takes one of the following format strings: `webp`, `avif`, `jxl`, `jpeg`, `png`, or `gif`. Generated images will be returned in the given format. `webp` is recommended for compatibility and performance, and is the default option. [Read more about image formats on the web here.](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types)

&lt;/dd&gt;

&lt;dt&gt;
&lt;code&gt;quality&lt;/code&gt; (optional)&lt;/dt&gt;
&lt;dd&gt;

The quality setting takes a number between 0–100. Generated images will be returned at the quality level specified. It's best to choose a quality level that results in the smallest possible file size without significant degradation in image quality — this can vary based on the content of the images being processed, and you may need to experiment a bit to find the best setting based on your content. The quality option defaults to 80.

&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;For each image passed to the &lt;code&gt;enhance-image&lt;/code&gt; single file component, our image transformation service will return one generated variant per &lt;code&gt;width&lt;/code&gt; specified in the configuration, formatted and optimized based on the &lt;code&gt;format&lt;/code&gt; and &lt;code&gt;quality&lt;/code&gt; settings. This saves authors from having to manually create and optimize images ahead of deployment, and allows different variants to be iteratively added or removed as your needs change over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pushing performance further
&lt;/h2&gt;

&lt;p&gt;One important thing to note is that, since Enhance applications don’t have a build step but are rather deployed as cloud functions, the generated variants for each image are created at the time those images are requested by a browser. Each image variant will be cached after its creation; however, you may experience a slight delay when the images are first requested, especially for large or complex images that may take longer to process.&lt;/p&gt;

&lt;p&gt;This is why Enhance Image also ships with a cache warming script that can be run either locally or via a CI service like GitHub Actions. This script will recursively scan a directory in your project for image files, and then — based on your configuration options — will make requests for each of your image variants, effectively ‘warming’ their caches before an end user even requests them.&lt;/p&gt;

&lt;p&gt;The cache warming script can be run like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @enhance/image warm &lt;span class="nt"&gt;--directory&lt;/span&gt; /public/images &lt;span class="nt"&gt;--domain&lt;/span&gt; https://example.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script takes two arguments:&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;&lt;code&gt;--directory&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;

The path to the directory in your project containing the images you’ll be using with Enhance Image, for which you’d like variants (and caches) generated, e.g. `/public/images`. This path **must start with `/public`**. The directory will be scanned recursively, so only the top most directory needs to be provided.

&lt;/dd&gt;

&lt;dt&gt;&lt;code&gt;--domain&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;

The URL of your application’s deployment, e.g. `https://example.org` or `https://image-4ab.begin.app`.

&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;For further details, see &lt;a href="https://enhance.dev/docs/enhance-ui/image"&gt;the Enhance Image docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beta available today!
&lt;/h2&gt;

&lt;p&gt;We’ve spent a surprisingly long time working to bring Enhance Image to a public beta, and we’re pretty excited about the results. It’s available for you to try today — &lt;a href="https://enhance.dev/docs/enhance-ui/image"&gt;check out the docs to get started&lt;/a&gt;! If you have any questions, problems, or other feedback, feel free to &lt;a href="https://enhance.dev/discord"&gt;join us on Discord&lt;/a&gt; or &lt;a href="https://github.com/enhance-dev/enhance-image/issues"&gt;open an issue&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Possible breaking changes, however, are in our sights — primarily as concerns the method of providing configuration options, as well as the possibility of using Enhance Image with private S3 buckets (eliminating the need to track image assets in your project’s git repository). While we always aim to make changes additively rather than destructively, we’re not sure how these changes will land once we get to work on them — and it’s for this reason that we’re labelling Enhance Image as a beta release for now.&lt;/p&gt;

&lt;p&gt;That said, we’ve been using Enhance Image (first in its alpha form, and more recently in its current beta form) on our own production domains (including this blog) for several weeks now, and we’re confident both in its abilities and its results.&lt;/p&gt;

&lt;p&gt;We hope you enjoy using Enhance Image to more easily author responsive images!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webcomponents</category>
      <category>html</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Building a Design Portfolio with Enhance</title>
      <dc:creator>Cole Peters</dc:creator>
      <pubDate>Tue, 20 Jun 2023 21:16:39 +0000</pubDate>
      <link>https://dev.to/begin/building-a-design-portfolio-with-enhance-3i6f</link>
      <guid>https://dev.to/begin/building-a-design-portfolio-with-enhance-3i6f</guid>
      <description>&lt;p&gt;There’s a lot to be said for &lt;a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food"&gt;dogfooding&lt;/a&gt;, and lately I’ve been eating a ton of Enhance™ brand dog chow. Whether using it to &lt;a href="https://begin.com/blog/posts/2023-04-21-building-the-enhance-landing-page"&gt;build our ludicrously fun landing page&lt;/a&gt; or &lt;a href="https://color-ig2.begin.app/"&gt;a quick demo of our fluid modular scales&lt;/a&gt;, I seem to appreciate Enhance the more I use it. (You might think it’s easy to heap this kind of praise on a product I’m partly responsible for nurturing, but like many creative folks, my standards tend to skyrocket when dealing with my own output.)&lt;/p&gt;

&lt;p&gt;Recently, our team was discussing the idea of producing more example applications — you may have noticed Ryan’s recent &lt;a href="https://begin.com/blog/posts/2023-05-10-why-you-should-roll-your-own-auth"&gt;authentication examples&lt;/a&gt; or Taylor’s &lt;a href="https://begin.com/blog/posts/2023-06-06-dbaas-in-lambda"&gt;test app for database providers&lt;/a&gt;. Meanwhile, I’ve been really excited to see what the web designers of the world will get up to once they get familiar with our framework — and with that in mind, I spent a week or so putting together an example of a design portfolio built with Enhance.&lt;/p&gt;

&lt;p&gt;My goal with this example app was to keep the code clean and easy to follow, while also demonstrating just how straightforward it can be to create appealing, engaging interfaces with a minimal, standards based stack. That stack consists solely of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://enhance.dev"&gt;Enhance&lt;/a&gt;, for authoring web pages and components&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://enhance.dev/docs/learn/concepts/styling/"&gt;Enhance Styles&lt;/a&gt; (which is built into Enhance itself), to style our pages and components with parametric CSS utility classes, fluid modular scales, custom properties, and component scoped CSS rulesets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite this stack being focused on Enhance, it’s important to note that most of this project’s code is just HTML and CSS. Yet, when viewing it in the browser, this app feels very much like what you might expect from a more involved tech stack — except that it loads faster, is more resilient, and works without JavaScript in the browser (teehee).&lt;/p&gt;

&lt;p&gt;In all seriousness, though, herein lies a fundamental truth: you really don’t need much beyond web standards these days to create beautiful, dynamic, engaging web content. I want to say that extra loud for all the designers and CSS focused engineers out there who I know have felt (quite understandably) shut out of a lot of JS centered web development over the past decade. It used to be that a web designer could carry out a lot of their work using just HTML and CSS. I feel like those days are coming back with a vengeance, especially with tools like Enhance helping folks take advantage of things like custom elements and cutting edge CSS techniques (along with a ton of under the hood optimizations to deployment and performance). The fact that the native web platform is just that good is also a win for folks more steeped in JS based web development, as it allows them to drop many additional frameworks that are no longer required for success.&lt;/p&gt;

&lt;p&gt;In order to focus on these benefits and avoid introducing potentially unnecessary complexity, I introduced a few constraints to this example — primarily in the form of keeping all the content local to the project (instead of using a CMS, for example). I cut a few corners as well, such as omitting &lt;a href="https://americananthro.org/accessibility/image-descriptions/"&gt;descriptive text for images&lt;/a&gt; (which I highly recommend you do in all real world projects) and using some duplicate images and placeholder text to pad out the example’s content.&lt;/p&gt;

&lt;p&gt;With all that said, let’s take a walk through the example app and see what’s cooking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can view the portfolio example live at: &lt;a href="https://snow-wfi.begin.app/"&gt;https://snow-wfi.begin.app/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Welcome to the Axol Design Collective
&lt;/h2&gt;

&lt;p&gt;The fictional Axol Design Collective — stylized as &lt;strong&gt;a.d.c&lt;/strong&gt; — is an interdisciplinary design studio, which basically means they somehow manage to do architectural design, landscape design, typography, product design, and more. In reality, I just wanted an excuse to use a variety of rad design outputs as demo material for this website.&lt;/p&gt;

&lt;p&gt;We’re introduced to a.d.c’s work on the homepage of this website, primarily through a series of impressive, high resolution photographs of ‘their’ work (in actuality, a bunch of images sourced from &lt;a href="https://unsplash.com/"&gt;Unsplash&lt;/a&gt;). This introduces one of the first components I’ll provide an overview of — the Cover Image component.&lt;/p&gt;



&lt;p&gt;The Cover Image component takes a series of images, renders them within a responsive container with a fixed aspect ratio, and then crossfades between each of those images on a loop — with each image sized to fill the container’s aspect ratio using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit"&gt;the &lt;code&gt;object-fit: cover&lt;/code&gt; CSS rule&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/cover-image.mjs"&gt;take a look at the code for this component&lt;/a&gt;, you’ll see there’s actually not much to it. It’s essentially comprised of a hardcoded list of image URLs, and a Single File Component which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Takes our list of images and turns them into &lt;code&gt;img&lt;/code&gt; tags with some utility classes for styling&lt;/li&gt;
&lt;li&gt;Renders some (component scoped!) CSS rulesets to set the component’s aspect ratio, the images’ transition styles, etc.&lt;/li&gt;
&lt;li&gt;Returns some HTML (on the server) to structure this component (including references to several Enhance Styles utility classes for the layout)&lt;/li&gt;
&lt;li&gt;Includes a script to run in the browser to toggle each image’s &lt;code&gt;opacity&lt;/code&gt; at a set interval, thus giving us our animation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With just these 64 lines of code, we get an engaging, customizable component to show off some beautiful photography on our homepage (or anywhere else we might want to use it).&lt;/p&gt;

&lt;p&gt;Another win for this component: if the end user’s browser can’t load that included chunk of JavaScript (for any of &lt;a href="https://www.kryogenix.org/code/browser/everyonehasjs.html"&gt;a myriad of reasons&lt;/a&gt; that can and do happen), this component won’t break. Instead, that user will simply see the first image in the list, without the transition to other images. This is much better than the spinner (or worse, nothing at all) that you tend to get when many ‘modern’ JavaScript frameworks fail in the browser. This represents an approach called &lt;a href="https://enhance.dev/docs/learn/practices/progressive-enhancement"&gt;progressive enhancement&lt;/a&gt; — which Enhance is designed to optimize for — and which delivers a better baseline experience for all users (which can then be incrementally improved upon). We’ll see another example of this approach later in the walkthrough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Work page
&lt;/h2&gt;

&lt;p&gt;Moving on to the Work page, we tackle a common UI design pattern — a grid of cards, in this case containing summaries of a.d.c’s case studies. This layout is composed primarily from two custom elements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zqkI6xfp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/js1tyi5agxqigewwag8a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zqkI6xfp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/js1tyi5agxqigewwag8a.jpg" alt="Image description" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, there’s &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/case-studies.mjs"&gt;the Case Studies component&lt;/a&gt;, which is a styled wrapper that applies a grid layout, with the number of columns in that layout determined by the space available to the Case Studies component itself (as opposed to the available size of the viewport). Because we’re using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries"&gt;CSS container queries&lt;/a&gt; to set the number of columns, this component and its layout could be reused in a number of macro layouts, and it would still render an appropriate number of columns regardless of how much space has been made available for that component.&lt;/p&gt;

&lt;p&gt;Second, there’s &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/case-summary.mjs"&gt;the Case Summary component&lt;/a&gt;, which handles rendering an individual entry in the Case Studies layout. To use the Case Summary component, we pass it a number of attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an image URL and an accompanying value for the alt attribute&lt;/li&gt;
&lt;li&gt;a title and description to display for the entry&lt;/li&gt;
&lt;li&gt;a URL to link to when the component is clicked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these attributes passed in, the component proceeds to render a couple of CSS rulesets for the image styling (all values of which are customizable via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties"&gt;CSS custom properties&lt;/a&gt;) as well as the markup for its content. By default, this component renders the image in a 3:2 aspect ratio, at 75% opacity, and applies a transition to the &lt;code&gt;scale&lt;/code&gt; and &lt;code&gt;opacity&lt;/code&gt; values when the component is hovered or focused. This results in a gentle but pleasing interaction that adds depth to an otherwise static grouping of cards — all with no JS required.&lt;/p&gt;

&lt;p&gt;Let’s dig into one of these case studies to see what’s under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going atomic with custom elements
&lt;/h2&gt;

&lt;p&gt;The first few sections of each case study page are laid out the same — a large title, a full width image, and an adaptive two column grid containing some background information about the project. This brings us to a few simpler components that are nonetheless worth a closer look.&lt;/p&gt;

&lt;p&gt;First up: &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/adaptive-2col.mjs"&gt;the Adaptive 2 Column Grid component&lt;/a&gt;. This basic component renders a single column view below a certain viewport width, and then switches to a 2 column grid above that viewport width. That width value is customizable by the user of this component, but defaults to &lt;code&gt;48em&lt;/code&gt;. Additionally, this component will swap the &lt;code&gt;order&lt;/code&gt; of its two columns above the given viewport width — this was done in order to show each project’s metadata list first on mobile screens, and second on larger screens in order to provide an ideal layout for either variant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i0uAxOYQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xtfef2i7djd9h2vlpdwp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i0uAxOYQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xtfef2i7djd9h2vlpdwp.jpg" alt="Image description" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Going even simpler, there’s &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/text-container.mjs"&gt;the Text Container&lt;/a&gt; component. The only thing this component is responsible for is setting a maximum width for its contents, and applying a margin between successive paragraph elements. This gives us a nice readable line length on every child element of this component, without restricting various elements (like &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; elements) to this constraint via global element selectors.&lt;/p&gt;

&lt;p&gt;We repeat this pattern again with &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/data-list.mjs"&gt;the Data List&lt;/a&gt; and &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/unordered-list.mjs"&gt;Unordered List&lt;/a&gt; components. These components are responsible solely for providing focused, repeatable styles to lists authored with &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; elements respectively.&lt;/p&gt;

&lt;p&gt;Components like this may not look like much, but they excel at creating abstractions of layout and component styles that you can easily reuse across single or multiple projects. Just as &lt;a href="https://begin.com/blog/posts/2023-01-10-past-informs-the-present-our-approach-to-css"&gt;utility classes inform a methodology for composing atomic styles in place with our content&lt;/a&gt;, these custom elements allow us to create highly focused ‘atomic’ components, which can themselves then be composed together. When using Enhance Single File Components to accomplish this, we gain a number of benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Colocation: styles are written alongside and travel with the component itself.&lt;/li&gt;
&lt;li&gt;Ease of updates: component styles (whether scoped rulesets or compositions of utility classes) can be changed once to update everywhere.&lt;/li&gt;
&lt;li&gt;Appropriate abstraction: we avoid having to repeat utility class compositions in multiple places, and — conversely — the need to resort to global selectors which might require cumbersome opt outs.&lt;/li&gt;
&lt;li&gt;Brevity: both the functionality and the required styles of the component remain compact and easy to reason about.&lt;/li&gt;
&lt;li&gt;Containment: styles apply only to the given component and its child content.&lt;/li&gt;
&lt;li&gt;Performance and resilience: unlike many other web component frameworks, Enhance Single File Components are rendered on the server, meaning there’s no waiting (or chances for errors) for these components to load via JavaScript.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To me, this strategy represents a beautiful evolution of the paradigm proposed by atomic CSS; you might even call components such as these ‘atomic elements’ or ‘utility elements’.&lt;/p&gt;

&lt;p&gt;To get a sense of how this all plays out, let’s take a closer look at a case study page — for example, the page for Axolotl Commons. On &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/pages/work/axolotl-commons.html#L7-L23"&gt;lines 7–23 of this page’s code&lt;/a&gt;, you’ll see a few components in use: first, the aforementioned Adaptive 2 Column Grid, and inside it, a Project Data component. &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/project-data.mjs"&gt;The Project Data component&lt;/a&gt; is itself another small abstraction: it applies a background color, and then wraps its children with the aforementioned Data List component. If you look back at &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/pages/work/axolotl-commons.html#L9-L22"&gt;lines 9–22 of our HTML page&lt;/a&gt;, you’ll see that we’re thus using the Project Data component to render an instance of the Data List component, and a nested Unordered List component inside of it.&lt;/p&gt;

&lt;p&gt;This should give you a good idea of how seemingly simple custom elements can be composed together to form more sophisticated layouts, all with a rigorous approach to styling and paired with highly legible, declarative source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A very snappy image gallery
&lt;/h2&gt;

&lt;p&gt;Moving further down the page for Axolotl Commons, we get to the juicy part of the case study, presented as a horizontally scrolling image gallery. This particular gallery has a few tricks up its sleeve, however — including the use of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_scroll_snap"&gt;CSS Snap Points&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To create this image gallery, I dusted off &lt;a href="https://github.com/enhance-dev/layout-elements/blob/main/packages/collection/layout-collection.js"&gt;a Collection Layout component&lt;/a&gt; I’d first stubbed out a number of months ago for this very purpose. This is another component that delivers a lot more than the brevity of its code might suggest, purely by relying on advances in the web platform itself. Specifically, it renders an overflowing flexbox layout of items with optional scroll snap properties. This allows us to render a gallery of items which, when scrolled, will stop at the closest ‘snapped’ element inside it. This UI pattern will be familiar to anyone who’s navigated through a streaming media service’s catalog.&lt;/p&gt;



&lt;p&gt;To complete this component (have a look at &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/scrollsnap-gallery.mjs"&gt;the source code&lt;/a&gt; if you’d like to follow along), we apply some styles to ensure the images in the gallery will be rendered at an ideal size. In particular:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We cap each image’s inline size (the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values"&gt;logical term&lt;/a&gt; for width) to 90% of the image gallery itself. This ensures a hint of the next image in the gallery is always visible as an affordance for users to scroll.&lt;/li&gt;
&lt;li&gt;We also cap the images’ block size (the logical term for height) at 80% of the current viewport’s height. This ensures the image remains within the vertical bounds of the browser, and also helps to create space to keep the site’s navigation bar from colliding with the image.&lt;/li&gt;
&lt;li&gt;Finally, we use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit"&gt;&lt;code&gt;object-fit: contain&lt;/code&gt;&lt;/a&gt; property to ensure that each image’s intrinsic aspect ratio is preserved, thus avoiding images getting stretched when satisfying the previous two size restrictions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives us a fully responsive horizontal image gallery with scroll snapping, which works with absolutely no JavaScript required.&lt;/p&gt;

&lt;p&gt;We aren’t quite done yet, though — we still have another type of image gallery to explore.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little light boxing
&lt;/h2&gt;

&lt;p&gt;The second type of image gallery included in our portfolio example renders a grid of thumbnails, each of which can be clicked to display the full size image in a lightbox (that is, the image rendered in a modal overlay). This type of gallery can be seen on the case studies for Moji, Rome Concepts, and Curve &amp;amp; Counter.&lt;/p&gt;

&lt;p&gt;To create this gallery, we start with &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/image-grid.mjs"&gt;the Image Grid component&lt;/a&gt;, another example of concise source code belying a depth of functionality. First, we use a couple utility classes to render a grid layout with fluid gaps. The real magic happens when &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/image-grid.mjs#L9"&gt;we set our &lt;code&gt;grid-template-columns&lt;/code&gt; property&lt;/a&gt; on the grid layout. It might be worth pulling this style apart in more detail to make things clear:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/repeat"&gt;the &lt;code&gt;repeat()&lt;/code&gt; function&lt;/a&gt; with the &lt;code&gt;auto-fit&lt;/code&gt; value to create a layout composed of as many columns as will fit without overflowing our grid container.&lt;/li&gt;
&lt;li&gt;Next, we use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/minmax"&gt;the &lt;code&gt;minmax()&lt;/code&gt; function&lt;/a&gt; to specify the minimum and maximum permitted width of each column; this will be used to determine how many columns can fit in each row.&lt;/li&gt;
&lt;li&gt;We define our minimum as either 20% of the component’s width, or 250px — whichever is larger — using the &lt;code&gt;max()&lt;/code&gt; function. We do this in order to prefer a fluid value (20%), but &lt;em&gt;not&lt;/em&gt; if that value ends up being smaller than 250px (which would make the images in the grid quite small).&lt;/li&gt;
&lt;li&gt;Finally, we define our maximum value as &lt;code&gt;1fr&lt;/code&gt;, which equates to 100% of the available width (&lt;a href="https://mozilladevelopers.github.io/playground/css-grid/04-fr-unit/"&gt;not the full width&lt;/a&gt;) of the component. This keeps the image grid usable if only a very small number of images are used, in which case each image would occupy an equal portion of the available space.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is a fully responsive grid of fluidly sized, reflowing images, created without a single media or container query.&lt;/p&gt;

&lt;p&gt;Next, we have our Lightbox component. We use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"&gt;the dialog element&lt;/a&gt; to power much of this component, as it includes a ton of helpful accessibility measures such as focus trapping and built in screen reader support. Unfortunately, the dialog element requires client side JavaScript to operate, so we need to account for the instances where JavaScript may fail to load via another dose of progressive enhancement.&lt;/p&gt;

&lt;p&gt;To do this, we first wrap our Lightbox content with &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/light-box.mjs#L39"&gt;a link to the full size image&lt;/a&gt; — this way, if JavaScript isn’t available, users clicking on the thumbnail will still be forwarded to the full size image. Then, we include &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/browser/light-box.mjs"&gt;a script on the client&lt;/a&gt; which, when loaded, takes over the click event for those links, and instead triggers the link’s corresponding dialog element to open, revealing the full size image as modal content instead. We also include &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/elements/light-box.mjs#L43-L45"&gt;a form with a button to close the dialog element&lt;/a&gt; once it’s been opened.&lt;/p&gt;

&lt;p&gt;Finally, &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio/blob/main/app/pages/work/rome-concepts.html#L39-L100"&gt;we use our Image Grid and Lightbox components together&lt;/a&gt; for the full effect — a responsive grid of image thumbnails that open full sized images in lightboxes (or as full page redirects if no JavaScript is available). Pretty neat, and built entirely with standard platform features!&lt;/p&gt;



&lt;h2&gt;
  
  
  Over to you!
&lt;/h2&gt;

&lt;p&gt;This example app was a ton of fun to build, but it wasn’t just a self serving exercise. As mentioned earlier, I’m really excited to find out what other design minded folks are going to get up to with Enhance — and to encourage that, I invite all of you to use &lt;a href="https://github.com/enhance-dev/enhance-example-portfolio"&gt;this project’s code&lt;/a&gt; however you’d like. The linked GitHub repo includes a readme with instructions on how to get started with running this app locally, as well as links to the &lt;a href="https://enhance.dev/docs"&gt;Enhance docs&lt;/a&gt; so you can start exploring our framework in more detail. If you happen to make use of any code in this project, attribution is always appreciated, but is absolutely not required.&lt;/p&gt;

&lt;p&gt;However, I do hope you’ll share whatever you make with me and the rest of the crew over on &lt;a href="https://enhance.dev/discord"&gt;our Discord&lt;/a&gt; (or hit us up with any questions or input you may have)!&lt;/p&gt;

&lt;p&gt;It’s such an exciting time to be working on the web, and the increasing pace and breadth of web standards is only making this all the more exciting. Combined with a little help from frameworks like Enhance to get potentially tricky aspects like server side rendering in place (and to provide a happy path to integrating databases and APIs), I think it’s safe to say the future is looking incredibly bright. I hope to see you there!&lt;/p&gt;

</description>
      <category>css</category>
      <category>design</category>
      <category>webdev</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>Introducing Themes for the Enhance Blog Template</title>
      <dc:creator>Cole Peters</dc:creator>
      <pubDate>Wed, 03 May 2023 17:59:37 +0000</pubDate>
      <link>https://dev.to/begin/introducing-themes-for-the-enhance-blog-template-13k6</link>
      <guid>https://dev.to/begin/introducing-themes-for-the-enhance-blog-template-13k6</guid>
      <description>&lt;p&gt;Today, we’re shipping a small but exciting update to our &lt;a href="https://github.com/enhance-dev/enhance-blog-template"&gt;Enhance blog template&lt;/a&gt;, in the form of a new starter theme and the ability to quickly switch between themes via a single line of code.&lt;/p&gt;

&lt;p&gt;We’ve been thinking about the idea of themeable Enhance apps for a while now, and this is our first step in that direction. Starting today, the Enhance blog template comes preloaded with two different themes — the original theme the template was first released with, and a new minimal theme. Both handle dark mode out of the box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KXhsLGBp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l98hvpxmz01zlm7xxv90.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KXhsLGBp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l98hvpxmz01zlm7xxv90.jpg" alt='The new "minimal" blog theme, featuring sans serif typography in light and dark variants' width="800" height="280"&gt;&lt;/a&gt;&lt;br&gt;The new minimal theme
  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gZkp0UiP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhjgthjs6544m7nm850f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gZkp0UiP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhjgthjs6544m7nm850f.jpg" alt='The previous default theme, now called "elegant", featuring a mix of sans serif and serify typography in light and dark variants' width="800" height="280"&gt;&lt;/a&gt;&lt;br&gt;The original theme
  &lt;/p&gt;

&lt;p&gt;Our new minimal theme (which is active by default) uses a similar layout to our original theme, but features more neutral colors and typography. If you prefer the original theme, however, switching between themes is as easy as changing one line of code in your project’s &lt;code&gt;.arc&lt;/code&gt; file. Under the &lt;code&gt;@enhance-styles&lt;/code&gt; pragma, point the &lt;code&gt;config&lt;/code&gt; option at &lt;code&gt;theme-elegant.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@enhance-styles
config theme-elegant.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two themes vary primarily in color palette and typefaces. However, we’re planning to explore affordances for much deeper theming in the future, which will allow you to tweak many different aspects of a theme with ease. We hope these themes give you a great starting point for your own customizations and styling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Read &lt;a href="https://dev.to/blog/posts/2023-03-17-introducing-the-enhance-blog-template"&gt;the release announcement&lt;/a&gt; for our blog template&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/blog/posts/2023-04-06-customizing-the-enhance-blog-template"&gt;Learn how to style&lt;/a&gt; the blog template&lt;/li&gt;
&lt;li&gt;Grab &lt;a href="https://begin.com/blog/posts/2023-04-03-begin-domains"&gt;a domain name&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get set up with &lt;a href="https://dev.to/blog/posts/2023-04-19-webmention-support-in-enhance-blog-template"&gt;webmentions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/blog/posts/2023-04-28-supporting-publish-own-site-syndicate-elsewhere"&gt;Syndicate your blog&lt;/a&gt; with POSSE&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Join &lt;a href="https://enhance.dev/discord"&gt;the Enhance Discord&lt;/a&gt; to share your blog, request theming features, or to ask for help from our team and the community&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>enhance</category>
      <category>blog</category>
      <category>theming</category>
    </item>
    <item>
      <title>Above the Clouds With the New Enhance Landing Page</title>
      <dc:creator>Cole Peters</dc:creator>
      <pubDate>Fri, 21 Apr 2023 16:01:15 +0000</pubDate>
      <link>https://dev.to/begin/above-the-clouds-with-the-new-enhance-landing-page-14ph</link>
      <guid>https://dev.to/begin/above-the-clouds-with-the-new-enhance-landing-page-14ph</guid>
      <description>&lt;p&gt;Those of you who’ve visited &lt;a href="https://enhance.dev"&gt;the Enhance website&lt;/a&gt; recently may have noticed a big change: we launched our first proper landing page! Although this project was primarily my baby over the last couple months, a lot of other folks were involved — from the stellar (and heartwarming) design &amp;amp; illustrations by &lt;a href="https://holadani.com/"&gt;Dani Raskovsky&lt;/a&gt;, to art and content direction by our very own &lt;a href="https://indieweb.social/@kristoferjoseph"&gt;kj&lt;/a&gt;, and additional ideas and feedback from the rest of the Begin team. We’re all thrilled to have this project out in the world, and we hope you’re as delighted as we are with it.&lt;/p&gt;

&lt;p&gt;This is one of the most substantial projects we’ve put Enhance itself to work on to date, so we thought this would be a great opportunity to take you under the hood — or through our ‘view source’, if you will — to cover some of the implementation details and our strategies behind them. We hope this will give you some ideas for your own Enhance projects — and beyond that, we hope this goes to show just how much you can do using web standards these days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design strategy
&lt;/h2&gt;

&lt;p&gt;Developing a distinct identity for Enhance has been a priority for us from the outset, but our new landing page absolutely revels in the spirit of playfulness and delight we always wanted to imbue our framework (and our work in general) with.&lt;/p&gt;

&lt;p&gt;If you ask us, the world of web technology has become a little stale in recent years when it comes to design. Many of us on the Begin team love to reminisce over the whimsical age of personal websites built on Tripod, Geocities and the like — a time of unbounded personal expression and fascination with the rapidly expanding toolkit of web design. Scrolling marquees, blink tags, copious animated GIFs, and unpausable background music powered by MIDI may not be considered best practices today, but we definitely look back on these artifacts with a lot of fondness.&lt;/p&gt;

&lt;p&gt;Today, a lot of the web looks the same — especially when it comes to sites for web technologies. Pared back but not stark, clean and inoffensive sans serif typefaces, rounded corners, a few subtle drop shadows, maybe some blob people illustrations… it’s not that there’s anything wrong with this kind of design language or any of these attributes, but it does get repetitive (and even hard to distinguish).&lt;/p&gt;

&lt;p&gt;With Enhance, we wanted to return to something more quirky, friendly, exuberant, and joyful. Crafting a personality that was immediately welcoming was also critical for us — we want to welcome everyone to the world of Enhance (and the wider world of the web), regardless of previous experience or identity. We want folks to start having fun on the web again, and we hope our new landing page declares that intention both loudly and proudly!&lt;/p&gt;

&lt;h2&gt;
  
  
  Open arms in action: prioritizing accessibility
&lt;/h2&gt;

&lt;p&gt;Accessibility is sometimes regarded as a nice to have — a feel good thing that we’ll get to if nothing else gets in the way. After all, how many people will actually be affected by our decisions (or lack thereof) on accessibility?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;According to the World Health Organization and the CDC, 16% of the world's population, and 26% of the U.S. population, have a disability. That's over 1 billion people worldwide and around 86 million people in the U.S. who may be unable to access websites that are not designed with accessibility in mind.&lt;/p&gt;

&lt;p&gt;— &lt;a href="https://www.forbes.com/sites/forbesbusinesscouncil/2023/03/20/understanding-the-importance-of-web-accessibility/?sh=5974f58b377f"&gt;Lesa Seibert, Forbes&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From a cold-hearted business perspective, turning away one in six people (or one in four Americans) is a huge loss. But more fundamentally (and importantly), making sure those folks have the best possible chance of engaging positively with your work isn’t just carrying out good business — it’s being a good member of human society. To that end, we paid particular attention to the accessibility of the Enhance landing page.&lt;/p&gt;

&lt;p&gt;When it comes to visual accessibility, aspects like &lt;a href="https://wise.design/design-at-wise/stories/accessible-but-never-boring"&gt;color contrast and perception&lt;/a&gt;, allowing for &lt;a href="https://craftcms.com/blog/designing-for-reduced-motion"&gt;motion reduction&lt;/a&gt;, and &lt;a href="https://fonts.google.com/knowledge/readability_and_accessibility/introducing_accessibility_in_typography"&gt;strong typography&lt;/a&gt; are critical considerations. While designing and building out the landing page, we kept an eye on our &lt;a href="https://webaim.org/resources/contrastchecker/"&gt;WCAG&lt;/a&gt; and &lt;a href="https://www.myndex.com/APCA/"&gt;APCA&lt;/a&gt; ratings, and reviewed our work with color blindness simulators to ensure information wasn’t lost in translation.&lt;/p&gt;

&lt;p&gt;Much of our landing page makes use of animations (which I’ll cover in more detail later in the article), but we also know that not everyone loves animations. I have friends with ADHD and other cognitive disabilities who find excessive animations extremely distracting — &lt;a href="https://www.tpgi.com/the-impact-of-motion-animation-on-cognitive-disability/"&gt;and these animations can even impair recall&lt;/a&gt;. Or, &lt;a href="https://alistapart.com/article/designing-safer-web-animation-for-motion-sensitivity/"&gt;consider those with vestibular disorders&lt;/a&gt; who can experience nausea, headaches, and even more problematic symptoms just because of animations, even after those animations have passed. I wouldn’t wish this on anyone, nor would I want to be the cause of such distressing episodes.&lt;/p&gt;

&lt;p&gt;Because of this, all of our landing page’s animations include fallbacks for the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion"&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt; media query&lt;/a&gt;. This means that users who have set a preference in their operating system to reduce the GUI’s use of motion won’t have to deal with animations they’ve already requested to opt out of. (In fact, the day after launching the landing page, &lt;a href="https://front-end.social/@matuzo/110193113099555811"&gt;Manuel Matuzović sent out a lovely toot about this aspect of the page, and a great discussion followed.&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Accessibility can also encompass typography. It’s &lt;a href="https://pubmed.ncbi.nlm.nih.gov/29753495/"&gt;been estimated&lt;/a&gt; that somewhere around 25% of the global population suffer from presbyopia (a type of vision deterioration making it hard to see things up close), with around half of those people unable to access corrective treatment. Many of us will begin to experience this condition ourselves by the time we reach middle age (my vision’s been far worse than that since adolescence), if not &lt;a href="https://my.clevelandclinic.org/health/articles/8567-common-age-related-eye-problems"&gt;other forms of retinal disorders&lt;/a&gt;. Even for those of us without a particular visual impairment, &lt;a href="https://jxnblk.com/blog/im-sick-of-your-tiny-tiny-type/"&gt;small typography can be annoying to say the least&lt;/a&gt;, but thankfully, &lt;a href="https://ia.net/topics/100e2r"&gt;good typography is often just a matter of following simple best practices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This, among other reasons, is why we make sure to use widely legible text sizes by default in our work (16px is a standard starting point), including on the Enhance landing page. We also ensured text was responsive to users’ text sizing preferences (for example: text should resize appropriately if the user zooms in, or if the user has set a default text size that differs from the browser default). There were just a few exceptions to this: several sections of &lt;a href="https://en.wikipedia.org/wiki/Display_typeface"&gt;display type&lt;/a&gt; on the page needed to be explicitly sized based on the viewport width (see an example below), but in these instances we also set minimum &lt;code&gt;rem&lt;/code&gt; based font sizes using &lt;a href="https://web.dev/min-max-clamp/"&gt;CSS &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;, and &lt;code&gt;clamp&lt;/code&gt; functions&lt;/a&gt;, or simply set the type large enough that it shouldn’t ever drop below a legible range (as in the example below, where one or two words are nearly full width even on a small device). That said, if you do spot an issue in legibility (or any other accessibility concern), feel free to &lt;a href="https://github.com/enhance-dev/enhance.dev/issues"&gt;file an issue&lt;/a&gt; or &lt;a href="https://enhance.dev/discord"&gt;let us know on Discord!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8VafNdg7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1wp7vjjv08ig556h9gix.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8VafNdg7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1wp7vjjv08ig556h9gix.jpg" alt='Illustration showing the words "Progressive by design" spanning the full width of our landing page, on both a mobile phone and a large desktop sized web browser' width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Speaking of typography, let’s talk about…&lt;/p&gt;

&lt;h2&gt;
  
  
  Fluid typography and layout
&lt;/h2&gt;

&lt;p&gt;One of the most interesting aspects of the design of our landing page is that it’s almost entirely a fluid, single column layout. After so many years of building with adaptive and responsive grid layouts by default, building out a single column layout that works as well on a small phone as it does on a 27” monitor ended up being an unexpectedly tough and nuanced challenge.&lt;/p&gt;

&lt;p&gt;The use of fluid typography and spacing ended up being a big part of the solution to this challenge. I’ve already touched on typography sized with viewport units in the previous section, but here I’m referring specifically to the kind of fluid type and layout techniques championed by the likes of &lt;a href="https://utopia.fyi"&gt;Utopia&lt;/a&gt;. You may already be familiar with the concept of using &lt;a href="https://www.modularscale.com/"&gt;modular scales&lt;/a&gt; for sizing typography and spacing units — and if you are, you know that one the most time consuming and potentially frustrating aspects of using these scales is having to iterate on them across multiple breakpoints. For example, you might want to set a first level heading element at a moderate font size on small devices, but then scale that font size larger on medium devices, and up to a maximum size on large devices. Or you may want to scale whitespace via padding in a similar fashion. Using functional CSS, this can look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'text2 text3-md text4-lg text5-xl'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- or… --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'padding4 padding5-md padding6-lg'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this is a tried and true technique that has worked reasonably well for many years now, it shares a problem with any other breakpoint-scoped style: that hard shift in layout at the boundaries of each breakpoint. In other words, a user working with a 1199px wide viewport could get an entirely distinct layout compared to a user on a 1200px wide viewport. Given the virtually infinite range of viewport sizes that exist in the wild, the decision of exactly when to make certain typographic and spatial changes within that spectrum quickly becomes arbitrary and awkward (something &lt;a href="https://utopia.fyi/blog/designing-with-fluid-type-scales"&gt;one of the coauthors of Utopia has written about in detail&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is where fluid typography and spacing shine. As designers and developers, we can stop fussing about a potentially infinite array of typographic and spatial variations, and instead focus almost entirely on the extremes: ‘What size should this type be at a minimum, and what size should it be at a maximum?’ Or, ‘How far away should element A be from element B at a minimum, and how far away should it be at a maximum?’&lt;/p&gt;

&lt;p&gt;In the case of the Enhance landing page, using fluid type and spacing helped to reduce typographic and layout problems to a much more reasonable size. All of the typography on the landing page (except for a few aforementioned cases) is sized using output from Utopia, as are most of the margins and paddings that help situate each element on the page in relation to others. This allowed us to start with a relatively compact layout on small screens, and have that layout scale up fluidly to much larger screens without having to use any breakpoint-scoped styles (except in a few small sublayouts where we wanted to move from a single to double column format when space was available). Given the complexity of the layout (if it’s not obvious, this page has a LOT of layers and assets at play!), this fluid approach saved us both a ton of time and a fair bit of code in bringing the page to life. In fact, we had so much success using fluid type and spacing that we’ve already started investigating the best possible way to bring this methodology into &lt;a href="https://enhance.dev/docs/learn/concepts/styling/"&gt;Enhance Styles&lt;/a&gt;. (You can follow &lt;a href="https://github.com/enhance-dev/enhance-styles/issues/14"&gt;this issue if you’re interested!&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--51VT5ojN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vq4ne5h5vbqbisna8oud.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--51VT5ojN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vq4ne5h5vbqbisna8oud.jpg" alt="Illustration showing a compact layout and font size on a mobile phone, and a spacious layout with a larger font size on a desktop browser." width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SVGs: not just for illustrations!
&lt;/h2&gt;

&lt;p&gt;The last trick we pulled out of our sleeves in terms of typography on the landing page was using type rendered with SVG. Despite being pretty nerdy about typography, I admittedly was recently surprised to find out that text can indeed be rendered accessible to both sighted users and those using screen readers with the use of SVG graphics. This technique proved useful specifically for a couple of instances where we need to scale type based on the width of another element — for example, in the section shown below where we wanted to scale the words ‘web components’ symmetrically with the ‘SSR’ cloud typography, which wouldn’t have been possible to do via &lt;code&gt;font-size&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--63I_Gs3q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3v4psh5uz0dbeo28g4od.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--63I_Gs3q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3v4psh5uz0dbeo28g4od.jpg" alt='Illustration showing the text "SSR web components". The "SSR" text is rendered with typography that looks like clouds.' width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, the ‘web components’ text is rendered with SVG’s &lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt; element. Because this renders actual type (rather than type outlined as vector graphics), it remains accessible to both screen readers and document parsers, and we can do things like typeset it with CSS — for example: apply our web font, set it in uppercase, use the extra bold font weight, etc. In fact, if you use an HTML page outliner tool on the landing page, you’ll notice that the ‘SSR Web Components’ graphic gets parsed just the same as any other &lt;code&gt;h2&lt;/code&gt; element would, thanks both to the SVG &lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt; element and the inclusion of a visually hidden (but still accessible) span containing the term ‘SSR’. Check out the reduced and commented code reproduction below to see this in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"font-extrabold uppercase m-auto"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Custom element for the 'SSR' cloud type: --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;landing-ssr-type&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-full mb0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/landing-ssr-type&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Invisible but accessible text for 'SSR': --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"clip"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;SSR&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- 'Web components' SVG text: --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt;
    &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 969 75"&lt;/span&gt;
    &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"webComponentsType"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"#E8F8FF"&lt;/span&gt; &lt;span class="na"&gt;font-family=&lt;/span&gt;&lt;span class="s"&gt;"Rubik"&lt;/span&gt; &lt;span class="na"&gt;font-size=&lt;/span&gt;&lt;span class="s"&gt;"101.48"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"72.7569"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Web Components&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, a few custom pieces of typography aren’t the only SVGs you’ll see on our landing page. SVG is a fantastic format to work with for graphics as long as you’re not rendering photographs or highly complex three dimensional imagery. Since it’s a graphics format built from &lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG"&gt;standards based XML&lt;/a&gt;, it’s both parsable and able to be styled by the browser. This also means that SVG content can be used in custom elements and web components, a strategy I used liberally for our landing page. Finally, being vector based, SVGs can also be resized infinitely without a loss in quality, while compressing to far smaller file sizes than bitmap formats like JPG and PNG.&lt;/p&gt;

&lt;p&gt;Over 40 other SVG assets are used to create the lush world of Axol and their friends, but thanks to the aforementioned compression, this all amounts to just 104kb over the wire (which is itself about a quarter of the total kilobytes transferred for the whole page). Compared to what our image payload would’ve been if we’d used PNG or JPG images, this is a massive savings in both page size and loading speed, which both we and our users appreciate. Furthermore, because our landing page is so incredibly tall, the lion’s share of these images initially appear offscreen; as such, we decided to load those images asynchronously as they approach the visible viewport by using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes"&gt;the &lt;code&gt;loading='lazy'&lt;/code&gt; attribute&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two examples of SVGs worth digging into are the code blocks we show on the page — one demonstrating Enhance’s project structure, and another showing an example of writing a client side update to a web component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k6KSG6Rq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ffbxz5k2v8k1yuswsq6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6KSG6Rq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ffbxz5k2v8k1yuswsq6.jpg" alt="Illustration showing two code editor windows, each floating in a purple cloud." width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These two SVGs were actually fairly tough to get right, for a somewhat frustrating reason: almost no modern graphics program seems to emit anything close to clean SVG code when multiline text is involved — and because we want to style the text in these images and have it exposed to technologies like screen readers, simply tossing a bunch of SVG markup into an external image request was simply not possible. For example, when opting not to convert text to outlines, Figma’s export renderer for some reason chooses to slice and dice individual characters and word fragments seemingly at random, spitting out a ton of unnecessary &lt;code&gt;tspan&lt;/code&gt; elements that would frankly never be possible to work with in a code editor. Many other SVG editors I tried found other ways to mutate my text in baffling ways. Here’s an excerpt from Figma’s SVG output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt;
    &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 970 570"&lt;/span&gt;
    &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt;
    &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- skipping ahead to the code block… --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
        &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"#74F1FF"&lt;/span&gt;
        &lt;span class="na"&gt;xml:space=&lt;/span&gt;&lt;span class="s"&gt;"preserve"&lt;/span&gt;
        &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"white-space: pre"&lt;/span&gt;
        &lt;span class="na"&gt;font-family=&lt;/span&gt;&lt;span class="s"&gt;"Roboto Mono"&lt;/span&gt;
        &lt;span class="na"&gt;font-size=&lt;/span&gt;&lt;span class="s"&gt;"15"&lt;/span&gt;
        &lt;span class="na"&gt;letter-spacing=&lt;/span&gt;&lt;span class="s"&gt;"0em"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"451.63"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"277.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;C&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
        &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"white"&lt;/span&gt;
        &lt;span class="na"&gt;xml:space=&lt;/span&gt;&lt;span class="s"&gt;"preserve"&lt;/span&gt;
        &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"white-space: pre"&lt;/span&gt;
        &lt;span class="na"&gt;font-family=&lt;/span&gt;&lt;span class="s"&gt;"Roboto Mono"&lt;/span&gt;
        &lt;span class="na"&gt;font-size=&lt;/span&gt;&lt;span class="s"&gt;"15"&lt;/span&gt;
        &lt;span class="na"&gt;letter-spacing=&lt;/span&gt;&lt;span class="s"&gt;"0em"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"433.612"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"109.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;paragraph&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"595.771"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"277.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;attr&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"649.823"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"277.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;o&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"739.911"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"277.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;n&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"424.604"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"301.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;o&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"541.718"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"301.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;n&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"442.621"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"325.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;attr&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"469.647"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"349.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;paragraph&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"559.735"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"349.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;t&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"685.858"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"349.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;n&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt;
        &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"#00F500"&lt;/span&gt;
        &lt;span class="na"&gt;xml:space=&lt;/span&gt;&lt;span class="s"&gt;"preserve"&lt;/span&gt;
        &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"white-space: pre"&lt;/span&gt;
        &lt;span class="na"&gt;font-family=&lt;/span&gt;&lt;span class="s"&gt;"Roboto Mono"&lt;/span&gt;
        &lt;span class="na"&gt;font-size=&lt;/span&gt;&lt;span class="s"&gt;"15"&lt;/span&gt;
        &lt;span class="na"&gt;letter-spacing=&lt;/span&gt;&lt;span class="s"&gt;"0em"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"397.577"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"85.8264"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;uper&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"397.577"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"109.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;his&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"550.727"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"109.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;his&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"712.885"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"109.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;#39;&lt;/span&gt;p&lt;span class="ni"&gt;&amp;amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"469.647"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"205.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;#39;&lt;/span&gt;message&lt;span class="ni"&gt;&amp;amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"523.7"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"325.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;#39;&lt;/span&gt;message&lt;span class="ni"&gt;&amp;amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tspan&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"433.612"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"349.826"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;his&lt;span class="nt"&gt;&amp;lt;/tspan&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- etc --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;small&gt;I’m not sure what Figma was drinking when it generated this output, but I’m worried for its health.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Along the way, I discovered that an app I sometimes use for bitmap editing — &lt;a href="https://www.pixelmator.com/pro/"&gt;Pixelmator&lt;/a&gt; — also works with SVGs, and its multiline text output is actually quite respectable (though it didn’t seem to allow me to apply multiple text colors to a single text block). Thus, the solution I finally landed on was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take the exports from our Figma design files&lt;/li&gt;
&lt;li&gt;Throw them into Pixelmator&lt;/li&gt;
&lt;li&gt;Replace Figma’s text output by pasting the raw text back into Pixelmator&lt;/li&gt;
&lt;li&gt;Output these results from Pixelmator&lt;/li&gt;
&lt;li&gt;Finally, fine tune the results by hand in code — for example, by inserting my own &lt;code&gt;tspan&lt;/code&gt; elements to apply the appropriate color fills as needed for syntax highlighting&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To vastly understate my feelings on this process, it was not ideal. However, this did end up saving us around 300kb of code as compared to using the raw SVG output with outlined text, in addition to allowing us to style the text and have it exposed to assistive technologies. (Relatedly, if anyone out there is working on an SVG editing tool: if you manage to come up with an export format that makes multiline text in SVGs easier to work with as code, I have a hug waiting for you.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Animations: bringing Axol to life
&lt;/h2&gt;

&lt;p&gt;Animation was a key consideration for the Enhance landing page from the beginning of the project, even before I came onboard. Earlier in this article, I mentioned how many of us Beginners look back fondly on the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/marquee"&gt;&lt;code&gt;marquee&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blink"&gt;&lt;code&gt;blink&lt;/code&gt;&lt;/a&gt; tags of years past; luckily it was pretty easy to bring these sorts of animations back to life with CSS! &lt;a href="https://ryanmulligan.dev/blog/css-marquee/"&gt;An article from Ryan Mulligan&lt;/a&gt; provided a great example of how to build a modern, responsive marquee with CSS; meanwhile, memories of the &lt;code&gt;blink&lt;/code&gt; tag were revived via several of our Axols’ blinking eyes.&lt;/p&gt;

&lt;p&gt;Other animations fell into place pretty easily but took some fine tuning to perfect. The Axols on swings underneath our ‘HTML first’ marquee, for example, are animated simply via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/rotate"&gt;the &lt;code&gt;rotate&lt;/code&gt; property&lt;/a&gt;, but the precise timing of each (as well landing on an appropriate &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function"&gt;timing function&lt;/a&gt;) took a few iterations to get just right. I really love how these two Axols drift in and out of sync with each other as they swing lazily back and forth. (Sometimes I feel like I’m starting to get hypnotized by them if I stare at them too long… thankfully I don’t think Axols are prone to too much mischief.) If you’re curious, &lt;a href="https://github.com/enhance-dev/enhance.dev/blob/main/app/elements/landing/axol/swing-blue.mjs"&gt;here’s the code for the blue swinging Axol&lt;/a&gt; — the only difference with the pink Axol is a very slightly longer animation duration. Small input, big output!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ou6fci0W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmnfbeqkyu0n6dir5cak.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ou6fci0W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmnfbeqkyu0n6dir5cak.jpg" alt="Illustration showing two of our Axol mascots happily swinging from some rope swings descending from some purple clouds." width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another animation that ended up giving back so much more energy than was put into is the Axol at the bottom of the landing page who intermittently offers their flower to the user. I’m proud to say I came up with this concept on my own, but I can’t take too much credit — this animation is also just adjusting the &lt;code&gt;rotate&lt;/code&gt; property on several SVG groups: the front arm, back arm, and flower. &lt;a href="https://github.com/enhance-dev/enhance.dev/blob/main/app/elements/landing/axol/gills-down.mjs"&gt;Here’s the code if you’d like to take a peek&lt;/a&gt; — the SVG content is a little verbose, but you’ll get the idea I’m sure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f4pHAhrk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8qr8a25y73dlrlywm4dx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f4pHAhrk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8qr8a25y73dlrlywm4dx.jpg" alt="Illustration of Axol holding up a flower, as if they’re offering it to you as a present." width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;None of these animations were terribly hard to implement, but there were definitely others than took quite a lot of corralling to get right. A prime example here is the Axol that flies across the screen, trailing a ‘No JS required’ banner behind them.&lt;/p&gt;

&lt;h3&gt;
  
  
  No JS (though some visual trickery) required
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T6AGy8B_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4aazds2djilt2q8sm3q6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T6AGy8B_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4aazds2djilt2q8sm3q6.jpg" alt='Illustration of Axol flying out from a heart shaped cloud, trailing a banner that reads "No JS required".' width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The initial challenge with this animation was the layering: Axol and their banner needed to appear to come &lt;em&gt;through&lt;/em&gt; the heart cloud, which meant one half of the cloud needed to be stacked behind Axol and the banner, with the other half in front. Then I went and made things harder for myself by deciding the heart cloud also needed to gently pulsate like a real heart.&lt;/p&gt;

&lt;p&gt;To put this all together, I started by making &lt;a href="https://github.com/enhance-dev/enhance.dev/blob/main/app/elements/landing/axol/no-js-required.mjs"&gt;a single component for Axol and the ‘No JS required’ banner&lt;/a&gt;. Not much magic here — an image, a bit of styled text, and some relative offsets along with flexbox to line everything up. Then it was time for some open heart surgery: I sliced up the heart cloud into two halves, so that I could stack one half on top, Axol and their banner in the middle, and the other half of the heart cloud below.&lt;/p&gt;

&lt;p&gt;This didn’t quite work out at first. Due to the fluid size of these images and the way antialiasing works, the heart cloud sometimes rendered with a tiny subpixel gap of space between its two halves. My friend &lt;a href="https://burdocks.ca/"&gt;Robyn&lt;/a&gt;, an amazing designer and artist herself, had some great advice for me here: all I needed to do was give each half of the graphic an 1px overlap, and then collapse that overlap when stacking the images up. That way, whenever that subpixel gap appears, it’s effectively canceled out by the 1px overlap that’s revealed underneath. Because these images are sized fluidly, I ended up having to compute the overlap in percentages based on the images’ intrinsic widths, but the result worked perfectly. &lt;a href="https://github.com/enhance-dev/enhance.dev/blob/main/app/elements/landing/no-js-required.mjs#L110-L152"&gt;You can take a look at the code for this here, if you’d like.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final step in this animation was to get Axol to actually fly out with the banner in tow. This was solved with a transition of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/translate"&gt;the &lt;code&gt;translate&lt;/code&gt; property&lt;/a&gt;, triggered with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API"&gt;Intersection Observer API&lt;/a&gt;. In short: once the heart cloud is detected to be at least 33% visible in the viewport, a JavaScript handler modifies the &lt;code&gt;translate&lt;/code&gt; property on Axol and the banner. Because the &lt;code&gt;translate&lt;/code&gt; property is declared to be transitional (via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition"&gt;the &lt;code&gt;transition&lt;/code&gt; property&lt;/a&gt;), we end up seeing Axol fly across the screen with the banner in tow. &lt;a href="https://github.com/enhance-dev/enhance.dev/blob/main/app/elements/landing/no-js-required.mjs#L173-L196"&gt;Take a look at the CSS behind this&lt;/a&gt; if you like!&lt;/p&gt;

&lt;p&gt;Because these animations are triggered by JavaScript, however, I also needed to account for what a user would see if JavaScript failed to load or was blocked. Consequently, Axol and the banner are first rendered on the server at the static, completed stage of the animation. Then, when JavaScript first kicks in on the client, &lt;a href="https://github.com/enhance-dev/enhance.dev/blob/main/app/elements/landing/no-js-required.mjs#L236"&gt;I reposition Axol and the banner&lt;/a&gt; to the starting position required for the animation. After this, &lt;a href="https://github.com/enhance-dev/enhance.dev/blob/main/app/elements/landing/no-js-required.mjs#L238-L249"&gt;the IntersectionObserver handler&lt;/a&gt; takes care of the rest. I also needed to make sure this animation isn’t run for users with the reduced motion preference, so this JavaScript is kept behind &lt;a href="https://github.com/enhance-dev/enhance.dev/blob/main/app/elements/landing/no-js-required.mjs#L221-L226"&gt;a guard which first checks for this preference&lt;/a&gt; using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia"&gt;matchMedia method&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This same approach was used to animate a few other Axols on our landing page, including the Axol that slides down the large rainbow and subsequently pops out of a cloud beneath. This animation used some slightly different techniques — in this case, swapping out classes rather than modifying custom property values directly — but the principle remains the same. I also used an SVG to define Axol’s path down the rainbow via the &lt;code&gt;offset-path&lt;/code&gt; property; CSS Tricks has &lt;a href="https://css-tricks.com/almanac/properties/o/offset-path/"&gt;a great summary of this technique&lt;/a&gt; if you’d like to dig further into it, or you can take a look at &lt;a href="https://github.com/enhance-dev/enhance.dev/blob/main/app/elements/landing/axol/rainbow-buttslide.mjs#L33-L49"&gt;the CSS&lt;/a&gt; and &lt;a href="https://github.com/enhance-dev/enhance.dev/blob/main/app/elements/landing/axol/rainbow-buttslide.mjs#L57-L96"&gt;the JavaScript&lt;/a&gt; behind this particular animation if you’d like!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zMMNRbiz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s94940xdwx8vpx1qarle.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zMMNRbiz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s94940xdwx8vpx1qarle.jpg" alt="Illustration of Axol getting ready to slide down a big, vibrant rainbow." width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready for liftoff
&lt;/h2&gt;

&lt;p&gt;It’s been a blast putting our new landing page together, and putting Enhance itself to use to produce such a dynamic, intricate, yet effective and performant web page (feel free to check out &lt;a href="https://pagespeed.web.dev/analysis/https-enhance-dev/wrgh9mu2ui?form_factor=mobile"&gt;our scores on Page Speed Insights&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It’s interesting to consider that this entire page could have reasonably been built without Enhance — after all, it’s mostly just static HTML, CSS, and a sprinkling of vanilla JavaScript (which, unlike a lot of server rendered output out there these days, is perfectly readable by humans when viewing the page source in your browser — &lt;a&gt;try it out&lt;/a&gt;)!&lt;/p&gt;

&lt;p&gt;What Enhance added to the experience of working with these web standards really came down to making my authoring experience magical. &lt;a href="https://enhance.dev/docs/learn/concepts/single-file-components"&gt;Single File Components&lt;/a&gt; allowed me to colocate related markup, styles, and scripts in a compartmentalized and straightforward way (which is even surfaced in the custom element names when viewing the page source); &lt;a href="https://enhance.dev/docs/learn/concepts/styling/"&gt;Enhance Styles&lt;/a&gt; gave me a configurable utility class system as well as component scoped styles, both of which made building out typography, layout, and animations a breeze; &lt;a href="https://enhance.dev/docs/learn/concepts/routing/api-routes"&gt;API routes&lt;/a&gt; made it a cinch to implement our email signup form (using an actual HTML form and HTTP requests — no form library or third party data management layer needed here); and of course, &lt;a href="https://enhance.dev/docs/learn/deployment/begin"&gt;deploying with Begin&lt;/a&gt; gave us server side rendering, deterministic builds, and super fast response times, all within independently scalable environments.&lt;/p&gt;

&lt;p&gt;At the end of the day, while a lot of skilled folks spent a lot of time making this landing page a reality, it’s thrilling for me to know that — technically speaking — anyone with an intermediate level of experience with HTML, CSS, and a tiny bit of JavaScript could reasonably build out a landing page of this caliber using Enhance themselves, all with an extremely minimal lead time to learn the framework’s ropes. I can’t wait to see the incredible work that folks in the wild will put together with Enhance, and I hope that our new landing page inspires you to dive into a project of your own soon!&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>html</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Customizing the Enhance Blog Template</title>
      <dc:creator>Cole Peters</dc:creator>
      <pubDate>Mon, 10 Apr 2023 20:02:27 +0000</pubDate>
      <link>https://dev.to/begin/customizing-the-enhance-blog-template-od</link>
      <guid>https://dev.to/begin/customizing-the-enhance-blog-template-od</guid>
      <description>&lt;p&gt;This past New Year’s Eve, Monique Jones published &lt;a href="https://www.theverge.com/23513418/bring-back-personal-blogging"&gt;an article on The Verge&lt;/a&gt; that could not have been more timely. Amidst the growing dumpster fire at Twitter and the more general sense that social media was no longer the fun, personal, and engaging realm it once was, Jones’ call to bring back personal blogging was both important and on point:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If what is happening on Twitter hasn’t demonstrated it, our relationship with these social media platforms is tenuous at best. The thing we are using to build our popularity today could very well be destroyed and disappear from the internet tomorrow, and then what? […] Owning your content and controlling your platform is essential, and having a personal blog is a great way to do that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think the future of blogging is bright for a number of reasons, not least of which is this ability to maintain ownership of your content. Another great reason to get back to blogging, however, is the option of structuring and presenting that content in a way that reflects you as an author. I personally recall the mid to late aughts as a beautiful time for blogging. Not only did I gain my first insights into the world of web development, but I was also exposed to a wide range of idiosyncratic senses of design, from the most pared down and functional (anyone remember &lt;a href="https://ia.net"&gt;iA&lt;/a&gt;’s WordPress theme?) to the whimsical and the ornate.&lt;/p&gt;

&lt;p&gt;With all of these thoughts in mind, we recently debuted our first application template for Enhance — &lt;a href="https://begin.com/blog/posts/2023-03-17-introducing-the-enhance-blog-template"&gt;the Enhance blog template&lt;/a&gt;! This template gives you everything you need to start writing and publishing your very own blog with Enhance. It comes with Markdown to HTML conversion built in, along with an RSS feed, code block syntax highlighting, a basic design with light and dark variants, and GitHub workflows to automate publishing. And, of course, you maintain ownership of your content — which means you can also style that content however you like.&lt;/p&gt;

&lt;p&gt;The design of our blog template is intentionally minimal while following some best practices for readability, so that you can get to publishing without having to change much if you don’t want to. In this article, however, I’m going to walk you through some steps I might take if I was designing a new blog with this template as a starting point.&lt;/p&gt;

&lt;p&gt;I’ll start with some basic changes to give you a feel of how things work, and then dive into some more substantial changes to demonstrate just how much freedom you have to craft your own look and feel for your blog. I’m a big fan of &lt;a href="https://www.the-mva.com/frst-book"&gt;brutalist graphic design&lt;/a&gt;, so I’ll be working with this style in mind, but feel free to make your own design decisions as you follow along! (Or, if you’d prefer to skip the end, you can find all the code from this article &lt;a href="https://github.com/enhance-dev/enhance-example-blog-styled"&gt;in this example repo&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The basics: color and typography
&lt;/h2&gt;

&lt;p&gt;For reference, here’s the design of our blog template as it appears out of the box:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nLZpiae---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0iwpjgu5p20ugvdb501.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nLZpiae---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0iwpjgu5p20ugvdb501.jpg" alt="Image description" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default, the blog template uses just two colors: one light, and one dark. In the light theme, the light color maps to the blog’s background, while the dark color maps to the typography; in the dark theme, this mapping is reversed. You can pick whatever values you’d like for these two colors as a first step towards making your blog your own.&lt;/p&gt;

&lt;p&gt;To do this, open the &lt;code&gt;styleguide.json&lt;/code&gt; file at the root of the project. You’ll see a configuration like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"base"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"minorThird"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"steps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"font-body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Avenir, 'Avenir Next LT Pro', Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"font-heading"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Didot, 'Bodoni MT', 'Noto Serif Display', 'URW Palladio L', P052, Sylfaen, serif;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"color-dark"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hsl(204deg 30% 30%)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"color-light"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hsl(260deg 30% 94%)"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"queries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"48em"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The properties we’re looking for here are &lt;code&gt;color-light&lt;/code&gt; and &lt;code&gt;color-dark&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To start crafting a brutalist look for this blog, I’m going to choose some more ‘severe’ swatches for these two colors, while staying away from pure black and white in order to add some depth. I’m also going to make sure my choice of colors have &lt;a href="https://wise.design/design-at-wise/stories/accessible-but-never-boring"&gt;sufficient contrast between them&lt;/a&gt;, to ensure my blog remains legible for as many users as possible.&lt;/p&gt;

&lt;p&gt;For my dark swatch, I’ve chosen a very dark and muted violet; for my light swatch, a slightly warm and muted off white. When used together, these two colors produce a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Perceivable/Color_contrast"&gt;WCAG contrast ratio&lt;/a&gt; of 10.8, which takes us into the AAA or ‘enhanced’ rating — in other words, more than enough contrast to keep text legible. Aesthetically, the subtle contrast between the two muted hues (violet and orange) will add some depth and personality to the design. Here’s the design after changing these colors:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qlWpKGq4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfr83ubzreijqr2tc1u4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qlWpKGq4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfr83ubzreijqr2tc1u4.jpg" alt="Image description" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s update the typography. Brutalist graphic design traditionally makes use of sans serif typography — or more specifically, &lt;a href="https://www.pixartprinting.co.uk/blog/brief-history-grotesque/"&gt;grotesque typefaces&lt;/a&gt;. Further along in this article, we’ll look at implementing web fonts, but for now, we’ll stick to &lt;a href="https://fonts.google.com/knowledge/glossary/system_font_web_safe_font"&gt;system fonts&lt;/a&gt;. Thanks to the useful website &lt;a href="https://modernfontstacks.com/"&gt;Modern Font Stacks&lt;/a&gt;, I’ve got a Neo Grotesque themed system font stack ready to go, so I’ll update both my &lt;code&gt;font-body&lt;/code&gt; and &lt;code&gt;font-heading&lt;/code&gt; settings with this stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"font-body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"font-heading"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add to the utilitarian look, I’m also going to change the alignment of the site header and the individual article headers. To do this, I’ll first remove the instances of the &lt;code&gt;text-center&lt;/code&gt; class in the file &lt;code&gt;app/elements/site-header.mjs&lt;/code&gt;. Then, to change the alignment of article headers, I’ll remove the same class from the file &lt;code&gt;app/pages/posts/$$.mjs&lt;/code&gt;, specifically from the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; elements which render the article title and publication date respectively.&lt;/p&gt;

&lt;p&gt;Because we’re using system fonts, the results of this change may vary depending on your operating system, but here’s how the template (specifically the post page) looks now on my machine:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1NdT0o1A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e5ts9ri14142oehptj3m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1NdT0o1A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e5ts9ri14142oehptj3m.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, I’m going to change the code syntax theme to be a little less vibrant, so it will better match the overall look of the blog. I’ll grab a different theme from &lt;a href="https://unpkg.com/browse/@highlightjs/cdn-assets@11.4.0/styles/"&gt;HighlightJS’s themes collection on Unpkg&lt;/a&gt;, and then drop the raw CSS into the &lt;code&gt;public/css&lt;/code&gt; directory in my project to save an external network request. Lastly, I’ll update the link to this stylesheet in the &lt;code&gt;app/head.mjs&lt;/code&gt; file. That gives us:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uuS8e1bY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ktv9w5mb5peu8f1k3x1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uuS8e1bY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ktv9w5mb5peu8f1k3x1.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next section, we’ll start making a few more substantial changes. If you’d like to review all the code changes I’ve made in this first section, you can view them all in &lt;a href="https://github.com/enhance-dev/enhance-example-blog-styled/commit/699a837a46ae28d58b15b77f81f94a3f8d4f7f6b"&gt;this single commit in the example repo on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using web fonts
&lt;/h2&gt;

&lt;p&gt;It’s time to add a bit more personality to this design. As Oliver Reichenstein famously wrote, &lt;a href="https://ia.net/topics/the-web-is-all-about-typography-period"&gt;web design is 95% typography&lt;/a&gt;, so I’m going to start with that.&lt;/p&gt;

&lt;p&gt;The first thing I’ll do is choose a typeface that will complement the design language I’m going for — which I’ve decided is the excellent (and open source) &lt;a href="https://fonts.google.com/specimen/Work+Sans"&gt;Work Sans&lt;/a&gt;. A bonus here is that Work Sans is available as a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide"&gt;variable web font&lt;/a&gt;, so I’ll only need two web font files (one regular, one italic) to be able to work with all the available styles of Work Sans.&lt;/p&gt;

&lt;p&gt;After downloading the font files from &lt;a href="https://fonts.google.com/"&gt;Google Fonts&lt;/a&gt;, I’ll drop the two variable web font files into my &lt;code&gt;public&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public/
├── css/
└── fonts/
         ├── WorkSans.woff2
         └── WorkSansItalic.woff2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, to make these fonts available to the browser, I’ll add some CSS to the &lt;code&gt;public/css/global.css&lt;/code&gt; stylesheet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@font-face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Work Sans'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('/_public/fonts/WorkSans.woff2')&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;'woff2-variations'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="m"&gt;900&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@font-face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Work Sans'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('/_public/fonts/WorkSansItalic.woff2')&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;'woff2-variations'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="m"&gt;900&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Note we use the &lt;code&gt;/_public&lt;/code&gt; route in the URL to take advantage of &lt;a href="https://enhance.dev/docs/learn/starter-project/public"&gt;automatic fingerprinting&lt;/a&gt; of the files.)&lt;/p&gt;

&lt;p&gt;Next, I’ll add the typeface to my font stacks in the styleguide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"font-body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"'Work Sans', Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"font-heading"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"'Work Sans', Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! My blog is now typeset in Work Sans:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ibf7p8bY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/voq8n26xuyqyusd21n9l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ibf7p8bY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/voq8n26xuyqyusd21n9l.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a summary of these changes, &lt;a href="https://github.com/enhance-dev/enhance-example-blog-styled/commit/5a16b2e510511b7494123c991319fe3be1728c18"&gt;check out this commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating the site header
&lt;/h2&gt;

&lt;p&gt;Next, I’ll update the site header to give the top of each page a little more character. I’ve decided I’d like the site header to have its own full width background color.&lt;/p&gt;

&lt;p&gt;To get started with this, I’ll open the file located at &lt;code&gt;/app/elements/site-layout.mjs&lt;/code&gt;, where the general page layout template lives. By default, the site header is nested within the &lt;code&gt;site-container&lt;/code&gt; element to keep all the pages’ contents horizontally aligned, but to give the site header its own full width background, I’ll need to pull the header out of that container, and place the header’s &lt;em&gt;contents&lt;/em&gt; back into a site container of its own.&lt;/p&gt;

&lt;p&gt;With these changes, the &lt;code&gt;site-layout.mjs&lt;/code&gt; file will now look like this (note &lt;code&gt;site-header&lt;/code&gt; is now adjacent to &lt;code&gt;site-container&lt;/code&gt;, whereas it was nested within it before):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;SiteLayout&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;site-header&amp;gt;&amp;lt;/site-header&amp;gt;
    &amp;lt;site-container class='pl0 pr0 pl4-lg pr4-lg'&amp;gt;
        &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
      &amp;lt;site-footer&amp;gt;&amp;lt;/site-footer&amp;gt;
    &amp;lt;/site-container&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within the &lt;code&gt;/app/elements/site-header.mjs&lt;/code&gt; file, I’ll now add a &lt;code&gt;site-container&lt;/code&gt; element, making sure to keep the same horizontal padding classes from the instance in &lt;code&gt;site-layout.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;SiteHeader&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;header&amp;gt;
      &amp;lt;site-container class='pl0 pr0 pl4-lg pr4-lg'&amp;gt;
        &amp;lt;h1 class='text2 text4-lg font-heading font-bold tracking-1 pt4 pt6-lg pb0 pb2-lg'&amp;gt;
          &amp;lt;a href='/' class='no-underline'&amp;gt;
            Enhance Blog Template
          &amp;lt;/a&amp;gt;
        &amp;lt;/h1&amp;gt;
        &amp;lt;p class='font-body pb4 pb6-lg'&amp;gt;
          A subtitle for this blog
        &amp;lt;/p&amp;gt;
      &amp;lt;/site-container&amp;gt;
    &amp;lt;/header&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, I’ll add some &lt;a href="https://enhance.dev/docs/learn/concepts/styling/element-styles"&gt;scoped element styles&lt;/a&gt; to this component to change its background color. Scoped element styles mean that any styles I write within this component will only be applied to this component (and its descendants) — which means, for example, that I can use bare element selectors without fear of other elements with the same selector changing outside of this component.&lt;/p&gt;

&lt;p&gt;To demonstrate, I’ll use the &lt;code&gt;header&lt;/code&gt; element selector and change its background color:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;SiteHeader&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;style&amp;gt;
      header {
        background-color: hsla(0deg 0% 0% / 10%);
      }
    &amp;lt;/style&amp;gt;
    &amp;lt;header&amp;gt;
      …
    &amp;lt;/header&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The site header now has its own background color — a slightly darker shade of the main background color. If you inspect the &lt;code&gt;header&lt;/code&gt; element in your dev tools, you’ll notice that the element selector has been prepended with the name of the component it’s been declared in, making the full selector &lt;code&gt;site-header header&lt;/code&gt;. This prevents those header styles from leaking outside of this component (although it’s not very common to have more than one &lt;code&gt;header&lt;/code&gt; on a given page, but the point stands).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nim9hpcU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/27daknq01rd49mvr3le6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nim9hpcU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/27daknq01rd49mvr3le6.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This looks great for the light theme, but I’ll need to write some styles for the blog’s dark theme as well. To do that, I’ll use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme"&gt;the &lt;code&gt;prefers-color-scheme&lt;/code&gt; media query&lt;/a&gt; and switch the background from a transparent black to a transparent white:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;SiteHeader&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;style&amp;gt;
      header {
        background-color: hsla(0deg 0% 0% / 10%);
      }

      @media (prefers-color-scheme: dark) {
        header {
            background-color: hsla(0deg 0% 100% / 10%);
        }
      }
    &amp;lt;/style&amp;gt;
    &amp;lt;header&amp;gt;
      …
    &amp;lt;/header&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I’ve got the header covered for the dark theme as well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sLWvYGwp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rngveyo87b6cpbje5m94.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sLWvYGwp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rngveyo87b6cpbje5m94.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a summary of these changes, &lt;a href="https://github.com/enhance-dev/enhance-example-blog-styled/commit/109653be254a28dc47774f1e46e76799175137cc"&gt;see the associated commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating table styles
&lt;/h2&gt;

&lt;p&gt;The last thing I’ll do is use &lt;a href="https://enhance.dev/docs/learn/concepts/styling/utility-classes"&gt;Enhance’s built in utility class system&lt;/a&gt; to update the way tables are rendered.&lt;/p&gt;

&lt;p&gt;Included in our blog template, you’ll find a post titled Element Styling Reference. This post contains some of the most commonly used HTML elements for blog posts, to give you an overview of the elements you might want to consider writing your own styles for. You’ll find an example of a table near the bottom of that post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qpZmQT4---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pnt5q253hurx7cwdfd6a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qpZmQT4---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pnt5q253hurx7cwdfd6a.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This table’s a little cramped for my taste, so I’m going to start by adding some padding to its cells.&lt;/p&gt;

&lt;p&gt;In this instance, because the blog’s posts are written in Markdown, and Markdown features &lt;a href="https://www.markdownguide.org/extended-syntax/#tables"&gt;special syntax for tables&lt;/a&gt;, I can take advantage of the included &lt;a href="https://github.com/architect/arcdown"&gt;Arcdown plugin&lt;/a&gt; to attach my choice of classes to various HTML elements automatically during the conversion from Markdown to HTML.&lt;/p&gt;

&lt;p&gt;To do this, I’ll open the file &lt;code&gt;/app/lib/markdown-class-mappings.mjs&lt;/code&gt;. This file exports a JavaScript object where each key represents an HTML element’s name; the value of each key is an array of strings, with each string containing a valid class name from our utility system. You’ll see that many of the common elements already have classes attached to them by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text4-lg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;font-heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tracking-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;font-bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mb0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mt4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;leading1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text2-lg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;font-heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tracking-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;font-bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mb0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mt4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;leading1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;h4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text1-lg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;font-heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tracking-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;font-bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mb0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mt4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;leading1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mt-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mb0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add some extra padding to my table cells, I’ll use both the &lt;code&gt;th&lt;/code&gt; (table header cell) and &lt;code&gt;td&lt;/code&gt; (table data cell) tags, attaching the &lt;code&gt;p-2&lt;/code&gt; class (short for ‘padding, -2 &lt;a href="https://github.com/enhance-dev/enhance-styles#scale"&gt;scale index&lt;/a&gt;’).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;…&lt;/span&gt;
  &lt;span class="na"&gt;th&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;td&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p-2],
  …
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now my table’s cells will be nicely padded out, giving some breathing room between each entry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O4kegCAf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uyki02wzhmu8pmrjh2jc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O4kegCAf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uyki02wzhmu8pmrjh2jc.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last thing I’ll do for this table is apply a background color to the entire element, to visually separate it from its surrounding content. I’ve decided I’d like to use the same semitransparent background colors I used on the site header, so the first thing I’ll do is redefine those colors as CSS custom properties. This will allow me to easily reuse these colors without worrying about making a typo (and, if I want to change these colors down the road, I’ll only have to update the custom property definition).&lt;/p&gt;

&lt;p&gt;I’ll define this custom property in the &lt;code&gt;global.css&lt;/code&gt; file we used earlier. A good convention is to write your custom properties at the very top of your stylesheet, which is what I’ll do now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--white-a10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsla&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0deg&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;10%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--black-a10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsla&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0deg&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;10%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ll then go back and update my styles in &lt;code&gt;site-header.mjs&lt;/code&gt; to use these custom properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;SiteHeader&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;style&amp;gt;
      header {
        background-color: var(--black-a10);
      }

      @media (prefers-color-scheme: dark) {
        header {
          background-color: var(--white-a10);
        }
      }
    &amp;lt;/style&amp;gt;
    …
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, in the same &lt;code&gt;global.css&lt;/code&gt; file, I’ll add these as background colors for the &lt;code&gt;table&lt;/code&gt; element (since we haven’t defined any classes using this color):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--black-a10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--white-a10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now my table’s looking a little more distinct:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2a81za6---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oan3m7vvmpqbckpi11po.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2a81za6---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oan3m7vvmpqbckpi11po.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing’s still bothering me, though: those table cells being center aligned (which is most browsers’ default styling) makes it hard to scan the rows of text. I’ll fix this by adding one more rule block to my &lt;code&gt;global.css&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures all content in my table head and data cells will be aligned to the top of the cell, making multiple lines of text much easier to read:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jV_0ZvBq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ttfbp0h5b8m8puqw5ntk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jV_0ZvBq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ttfbp0h5b8m8puqw5ntk.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To view these changes, &lt;a href="https://github.com/enhance-dev/enhance-example-blog-styled/commit/6e180bcecc892eb1954c9b5191e97b2de172c474"&gt;check out the commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  That’s a wrap!
&lt;/h2&gt;

&lt;p&gt;And there you have it! In just four simple commits, we’ve gone from this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rlD9ML25--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vwevcuqak9gv67717z3h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rlD9ML25--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vwevcuqak9gv67717z3h.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BqBLtfCb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5w88hu042m59r7b2iqg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BqBLtfCb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5w88hu042m59r7b2iqg.jpg" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is, of course, just the start of what you can do once you start customizing your blog’s styles. We’re really looking forward to seeing what you come up with, so be sure to &lt;a href="https://enhance.dev/discord"&gt;share your work on the Enhance Discord!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Until then, here’s to a big, happy return to blogging. Get started by &lt;a href="https://github.com/enhance-dev/enhance-blog-template"&gt;cloning the Enhance blog template&lt;/a&gt;, or &lt;a href="https://github.com/enhance-dev/enhance-example-blog-styled"&gt;dive into the full source code from this article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;See you on the blogosphere!&lt;/p&gt;

</description>
      <category>enhance</category>
      <category>blog</category>
      <category>css</category>
    </item>
    <item>
      <title>Redefining Developer Experience</title>
      <dc:creator>Cole Peters</dc:creator>
      <pubDate>Tue, 28 Feb 2023 16:17:02 +0000</pubDate>
      <link>https://dev.to/begin/redefining-developer-experience-12g7</link>
      <guid>https://dev.to/begin/redefining-developer-experience-12g7</guid>
      <description>&lt;p&gt;If recent activity is any indication, 2023 is looking like it could be a year of upheaval in the web development industry. For some, this upheaval may be welcome, and indeed a long time coming; for others, it may be more discomforting.&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://begin.com/blog/posts/2023-02-21-why-does-everyone-suddenly-hate-single-page-apps"&gt;Simon previously mentioned&lt;/a&gt;, Guillermo Rauch, CEO of Vercel, &lt;a href="https://twitter.com/rauchg/status/1619492334961569792"&gt;recently tweeted&lt;/a&gt; that ‘SPAs were a zero interest rate phenomenon.’ Although criticism of the Single Page Application (SPA) architecture had been growing for quite some time before this, Guillermo’s tweet certainly amplified the debate. A number of articles were published shortly thereafter, responding to this tweet and more broadly the impact of modern JavaScript frameworks (and Guillermo’s own role in popularizing them). Among those articles were pieces from &lt;a href="https://andy-bell.co.uk/speed-for-who/"&gt;Andy Bell&lt;/a&gt;, &lt;a href="https://infrequently.org/2023/02/the-market-for-lemons"&gt;Alex Russell&lt;/a&gt;, and &lt;a href="https://www.matuzo.at/blog/2023/single-page-applications-criticism/"&gt;Manuel Matuzović&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This article is not an argument for or against SPAs per se. Instead, I want to focus on a theme running through these articles and the accompanying dialogue throughout the web development community — that is, the topic of developer experience (DX).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ve always found the focus on developer experience as a framework feature uncomfortable. […] I personally think developer experience is one of the least important aspects.&lt;br&gt;
—&lt;a href="https://andy-bell.co.uk/speed-for-who/"&gt;Andy Bell&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the lemon sellers embed the data-light idea that improved “Developer Experience” (“DX”) leads to better user outcomes, improving “DX” became and end unto itself. […] As marketing goes, the “DX” bait-and-switch is brilliant, but the tech isn't delivering for anyone but developers.&lt;br&gt;
—&lt;a href="https://infrequently.org/2023/02/the-market-for-lemons"&gt;Alex Russell&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If I hear one more time about how developer experience is better and crickets for user experience I’ll implode&lt;br&gt;
—&lt;a href="https://bell.bz/@fox@front-end.social/109785750860828976"&gt;Karolina Szczur&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For years now, the most popular JS frameworks have carried out intense marketing initiatives based on the premise of improving DX. On the surface, this doesn’t seem too contentious — developers are, after all, the consumers of these frameworks, and developers are the ones who will advocate for using one framework or another within their organizations. Why not prioritize the development and marketing of quality of life improvements for those developers?&lt;/p&gt;

&lt;p&gt;As the quotes above describe, the problem is that this massive focus on DX has &lt;a href="https://infrequently.org/2022/12/performance-baseline-2023/"&gt;measurably been to the detriment of user experience (UX)&lt;/a&gt;. Frameworks promising next generation DX are almost invariably the same ones prone to delivering sub par UX, due largely to factors such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://almanac.httparchive.org/en/2022/javascript"&gt;Ballooning JS bundle sizes&lt;/a&gt;, which require more of an end user’s time (and money) to be downloaded, parsed, and executed on the browser&lt;/li&gt;
&lt;li&gt;Reliance on JS being properly downloaded, parsed and executed on those browsers (which is &lt;a href="https://www.kryogenix.org/code/browser/everyonehasjs.html"&gt;far from guaranteed&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jvns.ca/blog/2023/02/16/writing-javascript-without-a-build-system/"&gt;Build systems&lt;/a&gt; being a core feature of many of these frameworks (TypeScript and JSX, for example, cannot be run in the browser, so they must be transpiled to JS), thus creating a delta between authored code and executed code which can make debugging significantly more challenging&lt;/li&gt;
&lt;li&gt;Frequent breaking changes and shifting API surfaces, requiring developers to repeatedly relearn core aspects of these frameworks, which can also make an excellent breeding ground for bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This relationship between apparent improvements to DX and obvious degradations in UX begs the question: shouldn’t we just drop the focus on DX altogether?&lt;/p&gt;

&lt;p&gt;Personally, I’d raise a different question — that is: what is it about these promises of great developer experience that is consistently delivering poor user experiences?&lt;/p&gt;

&lt;h2&gt;
  
  
  What do we mean when we say ‘DX’?
&lt;/h2&gt;

&lt;p&gt;While the definition of ‘developer experience’ could be debated, &lt;a href="https://microsoft.github.io/code-with-engineering-playbook/developer-experience/"&gt;Microsoft’s take&lt;/a&gt; feels like a good summation that most could agree on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Developer experience refers to how easy or difficult it is for a developer to perform essential tasks needed to implement a change. A positive developer experience would mean these tasks are relatively easy for the team.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When we talk about DX, we’re generally referring to various measures that impact a software team’s ability to get things done with as little difficulty as possible. For a product to deliver good DX, it would typically need to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easy to learn (to help teams get up to speed quickly)&lt;/li&gt;
&lt;li&gt;efficient (to enable tasks to be completed as quickly as possible)&lt;/li&gt;
&lt;li&gt;consistent (to prevent teams from wasting time relearning how to do their work)&lt;/li&gt;
&lt;li&gt;transparent (to allow bugs to be diagnosed and solved easily)&lt;/li&gt;
&lt;li&gt;resilient (to ensure what works today also works next year)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end of the day, the promise of good DX typically boils down to improved team velocity and a reduction of points of failure. This makes sense — software teams are often encouraged to work faster and to deliver more value and fewer bugs, in order to generate more business for their company in as short a time as possible.&lt;/p&gt;

&lt;p&gt;But while ‘more business’ inherently requires end users to actually engage with the product or service being labored over, it’s hard to miss that end users are typically not represented within the goals of developer experience. This is not always the case — some frameworks like &lt;a href="https://remix.run/"&gt;Remix&lt;/a&gt; and &lt;a href="https://nuxt.com/"&gt;Nuxt&lt;/a&gt; do (as of this writing) allude to improvements to user experience in their marketing materials — but more frequently, good DX is simply presumed to cause a trickle down effect to the benefit of end users. &lt;a href="https://andy-bell.co.uk/speed-for-who/"&gt;This is, unfortunately, not often the case&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But even if good DX doesn’t inherently deliver good UX, surely it at least delivers on improving the quality of life and work of developers, right? Personally, I’m not sure this is often the case, either.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer experience in practice
&lt;/h2&gt;

&lt;p&gt;I’ve been working on the web professionally for the better part of 15 years — and for years before that as an amateur — and my work has primarily revolved around design and frontend engineering. At the risk of painting too pastoral a scene, my first years in frontend were almost blissfully simple compared to the status quo of today. My toolchain consisted of HTML (version 4), CSS (version 2), and a sprinkling of jQuery on those rare occasions when I could understand how to use it. Sometimes I’d bash out some half decent PHP if a client needed some help on their Wordpress. Getting my code from my computer to the Web was a matter of dropping my HTML and CSS files from Finder onto an FTP client (after checking to make sure I wasn’t about to blow away the work of another developer, of course — version control was not something I’d yet encountered). What might be considered ‘developer experience’ at this point in my career mainly consisted of learning web platform fundamentals — largely from books, or from articles posted by generous and talented professionals in the industry. A simpler time, indeed.&lt;/p&gt;

&lt;p&gt;Fast forward to 2022, when I was working as a lead frontend engineer at a technology startup. At this point in my career, I’d learned to wield several major versions of React with substantial expertise (starting with class components, then function components, then hooks…), along with the wider React ecosystem. React Router, Redux, Redux Select, Redux Saga, the Context API, XState, React Query, Styled Components, Styled System, Radix, React Spring, Framer Motion, React DND, DNDKit, Jest, Enzyme, React Testing Library, NextJS, and others were all under my belt, and in most cases my experience in using them tended toward the skilled end of the spectrum. And this was just to render and test UI!&lt;/p&gt;

&lt;p&gt;Deeper under the hood, I was working with NPM, Babel and Webpack to manage packages and builds (having previously worked with tools like Bower, Grunt, and Gulp), and deployment providers like Heroku and Vercel. Of course, all of this was managed through GitHub version control, with some of the pipeline instrumented with GitHub Packages and Actions, and with everything running through ESLint and Prettier to catch formatting inconsistencies, opportunities for optimization, and potential bugs. Underlying all of this were my ongoing efforts to stay on top of developments in HTML, CSS, and JS/ES-What-Year-Is-This, including aspects like accessibility and performance, but also with respect to concepts like functional programming and frontend architecture in general. I was also trying to get familiar with the principles of technical leadership, as I was continuing to advance in my career.&lt;/p&gt;

&lt;p&gt;When I decided to start looking for a new role later in the year, I realized I’d likely need to start picking up TypeScript to maintain my competitiveness as a candidate for frontend engineering roles, and I’d likely have to start picking up knowledge about other frameworks like Vue or Svelte, and the wider ecosystems and metaframeworks that go along with them. Oh, and along with these technologies, I was also constantly working to excel in my speciality as a design engineer — which meant staying on top of design trends and &lt;a href="https://www.interaction-design.org/literature/topics/gestalt-principles"&gt;gestalt principles&lt;/a&gt;, the rapidly shifting space of design systems, and the soft skills associated with being able to perform in these spaces.&lt;/p&gt;

&lt;p&gt;At this point, let me assure you: I am not enumerating all of these areas of concern to impress you or to sell you on my expertise. The picture I’ve just painted you is the reality of ‘developer experience’ in the 2020s — at least as far as frontend engineering is concerned. ‘Developer experience’ means getting familiar with an increasingly expansive array of tools — all of which, frankly, change to some degree every single year. What’s hot today is not tomorrow, what worked one way yesterday is broken today. And every day, the expectation is that you accept this state of affairs, and keep the pedal to the metal (and maybe hit the turbo thruster while you’re at it). Your team’s velocity depends on it.&lt;/p&gt;

&lt;p&gt;There was a time, probably somewhere between 2018 and 2021 where the amount of things I knew — indeed, &lt;em&gt;had&lt;/em&gt; to know in order to do my job at my level — filled me with pride and excitement. I even got to feeling a little cocky about the amount of framework minutia taking up space in my brain (which in retrospect might be a bit cringe worthy, but hey, we’re all works in progress). As 2022 came to a close and I began to reconsider my professional goals, however, the amount of things I &lt;em&gt;had&lt;/em&gt; to know started to feel less like an accomplishment and more like a burden. I began to realize that every hour I spent learning how React 18 was subtly but critically different from React 17 was an hour I’d have to spend again next year when React 19 inevitably landed (substitute ‘React’ for your JS tool of choice — the outcome will likely be the same or worse). At the end of the day, how much of this thrash was making me more effective at crafting supremely enjoyable user experiences?&lt;/p&gt;

&lt;p&gt;Suffice it to say that Late 2022 Cole got pretty frustrated with the state of developer experience; I even considered transitioning entirely into a technical writing role at one point, just to get out of the constant cycle of relearning how to do my job as an engineer. (Thankfully, an offer from the crew at Begin saved me from the stress of a complete change of career, and also gave me the opportunity to get back to working with platform fundamentals!)&lt;/p&gt;

&lt;p&gt;Of course, all of what I’ve just told you is anecdotal, and not all engineers will have the exact same ‘developer experience’. But based on the aforementioned blog posts published recently, I’m willing to bet that my experience isn’t terribly unique. If that’s true, this should be a huge red flag for our industry. Developer experience of this sort leads to a number of serious problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Senior engineers are required to know an ever exploding number of technologies in order to carry out the most basic parts of their jobs.&lt;/li&gt;
&lt;li&gt;The vast number of things one needs to know to advance to a senior engineer position thus makes that progress harder to realize.&lt;/li&gt;
&lt;li&gt;This can in turn make for a more intimidating career path for earlier career engineers — or, alternatively, it can create the impression that professional seniority is simply about the number of things you know as opposed to the quality of work you can produce with a subset of those things.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perhaps most problematic of all is the effect that contemporary developer experience has on educational programs (be they traditional classes, bootcamps, workshops, or anything in between). Such a rapidly expanding and ever changing technological ecosystem necessarily means that curricula struggle to keep up, and that the fundamentals of web development (e.g. HTML, CSS, HTTP, browser APIs…) are often glossed over in favor of getting students into the technologies more likely to land them jobs (like React and its many many pals). This leads to an outpouring of early career developers who may speak confidently about things like React hooks or Redux state reducers, but who also lack any concept about the nature of HTML semantics or the most basic accessibility considerations. To be clear, I’m not throwing shade at those developers — they have been failed by an industry obsessed with the new and shiny at the expense of foundational practices and end user experiences.&lt;/p&gt;

&lt;p&gt;And so, I ask: what exactly are we buying when we are sold ‘developer experience’ today? Who is benefitting from it? And if it is indeed something many of us aren’t too excited about (to put it kindly), how can we change it for the better?&lt;/p&gt;

&lt;h2&gt;
  
  
  Realigning
&lt;/h2&gt;

&lt;p&gt;In my opinion, simply dropping our industry’s focus on DX is not the solution here. &lt;a href="https://slash.co/articles/people-behind-your-tech-how-many-developers-are-there-in-the-world/"&gt;There were over 26 million web developers in the world as of 2021&lt;/a&gt;, and that number is expected to double by 2030. This is a massive industry of people who care about their experience with web technologies, and simply telling them to drop the focus on their quality of life for the sake of altruism isn’t likely to cut it.&lt;/p&gt;

&lt;p&gt;Instead, I think we need to carefully consider what we mean when we talk about developer experience — and how what we say translates into what we do.&lt;/p&gt;

&lt;p&gt;If I may risk verging on the idealistic, I would suggest that developer experience needs to pivot from a concept centered on feeling fast and living on the bleeding edge to one based on the enabling of developers to deliver reliable and first rate end user experiences — for as many users as possible, and for as long as possible. This doesn’t mean developers shouldn’t have great tools with which to carry out the crafting of great UX — but it does shift the narrative from one of ‘trickle down UX’ to something more honest, where the fruit of our labors is prioritized a little more above our labors themselves.&lt;/p&gt;

&lt;p&gt;As discussed, developer experience today encompasses a nearly countless number of technologies that change and break both reliably and regularly, and which deliver poor user experiences rife with loading spinners, random bugs that are difficult to diagnose and fix, and interminable load times (especially for users not fortunate enough to be working with a rock solid connection to fiber based internet). But there are alternatives — alternatives focused first and foremost not on the new and shiny, but on the stable, enduring, and reliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  A brief tangent on scissors
&lt;/h3&gt;

&lt;p&gt;Allow me a moment to construct a metaphor. Take a look at these scissors:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--flmdjZEU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/agym7k3e5r3ljzt7dkm0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--flmdjZEU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/agym7k3e5r3ljzt7dkm0.jpg" alt="A pair of herb clippers by Tajika. Rustic looking appearance, two small hand forged blades are joined by an elongated U shaped piece of steel." width="880" height="807"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These scissors may look ancient, but in fact they are herb clippers, crafted today by Tajika Haruo Ironworks in Oto city, Japan. The father and son duo of Takeo and Daisuke Tajika forge these clippers by hand, transforming raw steel into precise configurations. These herb clippers in particular are intended for the kind of work where ordinary scissors would lack the required measure of precision: the delicate trimming of herbs in the garden or kitchen, or the creation of exquisite floral arrangements. Because of their design, the user need only apply a small amount of pressure to make a perfect cut, and the extreme sharpness of the blades means that the plants from which cuttings are made will be able to heal with the least amount of effort (just as in human tissue, where an incision from a scalpel is easier to heal than, say, the sort of rough abrasion caused by accidentally running one’s hand into cheese grater — don’t ask me how I know).&lt;/p&gt;

&lt;p&gt;In short, these herb clippers are designed to do one thing, and they do it extremely well; indeed, they have done so for centuries since their design was first introduced. Secondarily to this, they are also exquisitely beautiful; the simplicity of their purpose is born out in their rustic, utilitarian design. No bells or whistles, nothing unnecessary, just a pair of perfectly edged blades joined together by a simple spring mechanism.&lt;/p&gt;

&lt;p&gt;Now consider these ‘scissors’:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cJxUNatL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/65xo3wguz66xtegxey09.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cJxUNatL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/65xo3wguz66xtegxey09.jpg" alt="A Swiss Army Knife, with its can opener, knives, scissors, screwdriver, and other assorted tools exposed." width="679" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ah, the venerable Swiss Army Knife. A pair of herb clippers this is not. The Swiss Army Knife takes the opposite approach of the Tajikas. This is a tool designed to maximize functionality — a tool for heading out into the great unknown, for tackling emergencies and tasks you may not be able to anticipate. With this one piece of equipment, you could accomplish any number of tasks, from fastening a screw, opening a can or bottle, chopping small vegetables, preparing a fish for cooking, whittling a piece of wood, and, yes, cutting up materials with the scissors. I wouldn’t expect to be able to do any of these tasks particularly &lt;em&gt;well&lt;/em&gt; (nor would I trim herbs or flowers with those scissors if another option was available), but that’s never been the point of the Swiss Army Knife; the point has always been to be prepared to deal with a variety of tasks with a modicum of success. It’s a tool for survival, not fine craft.&lt;/p&gt;

&lt;p&gt;Contemporary developer experience, to me, feels very much like we’ve been building websites with the code equivalent of Swiss Army Knives. We’ve erected Swiss Army Knife schools, and venerated the ability to jump fluidly from the knife to the scissor to the can opener to the corkscrew, celebrating our speed with the screwdriver even as we ignore the fact that the screws we’ve installed all have stripped heads, and that the plants we’ve made cuttings from all bear unattractive scars that have prevented those same plants from putting energy into new growth. Meanwhile, the thought of going back to using older, more focused tools — like the herb clippers — seems unfathomable. Why return to such an outdated, limited tool when we have the Swiss Army Knife?&lt;/p&gt;

&lt;p&gt;It’s understandable that those of us who may have invested many years of practice with Swiss Army Knives (okay, JS mega stacks) would be reticent to leave them behind — they’re familiar, they’re comfortable, even if we recognize their shortcomings, and after all, we spent all that time honing our skills with them. But I think it’s also a very worthwhile practice to consider whether &lt;em&gt;the experience&lt;/em&gt; of using these tools is really something we’re enjoying (and if these tools are even right for the job), and more importantly, whether that experience is leading to positive outcomes for those using what we make.&lt;/p&gt;

&lt;p&gt;But herein lies an opportunity to refocus ourselves, our tools, and our craft. Why would we abandon the Swiss Army Knife? Because it’s not the right tool for the job — it’s not really that enjoyable to use, its many tools don’t even work that well, and the work we make with them isn’t of the caliber it deserves to be for those making use of it.&lt;/p&gt;

&lt;p&gt;If we want to achieve perfect trimming of herbs and delicate plants, we can choose to get comfortable with those herb clippers. If we want to prepare a meal, we can seek out a single good kitchen knife (please ditch those knife block sets, they’re horrible and a waste of money). And if we want to create lasting, reliable, excellent user experiences? Well, I know of a tool for that too. It’s been around for awhile, and it might appear a little bit primitive to some at first glance, but it’s actually quite effective — and it happens to come with a pretty great developer experience, too. &lt;/p&gt;

&lt;h2&gt;
  
  
  The web platform as developer experience
&lt;/h2&gt;

&lt;p&gt;To restate my proposition, I believe ‘good developer experience’ needs to be reimagined as the enabling of developers to deliver reliable and first rate end user experiences — for as many users as possible, and for as long as possible. With this goal in mind, I think we’d be hard pressed to find a better starting point than &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/The_web_and_web_standards#overview_of_modern_web_technologies"&gt;the fundamentals of the web platform&lt;/a&gt; — that is, web browsers, HTTP, HTML, CSS, and JavaScript. These technologies have existed for decades in a near total state of backwards compatibility, while also consistently evolving to offer web developers ever more powerful techniques to deliver exceptional user experiences.&lt;/p&gt;

&lt;p&gt;It’s true that many modern JavaScript frameworks began as attempts to fill in some gaps in web standards, but in 2023, many of those gaps have been filled by the web standards themselves. HTML now has templates and custom elements, CSS has an incredible range of APIs for animation and complex, dynamic, and responsive layouts, and JavaScript itself has evolved to become a powerful programming language without the need for supersets or frameworks.&lt;/p&gt;

&lt;p&gt;Browsers, meanwhile, have become better at converging on implementations (for the most part, anyway… WebKit has some catching up to do) and at offering built in optimizations like back/forward caching, and they automatically update so that end users have access to the best new features. Cloud based deployment solutions and general increases in global internet speeds have done much to mitigate slow response times (providing you’re not shipping megabytes of JavaScript over the wire), allowing multipage applications to become just as fast (if not faster overall) than single page applications.&lt;/p&gt;

&lt;p&gt;All of this is available to every single web developer on the planet, and (perhaps with the exception of deployment providers) without having to &lt;code&gt;npm install&lt;/code&gt; a single thing. Even more impressively, time spent learning these fundamentals today is not time you will need to spend again next month or next year. Because of the web’s inherent backwards compatibility, the knowledge you acquire today will never go out of date. This is not, however, to say that the web platform is stagnant and never changes — changes are arriving faster than ever before, but these changes are additive and stable, rather than subtractive and in constant flux.&lt;/p&gt;

&lt;p&gt;On top of all of this, web platform fundamentals deliver exceptional user experiences, as long as they are used with an eye towards accessibility and performance. The results that can be achieved solely with vanilla HTML, CSS, and the lightest sprinkling of JavaScript in 2023 is truly remarkable (just wait until you see what we’ve been cooking up for the launch of &lt;a href="https://enhance.dev"&gt;Enhance&lt;/a&gt; 1.0), and these results can be delivered to end users with unparalleled speed and resiliency.&lt;/p&gt;

&lt;p&gt;The web standards platform in 2023 is, to put it bluntly, fantastic. I joined Begin in 2022 and haven’t touched a lick of React or any of the aforementioned frameworks or ecosystems since (aside from NPM), and while I accept that my position in the company may bias me, I have to say: this has been both a relief and a joy. Getting back into working with the fundamentals has been supremely exciting, especially where picking up knowledge of the world of web components is concerned. I spend so much more of my time solving important (end user) problems now, and when my work ships, it’s delivered in a fraction of the time and with so much more stability and resiliency than anything I’ve shipped in the past decade.&lt;/p&gt;

&lt;p&gt;I won’t go so far as to say the web platform is perfect. There are things about the nature of web components (primarily their tight coupling to JavaScript classes that are designed to run on the client) that frustrate me. There are sometimes hold ups (like &lt;a href="https://github.com/WebKit/standards-positions/issues/97"&gt;WebKit&lt;/a&gt;) to certain standards being implemented universally (like &lt;a href="https://lists.w3.org/Archives/Public/public-webapps/2013OctDec/1051.html"&gt;extending built in HTML elements&lt;/a&gt;). But compared to the feeling of permanent red alert that I felt working with JS mega stacks, things are blissfully calm.&lt;/p&gt;

&lt;p&gt;What’s really exciting for me to think about is the fact that more people getting on board with web standards makes for more people to help push those standards ahead. Imagine if instead of fighting for certain updates to land in React, we could all work together to help certain updates land across the entire web platform, and every single user of it. Isn’t that a developer experience that would be truly exciting?&lt;/p&gt;

&lt;p&gt;To put it simply, the developer experience I’ve been wanting for years has, in fact, been around for years — I was just too caught up in other stacks to notice it. And the fact that this developer experience also has the power and tendency to create exceptional user experiences…? Now that’s a phenomenon I want to be a part of.&lt;/p&gt;

&lt;h2&gt;
  
  
  First steps
&lt;/h2&gt;

&lt;p&gt;If you’re ready to take the first step towards better DX and better UX, the first step is to (re)acquaint yourself with web platform fundamentals. The Mozilla Developer Network (MDN) has &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Front-end_web_developer"&gt;a great learning pathway touching on HTML, CSS and JavaScript&lt;/a&gt;, and this could be a great place to start.&lt;/p&gt;

&lt;p&gt;Once you’re familiar with the basics (or if you are already and want to take them further), &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components"&gt;Web Components&lt;/a&gt; offer a standards based methodology for creating custom reusable components. As previously mentioned, Web Components require JavaScript to run in the browser by default, and their class based interface can take some getting used to. Enhance, our HTML first framework, steps around this issue by providing you with a standards compliant way to render custom elements on the server, only requiring you to interface with the Web Components JavaScript API when progressive enhancement is called for. Plus, we’ve got a really cute mascot. &lt;a href="https://enhance.dev"&gt;Give Enhance a try today!&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Article photo by &lt;a href="https://www.analoguelife.com/en/products/tajika-household-scissors"&gt;Analogue Life&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webcomponents</category>
      <category>javascript</category>
      <category>html</category>
    </item>
    <item>
      <title>Past Informs the Present: Begin’s Approach to CSS</title>
      <dc:creator>Cole Peters</dc:creator>
      <pubDate>Tue, 10 Jan 2023 16:03:09 +0000</pubDate>
      <link>https://dev.to/begin/past-informs-the-present-begins-approach-to-css-17io</link>
      <guid>https://dev.to/begin/past-informs-the-present-begins-approach-to-css-17io</guid>
      <description>&lt;p&gt;CSS occupies an interesting position among web technologies: while it can appear almost quaint in its simplicity, it’s also been interpreted by some as the most vexing language in web development. Despite its approachability, CSS sometimes gets a bad rap — one that I believe derives from a fundamental misunderstanding of CSS’ history, evolution, and function as an API for styling on the web.&lt;/p&gt;

&lt;p&gt;In this article, we’re going to review some of that history and evolution. We’ll then use that information to inform a focus on several methodologies — some battle tested, some more recent — for making the process of styling web apps and components both enjoyable and effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  The origins of CSS
&lt;/h2&gt;

&lt;p&gt;In order to gain a deep understanding of CSS, it’s important to first understand the ecosystem from which it emerged: that is, the early days of the World Wide Web. Getting familiar with this context is essential to understanding why CSS works the way it does — and also provides some insight into just how far it has come since its inception.&lt;/p&gt;

&lt;p&gt;In contrast to the dynamic and interactive nature of the Web of today, the Web started out as a comparatively simple medium: that is, one for publishing documents. This intent was clearly stated on &lt;a href="http://info.cern.ch/hypertext/WWW/TheProject.html" rel="noopener noreferrer"&gt;the first ever website, authored by Tim Berners-Lee&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The WorldWideWeb (W3) is a wide-area hypermedia information retrieval initiative aiming to give universal access to a large universe of documents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This first website was launched on August 6, 1991, but the world would have to wait until December of 1996 for &lt;a href="https://www.w3.org/Press/CSS1-REC-PR.html" rel="noopener noreferrer"&gt;the official arrival of CSS&lt;/a&gt;, at the hands of Håkon Wium Lie and Bert Bos. At the risk of oversimplifying things, at its highest level, this first draft of CSS could be reduced to three fundamental tenets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;CSS is a language for authoring style sheets for HTML documents.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CSS encourages the independence of markup from style sheets, thereby preserving content fidelity and structure, while allowing for the application of reusable styles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CSS’ style sheets &lt;em&gt;cascade&lt;/em&gt; — that is, styling rules declared by the user agent may be overruled by styles declared by the document author, which themselves may be overruled by the end user.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This third tenet — the cascading nature of CSS — was a source of great debate at the time, and traces of this debate even carry on amongst web practitioners today. And yet, CSS’ cascade is perhaps one of its most defining attributes: it underscores the web as a medium where content and its presentation is informed not only by browser makers, but also by content authors, end users, and those users’ devices and their capabilities. Even from the Web’s earliest days as a platform for static documents, CSS was, in its own way, declaring in no uncertain terms that the presentation of content on the Web must be approached not dictatorially, but democratically — or, to use a more modern and technical term: responsively.&lt;/p&gt;

&lt;p&gt;The second tenet, referencing CSS’ global scope and its independence from HTML, will of course be a familiar topic to anyone who has touched frontend development over the past two decades. We’ll get deeper into this later in the article.&lt;/p&gt;

&lt;p&gt;The first tenet I’ve proposed above, however, is perhaps the most impactful, yet also the easiest to overlook. In fact, I believe the nature of this principle is one that a great many web developers to this day tend to forget (or never learn in the first place), and this in turn has become a source of some of the deepest struggles in frontend web development over the years.&lt;/p&gt;

&lt;p&gt;And so, let us spell it out clearly: &lt;strong&gt;CSS, as game changing as it was, was not created as an application or component styling API&lt;/strong&gt; — it was designed as a means of styling static documents, authored in HTML. Documents and applications (and components), however, present drastically different contexts for design. The nature of the standardized Web, meanwhile, as one of (perhaps the most) &lt;a href="https://www.w3.org/People/Bos/DesignGuide/compatibility.html#:~:text=The%20Web%20itself%20is%20designed,of%20files%2C%20not%20just%20HTML." rel="noopener noreferrer"&gt;backwards compatible&lt;/a&gt; software platforms, has in turn meant that CSS’ origin story was always going to be inescapable. Unlike so many technology stacks of today, turning CSS into an application styling API would never be a matter of simply shipping a breaking change and letting end users deal with the fallout. As the web matured into a platform not just for documents, but rather one for the multidirectional flow of &lt;em&gt;information&lt;/em&gt;, CSS as its UI layer would have to evolve gradually along with it.&lt;/p&gt;

&lt;p&gt;This, then, establishes what I consider the &lt;em&gt;grain&lt;/em&gt; of CSS — a grain that many web developers continue to struggle with.&lt;/p&gt;

&lt;h2&gt;
  
  
  With and against the grain
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fviv6vbjqmrg92ckhvvpr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fviv6vbjqmrg92ckhvvpr.jpg" alt="Photograph of a tree with deeply ridged bark in a forest in Sequoia National Park, United States" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;small&gt;Photo by &lt;a href="https://unsplash.com/photos/zL4o0NkEURY" rel="noopener noreferrer"&gt;Lucas Davies on Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;With the transition away from documents and toward applications and components, web developers began devising methodologies to execute increasingly complex user interfaces with a styling API that was still heavily targeted towards static documents. Some of these methodologies were successful — even essential — in pushing both CSS and styling on the web more broadly into the future; others were less so. In my experience, the most important developments in CSS methodologies were those that were designed with the grain of CSS in mind; the least successful candidates, meanwhile, tended to push quite hard against it.&lt;/p&gt;

&lt;p&gt;Perhaps the most important CSS methodology to emerge during the web’s transition towards application-like websites was &lt;a href="https://github.com/stubbornella/oocss/wiki" rel="noopener noreferrer"&gt;Object Oriented CSS (OOCSS)&lt;/a&gt;, devised by &lt;a href="http://www.stubbornella.org/content/" rel="noopener noreferrer"&gt;Nicole Sullivan&lt;/a&gt; in 2009. Nicole’s now legendary article, &lt;a href="http://www.stubbornella.org/content/2010/06/25/the-media-object-saves-hundreds-of-lines-of-code/" rel="noopener noreferrer"&gt;‘The Media Object Saves Hundreds of Lines of Code’&lt;/a&gt;, represented a fundamental rethinking on the composition of &lt;a href="https://css-tricks.com/css-ruleset-terminology/" rel="noopener noreferrer"&gt;CSS rulesets&lt;/a&gt; and their relationship to HTML content. Instead of writing CSS styles around specific HTML content or basing styles on the location of content within the DOM, OOCSS prioritized writing reusable styling rules based on design patterns (in the case of the media object: ‘a fixed size media element (e.g. image or video) along with other variable size content (e.g. text)’). As perhaps the first instance of a CSS methodology systematically informed by a visual pattern language, OOCSS was also a critical step towards a more modular, reusable approach to writing CSS.&lt;/p&gt;

&lt;p&gt;As style sheets became the responsibility of larger and larger teams, CSS’ global scope and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity#:~:text=Specificity%20is%20an%20algorithm%20that,(or%20pseudo%2Delement)" rel="noopener noreferrer"&gt;specificity&lt;/a&gt; were often at odds with team dynamics. Style collisions became increasingly common, where changes introduced by one developer would inadvertently affect styles elsewhere on the website. As the old joke goes: two CSS properties walk into a bar; a bar stool in a completely different bar falls over. As these issues and the number of people experiencing them multiplied, so too did new CSS methodologies, particularly those focused on style sheet architectures. Before long, we had &lt;a href="http://smacss.com/" rel="noopener noreferrer"&gt;SMACSS&lt;/a&gt;, &lt;a href="https://suitcss.github.io/" rel="noopener noreferrer"&gt;SUIT CSS&lt;/a&gt;, &lt;a href="https://getbem.com/" rel="noopener noreferrer"&gt;BEM&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=1OKZOV-iLj4" rel="noopener noreferrer"&gt;ITCSS&lt;/a&gt;, and more. Third party supersets of CSS also appeared during this time, such as &lt;a href="https://sass-lang.com/" rel="noopener noreferrer"&gt;Sass&lt;/a&gt; and &lt;a href="https://lesscss.org/" rel="noopener noreferrer"&gt;LESS&lt;/a&gt;, which gave style sheet authors access to scripting features like variables and loops.&lt;/p&gt;

&lt;p&gt;The extent to which CSS supersets benefitted or hindered the progress of styling on the web is debatable. Sass, for example, should be credited for introducing variables to CSS, which in turn inspired CSS’ own &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties" rel="noopener noreferrer"&gt;custom properties&lt;/a&gt; (which improved upon Sass’ implementation in several ways). In the same breath, however, I personally believe techniques such as nesting, mixins, loops, and extends, introduced by Sass and LESS, were less beneficial. These techniques resulted in excessively bloated and complex CSS being shipped to the browser. To add insult to injury, due to inherent differences between authored code and generated code, CSS written in these supersets became much harder to debug (a task which, due to increases in complexity, became increasingly necessary).&lt;/p&gt;

&lt;p&gt;Similarly, and despite the best of intentions, some CSS methodologies could be considered more beneficial than others. For example, let’s take the ruleset format proposed by the likes of BEM, where classes are constructed with multifaceted declaration blocks bound to context aware class names. The ‘context aware’ part here is important — BEM’s &lt;a href="https://getbem.com/naming/" rel="noopener noreferrer"&gt;‘Block, Element, Modifier’ construct&lt;/a&gt; declares that classes should be named based on a hierarchy derived from both markup and state. This strategy introduces a dependency between the structure of a page’s markup and its styles, a strategy CSS itself had attempted to avoid.&lt;/p&gt;

&lt;p&gt;BEM is not the only methodology to use this kind of ruleset format — many other methodologies rely on markup context (or content context) to inform the construction of classes. Herein lies the problem, though: while this approach could be said to encourage pleasant developer ergonomics, the results are inherently brittle (due to the tight coupling between markup and styles). On top of that, the prioritization of selector nomenclature above the actual styles being applied to those selectors often results in style sheets that are bloated with repeating property declarations — see for example these styles from the Financial Times’ website:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.o-ads--label-left&lt;/span&gt; &lt;span class="nc"&gt;.o-ads__inner&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"▼ Advertisement ▼"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"left"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.o-ads--label-right&lt;/span&gt; &lt;span class="nc"&gt;.o-ads__inner&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"▼ Advertisement ▼"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"right"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.o-ads--label-center&lt;/span&gt; &lt;span class="nc"&gt;.o-ads__inner&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"▼ Advertisement ▼"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"center"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.o-ads--label-with-borders&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"left"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In these ways and others, many of the aforementioned methodologies could be said to work against the grain of CSS, despite their intent to make styling easier and more robust. As such, the process of writing and maintaining CSS in the mid 2010s had become increasingly complex; but it also set the stage for a radical rethinking, and a move towards simpler, more efficient, and more resilient methods of styling content on the web.&lt;/p&gt;

&lt;h2&gt;
  
  
  The atomization of CSS
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forkqxhqdqgikjqb0e1m3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forkqxhqdqgikjqb0e1m3.jpg" alt="Image of a swirling sphere-like shape made up of individual blue particles" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;small&gt;Image by &lt;a href="https://unsplash.com/photos/n6_MK8-xMP4" rel="noopener noreferrer"&gt;Pawel Czerwinski on Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;For many years, the semantic nature of HTML led many to proclaim that CSS should also be written ‘semantically’. However, this tight coupling between HTML semantics and CSS selectors, despite being recommended even by the W3C as a best practice, does not have a basis in reality. &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Semantics" rel="noopener noreferrer"&gt;Content semantics in HTML&lt;/a&gt; are expressed through the use of meaningful elements like &lt;code&gt;h1&lt;/code&gt;, &lt;code&gt;nav&lt;/code&gt;, &lt;code&gt;footer&lt;/code&gt;, &lt;code&gt;ul&lt;/code&gt;, etc., and the way in which these elements are structured to create a document tree. CSS, meanwhile — being a &lt;em&gt;presentational language&lt;/em&gt; — has no notion of content semantics; there is no way for a machine to glean information about HTML content from a style sheet. Nicolas Gallagher, in &lt;a href="https://nicolasgallagher.com/about-html-semantics-front-end-architecture/" rel="noopener noreferrer"&gt;an article I still consider to be of foundational importance&lt;/a&gt;, spelled this out quite clearly in 2012:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The primary purpose of a class name is to be a hook for CSS and JavaScript. If you don’t need to add presentation and behavior to your web documents, then you probably don’t need classes in your HTML.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the absence of a mandate to describe particular nodes of content or &lt;a href="http://web.dfc.unibo.it/buzzetti/IUcorso2007-08/mdidattici/ontology-definition-2007.htm#:~:text=In%20computer%20and%20information%20science,some%20domain%2C%20real%20or%20imagined." rel="noopener noreferrer"&gt;ontological&lt;/a&gt; relationships between them, CSS authors were free to consider other approaches to authoring CSS — and by the early 2010s, many were doing just that. The first article I recall reading that suggested a fundamental shift was underway was one written in 2013 by Thierry Koblentz, appropriately entitled &lt;a href="https://www.smashingmagazine.com/2013/10/challenging-css-best-practices-atomic-approach/" rel="noopener noreferrer"&gt;‘Challenging CSS Best Practices’&lt;/a&gt;. At the heart of Koblentz’s article was a well-argued overview of how so-called ‘best practices’ in CSS at the time often lead to multiple rewrites of both CSS and HTML whenever UI requirements change (that hardly ever happens, right?), leading to ever-growing, &lt;a href="https://css-tricks.com/oh-no-stylesheet-grows-grows-grows-append-stylesheet-problem/" rel="noopener noreferrer"&gt;‘append only’ style sheets&lt;/a&gt; that become more brittle over time. His proposal, worked out in practice during his time at Yahoo!, was simple but nigh on heretical to many at the time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The smaller the unit [in a larger system], the more reusable it is. To break down styles into irreducible units, we can map classes to a single style, rather than many. This will result in a more granular palette of rules, which in turn improves reusability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This Lego-like approach to CSS can arguably be traced back to Nicole Sullivan’s OOCSS (and, it could be argued, early ‘utility’ classes like &lt;a href="https://css-tricks.com/clearfix-a-lesson-in-web-development-evolution/" rel="noopener noreferrer"&gt;&lt;code&gt;.clearfix&lt;/code&gt;&lt;/a&gt;), but what Koblentz and others were proposing — generally referred to as ‘atomic CSS’ — took this approach to the logical extreme.&lt;/p&gt;

&lt;p&gt;To illustrate the drastic difference in approaches, consider the following two implementations of the media object (for simplicity, implemented with flexbox):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ‘Best practices’ media object --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.media&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.media-img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;flex-shrink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.media-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;flex-grow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'media'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'media-img'&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'…'&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;'…'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'media-content'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Here’s a traditional media object.
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ‘Atomic’ media object --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.flex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.flex-shrink0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;flex-shrink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.flex-grow1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;flex-grow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.padding-right2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.width6&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.height6&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'flex'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'flex-shrink0 padding-right2 width6 height6'&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'…'&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;'…'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'flex-grow1'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Here’s an atomic media object.
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;small&gt;Note how each class in the atomic version maps to just a single CSS property and value. In fact, if I hadn’t included the second &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; block, I bet you’d have had no problem determining each class’ effect from the markup alone! This is a hallmark of atomic CSS — the effect of a class is typically self evident from its name alone, whereas the specifics of a class name like &lt;code&gt;media&lt;/code&gt; are more ambiguous.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;For anyone familiar with atomic CSS today, the example above will likely appear unremarkable. The transition towards this approach was anything but, however — and on some corners of the web today, debate still rages about whether atomic CSS has been the best or worst thing to happen to styling on the web since CSS.&lt;/p&gt;

&lt;p&gt;There was, however, clearly an appetite for this approach amongst a non-trivial swath of web developers: the year 2014 saw the release of both Adam Morse’s &lt;a href="https://tachyons.io/" rel="noopener noreferrer"&gt;Tachyons&lt;/a&gt; and Brent Jackson’s &lt;a href="https://basscss.com/" rel="noopener noreferrer"&gt;Basscss&lt;/a&gt;, the first two frameworks to go all-in on atomic CSS. These frameworks were instrumental in writing the blueprints for the atomic CSS methodology and turning the status quo on its head — and indeed, the shift was so monumental that, within a number of years, ‘utility-first’ CSS frameworks started becoming &lt;a href="https://adamwathan.me/tailwindcss-from-side-project-byproduct-to-multi-mullion-dollar-business/" rel="noopener noreferrer"&gt;multimillion dollar businesses&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The atomization of CSS had officially begun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Atomic CSS: successes and perceived failures
&lt;/h2&gt;

&lt;p&gt;In order to understand the success of atomic CSS (even if that success remains a point of debate in some circles), we should first examine its principles, and the goals those principles seek to achieve. Many of these principles are derived from &lt;a href="https://en.wikipedia.org/wiki/Functional_programming" rel="noopener noreferrer"&gt;functional programming&lt;/a&gt;, hence the alternative name ‘functional CSS’. Additional inspiration came from &lt;a href="https://en.wikipedia.org/wiki/Unix_philosophy" rel="noopener noreferrer"&gt;the Unix philosophy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The most fundamental principles of atomic CSS are:&lt;/p&gt;

&lt;dl&gt;

  &lt;dt&gt;Classes should have a single purpose.&lt;/dt&gt;
  &lt;dd&gt;Classes should do one thing, and they should do it well. This makes each class more reusable. A class that applies a margin, and only a margin, is more reusable than a class that applies and margin and a text colour.&lt;/dd&gt;

  &lt;dt&gt;A class’ effect should be self evident.&lt;/dt&gt;
  &lt;dd&gt;There should be no mystery about the effect of using a class — clarity should always trump cleverness. The effect of a class named &lt;code&gt;flex&lt;/code&gt; which sets the &lt;code&gt;display&lt;/code&gt; property to &lt;code&gt;flex&lt;/code&gt; is self evident. The effect of a class named &lt;code&gt;media&lt;/code&gt; which may set any number of property values is ambiguous.&lt;/dd&gt;

  &lt;dt&gt;Classes should be composable.&lt;/dt&gt;
  &lt;dd&gt;Complex styles should be achieved by composing multiple single purpose classes together, rather than by writing new, complex, and less reusable classes.&lt;/dd&gt;

  &lt;dt&gt;Classes should be immutable and free of side effects.&lt;dt&gt;
  &lt;/dt&gt;
&lt;/dt&gt;
&lt;dd&gt;For example, the &lt;code&gt;underline&lt;/code&gt; class should only ever apply an underline style. It should never &lt;em&gt;not&lt;/em&gt; apply the underline, or apply another style, or change any other property of any other element. Under no circumstances should it change the effect of another class.&lt;/dd&gt;

&lt;/dl&gt;

&lt;p&gt;It’s important to note that these principles were not devised for their own sake — each plays an important role in authoring performant, maintainable, robust styles: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Single purpose classes are more reusable and composable than multipurpose classes. Thus, single purpose classes provide &lt;strong&gt;greater flexibility&lt;/strong&gt; as well as &lt;strong&gt;reduced CSS file sizes&lt;/strong&gt;, both at the outset of new projects and throughout their lifecycle (as fewer styles need to be added to deliver iterations and additions to UI).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Classes with singular, self evident effects &lt;strong&gt;reduce cognitive overhead for developers&lt;/strong&gt;; the resultant styling systems are thus easier to learn, and this in turn helps frontend teams scale their efforts across people and time. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Classes which are immutable and free of side effects result in &lt;strong&gt;fewer bugs&lt;/strong&gt; — and where bugs occur, easier debugging and resolution follows.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these ways and in others, I have always felt that the nature of atomic CSS flows very much with the grain of CSS itself. Remember that CSS was designed to be independent of markup, and atomic CSS is by design untethered to any particular markup structure or content based semantics. Atomic CSS also honors CSS’ specificity algorithm rather than attempting to game it — it does not concern itself with optimized selector ranking or scope, since every class is of single purpose and equal specificity. This also means &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance" rel="noopener noreferrer"&gt;CSS’ inheritance model&lt;/a&gt; becomes an advantage as it was originally intended: compositions can be built up with inheritance in mind, over several layers of markup.&lt;/p&gt;

&lt;p&gt;There are, however, many common objections raised against the atomic CSS methodology. In general, these tend to be:&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;’It’s not semantic.’&lt;/dt&gt;
  &lt;dd&gt;We’ve touched on this already, but it’s worth repeating: semantics, accessibility, and clarity &lt;em&gt;do&lt;/em&gt; matter, but with all due respect to &lt;a href="https://www.zeldman.com/2017/01/03/kiss-my-classname/" rel="noopener noreferrer"&gt;Zeldman&lt;/a&gt;, there is nothing inherently unsemantic, inaccessible, or unclear about ‘visual class names’, nor is there a reason for CSS to map to the same semantics as HTML.&lt;/dd&gt;

  &lt;dt&gt;‘This is inline styles all over again.’&lt;/dt&gt;
  &lt;dd&gt;Nope. Inline styles are defined in HTML; atomic classes are defined in a style sheet. Inline styles do not permit media queries, pseudo elements, or pseudo classes; atomic classes do. Inline styles have a specificity ranking of 1-0-0-0, which can only be outranked by &lt;code&gt;!important&lt;/code&gt;; atomic classes have a specificity of 0-0-1-0, the same as any single class. An inline style’s source of truth is its own singular invocation on a given element; an atomic class’ source of truth is a style sheet. There is a lexical resemblance between &lt;code&gt;class='red'&lt;/code&gt; and &lt;code&gt;style='color: red'&lt;/code&gt;; this is where the similarities end.

  &lt;/dd&gt;
&lt;dt&gt;‘Putting so many classes on my elements looks ugly/is hard to read.’&lt;/dt&gt;
  &lt;dd&gt;Admittedly, &lt;code&gt;&amp;lt;section class='max-width-post-layout m-auto pt5 pr3 pb3 pl3'/&amp;gt;&lt;/code&gt; doesn’t read like poetry (and yes, that snippet is taken from this very page as of this writing). However, something that &lt;em&gt;is&lt;/em&gt; a delight is being able to rapidly iterate on this composition — from the logical origin of that composition (the markup), whether in the browser or my editor — to explore &lt;a href="https://www.colepeters.dev/posts/an-introduction-to-constraint-based-design-systems" rel="noopener noreferrer"&gt;different combinatorial spaces within the bounds of a design system&lt;/a&gt;. Iterating in this fashion simply cannot be matched when using other methodologies.&lt;/dd&gt;

  &lt;dt&gt;‘This is so not &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;DRY&lt;/a&gt;.’&lt;/dt&gt;
  &lt;dd&gt;It’s true, atomic CSS can lead to repeating &lt;em&gt;declarations&lt;/em&gt; of various styling rules — but I vastly prefer repeating declarations to repeating &lt;em&gt;definitions&lt;/em&gt; (which, in my experience, are much harder to maintain). Also, remember that every time you repeat a class name, that’s one more addition you didn’t have to make to your style sheet! Ultimately, this is a matter of choosing what kind of repetition you want, not one of avoiding repetition altogether.&lt;/dd&gt;

  &lt;dt&gt;‘Atomic CSS is at odds with modern component modeling.’&lt;/dt&gt;
  &lt;dd&gt;
&lt;a href="https://reactjs.org/docs/thinking-in-react.html" rel="noopener noreferrer"&gt;‘Thinking in React’&lt;/a&gt; is one of those articles that changed the way I thought about web development when it was published, and there’s no denying that building frontends on the web has become a component centric process. However, it’s important to differentiate the process of &lt;em&gt;thinking&lt;/em&gt; in components and the process of &lt;em&gt;styling&lt;/em&gt; components. A conceptual abstraction does not require an equivalent material abstraction, and the fact of a component’s existence does not necessitate a dedicated CSS class.

  &lt;/dd&gt;
&lt;dt&gt;‘This still doesn’t solve the problem of global scope or one off styles.’&lt;/dt&gt;
  &lt;dd&gt;It doesn’t, and in fact atomic CSS is not designed for this. For scoped or one off styles, a different approach is absolutely required.&lt;/dd&gt;

&lt;/dl&gt;

&lt;p&gt;Atomic CSS can provide a fantastic foundation that covers the vast majority of styling needs for a given website and its constituent components, and it can deliver those styles in a fraction of the file size and complexity of other methodologies. To be clear, these claims are not theoretical: this has been my experience both as a contributor and leader of frontend teams over the past 8 years, and the same has been true for many others both within and outside of my professional circle. But as we’ve noted, atomic CSS doesn’t cover every use case: scoped and one off styles are not part of its wheelhouse. So what’s to be done when a need for these sorts of styles emerges?&lt;/p&gt;

&lt;h2&gt;
  
  
  Going bespoke
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F66xn8dckik3dlg7zrrof.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F66xn8dckik3dlg7zrrof.jpg" alt="Photograph of a blacksmith working metal at a grinder." width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;small&gt;Photo by &lt;a href="https://unsplash.com/photos/AGhXDA_l8KI" rel="noopener noreferrer"&gt;Chris Ralston on Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Where one off styles are needed, or where we want to ensure certain styles are scoped to a given component, additional measures beyond an atomic CSS methodology will be required. There are several techniques that can be used to address these concerns, with a few notable examples having become more popular in recent years:&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;CSS in JS&lt;/dt&gt;
  &lt;dd&gt;The obvious contender in this list. I used CSS in JS for many years myself, and have to say the developer ergonomics are pretty impressive, as is the ability to leverage both repeatable and bespoke, scoped styles (especially when using libraries like &lt;a href="https://styled-system.com/" rel="noopener noreferrer"&gt;Styled System&lt;/a&gt; or &lt;a href="https://theme-ui.com/" rel="noopener noreferrer"&gt;Theme UI&lt;/a&gt;). Unfortunately, great developer ergonomics and scoping are not enough. CSS in JS can add significant weight to client side bundles, along with increased setup complexity (especially when server side rendering is involved). Some solutions can also lock you in to certain frontend frameworks, limiting the portability of your styles. There are some solutions emerging to address these concerns (e.g. &lt;a href="https://vanilla-extract.style/" rel="noopener noreferrer"&gt;Vanilla Extract&lt;/a&gt;), but at the end of the day, I admit I’m growing tired of learning abstractions of CSS — there are so many more valuable things I could be doing with my time. This isn’t necessarily a popular opinion, but I think CSS is actually pretty amazing on its own, and the closer to the metal I can stay, the happier I am.&lt;/dd&gt;

  &lt;dt&gt;CSS Modules&lt;/dt&gt;
  &lt;dd&gt;The name may suggest that CSS Modules are part of the CSS spec, but this is not the case. CSS Modules allow authors to extract styles from a vanilla &lt;code&gt;.css&lt;/code&gt; file and into a JavaScript file containing markup; at build time, these extracted styles are then regenerated as locally scoped styles wherever they are used. This seems to offer some of the benefits of CSS in JS, but without the ergonomics of colocating styles, content, and behavior within a given component.&lt;/dd&gt;

  &lt;dt&gt;Shadow DOM&lt;/dt&gt;
  &lt;dd&gt;Shadow DOM is a web standards specification which is designed to provide encapsulation of content, styles, and behavior — but it has a number of hard to swallow caveats. For one, Shadow DOM roots need to be initialized in JavaScript (though &lt;a href="https://web.dev/declarative-shadow-dom/" rel="noopener noreferrer"&gt;Declarative Shadow DOM&lt;/a&gt; should address this in the future.) Further, &lt;a href="https://lamplightdev.com/blog/2019/03/26/why-is-my-web-component-inheriting-styles/" rel="noopener noreferrer"&gt;styling encapsulation doesn’t work quite like you think it does&lt;/a&gt;, and this can cause some headaches. I believe the Shadow DOM holds promise, but for many use cases, it can end up being more trouble than it’s worth.&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;Fortunately, a compelling solution for dealing with scoped and one off styles exists in the form of HTML custom elements, which are part of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components" rel="noopener noreferrer"&gt;the web components spec&lt;/a&gt; along with Shadow DOM and HTML templates. I may be biased, but I think the best way to work with custom elements right now is with &lt;a href="https://enhance.dev/docs/" rel="noopener noreferrer"&gt;Enhance&lt;/a&gt; (though to be fair, I got a sneak peak at Enhance before joining Begin in 2022, and was just as enthusiastic at that time).&lt;/p&gt;

&lt;p&gt;Using Enhance to author custom elements in the form of &lt;a href="https://enhance.dev/docs/learn/concepts/single-file-components" rel="noopener noreferrer"&gt;Single File Components&lt;/a&gt; (SFCs) has a number of huge benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Custom elements are expanded on the server, providing &lt;strong&gt;great performance and an excellent baseline for progressive enhancement&lt;/strong&gt; on the client.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Locally scoped, one off styles&lt;/strong&gt; can be authored simply by including a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; block in your SFC. When your component is expanded on the server, these style blocks will be hoisted into the document head, with all of that style block’s selectors scoped to your custom element. This allows for one off styles to be encapsulated and scoped to the component they’re authored in, without needing to touch the Shadow DOM. Scoped styles written within an SFC are also a great place to leverage strategies like &lt;a href="https://css-tricks.com/are-we-in-a-new-era-of-web-design-what-do-we-call-it/" rel="noopener noreferrer"&gt;intrinsic design&lt;/a&gt;, which can happily coexist alongside a global, atomic class system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you don’t need to write client side behavior, &lt;strong&gt;you never have to interface with JavaScript classes or the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry" rel="noopener noreferrer"&gt;Custom Elements Registry&lt;/a&gt;&lt;/strong&gt;. This is particularly handy for engineers (or designers) who might excel at HTML and CSS but lack experience in JavaScript. Although SFCs are authored as JavaScript functions, the bulk of the authored code is written in HTML and CSS, as seen below:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// my-button.mjs&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyButton&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;style&amp;gt;
      /* One off styles applied only to button elements rendered by MyButton. */
      /* Any button outside this component will not be affected. */
      button {
        appearance: none;
        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
      }
    &amp;lt;/style&amp;gt;
    &amp;lt;!-- Atomic classes used for repeating styles --&amp;gt;
    &amp;lt;button class='p1 radius-pill cursor-pointer'&amp;gt;
      &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
    &amp;lt;/button&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// index.html&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Click&lt;/span&gt; &lt;span class="nx"&gt;Me&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/my-button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, one need not use Enhance to gain the benefits of using custom elements. Being &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements" rel="noopener noreferrer"&gt;a web platform standard&lt;/a&gt;, you could author the above component and scoped styles without the use of Enhance — it would simply involve writing more boilerplate (although server rendering would be harder to implement from scratch). My personal experience, however, is that the current implementation of custom elements (and web components as a whole) leaves a fair bit to be desired, influenced as it is by the JavaScript framework wars of years past. Perhaps one day we’ll have a more HTML centric spec for web components (as hinted at by specs like Declarative Shadow DOM), but for now, I find the abstractions provided by Enhance to be incredibly useful and pleasant to use.&lt;/p&gt;

&lt;p&gt;Enhance also comes with &lt;a href="https://enhance.dev/docs/learn/concepts/styling/utility-classes" rel="noopener noreferrer"&gt;an atomic CSS system out of the box&lt;/a&gt;, which can be easily customized to integrate with design systems or brand guidelines. Enhance thus presents an end to end styling solution that offers all the benefits of atomic CSS as well as the power to easily create one off, locally scoped styles on a per component basis. (You may have also realized that, since SFC styles are authored within a JavaScript file, those style blocks can also take advantage of some CSS in JS niceties — such as leveraging JS variables or functions — without authors having to worry about client side performance. While I’ve yet to find much of a need for this, the possibility is there.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Summing up
&lt;/h2&gt;

&lt;p&gt;We’ve covered a lot of ground in this article — some of it historical, some of it subjective. Although I’ve used a lot of words to describe the benefits that I and many others have encountered with atomic CSS in comparison to other methodologies, I do want to assert that, as with so much on the web, your mileage may vary. Technical methodologies of all kinds inherently attract certain folks and repel others, and &lt;a href="https://adactio.com/journal/18982" rel="noopener noreferrer"&gt;as Jeremy Keith has said&lt;/a&gt;, ‘this is about matching the right tool to the right mindset’ (though, with the deepest respect to Jeremy, I look forward to rebutting some other aspects of his article in the near future).&lt;/p&gt;

&lt;p&gt;With that said, I’ve found that a great deal of misinformation has been shared over the years concerning atomic CSS, and I think this has helped to create a mindset that may have kept many web professionals from giving this methodology a fair shake. As a thorough foundation for styling — especially when configured to align with a team’s design system — atomic CSS is tough to beat in terms of its performance, flexibility, and robustness across scales of complexity and time. In combination with a tight strategy for dealing with one off or scoped styles (as with Enhance SFCs), atomic CSS can act as a powerful styling API for documents, applications, and components, which will serve individuals and teams (and thus end users) well for a long time to come.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/Style/CSS20/history.html" rel="noopener noreferrer"&gt;A Brief History of CSS Until 2016&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mrmrs.cc/writing/what-are-classes-for/" rel="noopener noreferrer"&gt;What Are Classes For?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mrmrs.cc/writing/scalable-css" rel="noopener noreferrer"&gt;CSS and Scalability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jxnblk.com/blog/designing-in-the-browser-faster/" rel="noopener noreferrer"&gt;Designing in the Browser Faster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://css-tricks.com/growing-popularity-atomic-css/" rel="noopener noreferrer"&gt;On the Growing Popularity of Atomic CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bradfrost.com/blog/post/lets-talk-about-web-components/?ck_subscriber_id=478716172" rel="noopener noreferrer"&gt;Let's Talk About Web Components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>discuss</category>
      <category>security</category>
      <category>json</category>
    </item>
  </channel>
</rss>
