<?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: Jake Partusch</title>
    <description>The latest articles on DEV Community by Jake Partusch (@jakepartusch).</description>
    <link>https://dev.to/jakepartusch</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%2F228088%2F474114ea-346b-49cc-b8f5-07a9b625c71a.jpeg</url>
      <title>DEV Community: Jake Partusch</title>
      <link>https://dev.to/jakepartusch</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jakepartusch"/>
    <language>en</language>
    <item>
      <title>Introducing Serverless UI: A command-line utility for deploying serverless applications to AWS</title>
      <dc:creator>Jake Partusch</dc:creator>
      <pubDate>Thu, 25 Feb 2021 12:42:38 +0000</pubDate>
      <link>https://dev.to/jakepartusch/introducing-serverless-ui-a-command-line-utility-for-deploying-serverless-applications-to-aws-4np5</link>
      <guid>https://dev.to/jakepartusch/introducing-serverless-ui-a-command-line-utility-for-deploying-serverless-applications-to-aws-4np5</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/JakePartusch" rel="noopener noreferrer"&gt;
        JakePartusch
      &lt;/a&gt; / &lt;a href="https://github.com/JakePartusch/serverlessui" rel="noopener noreferrer"&gt;
        serverlessui
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A command-line utility for deploying serverless applications to AWS. Complete with custom domains, deploy previews, TypeScript support, and more.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
    &lt;a rel="noopener noreferrer" href="https://github.com/JakePartusch/serverlessui./docs/images/undraw_To_the_stars_qhyy.svg"&gt;&lt;img alt="Serverless UI" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FJakePartusch%2Fserverlessui.%2Fdocs%2Fimages%2Fundraw_To_the_stars_qhyy.svg" width="150"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
  Serverless UI
&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;
  💻 🚀 ☁
&lt;/h3&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;
  Deploying Websites to AWS on Easy Mode
&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;
  Serverless UI is a free, open source command-line utility for quickly building and deploying serverless applications on AWS
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bring your own UI&lt;/strong&gt; It doesn't matter if it's React, Vue, Svelte or JQuery. If it compiles down to static files, then it is supported.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Serverless Functions&lt;/strong&gt; Your functions become endpoints, automatically. Serverless UI deploys each function in your &lt;code&gt;/functions&lt;/code&gt; directory as a Node.js lambda behind a CDN and API Gateway for an optimal blend of performance and scalability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deploy Previews&lt;/strong&gt; Automatically deploy each iteration of your application with a separate URL to continuously integrate and test with confidence.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Custom Domains&lt;/strong&gt; Quickly configure a custom domain to take advantage of production deploys!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TypeScript Support&lt;/strong&gt; Write your serverless functions in JavaScript or TypeScript. Either way, they'll be bundled down extremely quickly and deployed as Node.js 14 lambdas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Own your code&lt;/strong&gt; Skip…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/JakePartusch/serverlessui" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h1&gt;
  
  
  Deploying Websites to AWS on Easy Mode
&lt;/h1&gt;

&lt;p&gt;Serverless UI is a free, open source command-line utility for quickly building and deploying serverless applications on AWS&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bring your own UI&lt;/strong&gt; It doesn't matter if it's React, Vue, Svelte or JQuery. If it compiles down to static files, then it is supported.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Serverless Functions&lt;/strong&gt; Your functions become endpoints, automatically. Serverless UI deploys each function in your &lt;code&gt;/functions&lt;/code&gt; directory as a Node.js lambda behind a CDN and API Gateway for an optimal blend of performance and scalability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deploy Previews&lt;/strong&gt; Automatically deploy each iteration of your application with a separate URL to continuously integrate and test with confidence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom Domains&lt;/strong&gt; Quickly configure a custom domain to take advantage of production deploys!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TypeScript Support&lt;/strong&gt; Write your serverless functions in JavaScript or TypeScript. Either way, they'll be bundled down extremely quickly and deployed as Node.js 14 lambdas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Own your code&lt;/strong&gt; Skip the 3rd Party services — get all of the benefits and security of a hosted AWS application, without going through a middleman. Deploy to a new AWS account, or an existing account and get up and running in five minutes!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Get Up and Running in 5 Minutes
&lt;/h2&gt;

&lt;p&gt;You can get a new Serverless UI site deployed to you AWS account in just a few steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AWS Prerequisites&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In order to deploy to AWS, you'll have to configure your machine with local credentials. You'll find the best instructions &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the AWS CDK.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; aws-cdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the Serverless UI Command-Line Interface&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @serverlessui/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bootstrap your AWS Environment&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, specify your account and region to bootstrap the CDK environment for quicker subsequent deployments&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   cdk bootstrap aws://ACCOUNT-NUMBER-1/REGION-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Deploy your static website&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, tell the Serverless UI where to find your website's static files&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   sui deploy &lt;span class="nt"&gt;--dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./dist"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Enjoy your serverless application, deployed on your &lt;em&gt;own&lt;/em&gt; infrastructure.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>javascript</category>
      <category>aws</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Introducing Lumberjack: an Automated Accessibility Scanner</title>
      <dc:creator>Jake Partusch</dc:creator>
      <pubDate>Tue, 11 Feb 2020 13:28:25 +0000</pubDate>
      <link>https://dev.to/jakepartusch/introducing-lumberjack-an-automated-accessibility-scanner-4om3</link>
      <guid>https://dev.to/jakepartusch/introducing-lumberjack-an-automated-accessibility-scanner-4om3</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/JakePartusch"&gt;
        JakePartusch
      &lt;/a&gt; / &lt;a href="https://github.com/JakePartusch/lumberjack"&gt;
        lumberjack
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An automated website accessibility scanner and cli
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
  Lumberjack
&lt;/h1&gt;
&lt;p&gt;
  Chop down accessibility issues with this full-website accessibility scanner
&lt;/p&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/JakePartusch/lumberjack/master/./.github/undraw_winter_activities_vv0v.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bIYOYfd_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/JakePartusch/lumberjack/master/./.github/undraw_winter_activities_vv0v.png" alt="Woman in winter attire standing with an ax next to a tree" width="350px"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;h1&gt;
About&lt;/h1&gt;

&lt;p&gt;Lumberjack runs &lt;a href="https://www.deque.com/axe/" rel="nofollow"&gt;axe&lt;/a&gt; accessibility checks on your entire website!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads your website's sitemap&lt;/li&gt;
&lt;li&gt;Spawns multiple browser instances and starts scanning with axe&lt;/li&gt;
&lt;li&gt;Aggregates results and reports back
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/JakePartusch/lumberjack/master/./.github/cli-screenshot.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wEv4WPpq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/JakePartusch/lumberjack/master/./.github/cli-screenshot.png" alt="Screenshot of lumberjack in action. Print the individual accessibility issues found in an example"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
Usage&lt;/h1&gt;

&lt;h2&gt;
CLI&lt;/h2&gt;
&lt;p&gt;NPX (recommended for a single run)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx @jakepartusch/lumberjack --url https://google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Global Install (recommended for multiple runs)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install -g @jakepartusch/lumberjack
lumberjack --url https://google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;
Options&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;--url         // Required — The base url to scan. If a sitemap exists, its pages will be scanned as well
--strict      // Optional (default: false) — Fail the process if any accessibility issues are found
--baseUrlOnly // Optional (default: false) — Skip the sitemap scan and only run the audit on the base url
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
JavaScript&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install @jakepartusch/lumberjack
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;const lumberjack = require('@jakepartusch/lumberjack')
const myFunction = async () =&amp;gt; {
  const results = await lumberjack("https://google.com")
  console.log(results);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Continuous Integration&lt;/h2&gt;
&lt;p&gt;GitHub Actions Example
(eg. ".github/workflows/accessibility.yml")&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Accessibility Audits&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/JakePartusch/lumberjack"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Accessibility testing is hard. And for the most part, accessibility testing is also &lt;em&gt;manual&lt;/em&gt;. Testers interact with their websites with not only their keyboards, but also with multiple screen readers and browsers. Luckily, with static html analysis and pin-pointed JavaScript execution, we can detect a subset of accessibility issues with automated tools 🎉.&lt;/p&gt;

&lt;p&gt;We can automatically find issues like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Poor color contrast&lt;/li&gt;
&lt;li&gt;Heading levels out of order&lt;/li&gt;
&lt;li&gt;Buttons and links without accessible text&lt;/li&gt;
&lt;li&gt;Inputs without labels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using automation, we can quickly find accessibility issues and prevent future regressions!&lt;/p&gt;
&lt;h2&gt;
  
  
  Axe
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/dequelabs/axe-core"&gt;Axe&lt;/a&gt; is an accessibility tool that has gained a lot of traction over the past few years. And it even has a &lt;a href="https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US"&gt;Chrome extension&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Axe is great, but can only be run on a single page at a time. So, let's extend the power of axe to level-up our accessibility game!&lt;/p&gt;

&lt;h2&gt;
  
  
  Lumberjack
&lt;/h2&gt;

&lt;p&gt;Lumberjack uses the power and rulesets of axe, but orchestrates the scanning of many pages simultaneously.&lt;/p&gt;

&lt;p&gt;Lumberjack can scan 100 individual pages in under two minutes!&lt;/p&gt;

&lt;p&gt;Here is how Lumberjack works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You give lumberjack a url (eg. &lt;a href="https://google.com"&gt;https://google.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Lumberjack reads your website's sitemap&lt;/li&gt;
&lt;li&gt;It spawns multiple browser instances and starts scanning with axe&lt;/li&gt;
&lt;li&gt;It aggregates results and reports back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RS_A9tXb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4ktrbctg49ccw4hoc3df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RS_A9tXb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4ktrbctg49ccw4hoc3df.png" alt="Screenshot of lumberjack in action. Print the individual accessibility issues found in an example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even better, this can all be triggered with a single command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx @jakepartusch/lumberjack --url https://google.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Lumberjack CI
&lt;/h2&gt;

&lt;p&gt;Finally, we can integrate Lumberjack into our continuous integration pipeline to ensure that we aren't introducing any new accessibility issues into our websites.&lt;/p&gt;

&lt;p&gt;An Example with GitHub Actions (eg. ".github/workflows/accessibility.yml")&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Accessibility Audits

on: [push]

jobs:
  build:
    runs-on: ubuntu-18.04

    steps:
      - uses: actions/checkout@v1
      - name: Install required Linux packages
        run: |
          sudo apt-get update
          sudo apt-get install libgbm-dev
          sudo apt-get install xvfb
      - name: Use Node.js 12.x
        uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - name: Install npm packages
        run: |
          npm ci
      - name: Build
        run: |
          npm run build
      - name: Accessibility Audits
        run: |
          npm install -g @jakepartusch/lumberjack
          xvfb-run --auto-servernum lumberjack --url https://google.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As a community let's build accessibility tooling as often and with as much fervor as we build IDE themes :). Together, we can make the web a better place ♥️.&lt;/p&gt;

&lt;p&gt;For more info, and documentation, visit &lt;a href="https://github.com/JakePartusch/lumberjack"&gt;https://github.com/JakePartusch/lumberjack&lt;/a&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The Best Way to Automate Performance Checks in GitHub</title>
      <dc:creator>Jake Partusch</dc:creator>
      <pubDate>Wed, 15 Jan 2020 12:49:59 +0000</pubDate>
      <link>https://dev.to/jakepartusch/the-best-way-to-automate-performance-checks-in-github-584e</link>
      <guid>https://dev.to/jakepartusch/the-best-way-to-automate-performance-checks-in-github-584e</guid>
      <description>&lt;p&gt;We all want to build fast websites. But we don't want to manually run performance checks on every build. Right? Moving performance checks into a continuous integration process removes this manual audit and alerts us when the web performance of our application may be degrading. In this article, we'll discuss a great way to automate performance checks in GitHub projects with Lighthouse, Page Speed Insights and GitHub Actions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Lighthouse
&lt;/h1&gt;

&lt;p&gt;Google's &lt;a href="https://developers.google.com/web/tools/lighthouse" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; is an open-source, automated tool for improving the quality of web pages. Lighthouse can be run locally with developer tools, or remotely via &lt;a href="https://web.dev" rel="noopener noreferrer"&gt;web.dev&lt;/a&gt; and provides users with 5 distinct scoring categories.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Accessibility&lt;/li&gt;
&lt;li&gt;Best Practices&lt;/li&gt;
&lt;li&gt;SEO&lt;/li&gt;
&lt;li&gt;Progressive Web App&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An overall score is given for each category along with issues and recommendations to improve the scores.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fz1szvs5bt04lwel9pozv.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fz1szvs5bt04lwel9pozv.png" alt="A screenshot of the Lighthouse scores"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running Lighthouse in Chrome Dev Tools or on &lt;a href="https://web.dev" rel="noopener noreferrer"&gt;web.dev&lt;/a&gt; is great, but in order to automate this process, we'll need a command line interface (CLI).&lt;/p&gt;

&lt;h2&gt;
  
  
  Lighthouse CLI
&lt;/h2&gt;

&lt;p&gt;Google also provides a &lt;a href="https://github.com/GoogleChrome/lighthouse-ci" rel="noopener noreferrer"&gt;CLI&lt;/a&gt; for running Lighthouse on a local machine or a build server. The great thing about the CLI is that it can be run against a live site, or local build directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running Lighthouse CLI on a live website
&lt;/h3&gt;

&lt;p&gt;After globally installing (&lt;code&gt;npm install -g @lhci/cli&lt;/code&gt;) the npm package, we can use the &lt;code&gt;lhci&lt;/code&gt; script to trigger Lighthouse audits. By using &lt;code&gt;--upload.target&lt;/code&gt; and &lt;code&gt;--collect.url&lt;/code&gt;, we can run the audit against a URL and have the HTML result uploaded to a temporary web page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lhci autorun --upload.target=temporary-public-storage --collect.url=https://jake.partus.ch
✅  .lighthouseci/ directory writable
⚠️   Configuration file not found
✅  Chrome installation found
Healthcheck passed!

Running Lighthouse 3 time(s) on https://jake.partus.ch
Run #1...done.
Run #2...done.
Run #3...done.
Done running Lighthouse!

Uploading median LHR of https://jake.partus.ch/...success!
Open the report at https://storage.googleapis.com/lighthouse-infrastructure.appspot.com/reports/1578972923376-47459.report.html
No GitHub token set, skipping.

Done running autorun.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Running Lighthouse CLI on a local directory
&lt;/h3&gt;

&lt;p&gt;If a URL is not specified, the CLI will attempt to find the static build directory and host the application on a local web server. In the following example, it was able to determine that the build directory was named &lt;code&gt;public&lt;/code&gt;, host the website on port 52259, and then run the audits against the local build.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lhci autorun --upload.target=temporary-public-storage
✅  .lighthouseci/ directory writable
⚠️   Configuration file not found
✅  Chrome installation found
Healthcheck passed!

Automatically determined ./public as `staticDistDir`.
Set it explicitly in lighthouserc.json if incorrect.

Started a web server on port 52259...
Running Lighthouse 3 time(s) on http://localhost:52259/404.html
Run #1...done.
Run #2...done.
Run #3...done.
Running Lighthouse 3 time(s) on http://localhost:52259/index.html
Run #1...done.
Run #2...done.
Run #3...done.
Done running Lighthouse!

Uploading median LHR of http://localhost:52259/404.html...success!
Open the report at https://storage.googleapis.com/lighthouse-infrastructure.appspot.com/reports/1578972840850-69068.report.html
Uploading median LHR of http://localhost:52259/index.html...success!
Open the report at https://storage.googleapis.com/lighthouse-infrastructure.appspot.com/reports/1578972841932-44445.report.html
No GitHub token set, skipping.

Done running autorun.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Page Speed Insights
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/speed/docs/insights/v5/about#lab" rel="noopener noreferrer"&gt;Page Speed Insights&lt;/a&gt; is another tool created by Google which combines Lighthouse performance scores with real-world performance data. &lt;/p&gt;

&lt;p&gt;For more in-depth information on the benefits and APIs available via Page Speed Insights, read this 👇&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/addyosmani" 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%2F5259%2F2f2afc8a-b9ae-4138-9fec-2622eb06eb37.jpg" alt="addyosmani"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/addyosmani/monitoring-performance-with-the-pagespeed-insights-api-33k7" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Monitoring Performance with the PageSpeed Insights API&lt;/h2&gt;
      &lt;h3&gt;Addy Osmani ・ Jan 6 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#pagespeedinsights&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webperf&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#monitoring&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;Page Speed Insights focuses solely on the performance aspects of a webpage. While this is useful, we should try to utilize &lt;em&gt;both&lt;/em&gt; Lighthouse and Page Speed Insights to get a complete picture on the performance and other characteristics of our website.&lt;/p&gt;

&lt;h1&gt;
  
  
  GitHub Actions
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; is a new product from GitHub which allows the configuration of a custom CI/CD pipeline for your project. GitHub Actions can be run for individual pull requests, whenever code is pushed, or a whole host of other &lt;a href="https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows" rel="noopener noreferrer"&gt;events&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Running Lighthouse with a GitHub Action
&lt;/h1&gt;

&lt;p&gt;Here is an example of GitHub Action script to run an audit on every pull request. To get started, put the following yaml config in a file in your project at &lt;code&gt;.github/workflows/audit.yml&lt;/code&gt;. The checkout, install, and build scripts may need to be tweaked depending on the specifics of your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Performance Audit&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use Node.js 12.x&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12.x&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Lighthouse CI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install -g @lhci/cli@0.3.x&lt;/span&gt;
          &lt;span class="s"&gt;lhci autorun --upload.target=temporary-public-storage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring a GitHub App for integrated status checks (Optional)
&lt;/h2&gt;

&lt;p&gt;In order to get integrated GitHub status checks, the Lighthouse CI GitHub app must be &lt;a href="https://github.com/apps/lighthouse-ci" rel="noopener noreferrer"&gt;installed and configured&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, the token must be added to the projects "Secrets".&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6bz4ffx5pouax88f442s.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6bz4ffx5pouax88f442s.png" alt="Screenshot of the Secrets Tab in the GitHub settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, the build script can be tweaked to reference the secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Lighthouse CI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install -g @lhci/cli@0.3.x&lt;/span&gt;
          &lt;span class="s"&gt;lhci autorun --upload.target=temporary-public-storage"&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;LHCI_GITHUB_APP_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LHCI_GITHUB_APP_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After all of this is configured, the status checks should now be created by the Lighthouse CI GitHub App.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fleu77kyk9hv3ctq21wvj.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fleu77kyk9hv3ctq21wvj.png" alt="A screenshot of the GitHub status check in a pull request"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Adding PSI to the GitHub Action Script
&lt;/h1&gt;

&lt;p&gt;To help to quickly and easily run PSI checks, I created a &lt;a href="https://github.com/marketplace/actions/page-speed-insights" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt; 🎉. In the following example, we'll add the PSI check to the end of our performance audit script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Performance Audit&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use Node.js 12.x&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12.x&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Lighthouse CI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install -g @lhci/cli@0.3.x&lt;/span&gt;
          &lt;span class="s"&gt;lhci autorun --upload.target=temporary-public-storage&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;LHCI_GITHUB_APP_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LHCI_GITHUB_APP_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Running Page Speed Insights&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jakepartusch/psi-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://jake.partus.ch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this is definitely useful, we might want to run our performance checks against a deploy preview of our application, so we can insure that we catch the performance regressions &lt;em&gt;before&lt;/em&gt; we merge the PR.&lt;/p&gt;

&lt;h1&gt;
  
  
  Integrating with PR Previews
&lt;/h1&gt;

&lt;p&gt;For this example we'll utilize Netlify for &lt;a href="https://docs.netlify.com/site-deploys/overview/#branches-and-deploys" rel="noopener noreferrer"&gt;Pull Request Deploy Previews&lt;/a&gt;. After configuring deploy previews, Netlify will build and deploy the site when a PR is opened. The site will exist at the URL (deploy-preview-42--yoursitename.netlify.com). Since the deploy preview takes some time to process, I wrote another &lt;a href="https://github.com/marketplace/actions/wait-for-netlify" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt; 🎉 to wait until the preview URL is available. Here is a complete example of a deploy preview performance audit with GitHub Actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Performance Audit&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use Node.js 12.x&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12.x&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Waiting for 200 from the Netlify Preview&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jakepartusch/wait-for-netlify-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;waitFor200&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;site_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yoursitename"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lighthouse CI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install -g @lhci/cli@0.3.x&lt;/span&gt;
          &lt;span class="s"&gt;lhci autorun --upload.target=temporary-public-storage --collect.url=${{ steps.waitFor200.outputs.url }}&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;LHCI_GITHUB_APP_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LHCI_GITHUB_APP_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Running Page Speed Insights&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jakepartusch/psi-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.waitFor200.outputs.url }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Now we have a complete suite of performance audits that run with each Pull Request in GitHub 💯. To see all of this in practice, feel free to visit my &lt;a href="https://github.com/JakePartusch/website" rel="noopener noreferrer"&gt;personal website repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference Links
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/GoogleChrome/lighthouse-ci" rel="noopener noreferrer"&gt;Lighthouse CI&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/GoogleChrome/lighthouse-ci/blob/master/docs/getting-started.md#github-status-checks" rel="noopener noreferrer"&gt;Lighthouse CI GitHub status checks&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/GoogleChromeLabs/psi" rel="noopener noreferrer"&gt;Page Speed Insights (library)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/JakePartusch/psi-action" rel="noopener noreferrer"&gt;Page Speed Insights GitHub Action (shameless plug)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/JakePartusch/wait-for-netlify-action" rel="noopener noreferrer"&gt;Wait for Netlify GitHub Action (shameless plug)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>webperf</category>
      <category>github</category>
    </item>
    <item>
      <title>How to create a button with a div in React (and why you shouldn't)</title>
      <dc:creator>Jake Partusch</dc:creator>
      <pubDate>Mon, 06 Jan 2020 15:22:08 +0000</pubDate>
      <link>https://dev.to/jakepartusch/how-to-create-a-button-with-a-div-in-react-and-why-you-shouldn-t-1743</link>
      <guid>https://dev.to/jakepartusch/how-to-create-a-button-with-a-div-in-react-and-why-you-shouldn-t-1743</guid>
      <description>&lt;p&gt;As web developers, we are often tasked with creating various interactive components in our web applications. On occasion, it might seem like a tempting solution to add a click handler to an existing &lt;code&gt;div&lt;/code&gt;, or to create a custom &lt;code&gt;&amp;lt;Button/&amp;gt;&lt;/code&gt; component out of non-semantic elements.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1176161245352927237-50" src="https://platform.twitter.com/embed/Tweet.html?id=1176161245352927237"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1176161245352927237-50');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1176161245352927237&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;In this article I hope to illustrate the "hidden" bugs that are created by &lt;em&gt;not&lt;/em&gt; using a &lt;code&gt;button&lt;/code&gt; as a button and the amount of code that is required to overcome these deficits. &lt;/p&gt;

&lt;h1&gt;
  
  
  1. onClick handler
&lt;/h1&gt;

&lt;p&gt;This is the obvious first step to add an interaction to a &lt;code&gt;div&lt;/code&gt;. But, as its name suggests, the &lt;code&gt;onClick&lt;/code&gt; handler on a &lt;code&gt;div&lt;/code&gt; only supports mouse click events (&lt;code&gt;onClick&lt;/code&gt; with a &lt;code&gt;button&lt;/code&gt; does much more). Unfortunately, only supporting mouse events leaves keyboard and screen reader users in the dark. They are not informed that this has become an actionable element and they are also not able to trigger the interaction with the keyboard.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="c1"&gt;//DO NOT USE: non-accessible implementation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="o"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&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;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;My Button&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  2. Adding a Role, tabindex
&lt;/h1&gt;

&lt;p&gt;We can inch towards and accessible solution by adding a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex" rel="noopener noreferrer"&gt;tabindex&lt;/a&gt; and a role to the &lt;code&gt;div&lt;/code&gt;. The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/button_role" rel="noopener noreferrer"&gt;button role&lt;/a&gt; will tell screen readers to announce this element as a button, and hint that it is actionable. Setting the &lt;code&gt;tabindex="0"&lt;/code&gt; (&lt;code&gt;tabIndex={0}&lt;/code&gt; in React), allows this element to be focused by the keyboard. Even so, our custom &lt;code&gt;div&lt;/code&gt; can still not be triggered by the keyboard.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1183020913509502978-779" src="https://platform.twitter.com/embed/Tweet.html?id=1183020913509502978"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1183020913509502978-779');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1183020913509502978&amp;amp;theme=dark"
  }



&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="c1"&gt;//DO NOT USE: non-accessible implementation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="o"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&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;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;tabIndex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;My Button&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  3. Keyboard events
&lt;/h1&gt;

&lt;p&gt;When a &lt;code&gt;button&lt;/code&gt; is focused, it can be triggered by either the &lt;code&gt;SPACE&lt;/code&gt; or &lt;code&gt;ENTER&lt;/code&gt; key. To bring this functionality to our &lt;code&gt;div&lt;/code&gt; button, we need to implement &lt;code&gt;onKeyPress&lt;/code&gt; and watch for those specific events. Then, we can manually forward matching events to our &lt;code&gt;onClick&lt;/code&gt; handler.&lt;/p&gt;

&lt;p&gt;By implementing these first three steps, our &lt;code&gt;div&lt;/code&gt; button is now mostly accessible. &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;Button&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="o"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clicked&lt;/span&gt;&lt;span class="dl"&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;onKeyPress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&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;enterOrSpace&lt;/span&gt; &lt;span class="o"&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;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&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;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&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;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Spacebar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&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;which&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="o"&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;which&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;32&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;enterOrSpace&lt;/span&gt;&lt;span class="p"&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="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;onClick&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&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;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
      &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;tabIndex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onKeyPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onKeyPress&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      My Button
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h1&gt;
  
  
  4. Disabled state
&lt;/h1&gt;

&lt;p&gt;Oftentimes, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled" rel="noopener noreferrer"&gt;&lt;code&gt;disabled&lt;/code&gt;&lt;/a&gt; attribute is used for preventing user interaction with a &lt;code&gt;button&lt;/code&gt;. We can add that same functionality to our &lt;code&gt;div&lt;/code&gt; button by setting the &lt;code&gt;tabindex&lt;/code&gt; to &lt;code&gt;-1&lt;/code&gt; (which removes it from keyboard focus) and by ignoring any clicks in our &lt;code&gt;onClick&lt;/code&gt; handler while the button is disabled.&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;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;disabled&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;onClick&lt;/span&gt; &lt;span class="o"&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="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;disabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clicked&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onKeyPress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&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;enterOrSpace&lt;/span&gt; &lt;span class="o"&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;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&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;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&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;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Spacebar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&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;which&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="o"&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;which&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;32&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;enterOrSpace&lt;/span&gt;&lt;span class="p"&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="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;onClick&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&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;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;btn disabled&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="s2"&gt;btn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
      &lt;span class="na"&gt;tabIndex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;disabled&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="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onKeyPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onKeyPress&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      My div Button
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h1&gt;
  
  
  5. Button Styles
&lt;/h1&gt;

&lt;p&gt;If we also want to style our &lt;code&gt;div&lt;/code&gt; to look like a &lt;code&gt;button&lt;/code&gt; we can steal the default styles from Chrome (not recommended).&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;DO&lt;/span&gt; &lt;span class="nt"&gt;NOT&lt;/span&gt; &lt;span class="nt"&gt;USE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;browser-specific&lt;/span&gt; &lt;span class="nt"&gt;styles&lt;/span&gt;
&lt;span class="nc"&gt;.btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;7px&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt; &lt;span class="m"&gt;11px&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;buttontext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;buttonface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.btn.disabled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;170&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;170&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;170&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;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I think that the easiest way to illustrate the differences between creating a button out of a &lt;code&gt;div&lt;/code&gt; and using an &lt;em&gt;actual&lt;/em&gt; &lt;code&gt;button&lt;/code&gt; is to show the amount of code required to implement all of the above (and more) with the following &lt;code&gt;button&lt;/code&gt; snippet.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// PLEASE USE: most accessible solution&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&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;onClick&lt;/span&gt; &lt;span class="o"&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="p"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clicked&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
      &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      My button Button
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;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;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>html</category>
      <category>react</category>
    </item>
    <item>
      <title>Quick Tips for Using a Screen Reader (VoiceOver) on a Mac</title>
      <dc:creator>Jake Partusch</dc:creator>
      <pubDate>Sat, 28 Dec 2019 19:33:27 +0000</pubDate>
      <link>https://dev.to/jakepartusch/quick-tips-for-using-a-screen-reader-voiceover-on-a-mac-kgh</link>
      <guid>https://dev.to/jakepartusch/quick-tips-for-using-a-screen-reader-voiceover-on-a-mac-kgh</guid>
      <description>&lt;h1&gt;
  
  
  What is VoiceOver?
&lt;/h1&gt;

&lt;p&gt;VoiceOver is a free screen reader built into Apple products (MacOS, iOS, etc). Screen readers are used by visually impaired users to navigate through content on their device(s). &lt;/p&gt;

&lt;p&gt;As a web developer, it is vital that testing with a screen reader is part of the development workflow – much like running a linter, writing tests, or adjusting mobile responsiveness. Screen readers are a surprisingly large part of the web's user-base. It is estimated that between 1-2% of internet users in the United States use screen readers (~4 million) 😲.&lt;/p&gt;

&lt;p&gt;A great first step in &lt;em&gt;finding&lt;/em&gt; accessibility issues in a website is by becoming familiar with and using a screen reader on a consistent basis. Since VoiceOver comes pre-installed on MacOS, there is no need to download or configure a third party application!&lt;/p&gt;

&lt;h1&gt;
  
  
  Starting VoiceOver
&lt;/h1&gt;

&lt;p&gt;For the purposes of this article, we'll focus on using VoiceOver with MacOS. VoiceOver works best with Safari, but also works well with both Chrome and Firefox. Luckily, starting VoiceOver is super simple:&lt;/p&gt;

&lt;p&gt;Turn VoiceOver on/off → &lt;code&gt;⌘ + F5&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XY9SC8tO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/p7z3qx2ucrmxsoeuo9mk.png" class="article-body-image-wrapper"&gt;&lt;img alt="VoiceOver welcome screen" width="400px" src="https://res.cloudinary.com/practicaldev/image/fetch/s--XY9SC8tO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/p7z3qx2ucrmxsoeuo9mk.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After selecting "Use VoiceOver" the computer will start speaking the highlighted item on your focused application (black outline) while also providing a dialog box with the current transcript. At this point, the selected item on the screen can be changed by navigating with mouse. Feel free to click around to get a feel for the announcement of different types of elements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OLg1jbcq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/acpf69x6arm2vr0gkgeq.png" class="article-body-image-wrapper"&gt;&lt;img alt="VoiceOver caption for Dev.to search bar" width="400px" src="https://res.cloudinary.com/practicaldev/image/fetch/s--OLg1jbcq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/acpf69x6arm2vr0gkgeq.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  VoiceOver Keys
&lt;/h1&gt;

&lt;p&gt;The VoiceOver command keys (VO) are required to interact with VoiceOver shortcuts:&lt;br&gt;
&lt;code&gt;⌃ + ⌥ (Control + Option)&lt;/code&gt;&lt;br&gt;
or&lt;br&gt;
&lt;code&gt;⇪ (Caps Lock)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The following shortcuts will utilize the VoiceOver command keys to interact with VoiceOver. &lt;/p&gt;

&lt;h1&gt;
  
  
  Navigation
&lt;/h1&gt;

&lt;p&gt;Read next item → &lt;code&gt;VO + Right Arrow&lt;/code&gt;&lt;br&gt;
Read previous item → &lt;code&gt;VO + Left Arrow&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With these two commands, it is possible to navigate through the entire website as a VoiceOver user would. Screen readers not only focus on buttons and links, but also elements with text content. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rdy8wJCT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/66p9s878bujkol9ismun.gif" class="article-body-image-wrapper"&gt;&lt;img alt="navigating VoiceOver with arrow keys" width="400px" src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rdy8wJCT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/66p9s878bujkol9ismun.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After using VoiceOver navigation, it may quickly be apparent that some items on the website are not announced in a meaningful way. This is a great opportunity to read up on &lt;a href="https://www.w3.org/WAI/standards-guidelines/aria/"&gt;WAI-ARIA&lt;/a&gt; and &lt;a href="https://www.w3.org/TR/WCAG21/"&gt;WCAG 2.1&lt;/a&gt;. As web developers, we want our web applications to be usable by as many people as possible – fixing accessibility issues is often quick and extremely helpful to our screen reader users.&lt;/p&gt;

&lt;h1&gt;
  
  
  Useful Shortcuts
&lt;/h1&gt;

&lt;p&gt;The following shortcuts are not mandatory for using VoiceOver, but can be useful for quickly navigating through an application (rather than pressing the right arrow 1 million times 😉).&lt;/p&gt;

&lt;h2&gt;
  
  
  Lock into Command Mode
&lt;/h2&gt;

&lt;p&gt;Optionally, it is possible to lock into VoiceOver command mode, with the following shortcut:&lt;/p&gt;

&lt;p&gt;Lock into command mode → &lt;code&gt;VO + ;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After locking into command mode, it is not necessary to use the VoiceOver command keys, but this will restrict the ability to type normally as many keys are tied to VoiceOver shortcuts.&lt;/p&gt;

&lt;h2&gt;
  
  
  VoiceOver Rotor
&lt;/h2&gt;

&lt;p&gt;The Rotor allows the user to quickly navigate to different sections of the website and provides a useful widget to visualize the content of the web page. Navigation within the rotor can be accomplished with the arrow keys.&lt;/p&gt;

&lt;p&gt;Start the Rotor →&lt;code&gt;VO + U&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QzsvuYJJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/3efjbhb7hd1ztfh48ytn.gif" class="article-body-image-wrapper"&gt;&lt;img alt="Navigating a website with the rotor" width="400px" src="https://res.cloudinary.com/practicaldev/image/fetch/s--QzsvuYJJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/3efjbhb7hd1ztfh48ytn.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigation Shortcuts
&lt;/h2&gt;

&lt;p&gt;Although this can also be accomplished with the rotor, sometimes it is useful to navigate to headings or links with the following commands:&lt;/p&gt;

&lt;p&gt;Next Heading → &lt;code&gt;VO + ⌘ + H&lt;/code&gt;&lt;br&gt;
Previous Heading → &lt;code&gt;VO + ⌘ + ⇧ + H&lt;/code&gt;&lt;br&gt;
Next Link → &lt;code&gt;VO + ⌘ + L&lt;/code&gt;&lt;br&gt;
Previous Link → &lt;code&gt;VO + ⌘ + ⇧ + L&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Useful Links/Further Reading
&lt;/h1&gt;

&lt;p&gt;W3 references&lt;br&gt;
&lt;a href="https://www.w3.org/WAI/standards-guidelines/aria/"&gt;WAI-ARIA&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.w3.org/TR/WCAG21/"&gt;WCAG 2.1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VoiceOver references&lt;br&gt;
&lt;a href="https://www.apple.com/voiceover/info/guide/_1131.html"&gt;VoiceOver complete command list&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dequeuniversity.com/assets/pdf/screenreaders/voiceover-macos-guide.pdf"&gt;Deque's printable quick reference PDF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Accessibility Testing&lt;br&gt;
&lt;a href="https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd"&gt;aXe Accessibility testing tool&lt;/a&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>beginners</category>
      <category>webdev</category>
      <category>inclusion</category>
    </item>
  </channel>
</rss>
