<?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: Suzanne Aitchison</title>
    <description>The latest articles on DEV Community by Suzanne Aitchison (@s_aitchison).</description>
    <link>https://dev.to/s_aitchison</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%2F197075%2Fac841cbd-abbb-4760-be69-6909cef48656.jpg</url>
      <title>DEV Community: Suzanne Aitchison</title>
      <link>https://dev.to/s_aitchison</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/s_aitchison"/>
    <language>en</language>
    <item>
      <title>What Cypress E2E testing has taught us about our code</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Wed, 18 May 2022 16:24:40 +0000</pubDate>
      <link>https://dev.to/devteam/what-cypress-e2e-testing-has-taught-us-about-our-code-5aco</link>
      <guid>https://dev.to/devteam/what-cypress-e2e-testing-has-taught-us-about-our-code-5aco</guid>
      <description>&lt;p&gt;A little over a year ago Forem introduced End to End testing to the app using &lt;a href="https://go.cypress.io"&gt;Cypress&lt;/a&gt;. Since that time we've definitely evolved our understanding of Cypress, but we've also learned a lot about our app too. So much so, that I wanted to share some of the things Cypress testing has taught us 😄&lt;/p&gt;

&lt;h2&gt;
  
  
  Some background
&lt;/h2&gt;

&lt;p&gt;We use Cypress alongside &lt;a href="https://github.com/testdouble/cypress-rails"&gt;cypress-rails&lt;/a&gt; and &lt;a href="https://testing-library.com/docs/cypress-testing-library/intro/"&gt;Cypress Testing Library&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Testing Library gives us a range of enhanced queries that help us focus on testing in a way that closely mirrors how users interact with the app. Instead of targeting page elements with e.g. CSS selectors or IDs, &lt;a href="https://developers.forem.com/tests/e2e-tests#finding-elements"&gt;we lean towards finding elements by their role and accessible name&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;When I talk about testing with Cypress at Forem, I mean specifically testing with Cypress and Cypress Testing Library 😄&lt;/p&gt;

&lt;h2&gt;
  
  
  A greater awareness of accessibility
&lt;/h2&gt;

&lt;p&gt;I've made no secret of &lt;a href="https://dev.to/s_aitchison/accessibility-first-integration-tests-with-react-testing-library-5fk"&gt;personally being a huge fan of Testing Library&lt;/a&gt; for the impact it can have on accessibility testing and awareness. It therefore isn't a surprise to me that we have learned &lt;em&gt;a lot&lt;/em&gt; about the accessibility of our app since introducing E2E tests with Cypress.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uniqueness of elements
&lt;/h3&gt;

&lt;p&gt;One of the challenges we've had with testing some older views in Cypress is discovering that the same element/name combination is present multiple times.&lt;/p&gt;

&lt;p&gt;A great example of this is the "Follow" buttons throughout the app. We tried to target a Follow button in test with &lt;code&gt;cy.findByRole('button', { name: 'Follow' }&lt;/code&gt; and found it incredibly difficult to target the one we wanted.&lt;/p&gt;

&lt;p&gt;This immediately flagged itself up as an accessibility issue - users of screen readers would hear every follow button announced as "Follow"... but, follow who? 😅 Just as we were struggling to target the right button in test, a screen reader user would struggle to target the button of the user they wanted to follow.&lt;/p&gt;

&lt;p&gt;Big shout out to community member &lt;a class="mentioned-user" href="https://dev.to/keshavbiswa"&gt;@keshavbiswa&lt;/a&gt; for making &lt;a href="https://github.com/forem/forem/pull/14389"&gt;improvements to the follow buttons accessible names&lt;/a&gt; after we discovered this!&lt;/p&gt;

&lt;h3&gt;
  
  
  The power of landmarks
&lt;/h3&gt;

&lt;p&gt;We take advantage of Cypress' scoping abilities by writing code like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Everything in here is scoped to the "main" element only&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, the &lt;code&gt;within&lt;/code&gt; helper allows us to scope the test to all the HTML inside the &lt;code&gt;main&lt;/code&gt; landmark. This can be really handy and powerful! For example, when we wanted to test the main feed navigation, we could do:&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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;View posts by&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Relevant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;relevant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@relevant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.attr&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;aria-current&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;page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We were able to go directly to the feed navigation landmark with ease. This is cool for testing, but it's also been great for building awareness and understanding of how important landmarks and their labels can be for screen reader users. Just like we could skip directly to the "View posts by" navigation in Cypress, screen reader users can skip directly there with their screen reader too! Great news for anyone that wants to get straight to the latest posts 😄&lt;/p&gt;

&lt;h3&gt;
  
  
  Headings, headings, headings
&lt;/h3&gt;

&lt;p&gt;Headings play an important role in accessibility (I have &lt;a href="https://dev.to/s_aitchison/getting-heading-levels-right-including-here-on-dev-439"&gt;an older post that digs into this a bit&lt;/a&gt;), and they've become really important to our tests too.&lt;/p&gt;

&lt;p&gt;One struggle we had with Cypress was that &lt;em&gt;sometimes&lt;/em&gt; when we followed a link to a new page, the new page wouldn't have loaded yet before Cypress tried to click some button/check for some behaviour.&lt;/p&gt;

&lt;p&gt;For example, we had some test code:&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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Test article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// After clicking the link we can _sometimes_ accidentally get a reference to the 'main' element on the page we just left&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Share post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Cypress fails to find the 'Share post' button inside the previous page's `main` element, and the test fails&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since a &lt;code&gt;main&lt;/code&gt; existed on both the first and the second page, Cypress would occasionally get a reference to the wrong one 🙈&lt;/p&gt;

&lt;p&gt;One way that we prevent this type of flake now is to make sure we look for an element that is unique to the new page, before continuing our test steps. The example above would become:&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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Test article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Wait for the correct h1 element&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Test article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Now we can be sure the main below is the right one!&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Share post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Needless to say, this in turn has made us more aware of pages &lt;em&gt;missing&lt;/em&gt; headings, or headings with the wrong level, duplicated, etc. All of which is helping us tighten up our accessibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Javascript races 🏁
&lt;/h2&gt;

&lt;p&gt;Aside from these accessibility concerns, we've also become very aware of some side effects of how we handle our Javascript.&lt;/p&gt;

&lt;p&gt;We try our best to make sure our pages load quickly, with Javascript asynchronously layered on after the first page load to enhance functionality. In reality, this means it takes &lt;em&gt;some amount&lt;/em&gt; of time before some elements on the page are fully initialized and clickable.&lt;/p&gt;

&lt;p&gt;For example, when the logged out version of the home feed first becomes visible, the author names don't yet have a click handler to trigger the appearance of the profile preview card - we &lt;a href="https://github.com/forem/forem/blob/main/app/javascript/previewCards/feedPreviewCards.jsx"&gt;initialize this functionality after the page first loads&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a human, it's pretty hard to click the button before it's initialized (largely because the page load is pretty fast 😄), but it's &lt;em&gt;technically&lt;/em&gt; possible. Certainly Cypress is speedy enough to do this sometimes!&lt;/p&gt;

&lt;p&gt;We've been working around this with a variety of solutions, including using &lt;a href="https://github.com/NicholasBoll/cypress-pipe"&gt;cypress-pipe&lt;/a&gt; which lets us retry a click until a certain condition is met. For example, in the case of the feed preview cards we can do:&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="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Admin McAdmin profile details&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.attr&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;aria-expanded&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;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Cypress - click this button until &lt;code&gt;aria-expanded&lt;/code&gt; is equal to &lt;code&gt;'true'&lt;/code&gt; (meaning the preview card has opened). Don't worry - it won't retry forever - Cypress has a default waiting period for a condition to become true, and &lt;code&gt;pipe&lt;/code&gt; will only retry for that long.&lt;/p&gt;

&lt;p&gt;It's definitely made us think a lot more about progressive enhancement, and what is and isn't usable from the very first moment of page load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to know more about E2E testing at Forem?
&lt;/h2&gt;

&lt;p&gt;We've been adding to our &lt;a href="https://developers.forem.com/tests/e2e-tests"&gt;E2E testing docs&lt;/a&gt; as we learn and evolve our approach, including the "gotchas" we've found along the way - feel free to check it out!&lt;/p&gt;

&lt;p&gt;I'd love to hear about your experiences with Cypress and Testing Library too!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>testing</category>
      <category>a11y</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Assistive technologies your users might be using</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Fri, 25 Mar 2022 08:12:56 +0000</pubDate>
      <link>https://dev.to/s_aitchison/assistive-technologies-your-users-might-be-using-1af9</link>
      <guid>https://dev.to/s_aitchison/assistive-technologies-your-users-might-be-using-1af9</guid>
      <description>&lt;p&gt;I've found that when developers talk about accessibility, we often make the assumption that accessibility === screen readers. In reality, not all users with disabilities use assistive technologies, and not all assistive technologies are screen readers!&lt;/p&gt;

&lt;p&gt;This post collects some demo videos I've found helpful in learning how some of the most popular assistive technologies help users access our apps and engage with our code. &lt;/p&gt;

&lt;p&gt;If you know any other good demos, please do link them in the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  Screen reader
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Reads the content of the screen aloud&lt;/li&gt;
&lt;li&gt;Provides shortcuts to skip to certain parts of the page or perform certain actions&lt;/li&gt;
&lt;li&gt;Provides contextual information about control types and state&lt;/li&gt;
&lt;li&gt;Popular screen readers include &lt;a href="https://www.freedomscientific.com/products/software/jaws/"&gt;JAWS&lt;/a&gt; (Windows), &lt;a href="https://www.nvaccess.org/"&gt;NVDA&lt;/a&gt; (Windows), &lt;a href="https://www.apple.com/voiceover/info/guide/_1121.html"&gt;VoiceOver&lt;/a&gt; (macOS and iOS) and &lt;a href="https://support.google.com/accessibility/android/answer/6283677?hl=en-GB"&gt;Talkback&lt;/a&gt; (android mobile).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this video, Léonie Watson discusses and demonstrates how she uses a screen reader:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/OUDV1gqs9GA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Refreshable braille display
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Can be used in conjunction with a screen reader, with screen reader output translated into braille&lt;/li&gt;
&lt;li&gt;May also support typing in braille&lt;/li&gt;
&lt;li&gt;Some devices have their own integrated apps for e.g. note-taking, calculations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out this demonstration and Q &amp;amp; A with a braille display user:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/dV0KZtVmcIo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Screen magnification software
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Allows users to control the size of text and graphics on the screen&lt;/li&gt;
&lt;li&gt;Allows users to change cursor size, focus styles, and the colors used on the screen&lt;/li&gt;
&lt;li&gt;Some magnification software has basic screen reading capabilities built-in too&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This video demonstrates (an old version of) ZoomText, a popular screen magnification tool:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/EEN79RRvKqE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Switch controls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Primarily used by users with motor disabilities&lt;/li&gt;
&lt;li&gt;Multiple formats, but commonly a large button&lt;/li&gt;
&lt;li&gt;"Sip and puff" and movement sensors are also common formats of switch controls&lt;/li&gt;
&lt;li&gt;A focus indicator moves through items on the screen, pressing the switch activates the control&lt;/li&gt;
&lt;li&gt;Multiple switches can be used in combination&lt;/li&gt;
&lt;li&gt;Some mobile devices now have gesture-based switches using the camera&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out Christopher Hills using a two switch set up with an iPhone:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/L2HbPVRVH3s"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Speech input software
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Provides a speech-operated method for typing and controlling the computer&lt;/li&gt;
&lt;li&gt;Allows users to execute actions using voice commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See a demo of the popular Dragon NaturallySpeaking:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/kJKQmTumFP0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You can also check out Josh W Comeau's write up on their experience using speech input for software development: &lt;a href="https://www.joshwcomeau.com/blog/hands-free-coding/"&gt;"Hands-Free Coding"&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you have any others to share?
&lt;/h2&gt;

&lt;p&gt;If you've seen any other great demos of assistive tech, please do drop them in the comments! &lt;/p&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
    </item>
    <item>
      <title>5 years in tech, and she's still coding</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Tue, 08 Mar 2022 13:29:07 +0000</pubDate>
      <link>https://dev.to/s_aitchison/5-years-in-tech-and-shes-still-coding-89l</link>
      <guid>https://dev.to/s_aitchison/5-years-in-tech-and-shes-still-coding-89l</guid>
      <description>&lt;p&gt;The &lt;a href="https://dev.to/t/shecoded"&gt;#shecoded&lt;/a&gt; prompt this year made me realise that 2022 officially marks 5 years in tech for me! What better time to reflect on my experience so far 😄 &lt;/p&gt;

&lt;h2&gt;
  
  
  The career switch
&lt;/h2&gt;

&lt;p&gt;Back in 2016 I was doing well for myself career-wise. I'd pretty successfully turned a wandering few years as an English as a Foreign Language (EFL) teacher into a career in supporting overseas students to study in the UK. I'd been promoted regularly, managed a great team, and felt like I was genuinely helping some people. But I just felt so "meh" about my work, and had done for such a long time. I didn't want to feel that way about something I did for 40 hours a week.&lt;/p&gt;

&lt;p&gt;I spent &lt;em&gt;such&lt;/em&gt; a long time analysing what I enjoyed, what I was good at, and where the intersection of those two things lay (spoiler: I am not great at &lt;em&gt;many&lt;/em&gt; things I enjoy 😄). I knew I wanted to &lt;em&gt;make&lt;/em&gt; things, but I knew that my talents weren't really artistic, more logical/analytical (I'd previously pivoted away from a career in puzzle editing!).&lt;/p&gt;

&lt;p&gt;I had a little lightbulb moment remembering how I used to love creating geocities websites (I think the first one was a Will Smith fan site 😆). A few months of ploughing through &lt;a href="https://www.codecademy.com/"&gt;Codecademy&lt;/a&gt; tutorials and I was convinced - I was going to be a Software Developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better late than never
&lt;/h2&gt;

&lt;p&gt;My high school ran Computer Science classes that I never enrolled in. Instead I was pushed toward "Office and Information Skills" (i.e. making spreadsheets and word processor docs). It kinda makes me sad to think I could have started in tech so much earlier, if people had recognised how well suited I was to Comp Sci. I'm gonna add as well - &lt;strong&gt;I made my school a website&lt;/strong&gt;, and still nobody thought to suggest Computer Science to me 🙃&lt;/p&gt;

&lt;p&gt;I don't think anyone actively tried to exclude me from that Comp Sci class, but I'm also pretty sure if I wasn't a girl I would have ended up in it. Any subconscious bias that existed in my teachers also existed in me - it's not like I was beating down the door of that class. I just didn't know it could be for me 🤷‍♀️&lt;/p&gt;

&lt;p&gt;Our conditioning into what's "for" us starts so early, and breaking the bias requires conscious effort. It's great to see the work &lt;a href="https://girlswhocode.com/"&gt;Girls Who Code&lt;/a&gt; and so many other similar organisations are doing in this area.&lt;/p&gt;

&lt;h2&gt;
  
  
  My five years in tech
&lt;/h2&gt;

&lt;p&gt;On the whole, the last 5 years has been amazing, and I have zero regrets about career-changing into tech. However I've definitely come up against some challenges that I believe are symptomatic of bias in the industry. Let's have a quick run-down...&lt;/p&gt;

&lt;h3&gt;
  
  
  The bad stuff: a - thankfully - brief list
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Very often being noticeably the only woman in a room (one time in particular sticks in my mind - 11 men, and little ol' me presenting some work to them)&lt;/li&gt;
&lt;li&gt;Having questions directed at a (male) colleague, after I'd presented and demonstrated the code I worked on&lt;/li&gt;
&lt;li&gt;Being assumed to take notes in a meeting&lt;/li&gt;
&lt;li&gt;Being asked to make teas and coffees for others in a meeting&lt;/li&gt;
&lt;li&gt;Being mistaken for a designer, despite having introduced myself with my job title&lt;/li&gt;
&lt;li&gt;Having my (dismissed) ideas reframed by a man in the same meeting&lt;/li&gt;
&lt;li&gt;Having a male client give me what can only be described as an uninvited "in person" code review &lt;/li&gt;
&lt;li&gt;Having my own code explained back to me as if I didn't just write it 😆&lt;/li&gt;
&lt;li&gt;Being told that although a promotion would match up with my current responsibilities it "wouldn't be credible" to anyone looking in from the outside&lt;/li&gt;
&lt;li&gt;Getting one or two creepy DMs any time I give a meet up talk/workshop/etc&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The good stuff
&lt;/h3&gt;

&lt;p&gt;On the whole I've been pretty blessed with great colleagues throughout my tech journey.&lt;/p&gt;

&lt;p&gt;That time a question got fired at my male colleague instead of me, the presenter? My colleague simply passed the mic to me, pointing out I was the one in the know.&lt;/p&gt;

&lt;p&gt;That time my ideas got reframed by a man in the same meeting? Another male colleague pointed out what had just happened, the person was mortified and apologetic and in the end we all laughed.&lt;/p&gt;

&lt;p&gt;The teas and coffees? I pointed it out to my manager (the CEO of the company) who then made a point of &lt;em&gt;always&lt;/em&gt; doing this himself, for every client meeting.&lt;/p&gt;

&lt;p&gt;Oh and that time I was told a promotion wasn't credible? Well, I left that job and got a tidy pay raise to go somewhere else 😝&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking the bias
&lt;/h2&gt;

&lt;p&gt;I know it isn't such a smooth path for everyone, but what I want to stress in this post is the positive impact many of my colleagues have had throughout my journey. I've been lucky to have people around me to call out bias when they see it, listen and make changes to their own behaviour, and advocate for others that might be getting overlooked.&lt;/p&gt;

&lt;p&gt;I strive to do the same for others, and hope you will too ❤️&lt;/p&gt;

</description>
      <category>wecoded</category>
      <category>career</category>
    </item>
    <item>
      <title>Feature update: tag selector</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Fri, 21 Jan 2022 10:31:16 +0000</pubDate>
      <link>https://dev.to/devteam/feature-update-tag-selector-41nf</link>
      <guid>https://dev.to/devteam/feature-update-tag-selector-41nf</guid>
      <description>&lt;p&gt;Hello DEV community!&lt;/p&gt;

&lt;p&gt;Just a quick update to let you know about a new feature we've rolled out: a new tag selector for the V2 editor 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;The base functionality of the selector hasn't changed too much - you should be able to click into the tags field, see suggestions of "Top tags" and type to search for the tag you want.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a tag
&lt;/h3&gt;

&lt;p&gt;You can add a tag to your post by doing one of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clicking a tag that appears as a suggestion in the dropdown&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;Up&lt;/code&gt;/&lt;code&gt;Down&lt;/code&gt; arrow keys to highlight a suggestion and pressing &lt;code&gt;Enter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Typing a &lt;code&gt;,&lt;/code&gt; or space after your tag name (e.g. typing "javascript,"&lt;/li&gt;
&lt;li&gt;Typing your tag name and simply clicking elsewhere in the page to "move away" from the selector&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Editing or removing a tag
&lt;/h3&gt;

&lt;p&gt;Once your tag is added, it should be clearly reflected in the UI as a set of buttons. Clicking the 'X' will remove the tag, and clicking the tag's text will put it into "edit mode" for you to make any changes.&lt;/p&gt;

&lt;p&gt;If you are currently typing a long list of tags, you can also pop your previously added tag into "edit mode" by pressing &lt;code&gt;Backspace&lt;/code&gt; while you're in the empty text field.&lt;/p&gt;

&lt;h2&gt;
  
  
  What problems does it solve?
&lt;/h2&gt;

&lt;p&gt;We've had reports of various issues with the previous tag selector, which we believe this new version resolves. Particularly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It wasn't very clear when a tag had been successfully added&lt;/li&gt;
&lt;li&gt;If you added a lot of long tags, the field scrolled horizontally and became hard to read/edit&lt;/li&gt;
&lt;li&gt;The old selector created a "focus trap" on the page - when you used the keyboard to navigate through the form you would get stuck in the tags field&lt;/li&gt;
&lt;li&gt;It didn't provide any feedback to screen reader users, and didn't conform to a lot of accessibility best practices for this kind of component&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Future plans
&lt;/h2&gt;

&lt;p&gt;The new tag selector is built using a new component: &lt;code&gt;&amp;lt;MultiSelectAutocomplete /&amp;gt;&lt;/code&gt; which is designed to be reusable in other areas, including with items other than tags. In the near future you should start to see this component pop up in other places in DEV!&lt;/p&gt;

&lt;h2&gt;
  
  
  That's it!
&lt;/h2&gt;

&lt;p&gt;If you'd like to check out the code, have a look at &lt;a href="https://github.com/forem/forem/issues/14845"&gt;this epic in GitHub&lt;/a&gt; which links to the incremental PRs that rolled this out. You'll also find the component in &lt;a href="https://storybook.forem.com/"&gt;our Storybook&lt;/a&gt;, including documentation on how to implement it.&lt;/p&gt;

&lt;p&gt;We look forward to hearing how folks find the new tag selection experience, and if you have any feedback or questions please let us know!&lt;/p&gt;

</description>
      <category>meta</category>
      <category>product</category>
      <category>ux</category>
      <category>changelog</category>
    </item>
    <item>
      <title>How we made the markdown toolbar</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Thu, 02 Dec 2021 14:53:08 +0000</pubDate>
      <link>https://dev.to/devteam/how-we-made-the-markdown-toolbar-4f09</link>
      <guid>https://dev.to/devteam/how-we-made-the-markdown-toolbar-4f09</guid>
      <description>&lt;p&gt;You might have seen a new feature arrive in the editor this week - the markdown toolbar:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/devteam" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg" alt="The DEV Team"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F680513%2F3b2fc9a0-763a-4060-b7b5-f4706bc9c5b0.jpeg" alt=""&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/devteam/feature-update-markdown-toolbar-2lma" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Feature update: Markdown Toolbar&lt;/h2&gt;
      &lt;h3&gt;Amy Lin for The DEV Team ・ Nov 29 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#meta&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#product&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ux&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#changelog&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;As a follow up to Amy's post, I wanted to share a little bit about how we approached the development of the toolbar component, and some of the technical considerations we've had in mind during the implementation.&lt;/p&gt;

&lt;p&gt;Quick contents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storybook for sandboxed development&lt;/li&gt;
&lt;li&gt;Core functionality: insert and undo formatting&lt;/li&gt;
&lt;li&gt;Thinking about keyboard interactions&lt;/li&gt;
&lt;li&gt;Changes to image upload&lt;/li&gt;
&lt;li&gt;Final thoughts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Storybook for sandboxed development
&lt;/h2&gt;

&lt;p&gt;As much as possible, we like to create features in small incremental Pull Requests. It helps us make PRs more easily reviewable, and allows us to get feedback and adjust course as early in an implementation as possible.&lt;/p&gt;

&lt;p&gt;However, we don't want to ship incomplete features to DEV or any other Forem! Instead, we built out the markdown toolbar &lt;a href="https://storybook.forem.com/?path=/story/app-components-markdowntoolbar--default" rel="noopener noreferrer"&gt;in our Storybook&lt;/a&gt;. This gave us a sandboxed environment where we had access to all of our design system classes, components, etc, without having to actually add the toolbar to the editor (so now you know where to look if you want to creep on new frontend features in development 🤓).&lt;/p&gt;

&lt;p&gt;There were a couple of extra benefits to this approach, namely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use &lt;a href="https://storybook.js.org/addons/@storybook/addon-a11y/" rel="noopener noreferrer"&gt;&lt;code&gt;@storybook/addon-a11y&lt;/code&gt;&lt;/a&gt; which gave us continuous accessibility feedback as we built the component&lt;/li&gt;
&lt;li&gt;We were able to easily share the "work in progress" across our team, since although the code wasn't "live" in the app, it was "live" in Storybook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're new to Storybook, I'd recommend checking out &lt;a href="https://www.iamdeveloper.com/pages/talks/#heading-storybook-2021" rel="noopener noreferrer"&gt;this talk from @nickytonline&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Core functionality: insert and undo formatting
&lt;/h2&gt;

&lt;p&gt;The core functionality of the toolbar is to insert and remove formatting, and you can find the code responsible for this in &lt;a href="https://github.com/forem/forem/blob/main/app/javascript/crayons/MarkdownToolbar/markdownSyntaxFormatters.js" rel="noopener noreferrer"&gt;markdownSyntaxFormatters.js&lt;/a&gt;. The logic is all contained in this helper file, keeping it separate from the Preact component itself, to allow for better readability and testability (there are well over a hundred tests for this utility file!). &lt;/p&gt;

&lt;h3&gt;
  
  
  Grouping formatters
&lt;/h3&gt;

&lt;p&gt;We grouped the formatters broadly into two categories - inline (e.g. &lt;code&gt;**bold**&lt;/code&gt;, &lt;code&gt;_italic_&lt;/code&gt;) and multi-line (e.g. code blocks, lists). In the end, most of the formatters rely on two core functions: &lt;a href="https://github.com/forem/forem/blob/main/app/javascript/crayons/MarkdownToolbar/markdownSyntaxFormatters.js#L194" rel="noopener noreferrer"&gt;&lt;code&gt;undoOrAddFormattingForInlineSyntax&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/forem/forem/blob/main/app/javascript/crayons/MarkdownToolbar/markdownSyntaxFormatters.js#L246" rel="noopener noreferrer"&gt;&lt;code&gt;undoOrAddFormattingForMultilineSyntax&lt;/code&gt;&lt;/a&gt;. This means that most formatters can call the same function, just passing along what prefix and suffix is expected, e.g. the bold formatter looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;undoOrAddFormattingForInlineSyntax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;selectionStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// where the user's selected text starts&lt;/span&gt;
  &lt;span class="nx"&gt;selectionEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// where the user's selected text ends &lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the current text area value&lt;/span&gt;
  &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the formatting expected before selection&lt;/span&gt;
  &lt;span class="na"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// the formatting expected after selection&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Outliers to the groupings
&lt;/h3&gt;

&lt;p&gt;There are a couple of formatters which don't fall neatly into the two groups mentioned above, namely Heading and Link.&lt;/p&gt;

&lt;p&gt;The Heading formatter has special functionality, where the heading level is incremented on each click, up until a maximum of heading level 4, after which it removes the formatting completely.&lt;/p&gt;

&lt;p&gt;Similarly the Link formatter adjusts its behaviour depending on whether your selected text is a URL or not. Since they don't readily fit into the &lt;code&gt;undoOrAddFormattingForInlineSyntax&lt;/code&gt; or &lt;code&gt;undoOrAddFormattingForMultilineSyntax&lt;/code&gt; functions, they have their own custom code instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Allowing for formatting to be removed
&lt;/h3&gt;

&lt;p&gt;On face value, the core function of handling a button press is pretty straightforward - add the prefix before the selected text, and the suffix after it. However, we had a few additional cases to consider, for example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fp10sbr1t81q6kufhfdnu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fp10sbr1t81q6kufhfdnu.png" alt="Editor text "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the user's selected text is "hello world", but the characters immediately &lt;em&gt;before&lt;/em&gt; and &lt;em&gt;after&lt;/em&gt; the selection match the prefix/suffix, we want to remove the formatting. In this example above, the highlighted "hello world" should remain, and the stars on either side should be removed (rather than formatting it as bold for a second time and producing &lt;code&gt;****hello world****&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fps0yiczdyv3m6kkglo7n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fps0yiczdyv3m6kkglo7n.png" alt="Editor text with "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the user's selected text &lt;em&gt;includes&lt;/em&gt; the prefix/suffix, we also want to remove the formatting. In the example here, &lt;code&gt;**hello world**&lt;/code&gt; should become "hello world".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F82h7e5owog2lrkbg7vcc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F82h7e5owog2lrkbg7vcc.png" alt="A markdown link in the editor, displayed three ways. One fully highlighted, one with the URL only highlighted, and one with the link description only highlighted"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both of the above considerations become more complex in certain cases like links, where the user's selected text could be the URL, or the link description, or the entire format from beginning to end. For example, given the link &lt;code&gt;[my link text](http://myurl.com)&lt;/code&gt;, we want to remove the whole link formatting whether the user has selected "my link text", or "&lt;a href="http://myurl.com" rel="noopener noreferrer"&gt;http://myurl.com&lt;/a&gt;", or the full link including both parts.&lt;/p&gt;

&lt;p&gt;The upshot is that we need to check both the selected text, but also the text before and after the current selection before we decide what to do with the button press. We've favoured being a bit more verbose in the code to be clear about what we're doing at each stage of these checks, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectedTextAlreadyFormatted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;selectedText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prefixLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;selectedText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;suffixLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedTextAlreadyFormatted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// return the appropriate result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;surroundingTextHasFormatting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;textBeforeSelection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textBeforeSelection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;prefixLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt;
      &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;textAfterSelection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;suffixLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;surroundingTextHasFormatting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// return the appropriate result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would definitely be possible to make our formatter code terser, but we've veered on the side of readability so that the code is more maintainable and easier to contribute to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maintaining correct cursor position/text selection
&lt;/h3&gt;

&lt;p&gt;The final consideration on button press is making sure the user's text selection remains consistent after we use a formatter. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fqbv8422o93qbaub0ts2h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fqbv8422o93qbaub0ts2h.png" alt="Illustration showing that once we add two stars before a word to make it bold, its character position has increased by 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the user has text selected, we want to make sure it stays selected after adding/removing the formatting. Given the length of the text area value changes after adding/removing the formatting (e.g. adding or removing "**"), this means we have to calculate the indexes of the selection's new start and end point.&lt;/p&gt;

&lt;p&gt;If the user doesn't have text selected, we want to make sure their cursor is placed &lt;em&gt;inside&lt;/em&gt; the new formatting, ready to keep typing. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Frv4ds41jmoxn5e6ur7xa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Frv4ds41jmoxn5e6ur7xa.png" alt="Text that says "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In cases like links, we adjust where we place the cursor depending on whether a link description or URL already exists. For example, if you select the text &lt;code&gt;http://myurl.com&lt;/code&gt; and press the link button, you'll see this update to &lt;code&gt;[](http://myurl.com)&lt;/code&gt; and notice your cursor is placed inside the square brackets, ready to write the description. Conversely, if your selected text was "my awesome portfolio", you'll see &lt;code&gt;[my awesome portfolio](url)&lt;/code&gt;, with the placeholder "url" selected, ready for you to replace it with the link's actual URL.&lt;/p&gt;

&lt;p&gt;In the end then, all of our formatters return an object detailing all the information the Preact component needs to update the text area, including the properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;
&lt;span class="nx"&gt;editSelectionStart&lt;/span&gt; &lt;span class="c1"&gt;// The start index of the text we will replace&lt;/span&gt;
&lt;span class="nx"&gt;editSelectionEnd&lt;/span&gt; &lt;span class="c1"&gt;// The end index of the text we will replace&lt;/span&gt;
&lt;span class="nx"&gt;replaceSelectionWith&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// The new text to place between the editSelectionStart and editSelectionEnd&lt;/span&gt;
&lt;span class="nx"&gt;newCursorStart&lt;/span&gt; &lt;span class="c1"&gt;// Start index of new cursor selection&lt;/span&gt;
&lt;span class="nx"&gt;newCursorEnd&lt;/span&gt; &lt;span class="c1"&gt;// End index of new cursor selection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Thinking about keyboard interactions
&lt;/h2&gt;

&lt;p&gt;I'll preface this section by mentioning that there is a known bug on our editor page, in that there is a focus trap if you press the &lt;code&gt;Tab&lt;/code&gt; key and activate the tags input. Development to &lt;a href="https://github.com/forem/forem/issues/14845" rel="noopener noreferrer"&gt;replace the tags autosuggest component with an accessible version&lt;/a&gt; is underway and we aim to have this resolved very soon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Roving tabindex
&lt;/h3&gt;

&lt;p&gt;The markdown toolbar follows the &lt;a href="https://www.w3.org/TR/wai-aria-practices/#toolbar" rel="noopener noreferrer"&gt;toolbar authoring practices&lt;/a&gt;, and a substantial part of this is making it appropriately navigable by keyboard.&lt;/p&gt;

&lt;p&gt;Once your focus is inside the toolbar, it's navigable by Left/Right Arrow key, and you'll see that the focus cycles without interruption - e.g. if you press &lt;code&gt;LeftArrow&lt;/code&gt; when focused on the 'Bold' button, focus will move to the overflow menu (the last item on the right).&lt;/p&gt;

&lt;p&gt;We use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets#managing_focus_inside_groups" rel="noopener noreferrer"&gt;roving tabindex technique&lt;/a&gt; to achieve this, managing the buttons' &lt;code&gt;tabindex&lt;/code&gt; attribute in Javascript. I won't go into too much detail on that implementation here (maybe a follow up post!), but the result is that the controls are effectively grouped together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessible tooltips
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fpgs5jno46f3ypegks7y9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fpgs5jno46f3ypegks7y9.png" alt="Screenshot of the toolbar with the Heading button focused, and a tooltip reading "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prior to this toolbar work, the only tooltips we had in the codebase were "hover only", meaning they can't be triggered by keyboard. For this reason, we haven't used tooltips much to convey essential information, since not all users would be able to benefit from it. However, the toolbar design called for some extra detail for all users, to make sure the buttons' functions could be understood.&lt;/p&gt;

&lt;p&gt;We've updated our &lt;code&gt;Button&lt;/code&gt; component to accept a tooltip now, and by default this tooltip forms part of the button's accessible name (by including the text inside the button, even if it is visually hidden). The tooltip is shown on hover &lt;em&gt;and&lt;/em&gt; on focus, meaning that the keyboard can trigger its appearance. We've also made sure that a user can temporarily dismiss the tooltip by pressing &lt;code&gt;Escape&lt;/code&gt;, since it could be appearing over some other content and getting in the way!&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyboard shortcuts
&lt;/h3&gt;

&lt;p&gt;Some of the formatters also have keyboard shortcuts, which we implemented using a &lt;a href="https://github.com/forem/forem/blob/main/app/javascript/shared/components/useKeyboardShortcuts.js" rel="noopener noreferrer"&gt;&lt;code&gt;KeyboardShortcuts&lt;/code&gt;&lt;/a&gt; component we already use throughout the app.&lt;/p&gt;

&lt;p&gt;One thing that came to light quickly, however, was that our &lt;code&gt;KeyboardShortcuts&lt;/code&gt; component treated the macOS &lt;code&gt;cmd&lt;/code&gt; key and the &lt;code&gt;ctrl&lt;/code&gt; key interchangeably. This meant that on macOS, pressing &lt;code&gt;ctrl + b&lt;/code&gt; would activate the bold formatter the same as &lt;code&gt;cmd + b&lt;/code&gt;, when the standard behaviour would be for the cursor to move back one space. &lt;a href="https://github.com/forem/forem/pull/15265" rel="noopener noreferrer"&gt;We've now resolved this issue across the codebase&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another issue quickly raised by DEV community members after launch was that we'd neglected to call &lt;code&gt;event.preventDefault()&lt;/code&gt; on a shortcut key press, with the unfortunate side effect that some fairly disruptive browser shortcuts were also being triggered by our shortcuts (for example, &lt;code&gt;cmd + u&lt;/code&gt; in Firefox was adding underline formatting but also opening 'view source' for the page 🙈). Thanks to the quick feedback from the community, &lt;a href="https://github.com/forem/forem/pull/15509" rel="noopener noreferrer"&gt;we were able to resolve this within hours of launch&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changes to image upload
&lt;/h2&gt;

&lt;p&gt;The final aspect of the toolbar development was some changes to the image upload flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Styling the file input
&lt;/h3&gt;

&lt;p&gt;Styling file input selector buttons is notoriously tricky, and to make sure we could maintain the look and feel of our other toolbar buttons, we instead have relied on a visually hidden file input, with a separate button in the toolbar, which activates that hidden file input when it's clicked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making uploads cancelable
&lt;/h3&gt;

&lt;p&gt;Previously a user couldn't cancel an in-progress image upload, but we've changed that! We've achieved this by making use of the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal" rel="noopener noreferrer"&gt;AbortSignal interface&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;When an upload begins, we create an &lt;code&gt;AbortRequestController&lt;/code&gt;, and pass its "signal" to our helper function which makes the network request via &lt;code&gt;fetch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startNewRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;setAbortRequestController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;handleInsertionImageUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Triggered by handleInsertionImageUpload&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMainImage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;successCb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;failureCb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/image_uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&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;X-CSRF-Token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;csrfToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateUploadFormdata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;same-origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To cancel the in-progress request we can call &lt;code&gt;abortRequestController.abort()&lt;/code&gt;, and - tada - it's cancelled!&lt;/p&gt;

&lt;h3&gt;
  
  
  More feedback for screen reader users
&lt;/h3&gt;

&lt;p&gt;Prior to the toolbar work, there wasn't much feedback for screen reader users when using the image upload functionality. The generated image markdown, or any error, would appear next to the image upload button, but unless you could visually see that appearing, there was no other prompt to let you know the outcome.&lt;/p&gt;

&lt;p&gt;We now let users know when an upload successfully completes, via an &lt;code&gt;aria-live&lt;/code&gt; region which looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"upload-success-info"&lt;/span&gt;
  &lt;span class="na"&gt;aria-live&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"screen-reader-only"&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the image upload completes, we add text to this element by calling&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upload-success-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image upload complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is then announced to screen reader users.&lt;/p&gt;

&lt;p&gt;In the case of an error, we use our &lt;a href="https://github.com/forem/forem/blob/main/app/javascript/Snackbar/Snackbar.jsx" rel="noopener noreferrer"&gt;Snackbar component&lt;/a&gt; which uses a similar mechanism to make an announcement to screen reader users as it appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;I mentioned it further up, but a big shout out to the DEV community for quickly highlighting some issues with the toolbar when it went live. Thanks to your help, we were able to push fixes the same day it went live, and make the feature work better for others.&lt;/p&gt;

&lt;p&gt;We're continuing to keep track of potential future enhancements, and you can see the current status on the &lt;a href="https://github.com/forem/forem/issues/14807" rel="noopener noreferrer"&gt;GitHub epic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you'd like to dig deeper into the code, check out the &lt;a href="https://github.com/forem/forem/issues/14808" rel="noopener noreferrer"&gt;Toolbar issue&lt;/a&gt; on GitHub, and its related pull requests.&lt;/p&gt;

</description>
      <category>meta</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>5 things I'm thinking about when I check a Pull Request for accessibility</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Tue, 30 Mar 2021 11:24:05 +0000</pubDate>
      <link>https://dev.to/s_aitchison/5-things-i-m-thinking-about-when-i-check-a-pull-request-for-accessibility-3gmo</link>
      <guid>https://dev.to/s_aitchison/5-things-i-m-thinking-about-when-i-check-a-pull-request-for-accessibility-3gmo</guid>
      <description>&lt;p&gt;&lt;em&gt;(Photo by &lt;a href="https://unsplash.com/@cookiethepom?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Cookie the Pom&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/laptop?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Developing reliable, accessible, apps is really a whole-team effort, and something I've not written much about before is how I approach things when I'm &lt;em&gt;not&lt;/em&gt; the one writing the code. &lt;/p&gt;

&lt;p&gt;This is by no means a complete guide to implementing or testing for accessibility, but hopefully it gives a general idea of things to think about when reviewing a Pull Request 🙂 Feel free to leave any of your own tips, suggestions, or questions in the comments!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you'd like to skip directly to a section:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What even is this feature?&lt;/li&gt;
&lt;li&gt;Initial check with axe&lt;/li&gt;
&lt;li&gt;Keyboard operability&lt;/li&gt;
&lt;li&gt;Screen reader checks&lt;/li&gt;
&lt;li&gt;Considerations on focus management and dynamically appearing content&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What even is this feature? 🤔
&lt;/h2&gt;

&lt;p&gt;I find it's quite useful to take a step back and consider what UI pattern we're trying to introduce/refactor, and check the &lt;a href="https://www.w3.org/TR/wai-aria-practices-1.2/" rel="noopener noreferrer"&gt;WAI ARIA Authoring Practices&lt;/a&gt; for a reminder of how that kind of UI pattern &lt;em&gt;should&lt;/em&gt; behave. &lt;/p&gt;

&lt;p&gt;Those docs are great to bookmark for development too, as they lay out expected behaviours, aria attributes, and link to example implementations. &lt;/p&gt;

&lt;p&gt;Stepping back and asking yourself "what is this thing?" also helps spot opportunities to replace &lt;code&gt;div&lt;/code&gt;s with more semantic alternatives. I've lost count of the amount of times I've had a lightbulb moment of "oh wait, this is actually a [insert something that should be obvious here]!". &lt;/p&gt;

&lt;h2&gt;
  
  
  2. Initial check with axe 🪓
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://chrome.google.com/webstore/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd" rel="noopener noreferrer"&gt;axe browser extension&lt;/a&gt; helps us capture a lot of low hanging fruit, e.g. colour contrast, missing landmarks or labels.&lt;/p&gt;

&lt;p&gt;If there's multiple states the feature can be in, I'd try re-running axe &lt;strong&gt;in each state&lt;/strong&gt; (e.g. a dropdown that's collapsed, vs expanded), as axe can only inspect the current DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Keyboard operability 👩🏻‍💻
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NB: If you use a Mac and/or Safari, please make sure your settings allow for tabbing to interactive elements.&lt;/strong&gt; See &lt;a href="https://www.a11yproject.com/posts/2017-12-29-macos-browser-keyboard-navigation/" rel="noopener noreferrer"&gt;Browser Keyboard Navigation in macOS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All features should be operable by keyboard alone, and this includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pressing &lt;code&gt;Tab&lt;/code&gt; to move through each of the interactive elements in a logical order (e.g. often this would be top-to-bottom, left-to-right)&lt;/li&gt;
&lt;li&gt;Pressing &lt;code&gt;Shift + Tab&lt;/code&gt; to move backwards through those elements&lt;/li&gt;
&lt;li&gt;The currently focused element should be clearly visible (e.g. with a focus outline). &lt;/li&gt;
&lt;li&gt;You should be able to interact with focusable elements as per the conventions in the &lt;a href="https://www.w3.org/TR/wai-aria-practices-1.2/" rel="noopener noreferrer"&gt;WAI ARIA Authoring Practices&lt;/a&gt;. For example, activating a link with &lt;code&gt;Enter&lt;/code&gt;, moving through combobox suggestions with &lt;code&gt;Up Arrow/Down Arrow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Being able to disclose content that would otherwise be triggered on a mouse hover (e.g. a tooltip that makes helper text appear on hover)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bonus tip: invisible focus problems
&lt;/h3&gt;

&lt;p&gt;If you ever get stuck debugging invisible focus issues, you can add this in the console to log out the focused element as it changes:&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;focusin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Screen reader checks 🤖
&lt;/h2&gt;

&lt;p&gt;I've been asked a few times "Do you always check with a screen reader?" and the honest answer is "No".&lt;/p&gt;

&lt;p&gt;My personal view is that a screen reader check is a &lt;strong&gt;must&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We are introducing a new interactive feature (as opposed to a content change): things dynamically update/appear/disappear&lt;/li&gt;
&lt;li&gt;Any &lt;code&gt;aria&lt;/code&gt; attribute has been changed or introduced: these are only surfaced through assistive technology, so if we're not checking with a screen reader, we're not really checking at all&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  A little bit of initial setup
&lt;/h3&gt;

&lt;p&gt;I usually use VoiceOver and Safari to conduct screen reader checks. This is mostly because VoiceOver is the default Mac screen reader, and is designed to work best with Safari (if you try using it with another browser you'll notice some buggy/unusual things).&lt;/p&gt;

&lt;p&gt;If you use a Mac, I have a post with some initial setup instructions: &lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/s_aitchison" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F197075%2Fac841cbd-abbb-4760-be69-6909cef48656.jpg" alt="s_aitchison"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/s_aitchison/3-quick-tasks-to-get-familiar-with-screen-readers-voiceover-on-mac-1cig" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;3 Quick Tasks to Get Familiar with Screen Readers (VoiceOver on Mac)&lt;/h2&gt;
      &lt;h3&gt;Suzanne Aitchison ・ Dec 1 '19&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#a11y&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#html&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#beginners&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



&lt;p&gt;If you don't use a Mac, I would recommend using &lt;a href="https://www.nvaccess.org/" rel="noopener noreferrer"&gt;NVDA&lt;/a&gt; and Firefox/Chrome, purely because NVDA is a free download and is very widely used (check out the latest &lt;a href="https://webaim.org/projects/screenreadersurvey8/" rel="noopener noreferrer"&gt;Screen reader user survey&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Toggling VoiceOver on/off
&lt;/h3&gt;

&lt;p&gt;If you have a keyboard with the function keys in the top row, you can turn VoiceOver on and off with &lt;code&gt;Cmd + F5&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;If you don't have the function keys, you can press the TouchID button three times in quick succession to bring up the accessibility options, where you can check/uncheck 'Enable VoiceOver'.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rotor
&lt;/h3&gt;

&lt;p&gt;I usually start a screen reader check using the VoiceOver Rotor (opened with the key combination &lt;code&gt;ctrl + option + u&lt;/code&gt;)- it's an interactive menu that lists different elements by type (e.g. landmarks, links, form controls):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fjflz09zciszcll5j9qb3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fjflz09zciszcll5j9qb3.png" alt="Screenshot of DEV home screen with VoiceOver rotor open. A list of links is visible"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press the left/right arrow keys to move through the different menus and get a quick overview of the relevant landmarks and accessible names of elements on the page. In the example above, you can quickly spot the 'Home' link has accidentally been named twice!&lt;/p&gt;

&lt;p&gt;To jump to a particular element/section, use the up/down arrows to highlight and &lt;code&gt;Enter&lt;/code&gt; to select.&lt;/p&gt;

&lt;p&gt;You don't have to use the rotor at all if you don't find it useful, I just prefer it compared to reading through lots of the page to get to the section I'm interested in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessible names
&lt;/h3&gt;

&lt;p&gt;Axe will usually help flag this kind of issue, but as I navigate a feature with a screen reader, I tend to be on the lookout for elements where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is no accessible name (e.g. we've used an icon button with no aria label)&lt;/li&gt;
&lt;li&gt;The accessible name is vague (e.g. 'click here' - to do what?)&lt;/li&gt;
&lt;li&gt;The accessible name is repeated (e.g. lots of "toggle dropdown" - which dropdown?)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bonus tip: silencing VoiceOver
&lt;/h3&gt;

&lt;p&gt;It can sometimes be a bit much hearing &lt;em&gt;everything&lt;/em&gt; announced as you're navigating around, especially if you're debugging one particular section. You can silence any in-progress VoiceOver announcement by tapping the &lt;code&gt;ctrl&lt;/code&gt; key 🙂&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Considerations on focus management and dynamically appearing content ✨
&lt;/h2&gt;

&lt;p&gt;I usually pay particular attention to any features which involve content dynamically appearing/disappearing - for example: a dropdown menu, a warning banner, a modal. &lt;/p&gt;

&lt;p&gt;The main things I'd be thinking about for these is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How does a low-vision user know that content has appeared/disappeared? Is anything announced via the screen reader?&lt;/li&gt;
&lt;li&gt;If the new content contains any focusable element (e.g. a link) - how do I reach it? If I have to press &lt;code&gt;Tab&lt;/code&gt; 20 times something's probably not right. &lt;/li&gt;
&lt;li&gt;If my keyboard focus was inside some content that's now disappeared - where is the focus now? &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Usually some &lt;code&gt;aria&lt;/code&gt; attributes or manual focus management with Javascript is required to make these kinds of components accessible. Again, you can usually find what's needed in the &lt;a href="https://www.w3.org/TR/wai-aria-practices-1.2/" rel="noopener noreferrer"&gt;WAI ARIA Authoring Practices&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I joined the Forem team!</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Thu, 14 Jan 2021 13:29:36 +0000</pubDate>
      <link>https://dev.to/s_aitchison/i-joined-the-forem-team-1dh6</link>
      <guid>https://dev.to/s_aitchison/i-joined-the-forem-team-1dh6</guid>
      <description>&lt;p&gt;I'm delighted to say I'm starting 2021 with a new role in the Forem team as a Software Engineer where I'll be focusing all on things frontend 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Forem?
&lt;/h2&gt;

&lt;p&gt;As a career-changer into tech, the DEV community has been a huge source of support and inspiration for me on my developer journey. I really love what Forem is doing in helping create inclusive, empowered, communities, and I'm super excited to be part of that. &lt;/p&gt;

&lt;p&gt;I'm also really looking forward to working on an open-source project and interacting with the wider community of contributors. My first forays into open-source were during Hacktoberfest 2019 (&lt;a href="https://dev.to/s_aitchison/my-a11y-hacktoberfest-retro-34op"&gt;which I posted about here on DEV&lt;/a&gt;!), and it definitely helped broaden my experience and make some really rewarding connections.&lt;/p&gt;

&lt;p&gt;And finally, I care deeply about accessibility and inclusion (you might already know this from my previous posts, and my side project &lt;a href="https://www.upyoura11y.com"&gt;Up Your A11y&lt;/a&gt;), and I'm delighted to work for an organisation that feels the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little bit about me
&lt;/h2&gt;

&lt;p&gt;I got into coding about 4 years ago, and in a previous life I was an English teacher overseas, and an international student support manager here in the UK. I've lived in Cambodia, Vietnam and Prague, and travelled extensively in Nigeria and Ghana. Needless to say I'm not travelling so much any more, and I'm happily settled in my home country of Scotland, living by the beach in Edinburgh.&lt;/p&gt;

&lt;p&gt;My coding career began in Android development and then later React and the frontend, which I ended up falling in love with. I advocate for accessible web experiences, through &lt;a href="https://t.co/n6gkPSyYwE?amp=1"&gt;meet-up talks&lt;/a&gt; and &lt;a href="https://www.upyoura11y.com"&gt;Up Your A11y&lt;/a&gt;. I'm also really into CSS art! Last year I completed a project creating &lt;a href="https://dev.to/s_aitchison/100-days-of-css-art-complete-1i2m"&gt;a new piece of CSS art every day for 100 days&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Aside from coding, I love sewing and make a lot of my own clothes, I do yoga every day and like to lift weights and go on long beach walks with my dog. Roller derby is my sport of choice, and until recently I helped run and co-captain a roller derby team here in Edinburgh. I'm also a (part-time) foster carer, which has made me pretty well-versed in Marvel and Pixar movies 😂&lt;/p&gt;

</description>
      <category>meta</category>
      <category>career</category>
      <category>forem</category>
    </item>
    <item>
      <title>3 experiments with CSS paper effects</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Fri, 08 Jan 2021 14:19:30 +0000</pubDate>
      <link>https://dev.to/s_aitchison/3-experiments-with-css-paper-effects-2o56</link>
      <guid>https://dev.to/s_aitchison/3-experiments-with-css-paper-effects-2o56</guid>
      <description>&lt;p&gt;Over the last few days I've been playing around with a few paper-inspired CSS effects in CodePen, and thought I'd share!&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment 1:  Folded paper with taped edges
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/QWKmPqx?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Inspired by new year's resolutions (and my lack of them), for this experiment I had a play about with a folded paper effect, and transparent layers to look like sellotape (or whatever brand of tape is popular in your country 😂).&lt;/p&gt;

&lt;p&gt;The folded paper effect is created with an &lt;code&gt;::after&lt;/code&gt; pseudo element (I used &lt;code&gt;::after&lt;/code&gt; instead of &lt;code&gt;::before&lt;/code&gt; to make sure the text looked like it had been folded in the paper too), with two layered linear-gradients - one running left to right, and another top to bottom.&lt;/p&gt;

&lt;p&gt;The tape itself has opacity set to 0.5 to give the semi-transparent effect, and I added a small dotted border on the tape ends to make it look a little serrated like it'd been cut. &lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment 2: Pinned card
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/zYKaaJr?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I then wanted to play about with some less crumpled paper, and had a go at a pinned card. Sticking with the 'pop it on your noticeboard' theme, it just has a simple reminder about a zoom call on the card itself.&lt;/p&gt;

&lt;p&gt;The fun part of this - of course - was the pin. I love a bit of CSS art 😄. The different components of the pin are commented in the code so you can see how I went about it (much like all these experiments, the CSS could very usefully be tidied up in future, but hopefully you'll get the idea!). Much like the folded paper, it relies primarily on linear, and this time radial, gradients to make it look a bit more 'life like'.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment 3: "Contact me" tear-off paper
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/zYKaMGo?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Remember in the old days when you'd see adverts on noticeboards/lamp-posts/wherever with little phone number slips you could rip off and take home? I thought it might be fun to try to make one in CSS as an alternative "contact" section.&lt;/p&gt;

&lt;p&gt;I wanted to try and make this semantic and accessible, so the contact links are all marked up as  list items (this makes sure they're announced as a list to screen reader users, and show as a nice vanilla list if the CSS fails to load). I've also made heavy use of &lt;code&gt;rem&lt;/code&gt; units, as a slightly hacky way of making sure that if a user has a larger font-size set on their browser, the content scales up without affecting the "CSS art" of it 🙂&lt;/p&gt;

&lt;p&gt;I've tried to make some of the paper slips stick out a bit more, as if they'd been folded out from the poster slightly. To do this I used &lt;code&gt;skew&lt;/code&gt; and - yet again - some linear gradients. It would be cool to make this interactive at some point, to let a user "tear" an item off of the list. Maybe a future experiment!&lt;/p&gt;

</description>
      <category>css</category>
      <category>codepen</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>100 Days of CSS Art - Complete! 🎉</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Sun, 23 Aug 2020 15:06:53 +0000</pubDate>
      <link>https://dev.to/s_aitchison/100-days-of-css-art-complete-1i2m</link>
      <guid>https://dev.to/s_aitchison/100-days-of-css-art-complete-1i2m</guid>
      <description>&lt;p&gt;100 days ago I joined the &lt;a href="https://www.100daysscotland.co.uk/"&gt;100 Days Project Scotland&lt;/a&gt; which "gives anyone, regardless of age or ability, the framework and permission to get creative".&lt;/p&gt;

&lt;p&gt;People from all over Scotland have been creating art in various forms - pottery, graphic design, portraiture... and for me, CSS art!&lt;/p&gt;

&lt;p&gt;In case you missed it, I wrote a little bit about my motivation to take on this challenge here:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/s_aitchison" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9sdcJ28B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--U3J7CPQW--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/197075/ac841cbd-abbb-4760-be69-6909cef48656.jpg" alt="s_aitchison"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/s_aitchison/join-me-for-100-days-of-pure-css-48fo" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Join Me for 100 Days of Pure CSS&lt;/h2&gt;
      &lt;h3&gt;Suzanne Aitchison ・ May 16 '20 ・ 2 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#css&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#html&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#codepen&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;I also shared some lessons and favourite pieces from the first 50 days:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/s_aitchison" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9sdcJ28B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--U3J7CPQW--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/197075/ac841cbd-abbb-4760-be69-6909cef48656.jpg" alt="s_aitchison"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/s_aitchison/5-lessons-from-50-days-of-css-art-2ae1" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;5 lessons from 50 days of CSS art&lt;/h2&gt;
      &lt;h3&gt;Suzanne Aitchison ・ Jul 4 '20 ・ 4 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#css&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#codepen&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#showdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Today I finally finished the project, and to celebrate, I thought I'd share a couple of my favourite pieces from the second half 😄&lt;/p&gt;

&lt;h2&gt;
  
  
  My favourite piece overall - a self portrait!
&lt;/h2&gt;

&lt;p&gt;My favourite piece (which probably says a lot about my ego haha) is a portrait of myself! This was my only attempt in the project to make a human, which felt a bit daunting, but I think it looks like me and I'm really happy with the result!&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/xxZNzNO?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  My favourite CSS animal
&lt;/h2&gt;

&lt;p&gt;I made &lt;em&gt;a lot&lt;/em&gt; of animals over the 100 days. They make for really good projects, as they're instantly recognisable, can be simplified a lot into basic shapes, and also they're cute, so what's not to love?&lt;/p&gt;

&lt;p&gt;I made so many animals, I started a collection in CodePen for them. If you like you can visit my &lt;a href="https://codepen.io/collection/AVVabV"&gt;CSS Zoo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But since my last update on Dev 50 days ago, my favourite animal piece has been a portrait of my friend's dog, Colby. Here he is peeping over a picket fence:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/MWKLYBN?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  My favourite animation
&lt;/h2&gt;

&lt;p&gt;This one is harder to choose! A lot of my animations have been very simple, largely just due to the time constraints of creating an image a day. Now the project is finished I'm looking forward to learning more about animation and creating some more complex stuff! But of the second half of the project, my favourite animation is probably this little hamster wheel:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/KKVOMLK?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  My favourite "realistic" one
&lt;/h2&gt;

&lt;p&gt;To be honest I didn't do a lot of realistic images, and one thing I discovered over the project is that I much prefer the fun of finding the most basic shapes within an idea and creating fairly flat illustrations. But of the ones where I did try to make it slightly more realistic, I think this lava lamp is my favourite - my only regret is that I didn't have time to animate it!&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/pogqwgW?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  My favourite "weird one"
&lt;/h2&gt;

&lt;p&gt;Part of the challenge with this project has been to come up with an idea to make each day. Something that's simple enough to be achievable in whatever time I have that evening, but also something that will hopefully be fun and interesting.&lt;/p&gt;

&lt;p&gt;Some of the ideas that come into my head are... um.. strange, sometimes. My favourite from the "strange" pile is this block of cheese:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/yLexrdb?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What now?
&lt;/h2&gt;

&lt;p&gt;It'll be strange having more time on my hands in the next little while, it's become such a habit to make an image every day!&lt;/p&gt;

&lt;p&gt;One thing I'd like to do more of is writing tutorials here on Dev to help others get started with CSS art. I created one a while ago to make a sheep, and would love to do more:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/s_aitchison" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9sdcJ28B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--U3J7CPQW--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/197075/ac841cbd-abbb-4760-be69-6909cef48656.jpg" alt="s_aitchison"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/s_aitchison/get-started-with-css-art-make-a-sheep-step-by-step-cbf" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Get started with CSS art - make a sheep! Step by step&lt;/h2&gt;
      &lt;h3&gt;Suzanne Aitchison ・ Jul 19 '20 ・ 8 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#css&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#html&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#codenewbie&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#codepen&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;If you'd like a tutorial on any of the images I've created, please do let me know!&lt;/p&gt;

&lt;h2&gt;
  
  
  See the full collection
&lt;/h2&gt;

&lt;p&gt;You can view all the 100 pieces of CSS art over on CodePen: &lt;a href="https://codepen.io/collection/AeyMRz"&gt;Suzanne makes 100 things in CSS&lt;/a&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>codepen</category>
    </item>
    <item>
      <title>Get started with CSS art - make a sheep! Step by step</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Sun, 19 Jul 2020 13:06:13 +0000</pubDate>
      <link>https://dev.to/s_aitchison/get-started-with-css-art-make-a-sheep-step-by-step-cbf</link>
      <guid>https://dev.to/s_aitchison/get-started-with-css-art-make-a-sheep-step-by-step-cbf</guid>
      <description>&lt;p&gt;I recently hit the halfway point in my 100 days of CSS art journey - I've learned a lot since I started - you can read about it in my last post:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/s_aitchison" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9sdcJ28B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--U3J7CPQW--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/197075/ac841cbd-abbb-4760-be69-6909cef48656.jpg" alt="s_aitchison"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/s_aitchison/5-lessons-from-50-days-of-css-art-2ae1" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;5 lessons from 50 days of CSS art&lt;/h2&gt;
      &lt;h3&gt;Suzanne Aitchison ・ Jul 4 '20 ・ 4 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#css&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#codepen&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#showdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;I know first-hand it can be intimidating getting started with CSS art, and hopefully in this post I'll show you it's totally achievable, as we make this cute wee sheep, step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll learn by making this sheep
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Some key positioning techniques&lt;/li&gt;
&lt;li&gt;Making use of &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; pseudo elements&lt;/li&gt;
&lt;li&gt;Utilising &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties"&gt;CSS custom properties (variables)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One way &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path"&gt;clip-path&lt;/a&gt; can help you create shapes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checkpoints after each step
&lt;/h2&gt;

&lt;p&gt;At the end of each of the 5 steps there is a CodePen showing you both the code and result of what we've covered up til the end of that step, so you won't get left behind!&lt;/p&gt;

&lt;p&gt;If there is anything unclear in the instructions, do skip to that CodePen and take a closer look; hopefully all will become clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set up your canvas
&lt;/h2&gt;

&lt;p&gt;Start a new pen in &lt;a href="https://codepen.io/"&gt;CodePen&lt;/a&gt; (or wherever you like to create your code!). The first step is to get ready with your "canvas" - i.e. the area of the page you will create your image inside.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose a background color
&lt;/h3&gt;

&lt;p&gt;First we'll set the &lt;code&gt;body&lt;/code&gt; element's background color - I chose a nice grass green, but feel free to use whatever funky color you like!&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;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#54a865&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;h3&gt;
  
  
  Create a box and position center
&lt;/h3&gt;

&lt;p&gt;I've created a &lt;code&gt;div&lt;/code&gt; with the classname "sheep" to act as our canvas. Width and height has been set to 180px, and we'll give it some margin too so it looks nice on the page.&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;body&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;"sheep"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.sheep&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&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;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8%&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;180px&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;180px&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 the property &lt;code&gt;position: relative&lt;/code&gt;. This is important, as inside the canvas we will be using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/position"&gt;absolute positioning&lt;/a&gt;. By setting the position to 'relative' here, the content of our canvas will be positioned relative to this canvas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Give yourself a guide by adding a temporary canvas background
&lt;/h3&gt;

&lt;p&gt;I've set the background of the 'sheep' to &lt;code&gt;pink&lt;/code&gt; for now, purely so I can see exactly where the canvas is, and check it's positioned correctly. Choose whatever color stands out to you!&lt;/p&gt;

&lt;h3&gt;
  
  
  Work in progress - end of step 1:
&lt;/h3&gt;

&lt;p&gt;Check what you should have so far:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/JjGempB?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - Make some fluff
&lt;/h2&gt;

&lt;p&gt;The most important part of a sheep? The fluff! &lt;/p&gt;

&lt;p&gt;The fluff on this sheep is going to be made in two halves - left side and right side. On each side we will have three overlapping circles, like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dxru4SQl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kg7hlpss9gisldsg3diy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dxru4SQl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kg7hlpss9gisldsg3diy.png" alt="Diagram showing the 3 fluff parts on left and right sides, divided by lines" width="416" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a CSS variable for the fluff size
&lt;/h3&gt;

&lt;p&gt;As we can see above, we'll have six total fluff pieces, all of which will be the same size. When you have a property that is going to be shared across multiple CSS selectors, it's a good idea to create a CSS property/variable for them.&lt;/p&gt;

&lt;p&gt;This allows you to easily access and change the property in one place, without having to go through multiple places in your code.&lt;/p&gt;

&lt;p&gt;We can create this fluff size variable at the root of the document like so:&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;--fluff-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;70px&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 whenever we want to access the fluff size (e.g. for the width and height of all the fluff pieces) we can use &lt;code&gt;var(--fluff-size)&lt;/code&gt; to get it!&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the middle fluff pieces
&lt;/h3&gt;

&lt;p&gt;The middle fluff pieces will be created with div elements:&lt;br&gt;
&lt;code&gt;&amp;lt;div class="fluff left"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;code&gt;&amp;lt;div class="fluff right"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The shared &lt;code&gt;fluff&lt;/code&gt; class will contain the majority of our styles:&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;.fluff&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&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="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fluff-size&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="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fluff-size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&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;Then we can use the extra &lt;code&gt;left&lt;/code&gt;/&lt;code&gt;right&lt;/code&gt; class names to position each side correctly. &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;left side&lt;/strong&gt; is going to be positioned at &lt;code&gt;left: 0&lt;/code&gt;. Since this is the default for an absolute-positioned element, we don't need to add anything - yay!&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;right side&lt;/strong&gt; is going to be positioned at the right, which we can do as so:&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;.fluff.right&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;right&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create the upper fluff sections
&lt;/h3&gt;

&lt;p&gt;To do this, we're going to use the &lt;code&gt;::before&lt;/code&gt; pseudo element. I'm using this purely to cut down on the amount of HTML code. Using &lt;code&gt;.fluff::before&lt;/code&gt; will place an element as the first child of any element with the &lt;code&gt;.fluff&lt;/code&gt; class - e.g.&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fluff left"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!--  ::before fluff appears as if entered here      --&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;We want to position the upper fluff elements slightly higher than the parent &lt;code&gt;.fluff&lt;/code&gt; divs, and also more towards the center. &lt;/p&gt;

&lt;p&gt;We can use the shared &lt;code&gt;fluff&lt;/code&gt; class to do most of the work:&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;.fluff&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;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&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="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fluff-size&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="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--fluff-size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-35px&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;And then once again we can make use of the &lt;code&gt;left&lt;/code&gt;/&lt;code&gt;right&lt;/code&gt; classes to move each side closer to the center, as so:&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;.fluff.left&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;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;35px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.fluff.right&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;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-35px&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;h3&gt;
  
  
  Create the lower fluff sections
&lt;/h3&gt;

&lt;p&gt;This is very similar to the previous step. This time we'll use the &lt;code&gt;::after&lt;/code&gt; pseudo element. Using &lt;code&gt;.fluff::after&lt;/code&gt; will place an element as the last child of any element with the &lt;code&gt;.fluff&lt;/code&gt; class - e.g.&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fluff left"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!--  ::before fluff appears as if entered here      --&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!--  ::after fluff appears as if entered here      --&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;Similar to the above step, we'll use main &lt;code&gt;fluff&lt;/code&gt; class to specify the majority of styles, and the left/right classes to push each lower fluff piece into the center.&lt;/p&gt;

&lt;h3&gt;
  
  
  Work in progress - end of step 2:
&lt;/h3&gt;

&lt;p&gt;With that - our fluff is created - see what we have so far:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/KKVrGeW?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 - Add a head with ears
&lt;/h2&gt;

&lt;p&gt;Now we have some fluff, but we need a head! We'll use a div with class name &lt;code&gt;head&lt;/code&gt; to create this, and style it as round and black.&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;.head&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&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;70px&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;70px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&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;We'll also once again use &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; pseudo elements - this time to make the ears.&lt;/p&gt;

&lt;p&gt;The ears are ovals with a black background color. To make sure we keep the width and height the exact same on both, let's make some more variables for them:&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;--fluff-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;70px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--ear-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--ear-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&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;h3&gt;
  
  
  Position the head
&lt;/h3&gt;

&lt;p&gt;We want to make sure that the head is positioned in the center of the body. One way do that would be to crunch some numbers and work out exactly how many pixels to the left we want to place it.&lt;/p&gt;

&lt;p&gt;An easier way (in my opinion) would be to use the following:&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;left&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%;&lt;/span&gt;
&lt;span class="nt"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;translateX&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-50&lt;/span&gt;&lt;span class="o"&gt;%);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this does is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;code&gt;left&lt;/code&gt; property to set the start position of the head to 50% of the parent div (i.e. the canvas we created in step 1). Our canvas is 180px wide, so this would be the same as &lt;code&gt;left: 90px&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;translateX&lt;/code&gt; transform to move the head to the left. The amount it moves to the left is 50% of the head's width. Our head is 70px wide, so this is the same as &lt;code&gt;translateX(-35px)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The combination of the two places the &lt;strong&gt;center&lt;/strong&gt; of the head (i.e. 35px) in the &lt;strong&gt;center&lt;/strong&gt; of the parent&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Work in progress - end of step 3:
&lt;/h3&gt;

&lt;p&gt;You can check the ear positioning and sizes in the CodePen below, or try choosing your own first! After this step you should have something like:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/dyGQggr?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Add some face features!
&lt;/h2&gt;

&lt;p&gt;We want a happy smiley sheep, so lets add some eyes first. We'll use very similar style as before, creating two HTML elements inside the head:&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"head"&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;"eye left"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&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;"eye right"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;We'll use a border to help it look like a dark pupil inside of a white eye:&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;.eye&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;white&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;10px&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;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&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;You can then use the &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; classes to position each eye how you like (check the work in progress CodePen below if you're not sure though!)&lt;/p&gt;

&lt;h3&gt;
  
  
  Use clip-path to create the mouth
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;clip-path&lt;/code&gt; can be a very useful CSS property. It effectively creates a mask for your HTML element, allowing only a portion of it to be shown.&lt;/p&gt;

&lt;p&gt;In this case, we're going to use it to turn a block into a semi-circle.&lt;/p&gt;

&lt;p&gt;Let's start with an HTML element &lt;code&gt;&amp;lt;div class="mouth"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; inside the head. We'll style it as a white block, and position in the center of the head just like we did with the in Step 3.&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;.mouth&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&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;30px&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;30px&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;h3&gt;
  
  
  Use Clippy to create your mouth shape
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bennettfeely.com/clippy/"&gt;Clippy&lt;/a&gt; is an awesome tool for creating clip-path code easily. Visit the site, select the circle option from the right, and drag the element around to create the semi circle shape you want.&lt;/p&gt;

&lt;p&gt;The code it generates should look a bit like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clip-path: circle(50% at 50% 0);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Feel free to choose another shape for the mouth and play around with Clippy to your heart's content!&lt;/p&gt;

&lt;p&gt;Once you're ready, add the &lt;code&gt;clip-path&lt;/code&gt; property to the CSS for &lt;code&gt;.mouth&lt;/code&gt;, and adjust the vertical positioning to suit. Here's what I have 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="nc"&gt;.mouth&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&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;30px&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;30px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;clip-path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;50%&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Work in progress - end of step 4
&lt;/h3&gt;

&lt;p&gt;You're nearly at the finish line! So far, you should have something like this:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/NWxEOob?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Add the legs and finish up!
&lt;/h2&gt;

&lt;p&gt;There's no new techniques here, and the code for the legs should look fairly familiar by now. You'll need to create two new HTML elements:&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"leg left"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&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;"leg right"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you place these &lt;strong&gt;before&lt;/strong&gt; the fluff elements and the rest of the code you created. This makes sure they sit behind everything else.&lt;/p&gt;

&lt;p&gt;Each leg is a black rectangle, with a slight border radius to remove any sharp corners:&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;.leg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120px&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;50px&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;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30%&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;We can then use the &lt;code&gt;left&lt;/code&gt;/&lt;code&gt;right&lt;/code&gt; classes to position each leg. I've used the same positioning trick we looked at in step 3 to make the legs sit at 30% in from each side, i.e.&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;.leg.left&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-30%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.leg.right&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30%&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;h3&gt;
  
  
  Tidy up
&lt;/h3&gt;

&lt;p&gt;Now you have all your elements in place, it's time to clean up by removing that pink background we added to the canvas in step 1, and deleting any extra code comments you don't want any more!&lt;/p&gt;

&lt;h2&gt;
  
  
  Ta da!
&lt;/h2&gt;

&lt;p&gt;You should now have a finished sheep, a little bit like this:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/QWyJZPv?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Congratulations! If this is your first piece of CSS art, seriously - WELL DONE!&lt;/p&gt;

&lt;h3&gt;
  
  
  Round up
&lt;/h3&gt;

&lt;p&gt;Let's take a look at what techniques you can now re-use in your CSS art to come:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Positioning items vertically and horizontally, including with % values&lt;/li&gt;
&lt;li&gt;Using clip-path to make shapes&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; to save lots of extra HTML elements&lt;/li&gt;
&lt;li&gt;Using CSS variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Awesome!&lt;/p&gt;

&lt;p&gt;Thanks for following along to the end - if you do create a wee sheep through this tutorial I would absolutely &lt;strong&gt;love&lt;/strong&gt; to see it! Drop your codepen link in the comments so we can all revel in its glory 😃🐑&lt;/p&gt;

</description>
      <category>css</category>
      <category>html</category>
      <category>codenewbie</category>
      <category>codepen</category>
    </item>
    <item>
      <title>5 lessons from 50 days of CSS art</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Sat, 04 Jul 2020 09:37:54 +0000</pubDate>
      <link>https://dev.to/s_aitchison/5-lessons-from-50-days-of-css-art-2ae1</link>
      <guid>https://dev.to/s_aitchison/5-lessons-from-50-days-of-css-art-2ae1</guid>
      <description>&lt;p&gt;50 days ago I started out on my project for &lt;a href="https://www.100daysscotland.co.uk/"&gt;100 Days Project Scotland&lt;/a&gt; - to create a piece of CSS art every day for 100 days. I wrote about my motivations here:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/s_aitchison" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9sdcJ28B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--U3J7CPQW--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/197075/ac841cbd-abbb-4760-be69-6909cef48656.jpg" alt="s_aitchison"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/s_aitchison/join-me-for-100-days-of-pure-css-48fo" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Join Me for 100 Days of Pure CSS&lt;/h2&gt;
      &lt;h3&gt;Suzanne Aitchison ・ May 16 '20 ・ 2 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#css&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#html&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#codepen&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;I hadn't really tackled any CSS art before this project, and it's been a fun journey to date! Today marks the halfway point, and I thought I'd share an update on what I've learned so far. &lt;/p&gt;

&lt;h2&gt;
  
  
  My top 5 learnings so far
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Simple is often most effective
&lt;/h3&gt;

&lt;p&gt;Throughout the last 50 days I've spent a varying amount of time and effort on each creation, and some days I've gotten really carried away with complex shapes, animations, and so on.&lt;/p&gt;

&lt;p&gt;But - some of my most popular creations to date have been the simplest. Like this sheep, which is pretty much made of two colours, and a lot of circles!&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/RwrgWLy?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;So my advice to anyone else getting started with CSS art would be to focus on pictures you can make with a few basic shapes, and keep your colour palette as minimal as possible, as it can help the image look more striking.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Clip-path is very, very useful
&lt;/h2&gt;

&lt;p&gt;Before I started this project I'd never had a need to use the CSS property &lt;code&gt;clip-path&lt;/code&gt;. I'm not even sure I'd heard of it! &lt;/p&gt;

&lt;p&gt;Clip-path allows you to define a shape that will determine what parts of your HTML element are shown. You can use it to create all different kinds of shapes! Check out the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path"&gt;MDN web docs for clip-path&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Even better, there's a great tool for creating these shapes in a handy UI - &lt;a href="https://bennettfeely.com/clippy/"&gt;Clippy&lt;/a&gt;. I've used this tool a whole lot in the last 50 days 😄&lt;/p&gt;

&lt;p&gt;For example, I used it to create some random leaf shapes for my leafcutter ants to carry:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/ExPNJYr?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Gradients can be a time-saver (and a div-saver)
&lt;/h2&gt;

&lt;p&gt;Quite often I've found that I need either stripes, or several neighbouring blocks of different colour. Until I started this project I hadn't fully appreciated how far CSS gradients can get you in this respect, especially with the ability to add fixed stops in a gradient to create hard stripes, rather than colours gently fading from one to another.&lt;/p&gt;

&lt;p&gt;For example, I used a &lt;code&gt;linear-gradient&lt;/code&gt; with some very gentle grading to create both the tail and wing stripes for this parrot:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/XWXzxGq?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;And a &lt;code&gt;repeating-linear-gradient&lt;/code&gt; to repeat a bunch of stripes in opposite directions on this ice cream cone:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/gOPRvMw?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  4. CSS variables are scope-able and very helpful
&lt;/h2&gt;

&lt;p&gt;I'd used &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties"&gt;CSS variables&lt;/a&gt; before, and immediately employed them in this project as it makes life so much easier to be able to e.g. edit all your colours in one place, defining/using like so:&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;--ice-cream-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#FCB8C3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--ripple-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#FC889B&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--cone-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#F7C077&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--waffle-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#F3A63A&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.scoop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ice-cream-color&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 usually have the same colour used in a few places in the picture, and being able to change it in one place is such a huge benefit 🙏&lt;/p&gt;

&lt;p&gt;Something I hadn't really done with CSS variables before though was using them in a scoped fashion. Instead of declaring a variable value in the &lt;code&gt;:root&lt;/code&gt; like above, you can declare them within a CSS certain selector and they'll be applied within that scope.&lt;/p&gt;

&lt;p&gt;For example, in this cogs animation, the three cogs share a class which defines the majority of their appearance and behaviour. To make them slightly different sizes and colours, I used scoped CSS variables e.g.&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;.cog-one&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--cog-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#898888&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--cog-outer-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.cog-two&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--cog-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#A16036&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--cog-outer-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.cog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&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;--cog-outer-size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;solid&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;--cog-color&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;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/NWxvqey?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Inspiration can come from the mundane
&lt;/h2&gt;

&lt;p&gt;A big part of the challenge especially initially was trying to think what on earth to make. It had to be simple enough that I could actually do it within a reasonable time frame, but also interesting enough to be enjoyable and hopefully a bit eye-catching.&lt;/p&gt;

&lt;p&gt;And it turns out that some of the most boring things have been the most enjoyable to code and share 😄 So far my creations have included pretty banal stuff like my washing machine, a mug of tea, a cup of coffee, and a toaster:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/XWXbgWX?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Fancy joining me?
&lt;/h2&gt;

&lt;p&gt;I'm looking forward to the second half of the project, and if you feel inspired to try some CSS art I'd love to follow you too!&lt;/p&gt;

&lt;p&gt;You can connect with me and follow my creations on &lt;a href="https://codepen.io/aitchiss"&gt;my CodePen profile&lt;/a&gt;, or on &lt;a href="https://twitter.com/s_aitchison"&gt;my Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I've found CodePen, like DEV, to be a really supportive community and I've been given a lot of encouragement and motivation there. Hopefully if you decide to a give it a go too, you'll find the same!&lt;/p&gt;

&lt;p&gt;Hope to see you there, and for now I'll leave you with my day 50 creation - some celebratory balloons 👋🎉🎈&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/aitchiss/embed/gOPvbOm?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>css</category>
      <category>codepen</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Accessible images, icons and emojis</title>
      <dc:creator>Suzanne Aitchison</dc:creator>
      <pubDate>Sat, 20 Jun 2020 09:21:30 +0000</pubDate>
      <link>https://dev.to/s_aitchison/accessible-images-icons-and-emojis-53g7</link>
      <guid>https://dev.to/s_aitchison/accessible-images-icons-and-emojis-53g7</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://www.upyoura11y.com/images-icons-and-emojis/"&gt;Up Your A11y: Accessible, images, icons and emojis&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One of the first things we often learn about web accessibility is that we need to provide alternative text for images we use in our websites and apps. This is important because the alt text on an image provides contextual information to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visually impaired users (via screen readers), who can't otherwise see the image well&lt;/li&gt;
&lt;li&gt;Users with slow network connections who may have trouble loading your images&lt;/li&gt;
&lt;li&gt;Crawlers that dictate your SEO, which need a way to understand what content is presented on your page without seeing it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When it comes to actually implementing that alternative text however, it can be so hard to know what to write!&lt;/p&gt;

&lt;h2&gt;
  
  
  Common failings of alt text
&lt;/h2&gt;

&lt;p&gt;There are many instances of alt text on the web that fall into one of the below failings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repeating what surrounding text already describes&lt;/li&gt;
&lt;li&gt;Using redundant phrases like "Image of…"&lt;/li&gt;
&lt;li&gt;Providing meaningless alternative text for a purely decorative component (e.g. "divider line")&lt;/li&gt;
&lt;li&gt;Focusing on what the image looks like, rather than what it is being used to convey (either in content or function)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm going to step through a few key questions you can ask yourself when adding imagery to your content, to help you identify meaningful alternative text. I’ll also then take a look at some alternative imagery you might use (emojis &amp;amp; font awesome icons) and how to handle those in an accessible way.&lt;/p&gt;

&lt;h3&gt;
  
  
  A quick note upfront:
&lt;/h3&gt;

&lt;p&gt;In this post I have used screenshots of groups of HTML elements for illustration. Please understand that the examples below are created by neighboring &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; and text elements and we are considering surrounding text as a separate HTML element to the image itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 1: Does Your Image Actually Need Alternative Text?
&lt;/h2&gt;

&lt;p&gt;It’s worth saying loud and clear, although all &lt;code&gt;&amp;lt;img /&amp;gt;&lt;/code&gt; elements must have an &lt;code&gt;alt&lt;/code&gt; attribute, &lt;strong&gt;not all images need alternative text&lt;/strong&gt;. Often we use images in a way that are purely decorative, e.g. to help space out a page, add a bit of color to a very text-heavy page, use as fancy bullet points, etc. In cases where the image used is purely decorative, it is acceptable to provide empty alternative text.&lt;/p&gt;

&lt;p&gt;Ask yourself, if this image wasn’t present on the page, what would the user miss out on? If the answer is purely that it wouldn’t look as nice, then it sounds like you don’t need alternative text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some examples where alternative text wouldn’t be necessary:
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Images used for layout purposes only
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MlUHoh6f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/k4qoeuhd3efcyamlm7ny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MlUHoh6f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/k4qoeuhd3efcyamlm7ny.png" alt="A purely decorative dividing line" width="477" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the example above, the image is used to provide a decorative dividing line. It adds no contextual or functional value, and therefore the alt attribute can happily be left empty - i.e. &lt;code&gt;alt=""&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Images used as bullet points
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tROhqz8x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3h1lnk7op93uolmoam3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tROhqz8x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3h1lnk7op93uolmoam3s.png" alt="Two bullet points that use an orange image with an arrow inside, rather than the default CSS styling. The list items are called list item one and list item two" width="454" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, we would create the &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;, removing the standard list style, and using images in each &lt;code&gt;li&lt;/code&gt; item instead.&lt;/p&gt;

&lt;p&gt;If we did set alternative text on every bullet point, screen reader users would hear the same alt text announced at the start of every list item. This would be pretty noisy, and for no additional value, since the icons are purely decorative. It’s another great use case for empty alt text: &lt;code&gt;alt=""&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Images where the surrounding text is descriptive enough already
&lt;/h4&gt;

&lt;p&gt;Sometimes when we use an image to convey context on a page, we also add text which captions the image, or otherwise describes it. Consider the following example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Dw3Mu5Pp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ds56n6i7yy808mu81ssk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Dw3Mu5Pp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ds56n6i7yy808mu81ssk.png" alt="a slice of cherry pie, with text below it reading - Slice of Cherry Pie: 500 calories" width="472" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Depending on what we want to achieve, there might not be a need to add alternative text to this image of a slice of pie, as long as there is nothing further we want to communicate beyond what is already written in the text beside it. In this case again, empty alt text would be acceptable.&lt;/p&gt;

&lt;p&gt;However, if we &lt;strong&gt;do&lt;/strong&gt; want to convey something additional with the image, then we would need to add the relevant alternative text. For example, imagine if the text "Slice of Cherry Pie" was missing from the example above. In that case, it would be relevant to add alt text, otherwise we haven't successfully conveyed the message that a slice of cherry pie is 500 calories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 2: Does this image have a function?
&lt;/h2&gt;

&lt;p&gt;Through asking question 1, you’ve established you need some alternative text for your image. The next question then addresses what kind of image you’re trying to describe. Many images in your app might have a function attached to them, rather than purely being part of your content.&lt;/p&gt;

&lt;p&gt;For example, consider the header section of the DEV site, which includes an image of the DEV logo.&lt;/p&gt;

&lt;p&gt;In this case, it would make sense for the alternative text to succinctly convey the function of the image, e.g. “Home”.&lt;/p&gt;

&lt;p&gt;Don’t be tempted to add redundant additional text, for example: “Link to Home”, “Navigate Home”, “Back to Home Page”, “Home Icon”, etc. The semantics of the HTML take care of this information for you already.&lt;/p&gt;

&lt;p&gt;The image will be part of a link, and so the screen reader will already announced that it is an image and a link. Avoiding redundant alt text like this will really help streamline the experience for your screen reader users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question 3: What additional content does this image provide?
&lt;/h2&gt;

&lt;p&gt;Through steps 1 and 2, we’ve established that the image conveys some content, but it doesn’t have an associated function. The image is therefore part of the content you are providing, and alternative text is required to make sure that users don’t lose the context it provides.&lt;/p&gt;

&lt;p&gt;When considering what alternative text to add to your image the key guidance is once again to put yourself in the shoes of the user - “if I couldn’t see this image, what would I need to know to appreciate this content?”. It is not an easy question to answer, but it is worth considering carefully. Remember that the alternative text you provide is not only used by screen readers, but also to display on the page if the image fails to load, and for search engines to understand your content.&lt;/p&gt;

&lt;p&gt;The same image in different situations would require different alternative text. By way of an example, consider the following examples using the same image:&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: the image is used liked a title
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8wPm9Lnt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6t5wn246fz9o2wtuj0am.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8wPm9Lnt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6t5wn246fz9o2wtuj0am.png" alt="a slice of cherry pie, with text below reading - Ingredients: 100g cherries, 50g sugar" width="456" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see that we are using the image like a title, with the description below going on to explain the ingredients or recipe steps required to make the item. In this case the alt text could usefully be the name of the object pictured, i.e. "Cherry Pie".&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: the detail of the image is important
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mkvuG44D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vmejbojkm69jcgmfdmj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mkvuG44D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vmejbojkm69jcgmfdmj3.png" alt="A slice of cherry pie with text below it - Serving suggestion" width="464" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, we can see that the detail of the image is central to conveying meaning. In this case, we would want to provide more detail in the alt text, e.g. "A slice of cherry pie served on a white plate with two fresh cherries beside it"&lt;/p&gt;

&lt;p&gt;As you can see, there is no hard and fast rule other than to consider what content is missing when the image is missing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Some special kinds of imagery
&lt;/h2&gt;

&lt;p&gt;In some cases the visual imagery on the page is not actually an image. Common examples of this are font icons and emojis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Font Icons
&lt;/h3&gt;

&lt;p&gt;The examples in this post will focus particularly on &lt;a href="https://fontawesome.com"&gt;Font Awesome&lt;/a&gt; icons, as it is one of the most popular and widely used font icon providers. Given they are so widely used, it’s no surprise that they have &lt;a href="https://fontawesome.com/how-to-use/on-the-web/other-topics/accessibility"&gt;their own handy guide to how to use their icons accessibly&lt;/a&gt;; summarised below.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. As before, ask: does this need alternative text?
&lt;/h4&gt;

&lt;p&gt;If your icon is purely decorative, then alt text isn’t required. However, unlike with an image, a font icon does not have an “alt” attribute that you can set to empty. In this case, we can hide it from assistive technology users by using the ‘aria-hidden’ property. For example:&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;i&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fas fa-car"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that aria-hidden must be explicitly set to true as in the example, and the shorthand &lt;code&gt;aria-hidden&lt;/code&gt; will not be effective.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Does the icon have an action?
&lt;/h4&gt;

&lt;p&gt;If the icon is associated with an interactive element, such as a link, then we can add an ‘aria-label’ to the interactive element, while hiding the icon itself as in Step 1. For example:&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;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Home"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fas fa-home"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. In other cases, display some alternative text off-screen
&lt;/h4&gt;

&lt;p&gt;As we cannot add alternative text to the icon itself, what we can do is add some actual text, but display it in a way to make it only visible to assistive technology users. This can be done using CSS and displaying the text off screen. As before, we will add ‘aria-hidden’ to the icon itself (NB: the 'title' attribute in Font Awesome allows a tooltip to appear for sighted users):&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;i&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fas fa-plant"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Vegetarian"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&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;"screen-reader-only"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Vegetarian&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the CSS would look like:&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;.screen-reader-only&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-10000px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&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;1px&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;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&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;h3&gt;
  
  
  Emojis
&lt;/h3&gt;

&lt;p&gt;Emojis are another special case in terms of imagery that doesn’t present itself in an &lt;code&gt;img&lt;/code&gt; tag. We can copy and paste the Unicode value of an emoji and have it show in your browser, but some extra steps are needed to make this accessible.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hide if purely decorative
&lt;/h4&gt;

&lt;p&gt;Similar to the font icon example above, we can hide the emoji if it is purely decorative. As the emoji does not have its own tag, however, we will need to wrap it in a ’span’ to do 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;span&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;#x1F4D6;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Add an "img" role otherwise
&lt;/h4&gt;

&lt;p&gt;In all other cases, we can ensure that the emoji is treated by the browser as an image. We can do this with the 'role' and 'aria-label' attributes. For example:&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;span&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"img"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"open book"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;#x1F4D6;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;All of the preceding guidance boils down to those few key considerations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this image purely decorative?&lt;/li&gt;
&lt;li&gt;Does this image have a function, or is it purely content?&lt;/li&gt;
&lt;li&gt;What content would the user miss out on if the image was missing?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final question is the most difficult one to answer, but by taking a little time over this, you can improve the experience for all of your users, as well as your SEO!&lt;/p&gt;

&lt;p&gt;And as a special reward for making it to the end of this long post - I have a little decision tree you can download/save that summarises the steps above in a visual flowchart:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Kareqq14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hzfx5xydwgnkjvdnga8z.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Kareqq14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hzfx5xydwgnkjvdnga8z.jpeg" alt="Flow chart summarising the steps detailed above in a graphic form" width="880" height="1063"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>html</category>
    </item>
  </channel>
</rss>
