<?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: Scott Nath</title>
    <description>The latest articles on DEV Community by Scott Nath (@scottnath).</description>
    <link>https://dev.to/scottnath</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%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg</url>
      <title>DEV Community: Scott Nath</title>
      <link>https://dev.to/scottnath</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/scottnath"/>
    <language>en</language>
    <item>
      <title>How about a JSON Resume web component with configurable microdata?</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Fri, 17 May 2024 23:03:51 +0000</pubDate>
      <link>https://dev.to/scottnath/how-about-a-json-resume-web-component-with-configurable-microdata-2p1o</link>
      <guid>https://dev.to/scottnath/how-about-a-json-resume-web-component-with-configurable-microdata-2p1o</guid>
      <description>&lt;p&gt;Introducing &lt;code&gt;jsonresume-component&lt;/code&gt;, a web component which displays your resume, with microdata, theming, slots, and remote fetching of your JSON Resume resume.json file. &lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;jsonresume-component&lt;/code&gt; is a web component that generates your resume with microdata following the &lt;a href="https://schema.org"&gt;Schema.org&lt;/a&gt; vocabulary set. It's custom element is &lt;code&gt;&amp;lt;json-resume&amp;gt;&lt;/code&gt; and it fetches a &lt;code&gt;resume.json&lt;/code&gt; file following the &lt;a href="https://jsonresume.org"&gt;JSON Resume&lt;/a&gt; schema to create your resume. HTML is created from using the &lt;a href="https://github.com/scottnath/jsonresume-theme-microdata"&gt;jsonresume-theme-microdata&lt;/a&gt; theme, creating a resume documented with microdata following the &lt;a href="https://schema.org"&gt;Schema.org&lt;/a&gt; vocabulary set.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/scottnath/jsonresume-component"&gt;&lt;code&gt;jsonresume-component&lt;/code&gt; on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://6647817e5224ff5c42e64d5e-dmlkvzjlzg.chromatic.com/"&gt;combined storybook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scottnath.com/resume/"&gt;example of a resume within a website UI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  snippet:
&lt;/h3&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;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"importmap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;imports&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lit&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;https://esm.run/lit&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;@lit/task&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;https://esm.run/@lit/task&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://esm.run/jsonresume-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;json-resume&lt;/span&gt; &lt;span class="na"&gt;gist_id=&lt;/span&gt;&lt;span class="s"&gt;"54682f0aa17453d46cdc74bdef3172a3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/json-resume&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You have your resume in a JSON file following the JSON Resume schema structure&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick primer: How we got here
&lt;/h2&gt;

&lt;p&gt;This web component is built using the HTML-generating functionality of the module &lt;a href="https://github.com/scottnath/jsonresume-theme-microdata"&gt;jsonresume-theme-microdata&lt;/a&gt;. That module is a &lt;a href="https://jsonresume.org"&gt;JSON Resume&lt;/a&gt; theme which is the subject of the article &lt;a href="https://dev.to/scottnath/make-your-resume-seo-friendly-using-json-resume-with-microdata-1kln"&gt;"Make your resume SEO friendly using JSON Resume with microdata"&lt;/a&gt;. The underlying concepts of microdata and HTML were broken down in &lt;a href="https://dev.to/scottnath/how-to-boost-seo-by-enhancing-html-with-microdata-3nol"&gt;"How to Boost SEO by Enhancing HTML with Microdata"&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to implement &lt;code&gt;&amp;lt;json-resume&amp;gt;&lt;/code&gt; in node
&lt;/h2&gt;

&lt;h3&gt;
  
  
  install dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i lit @lit/task jsonresume-component
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;General usage&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;JsonResume&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsonresume-component&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;JsonResume&lt;/span&gt; &lt;span class="na"&gt;gist_id=&lt;/span&gt;&lt;span class="s"&gt;"9e7a7ceb9425336c6aa08d58afb63b8d"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/JsonResume&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to implement the &lt;code&gt;&amp;lt;json-resume&amp;gt;&lt;/code&gt; web component in HTML
&lt;/h2&gt;

&lt;h3&gt;
  
  
  include &lt;code&gt;lit&lt;/code&gt; dependencies
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;json-resume&amp;gt;&lt;/code&gt; uses &lt;a href="https://lit.dev"&gt;&lt;code&gt;lit&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://lit.dev/docs/data/task/"&gt;&lt;code&gt;@lit/task&lt;/code&gt;&lt;/a&gt; which must be imported into your HTML file. You can include dependencies with an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap"&gt;importmap&lt;/a&gt;, pulling them from a CDN:&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;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"importmap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;imports&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lit&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;https://esm.run/lit&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;@lit/task&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;https://esm.run/@lit/task&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add jsonresume-component
&lt;/h3&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;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://esm.run/jsonresume-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using just a &lt;code&gt;gist&lt;/code&gt; id
&lt;/h3&gt;

&lt;p&gt;This option automates fetching your &lt;code&gt;resume.json&lt;/code&gt; file from a GitHub gist. The gist must have a file called &lt;code&gt;resume.json&lt;/code&gt; in it.&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;json-resume&lt;/span&gt; &lt;span class="na"&gt;gist_id=&lt;/span&gt;&lt;span class="s"&gt;"9e7a7ceb9425336c6aa08d58afb63b8d"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/json-resume&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;check out &lt;a href="https://stackblitz.com/edit/json-resume"&gt;this stackblitz&lt;/a&gt; for examples with &lt;code&gt;slots&lt;/code&gt;, alternative stylesheets, and a local &lt;code&gt;resume.json&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/json-resume?embed=1&amp;amp;file=index.html&amp;amp;view=preview&amp;amp;initialpath=index.html" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;At the risk of recreating the &lt;a href="https://github.com/scottnath/jsonresume-component"&gt;detailed configuration docs in the &lt;code&gt;jsonresume-component&lt;/code&gt; readme&lt;/a&gt;, let's stop there. The repo contains an &lt;a href="https://github.com/scottnath/jsonresume-component/tree/main/examples/browser"&gt;examples&lt;/a&gt; directory for stackblitzin or wherever you open your examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important reminder&lt;/strong&gt;: this is not just your resume in a web component! It's also microdata! Check out the &lt;a href="https://search.google.com/test/rich-results/result?id=ctWocdt--8-0Kq5JFMb9tA"&gt;results from the Google Rich Results Test&lt;/a&gt; for &lt;a href="https://scottnath.com/resume/"&gt;the resume on my website&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jsonresume</category>
      <category>webcomponents</category>
      <category>seo</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Make your resume SEO friendly using JSON Resume with microdata</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Fri, 03 May 2024 01:09:23 +0000</pubDate>
      <link>https://dev.to/scottnath/make-your-resume-seo-friendly-using-json-resume-with-microdata-1kln</link>
      <guid>https://dev.to/scottnath/make-your-resume-seo-friendly-using-json-resume-with-microdata-1kln</guid>
      <description>&lt;p&gt;How to generate your resume with schema.org vocabulary HTML markup using JSON Resume, allowing your resume to be understood by search engine crawlers and other machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;Using &lt;a href="https://jsonresume.org"&gt;JSON Resume&lt;/a&gt; with the &lt;a href="https://github.com/scottnath/jsonresume-theme-microdata"&gt;jsonresume-theme-microdata&lt;/a&gt; theme will create a resume documented with microdata following the &lt;a href="https://schema.org"&gt;Schema.org&lt;/a&gt; vocabulary set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You skimmed &lt;a href="https://dev.to/scottnath/how-to-boost-seo-by-enhancing-html-with-microdata-3nol"&gt;How to Boost SEO by Enhancing HTML with Microdata&lt;/a&gt; or you know how to add microdata to HTML&lt;/li&gt;
&lt;li&gt;You know what &lt;strong&gt;objects&lt;/strong&gt; and &lt;strong&gt;arrays&lt;/strong&gt; are in programming&lt;/li&gt;
&lt;li&gt;You have &lt;a href="https://json-schema.org/understanding-json-schema"&gt;a general understanding of JSON Schema&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick Tech Primer
&lt;/h2&gt;

&lt;p&gt;This was originally one article, but I couldn't easily write about adding structured data to JSON Resume without explaining structured data itself. So...it's now a series and this article is gonna focus on adding microdata to your resume using the open source JSON Resume schema. There's a 3rd one, but I'm still cooking it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is structured data, microdata, Schema.org vocabulary?
&lt;/h3&gt;

&lt;p&gt;See &lt;a href="https://dev.to/scottnath/how-to-boost-seo-by-enhancing-html-with-microdata-3nol"&gt;How to Boost SEO by Enhancing HTML with Microdata&lt;/a&gt; for details. The article includes code examples and schema output. &lt;/p&gt;

&lt;h4&gt;
  
  
  Cliff notes:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;structured data? web 3-ish: &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data"&gt;Google explains structured data&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Schema.org? Founded by Google, Microsoft, Yahoo and Yandex: &lt;a href="https://schema.org"&gt;Schema.org&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Schema.org Vocabulary? common content Types and property names: &lt;a href="https://schema.org/Person"&gt;the &lt;strong&gt;Person&lt;/strong&gt; type&lt;/a&gt;, &lt;a href="https://schema.org/Article"&gt;the &lt;strong&gt;Article&lt;/strong&gt; type&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;microdata? &lt;a href="https://en.wikipedia.org/wiki/Microdata_(HTML)#Global_attributes"&gt;micridata primer&lt;/a&gt;, &lt;a href="https://youtu.be/Yj8ujUfAJM8"&gt;YouTube: Microdata vs JSON-LD: Which Structured Data Format Wins?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;microdata in HTML? see 1st article and the &lt;a href="https://schema.org/docs/gs.html"&gt;Schema.org microdata usage guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;microdata validation? &lt;a href="https://validator.schema.org"&gt;Schema.org's validator&lt;/a&gt;, &lt;a href="https://search.google.com/test/rich-results"&gt;Google's Rich Results Test&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is JSON Resume?
&lt;/h3&gt;

&lt;p&gt;To explain adding structured data to HTML, it made sense to use a commonly-known set of data. JSON Resume fits that bill and gave me a great reason to update the HTML of my resume.&lt;/p&gt;

&lt;p&gt;If you're unfamiliar with JSON Resume, it's core-premise is creating a standard data structure for a resume's content. The JSON in "JSON Resume" refers to the JSON schema which details the expected data structure of each piece of data. &lt;/p&gt;

&lt;p&gt;Basically it's a detailed content-model of commonly-used resume content which has been open-sourced and widely adopted.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://jsonresume.org"&gt;JSON Resume website: jsonresume.org&lt;/a&gt; - to dig deeper&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/jsonresume/resume-schema/blob/master/schema.json"&gt;the JSON Resume schema&lt;/a&gt; - to see our content model&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jsonresume.org/themes/"&gt;JSON Resume themes&lt;/a&gt; - 400+ themes available!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jsonresume.org/projects/"&gt;JSON Resume tools/projects&lt;/a&gt; - resume editors, LinkedIn resume converters, validators, pdf exports, etc&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  JSON Resume example
&lt;/h4&gt;

&lt;p&gt;Here is &lt;a href="https://gist.github.com/scottnath/9e7a7ceb9425336c6aa08d58afb63b8d"&gt;my resume.json file in a GitHub gist&lt;/a&gt; which is then combined with a JSON Resume theme and served up via the free and open-source JSON-resume-as-a-service here: &lt;a href="https://registry.jsonresume.org/scottnath?theme=even"&gt;registry.jsonresume.org/scottnath?theme=even&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;![Image has four examples of JSON Resume themes styling Scott Nath's resume][img-themes]&lt;/p&gt;

&lt;p&gt;resumes styled by themes, theme names left to right: &lt;code&gt;kendall&lt;/code&gt;, &lt;code&gt;mantra&lt;/code&gt;, &lt;code&gt;even&lt;/code&gt;, &lt;code&gt;stackoverflow&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is &lt;code&gt;jsonresume-theme-microdata&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/scottnath/jsonresume-theme-microdata"&gt;jsonresume-theme-microdata&lt;/a&gt; is a JSON Resume theme. It looks pretty much like the ⬆️ third one in the image above.&lt;/p&gt;

&lt;p&gt;It is distributed as an npm package and is a fork of &lt;a href="https://github.com/rbardini/jsonresume-theme-even"&gt;jsonresume-theme-even&lt;/a&gt;. The &lt;code&gt;jsonresume-theme-microdata&lt;/code&gt; version has the same styles as &lt;code&gt;jsonresume-theme-even&lt;/code&gt; (for the most part). The main differences is the &lt;code&gt;microdata&lt;/code&gt; fork has some HTML changes and the addition of &lt;code&gt;microdata&lt;/code&gt; throughout the HTML.&lt;/p&gt;

&lt;p&gt;Luckily, the &lt;code&gt;even&lt;/code&gt; theme already had good semantic HTML structure, so not tons of HTML changes - mostly converting sections to use description lists. DL's are my jam - a totally underused set of elements.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://github.com/scottnath/jsonresume-theme-microdata/TBD___"&gt;this basic example usage&lt;/a&gt; in the examples directory in the &lt;a href="https://github.com/scottnath/jsonresume-theme-microdata"&gt;&lt;code&gt;jsonresume-theme-microdata&lt;/code&gt; github repo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Explainer: adding &lt;strong&gt;structured data&lt;/strong&gt; to a JSON Resume theme
&lt;/h2&gt;

&lt;p&gt;A breakdown of how I adjusted the HTML and microdata. &lt;/p&gt;

&lt;p&gt;This is just a subset of the changes - &lt;a href="https://github.com/jsonresume/resume-schema/blob/master/schema.json"&gt;the JSON Resume schema&lt;/a&gt; is pretty big.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Look at two similar, but different sections
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;basics&lt;/code&gt; is all the metadata about you, the person, so name, phone, label ("e.g. Web Developer"), image, etc.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;work&lt;/code&gt; is your work history&lt;/p&gt;

&lt;p&gt;&lt;code&gt;volunteer&lt;/code&gt; is your volunteer history&lt;/p&gt;

&lt;p&gt;The similarities between &lt;code&gt;volunteer&lt;/code&gt; and &lt;code&gt;work&lt;/code&gt; are that you spent time at an organization, and that you had a position there. Here is the relevant subset of the data, using the JSON Resume schema structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"basics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Scott Nath"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"work"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Company ABC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"...company description..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Software developer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"...details about position..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Company Meow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sitting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"volunteer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Company 501c3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Software developer for free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"...details about position..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Write semantic HTML which matches the resume data hierarchy
&lt;/h3&gt;

&lt;p&gt;Making an &lt;code&gt;article&lt;/code&gt; with &lt;code&gt;h1&lt;/code&gt; cause a resume on a page should be the main document.&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;article&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Scott Nath&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Work History&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Company ABC&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;...company description...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;Software developer&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;...details about role...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Company Meow&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;Sitting&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Volunteer History&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Company 501c3&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;Software developer for free&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;...details about role...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Add microdata, using Schema.org types
&lt;/h3&gt;

&lt;p&gt;A resume describes a person, so the primary &lt;code&gt;itemscope&lt;/code&gt; of your resume will be &lt;a href="https://schema.org/Person"&gt;schema.org/Person&lt;/a&gt;. For both &lt;code&gt;volunteer&lt;/code&gt; and &lt;code&gt;work&lt;/code&gt;, the person needs to connect to them in the structured data, so they are an alimni of the orgs: &lt;a href="https://schema.org/alumniOf"&gt;schema.org/alumniOf&lt;/a&gt;. &lt;code&gt;alumniOf&lt;/code&gt; is a property of &lt;code&gt;Person&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The organization for both types would have &lt;code&gt;itemscope&lt;/code&gt; &lt;a href="https://schema.org/Organization"&gt;schema.org/Organization&lt;/a&gt;, but &lt;code&gt;Organization&lt;/code&gt; has a lot of specific Types available on top of it - &lt;code&gt;Airline&lt;/code&gt;, &lt;code&gt;LocalBusiness&lt;/code&gt;, &lt;code&gt;NGO&lt;/code&gt;, &lt;code&gt;EducationalOrganization&lt;/code&gt; - but since that specificity is hard to automate, for now the &lt;code&gt;microdata&lt;/code&gt; theme labels all these an &lt;code&gt;Organization&lt;/code&gt; Type.&lt;/p&gt;

&lt;p&gt;The position is something that happened &lt;em&gt;inside&lt;/em&gt; the organization. Here the semantic HTML aligns great with the schema.org Types! (yes, a &lt;code&gt;section&lt;/code&gt; was added...but...still semantic!) To connect the position is itemprop &lt;code&gt;employee&lt;/code&gt;. Then, to describe the employee, we're using a more specific Type of Person, &lt;code&gt;EmployeeRole&lt;/code&gt;.&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;article&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/Person"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Scott Nath&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Work History&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"alumniOf"&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/Organization"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Company ABC&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...company description...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"employee"&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/EmployeeRole"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h4&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"roleName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Software developer&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...details about role...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"alumniOf"&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/Organization"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Company Meow&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"employee"&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/EmployeeRole"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h4&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"roleName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sitting&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Volunteer History&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"alumniOf"&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/Organization"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Company 501c3&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"employee"&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/EmployeeRole"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h4&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"roleName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Software developer for free&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...details about role...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Validate your microdata to output the data
&lt;/h3&gt;

&lt;p&gt;Using &lt;a href="https://validator.schema.org"&gt;validator.schema.org&lt;/a&gt;, we can copypasta the HTML above and below is the validator output. &lt;/p&gt;

&lt;pre&gt;
Person
  @type Person
  name  Scott Nath
  alumniOf  
    @type   Organization
    name    Company ABC
    description ...company description...
    employee    
      @type EmployeeRole
      roleName  Software developer
      description   ...details about role...
  alumniOf  
    @type   Organization
    name    Company Meow
    employee    
      @type EmployeeRole
      roleName  Sitting
  alumniOf  
    @type   Organization
    name    Company 501c3
    employee    
      @type EmployeeRole
      roleName  Software developer for free
      description   ...details about role...
&lt;/pre&gt;

&lt;h2&gt;
  
  
  Other fun data structues that were added
&lt;/h2&gt;

&lt;p&gt;Here's a few of the other data types that came out as interesting ways to connect to Person&lt;/p&gt;

&lt;h3&gt;
  
  
  basics.profiles is all your social network profiles
&lt;/h3&gt;

&lt;p&gt;These I chose &lt;a href="https://schema.org/ContactPoint"&gt;ContactPoint&lt;/a&gt; as the Type, making HTML like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dl&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"ContactPoint"&lt;/span&gt; &lt;span class="na"&gt;itemscope=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/ContactPoint"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dt&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"contactType"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Mastodon&lt;span class="nt"&gt;&amp;lt;/dt&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dd&lt;/span&gt; &lt;span class="na"&gt;data-network=&lt;/span&gt;&lt;span class="s"&gt;"Mastodon"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&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;"https://mastodon.social/@scottnath"&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"identifier"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;scottnath@mastodon.social&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dd&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&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;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"ContactPoint"&lt;/span&gt; &lt;span class="na"&gt;itemscope=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/ContactPoint"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dt&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"contactType"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;DEV&lt;span class="nt"&gt;&amp;lt;/dt&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dd&lt;/span&gt; &lt;span class="na"&gt;data-network=&lt;/span&gt;&lt;span class="s"&gt;"DEV"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://dev.to/scottnath"&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"identifier"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;scottnath&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dl&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which ends up with this structure:&lt;/p&gt;

&lt;pre&gt;
Person
  @type Person
  name  Scott Nath
  contactPoint  
    @type   ContactPoint
    contactType Mastodon
    url https://mastodon.social/&lt;a class="mentioned-user" href="https://dev.to/scottnath"&gt;@scottnath&lt;/a&gt;
    identifier  scottnath@mastodon.social
  contactPoint  
    @type   ContactPoint
    contactType DEV
    url https://dev.to/scottnath
    identifier  scottnath
&lt;/pre&gt;

&lt;h3&gt;
  
  
  I added specificity with &lt;code&gt;skills&lt;/code&gt; to allow &lt;code&gt;itemtype&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Here's a couple  &lt;code&gt;skills&lt;/code&gt; from my resume.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Languages"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"itemtype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ComputerLanguage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Javascript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Typescript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"CSS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"HTML"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Bash/Shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Gherkin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"PHP"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UI Components"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"itemtype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SoftwareSourceCode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Web Components"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"VueJS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Lit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Sass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"HandlebarsJS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ReactJS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"AngularJS"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which came out as very interesting structured data in that it's nice to have information about me labeled like this in the wild:&lt;/p&gt;

&lt;pre&gt;
Person
  @type Person
  name  Scott Nath
  knowsAbout    
    @type   ComputerLanguage
    description Languages
    name    Javascript
    name    Typescript
    name    CSS
    name    HTML
    name    Bash/Shell
    name    Gherkin
    name    Python
    name    PHP
  knowsAbout    
    @type   SoftwareSourceCode
    description UI Components
    name    Web Components
    name    VueJS
    name    Lit
    name    Sass
    name    HandlebarsJS
    name    ReactJS
    name    AngularJS
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;kewl!!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  This is long, let's stop
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If you'd like to see the full data model extracted from my resume &lt;a href="https://search.google.com/test/rich-results/result?id=ukiyrbatK8G5K_ESOPBSBg"&gt;here is the Google Rich Results Test&lt;/a&gt; which shows all the data extracted from my resume using the &lt;code&gt;jsonresume-theme-microdata&lt;/code&gt; theme.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

</description>
      <category>seo</category>
      <category>jsonresume</category>
      <category>html</category>
      <category>job</category>
    </item>
    <item>
      <title>How to Boost SEO by Enhancing HTML with Microdata</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Wed, 01 May 2024 20:05:15 +0000</pubDate>
      <link>https://dev.to/scottnath/how-to-boost-seo-by-enhancing-html-with-microdata-3nol</link>
      <guid>https://dev.to/scottnath/how-to-boost-seo-by-enhancing-html-with-microdata-3nol</guid>
      <description>&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;I've been re-writing the HTML of my site and added &lt;strong&gt;structured data&lt;/strong&gt;, in the form of microdata attributes, following the &lt;a href="https://schema.org"&gt;Schema.org&lt;/a&gt; vocabulary set. Structured data can be understood by search engines and other machines, giving your content structure and context. &lt;/p&gt;

&lt;p&gt;The following is what I've learned so far, with some code examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You understand HTML&lt;/p&gt;

&lt;h2&gt;
  
  
  What is all this technology?
&lt;/h2&gt;

&lt;p&gt;There are a few different technologies involved, but in general this is about how to include &lt;strong&gt;strucuted data&lt;/strong&gt; in your HTML markup. Structured data falls under the &lt;strong&gt;semantic web&lt;/strong&gt; label which rolls up into the new hotness of Web 3.0. This article is not about Web 3.0, I'm only at the what-I-read-on-wikipedia level of knowledge about the next web. This article will focus on microdata, Schema.org vocabulary and how to add it to your HTML.&lt;/p&gt;

&lt;p&gt;Here's some short bits about each of those, with links to learn more. &lt;/p&gt;

&lt;h3&gt;
  
  
  What is structured data?
&lt;/h3&gt;

&lt;p&gt;There is a vast amount of information out there about &lt;strong&gt;structured data&lt;/strong&gt; and this article is more of a &lt;em&gt;how&lt;/em&gt; than a &lt;em&gt;what&lt;/em&gt;. Brevity is the soul of wit and all that, so I'll (try to) be brief and give you links to dive deeper. To shortcut "what is structured data", lemme just reference this article: &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data"&gt;Google explains structured data&lt;/a&gt;, which is an &lt;em&gt;excellent&lt;/em&gt; primer on the subject. Low on time? Just watch the 1st video in the article for a great sum-up.&lt;/p&gt;

&lt;p&gt;My sum-up: &lt;strong&gt;structured data&lt;/strong&gt; is a way to include machine-readable key-value versions of the content on your page alongside (or integrated with) your HTML markup.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the semantic web?
&lt;/h3&gt;

&lt;p&gt;In short - a machine-readable internet. &lt;/p&gt;

&lt;p&gt;More details are out-of-scope for this article. &lt;a href="https://www.smashingmagazine.com/2020/10/developing-semantic-web/"&gt;This Smashing Mag article&lt;/a&gt; has a nice primer with charts and stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Schema.org? What is their vocabulary?
&lt;/h3&gt;

&lt;p&gt;The term "vocabulary" means the names and expected structure of each type of data. &lt;/p&gt;

&lt;p&gt;Founded by Google, Microsoft, Yahoo and Yandex, &lt;a href="https://schema.org"&gt;Schema.org&lt;/a&gt; vocabularies are developed by an open community process. e.g. the largest search providers got together and came up with a shared way to document content on the web.&lt;/p&gt;

&lt;p&gt;The concept is that a shared vocabulary makes it easier for webmasters and developers to decide on a schema and get the maximum benefit for their efforts. The benefit being, web developers can document their content with data structures that work across all the major search players.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;huge caveat:&lt;/strong&gt; &lt;em&gt;this helps search engine &lt;strong&gt;accuracy&lt;/strong&gt;, it does not help ranking&lt;/em&gt;. (this disclaimer is repeated across all the Search Engines' docs about structued data)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://schema.org"&gt;Schema.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://schema.org/docs/gs.html"&gt;microdata usage guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://schema.org/Person"&gt;example: the &lt;strong&gt;Person&lt;/strong&gt; type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://schema.org/Article"&gt;example: the &lt;strong&gt;Article&lt;/strong&gt; type&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Schema.org type inheritance
&lt;/h4&gt;

&lt;p&gt;The Schema.org vocabulary is made up of "types" which build upon each other with ever-more increasing specificity in properties. The most root type is the &lt;a href="https://schema.org/Thing"&gt;Thing type&lt;/a&gt;. &lt;strong&gt;Thing&lt;/strong&gt; contains a set of generic properties like name, url, description. All other types generally derive from Thing. Person, Place, Action, etc. - they all fall under Thing which means they all automatically have the Thing properties (name, url, etc) and add their own on top. &lt;/p&gt;

&lt;p&gt;For instance, the &lt;a href="https://schema.org/Article"&gt;Article type&lt;/a&gt; is a superset of the &lt;a href="https://schema.org/CreativeWork"&gt;Creative Work type&lt;/a&gt;. &lt;strong&gt;Creative Work&lt;/strong&gt; adds a ton of properties on top of Thing, such as author, about, dateCreated and headline. &lt;strong&gt;Article&lt;/strong&gt; then has more properties in addition to what is in Creative Work, such as articleBody, backstory, and wordCount.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I chose &lt;code&gt;microdata&lt;/code&gt; to document my content
&lt;/h2&gt;

&lt;p&gt;To document your data in your HTML, you have four choices: microdata, JSON-LD, RDFa, and microformats&lt;/p&gt;

&lt;p&gt;rejected: microformats, due to limited structures and it being more focused on webmentions, not the whole-of-your-content&lt;/p&gt;

&lt;p&gt;rejected: RDFa had outdated docs and uses a different vocabulary from schema.org &lt;/p&gt;

&lt;p&gt;So that left microdata and JSON-LD. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jsonld.com/why-json-ld/"&gt;jsonld.com: Why JSON-LD?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Microdata_(HTML)#Global_attributes"&gt;micridata primer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;8 minutes that explain the difference: &lt;a href="https://youtu.be/Yj8ujUfAJM8"&gt;YouTube: Microdata vs JSON-LD: Which Structured Data Format Wins?&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JSON-LD
&lt;/h3&gt;

&lt;p&gt;A fairly common argument for JSON-LD says that JSON-LD is easier to maintain due to not being directly tied to HTML structure. You just create your data-mapping and when your system is writing your pages, it also writes the JSON-LD. &lt;/p&gt;

&lt;p&gt;Downside? That duplicates all your content into &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags. It also requires you to write or manage a system to generate them (since it is separate from your HTML). If you have unknown data or hand-written HTML, it's harder to add and maintain.&lt;/p&gt;

&lt;p&gt;Upside? Lots of plugins and tools our there to automate adding JSON-LD. (doesn't help me on &lt;a href="https://scottnath.com"&gt;scottnath.com&lt;/a&gt; because I do a lot of one-off HTML and I mostly don't use a CMS)&lt;/p&gt;

&lt;h3&gt;
  
  
  Microdata (spoiler alert: I chose this)
&lt;/h3&gt;

&lt;p&gt;Microdata is added, mostly, by adding attributes to your HTML elements.&lt;/p&gt;

&lt;p&gt;I added microdata across &lt;a href="https://scottnath.com"&gt;scottnath.com&lt;/a&gt; where using microdata made sense because I was writing the HTML in bespoke little components. It was fairly simple to add to my pages because I already use semantic HTML, and microdata expects your HTML to reflect the structure of your content. &lt;/p&gt;

&lt;p&gt;It also doesn't add a whole lot of new stuff, for instance, the title on my articles went from this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;h1&amp;gt;{title}&amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;&amp;lt;h1 itemprop="headline"&amp;gt;{title}&amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Pretty easy to add!&lt;/p&gt;

&lt;p&gt;FYI - I initially started this work wanting to write HTML for JSON Resume which conformed more stringently to a resume's hierarchical content. HTML is a programming language after all. That work is coming in next article - a semantic JSON Resume!&lt;/p&gt;

&lt;h2&gt;
  
  
  How to add microdata to HTML
&lt;/h2&gt;

&lt;p&gt;Did you know that HTML already has global attributes &lt;em&gt;specifically&lt;/em&gt; for including microdata? &lt;/p&gt;

&lt;p&gt;These attributes can go on most HTML elements and they follow a specific structure in their usage. The three you'll most often use are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemscope"&gt;itemscope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemtype"&gt;itemtype&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemprop"&gt;itemprop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemref"&gt;itemref&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/itemid"&gt;itemid&lt;/a&gt;, but those won't be used in this tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;itemscope&lt;/code&gt; and &lt;code&gt;itemtype&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;These both are always together on a container element, although some examples show just using &lt;code&gt;itemscope&lt;/code&gt;, but without &lt;code&gt;itemtype&lt;/code&gt; the &lt;code&gt;itemscope&lt;/code&gt; just denotes that everything contained within that element is related to the scope. For this learning, we only care about documenting our content for SEO, so we need &lt;code&gt;itemtype&lt;/code&gt; to point to a type at schema.org to know what the &lt;code&gt;itemscope&lt;/code&gt; is documenting.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scope&lt;/em&gt; in this case, means a related chunk of content. When parsing microdata within an HTML element with the boolean &lt;code&gt;itemscope&lt;/code&gt; attribute, it is expected that every &lt;code&gt;itemprop&lt;/code&gt; inside that element falls under &lt;em&gt;scope&lt;/em&gt; of &lt;code&gt;itemscope&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;itemtype&lt;/code&gt; is always a URL. In this case, a URL to a schema.org type. So, if you had an article with a title and summary, it would match the &lt;a href="https://schema.org/Article"&gt;schema.org Article type&lt;/a&gt; and your HTML container would be like so:&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;article&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/Article"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...article contents
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;itemscope&lt;/code&gt; is boolean, so it is &lt;em&gt;never&lt;/em&gt; &lt;code&gt;itemscope="meow"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;itemtype&lt;/code&gt; denotes what &lt;em&gt;type&lt;/em&gt; of content is being described by pointing to the documentation about that type&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;itemprop&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;itemprop&lt;/code&gt; adds properties to a scope. &lt;br&gt;
e.g. it adds data that is a subset of it's main &lt;code&gt;itemscope&lt;/code&gt;&lt;br&gt;
e.g. if &lt;code&gt;itemscope&lt;/code&gt; is an &lt;em&gt;object&lt;/em&gt;, &lt;code&gt;itemprop&lt;/code&gt; is a &lt;em&gt;property&lt;/em&gt; on that &lt;em&gt;object&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Assuming this is the main article on a page, the title should be the top heading, an &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;. The properties of an article are detailed on the &lt;a href="https://schema.org/Article"&gt;schema.org Article type page&lt;/a&gt; From that type, we'll be using &lt;code&gt;headline&lt;/code&gt;, a property inherited from &lt;a href="https://schema.org/CreativeWork"&gt;Creative Work&lt;/a&gt; and expected to be &lt;code&gt;Text&lt;/code&gt; (plain text) and described as "Headline of the article." With &lt;code&gt;itemprop&lt;/code&gt;, our HTML becomes:&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;article&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/Article"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"headline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;An important article about things&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding in a main article image, we can document that as well. We'll be using &lt;code&gt;image&lt;/code&gt;, which is part of &lt;a href="https://schema.org/Thing"&gt;Thing&lt;/a&gt;. Remember, the hierarchy goes Thing-&amp;gt;Creative Work-&amp;gt;Article in order to know the properties available.&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;article&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/Article"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"headline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;An important article about things&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/image.jpg"&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  What data do we know so far?
&lt;/h4&gt;

&lt;p&gt;This would be the output when reading our microdata:&lt;/p&gt;

&lt;pre&gt;
Article
  @type     Article
  headline  An important article about things
  image     https://example.com/image.jpg
&lt;/pre&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;itemprop&lt;/code&gt; which is an &lt;code&gt;itemscope&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the &lt;strong&gt;structure&lt;/strong&gt; part of structured data. &lt;/p&gt;

&lt;p&gt;Let's add an author! Under the Creative Work type, there is a property &lt;code&gt;author&lt;/code&gt; (&lt;a href="https://schema.org/author"&gt;schema.org/author&lt;/a&gt;), but it must be either a Person type or Organization type. These can only be documented via an &lt;code&gt;itemscope&lt;/code&gt;. The author will be a &lt;a href="https://schema.org/Person"&gt;schema.org/Person&lt;/a&gt;, which means we can have lots of author-specific properties documented.&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;article&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/Article"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"headline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;An important article about things&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/image.jpg"&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"author"&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/Person"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Written by 
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Scott Nath&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;,
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"jobTitle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Open Source Developer&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  What do we know from the above HTML?
&lt;/h4&gt;

&lt;p&gt;Author has been added as it's own section and has sub-properties:&lt;/p&gt;

&lt;pre&gt;
Article
  @type     Article
  headline  An important article about things
  image     https://example.com/image.jpg
  author
      @type     Person
      url       https://example.com/
      name      Scott Nath
      jobTitle  Open Source Developer
&lt;/pre&gt;

&lt;h2&gt;
  
  
  How to parse-out and validate your microdata
&lt;/h2&gt;

&lt;p&gt;Sure you added microdata, but how do you check it?&lt;/p&gt;

&lt;p&gt;There are two main validators, &lt;a href="https://validator.schema.org"&gt;Schema.org's validator&lt;/a&gt; and &lt;a href="https://search.google.com/test/rich-results"&gt;Google's Rich Results Test&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;They both accept a URL or a snippet of HTML, parse the HTML, then return whatever data it could find. The difference is Google's only parses a subset of the Schema.org vocabulary - the rest of your microdata will be ignored by Google. Both returned the same results for our HTML snippet from above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Rich Results Test
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://search.google.com/test/rich-results"&gt;search.google.com/test/rich-results&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google recommends that you start with the Rich Results Test to see what Google rich results can be generated for your page. You'll need to reference &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/search-gallery?hl=en"&gt;Google's list of structured data types they support&lt;/a&gt;, which is a &lt;em&gt;subset&lt;/em&gt; of the Schema.org types. If the type you use ain't on Google's list, it ain't gonna get read by Google (the other search engines will read it tho.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvx4dytdsyq5kv4jcfgj5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvx4dytdsyq5kv4jcfgj5.jpg" alt="Screenshot image shows the Rich Results Test output for the Article HTML snippet" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema.org validator
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://validator.schema.org"&gt;validator.schema.org&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For generic schema validation, use the Schema Markup Validator to test all types of schema.org markup, without Google-specific validation. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F32y3suflvb42o1z21xgo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F32y3suflvb42o1z21xgo.jpg" alt="Screenshot image shows the Schema.org validatory output for the Article HTML snippet" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Not The End
&lt;/h2&gt;

&lt;p&gt;So that's a high-level overview of how and why to add microdata to your HTML. I added microdata across my site but so far, my findings are inconclusive on how it helps with SEO. It takes a while for crawlers to fully index, so hopefully after I've written the article on JSON Resume with microdata, I'll have some kinda noticeable outcome. Stay tuned!&lt;/p&gt;

</description>
      <category>seo</category>
      <category>microdata</category>
      <category>html</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Storybook setup: Virtual Screen Reader with Web Components</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Thu, 07 Mar 2024 17:22:19 +0000</pubDate>
      <link>https://dev.to/scottnath/storybook-setup-virtual-screen-reader-with-web-components-1h6p</link>
      <guid>https://dev.to/scottnath/storybook-setup-virtual-screen-reader-with-web-components-1h6p</guid>
      <description>&lt;p&gt;&lt;strong&gt;How to set up Storybook to test shadowroot elements in web components using shadow-dom-testing-library and Guidepup's Virtual Screen Reader.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Simulated user testing is difficult with web components due to the unique nature of the &lt;code&gt;shadowroot&lt;/code&gt; - but it can be done in Storybook! This article details the basic setup with examples to get you writing tests for customElements that simulate user behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;Two modules, &lt;code&gt;@guidepup/virtual-screen-reader&lt;/code&gt; and &lt;code&gt;shadow-dom-testing-library&lt;/code&gt; give your Storybook the power to test web components inside Storybook's Interaction Tests. This means you can test the user-interactions and screen reader output of your web components. But beware ... &lt;code&gt;testing-library/user-test&lt;/code&gt; can't simulate &lt;code&gt;TAB&lt;/code&gt; navigation inside shadowroots yet.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/scottnath/storybook-web-components-testing"&gt;Example GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/scottnath/storybook-web-components-testing/blob/main/src/MyElement.stories.js"&gt;Example storybook file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackblitz.com/~/github.com/scottnath/storybook-web-components-testing"&gt;Running app on Stackblitz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.guidepup.dev/docs/virtual"&gt;@guidepup/virtual-screen-reader docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/KonnorRogers/shadow-dom-testing-library"&gt;shadow-dom-testing-library docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Knowledge of &lt;a href="https://testing-library.com/docs"&gt;testing-library&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Knowledge of &lt;a href="https://storybook.js.org/docs/writing-tests/interaction-testing"&gt;storybook interaction testing&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is this article about?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Problems testing web components &lt;/li&gt;
&lt;li&gt;How to test web components in Storybook Interaction Tests&lt;/li&gt;
&lt;li&gt;How to test web components using virtual-screen-reader&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Problems testing web components
&lt;/h2&gt;

&lt;p&gt;It's the &lt;strong&gt;shadowroot&lt;/strong&gt;. It's always the &lt;strong&gt;shadowroot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Lots of libraries haven't been updated to integrate shadow DOM. This is not to knock anybody - I think after years of waiting on browsers-makers to catch up, we were all genuinely surprised when extensive customElements support actually got integrated into major browsers 🤷.&lt;/p&gt;

&lt;p&gt;The below problems are found within &lt;code&gt;testing-library&lt;/code&gt;, but are also inherited by &lt;code&gt;@storybook/test&lt;/code&gt;. This means the bugs affect APIs like &lt;code&gt;userEvent&lt;/code&gt; and &lt;code&gt;screen&lt;/code&gt; regardless of the source module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem A: &lt;code&gt;screen&lt;/code&gt; doesn't look into the shadowroot
&lt;/h3&gt;

&lt;p&gt;Whether from &lt;code&gt;testing-library/dom&lt;/code&gt; or &lt;code&gt;@storybook/test&lt;/code&gt;, the &lt;code&gt;screen&lt;/code&gt; and all it's subsequent &lt;code&gt;queries&lt;/code&gt; won't find the elements in your shadow DOM. Real world - that translates to &lt;code&gt;screen.queryAllByRole('button')&lt;/code&gt; not seeing the &lt;code&gt;BUTTON&lt;/code&gt; inside your fancy &lt;code&gt;&amp;lt;fancy-button&amp;gt;&lt;/code&gt; web component. &lt;/p&gt;

&lt;p&gt;And it definitely comes up empty on recursive shadowroots (e.g. web components slotted into other web components)&lt;/p&gt;

&lt;h4&gt;
  
  
  Fix: &lt;code&gt;npm i shadow-dom-testing-library&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;To get around the &lt;code&gt;screen&lt;/code&gt; and &lt;code&gt;queries&lt;/code&gt; problems and allow our Storybook interaction tests to find the shadow DOM's secrets, we'll use &lt;a href="https://github.com/KonnorRogers/shadow-dom-testing-library"&gt;KonnorRogers/shadow-dom-testing-library&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Your new friend, &lt;code&gt;shadow-dom-testing-library&lt;/code&gt;, includes various methods in its api which mimic the queries made by testing-library - but now with &lt;strong&gt;shadowroot&lt;/strong&gt;! The win here is that &lt;code&gt;shadow-dom-testing-library&lt;/code&gt; gives your tests the power to query elements within the shadow DOM. To do this, &lt;code&gt;screen.findByLabelText&lt;/code&gt; is cloned and changed to become &lt;code&gt;screen.findByShadowLabelText&lt;/code&gt;, which has the smarts to see into the &lt;code&gt;shadowroot&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;More examples below, but see the &lt;a href="https://github.com/KonnorRogers/shadow-dom-testing-library"&gt;shadow-dom-testing-library github repo&lt;/a&gt; for detailed usage of that NPM module. I'm not covering it all here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem B: &lt;code&gt;userEvent&lt;/code&gt; can't traverse the shadowroot
&lt;/h3&gt;

&lt;p&gt;This doesn't mean &lt;code&gt;userEvent&lt;/code&gt; doesn't work on elements in the shadowroot. If you can get the element and give it to &lt;code&gt;userEvent&lt;/code&gt;, hot dog! it can do all the clicks and typin you need. But not &lt;code&gt;TAB&lt;/code&gt;. Traversing via &lt;code&gt;TAB&lt;/code&gt; is no. &lt;/p&gt;

&lt;p&gt;We'll be using &lt;code&gt;virtual-screen-reader&lt;/code&gt;'s &lt;code&gt;.next()&lt;/code&gt; method to navigate through the shadow DOM.&lt;/p&gt;

&lt;p&gt;AFAIK, internally &lt;code&gt;TAB&lt;/code&gt; uses global &lt;code&gt;window&lt;/code&gt; inside Storybook...which can't naturally see into the shadowroot. I'm still investigating this, but I found a lot of unresolved issues doing searches for "&lt;code&gt;userEvent&lt;/code&gt;" + "&lt;code&gt;shadowroot&lt;/code&gt;" - lemme know if I've missed something!&lt;/p&gt;

&lt;h4&gt;
  
  
  Fix: &lt;code&gt;npm i @guidepup/virtual-screen-reader&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;One cool aspect of &lt;code&gt;virtual-screen-reader&lt;/code&gt; is that it navigates through your elements. We can use this to programmatically traverse the inner shadow DOM. &lt;/p&gt;

&lt;p&gt;Returning folks be aware - this article has some repeat-examples from &lt;a href="https://dev.to/scottnath/simple-setup-virtual-screen-reader-in-storybook-2efo"&gt;"&lt;em&gt;Simple setup: Virtual Screen Reader in Storybook&lt;/em&gt;"&lt;/a&gt; ... with different usage for web components natch.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. How to test web components in Storybook Interaction Tests
&lt;/h2&gt;

&lt;p&gt;See the &lt;a href="https://github.com/scottnath/storybook-web-components-testing/blob/main/src/MyElement.stories.js"&gt;MyElement.stories.js file in the storybook-web-components-testing repo&lt;/a&gt; for the full version of these examples.&lt;/p&gt;

&lt;p&gt;The following examples use &lt;a href="https://storybook.js.org/docs/api/csf"&gt;Storybook's Component Story Format&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2a. install shadow-dom-testing-library
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npm i shadow-dom-testing-library -D&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2b. use &lt;code&gt;within&lt;/code&gt; from &lt;code&gt;shadow-dom-testing-library&lt;/code&gt; (I alias it to &lt;code&gt;shadowWithin&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CSF file: MyElement.stories.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;within&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;shadowWithin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shadow-dom-testing-library&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@storybook/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyElement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-element&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;my-element count=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;lt;/my-element&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;h3&gt;
  
  
  Step 2c. use shadow-dom-testing-library screen queries, then test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...CSF file contents...&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SomeExample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&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="na"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;// get a screen from the canvasElement generated by Storybook&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;shadowWithin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// get the button from within the shadowroot with `queryBy`&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryByShadowRole&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="c1"&gt;// test the button exists&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myButton&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&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;h2&gt;
  
  
  3. How to test web components with virtual-screen-reader
&lt;/h2&gt;

&lt;p&gt;You'll be capturing your element with &lt;code&gt;queryByShadowMEOW('the-meow')&lt;/code&gt; (&lt;code&gt;MEOW&lt;/code&gt; of course being whatever query you're using from the &lt;a href="https://testing-library.com/docs/queries/about"&gt;testing-library core API queries&lt;/a&gt;). After you have your element, using virtual-screen-reader is the same as the usage broken down in &lt;a href="https://dev.to/scottnath/simple-setup-virtual-screen-reader-in-storybook-2efo"&gt;Simple setup: Virtual Screen Reader in Storybook&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3a. install virtual-screen-reader (VSR)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npm i @guidepup/virtual-screen-reader -D&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3b. import it into your stories
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CSF file: MyElement.stories.js, now with VSR!&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;within&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;shadowWithin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shadow-dom-testing-library&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@guidepup/virtual-screen-reader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@storybook/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyElement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-element&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;my-element count=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;lt;/my-element&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;h3&gt;
  
  
  Step 3c: once you have your main Shadow container, give it to VSR
&lt;/h3&gt;

&lt;p&gt;If the HTML in your shadowroot is...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- note: `section` by default has an aria-role of `region` --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"My Element"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;button&amp;gt;&lt;/span&gt;my button&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...then your story's play method will be 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="c1"&gt;// ...CSF file contents...&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SomeExample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&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="na"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;// make a `screen` from the canvasElement by finding it's shadowroot&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;shadowWithin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// get the outer-most SECTION element in the shadowroot with `aria-label="My Element"`&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myShadowContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByShadowLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/My Element/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Start up the Virtual Screen Reader, giving it the SECTION container&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myShadowContainer&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Breakdown of elements-found, in order:&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. `virtual` starts on the container, a `SECTION` has the role `region`, &lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastSpokenPhrase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;region, My Element&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Go to next speak-able text&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. we're now on `BUTTON`&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastSpokenPhrase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&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, my button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Go to next speak-able text&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 5. We're done and VSR says so by closing the `region`:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastSpokenPhrase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end of region, My Element&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope this gets you unstuck. Web components are definitely the new hotness, the DevX tooling just needs a little help still.&lt;/p&gt;

&lt;p&gt;Bonus content for reading this far - check out &lt;a href="https://github.com/scottnath/storybook-web-components-testing/blob/main/src/helper-vsr-tabnodes.js"&gt;this hacky workaround that uses VSR to find all tabbable nodes inside a shadowroot&lt;/a&gt;!&lt;/p&gt;




</description>
      <category>webcomponents</category>
      <category>storybook</category>
      <category>accessibility</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Simple setup: Virtual Screen Reader in Storybook</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Fri, 01 Mar 2024 19:49:02 +0000</pubDate>
      <link>https://dev.to/scottnath/simple-setup-virtual-screen-reader-in-storybook-2efo</link>
      <guid>https://dev.to/scottnath/simple-setup-virtual-screen-reader-in-storybook-2efo</guid>
      <description>&lt;p&gt;How to set up Storybook with Guidepup's Virtual Screen Reader and test what the screen reader speaks&lt;/p&gt;

&lt;p&gt;Recent changes to the &lt;a href="https://github.com/guidepup/virtual-screen-reader"&gt;@guidepup Virtual Screen Reader library&lt;/a&gt; make it ridiculously easy to integrate into Storybook for use in your integration tests. This article shows how to get the basic setup going and how to write your first screen reader test in Storybook.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;The Virtual Screen Reader is a great tool to mimic screen reader output and it's works in Storybook without addons or configuration changes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/scottnath/virtual-screen-reader-storybook"&gt;Example GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/scottnath/virtual-screen-reader-storybook/blob/main/stories/Header.stories.js"&gt;Example story file with VSR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackblitz.com/~/github.com/scottnath/virtual-screen-reader-storybook"&gt;Running app on Stackblitz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.guidepup.dev/docs/virtual"&gt;Virtual Screen Reader docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is the Virtual Screen Reader?
&lt;/h2&gt;

&lt;p&gt;The Virtual Screen Reader (&lt;a href="https://www.guidepup.dev/docs/virtual"&gt;docs&lt;/a&gt;, &lt;a href="https://github.com/guidepup/virtual-screen-reader"&gt;GitHub&lt;/a&gt;) is a library, available via NPM, which mimics the functionality of a screen reader. The VSR is from Guidepup (&lt;a href="https://guidepup.dev/"&gt;guidepup.dev&lt;/a&gt;), which has tons of screen reader tooling. The Virtual Screen Reader is the easiest @guidepup tool to set up and it can read your components like a screen reader would, giving you back the text for use in testing.&lt;/p&gt;

&lt;p&gt;FYI: &lt;em&gt;Guidepup has a suite of tools that mirror and/or directly-simulate screen reader experiences and capture the results.  The tools are impressive and worth checking out if you want to automate accessibility testing into your workflow. For example, you can use directly use the system NVDA or Mac VoiceOver via Playwright. (check out &lt;a href="https://dev.to/craigmorten/a11y-unlocked-screen-reader-automation-tests-3mc8"&gt;Craig's dev.to article on implementing Guidepup with Playwright and Mac VoiceOver&lt;/a&gt; for details)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How does the screen reader interpret a chunk of HTML?
&lt;/h2&gt;

&lt;p&gt;Screen readers vary in interpretation, but this gives a &lt;em&gt;rough&lt;/em&gt; understanding of how your HTML could get broken down by the VSR.&lt;/p&gt;

&lt;p&gt;If the HTML a component puts out is 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;header&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;"storybook-header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;svg&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Acme&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"welcome"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Welcome, &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;Jane Doe&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;!&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log out&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what the Virtual Screen Reader will translate it into:&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;header&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!--BANNER--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Acme&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!--HEADING + text + HEADING-LEVEL--&amp;gt;&lt;/span&gt;
Welcome, &lt;span class="c"&gt;&amp;lt;!--Text--&amp;gt;&lt;/span&gt;
Jane Doe &lt;span class="c"&gt;&amp;lt;!--Text--&amp;gt;&lt;/span&gt;
! &lt;span class="c"&gt;&amp;lt;!--Punctuation--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Log out&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!--BUTTON + text--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!--END OF BANNER--&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to set up your Storybook instance to use the Virtual Screen Reader
&lt;/h2&gt;

&lt;p&gt;Follow these steps to test with VSR in your Storybook environment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ensure your storybook environment is set up for Interaction tests (see &lt;a href="https://storybook.js.org/docs/writing-tests/interaction-testing#set-up-the-interactions-addon"&gt;Storybook setup docs&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Install the VSR npm module 
&lt;code&gt;npm i @guidepup/virtual-screen-reader -D&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;PROFIT&lt;/em&gt; - &lt;strong&gt;No addon setup or other configuration needed!&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to write a Storybook integration test that uses the Virtual Screen Reader
&lt;/h2&gt;

&lt;p&gt;This example is using the default Header component that installs with default Storybook. We'll be adding Interaction tests which will use VSR. You can see the &lt;a href="https://github.com/scottnath/virtual-screen-reader-storybook/blob/main/stories/Header.stories.js"&gt;completed Header.stories.js on GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Import the library into your stories file
&lt;/h3&gt;

&lt;p&gt;To integrate the VSR into your tests, import it in your test file. In this example, we'll add the assertions into a &lt;code&gt;play&lt;/code&gt; test, so we need &lt;code&gt;expect&lt;/code&gt; from Storybook's &lt;code&gt;test&lt;/code&gt; library too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@guidepup/virtual-screen-reader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@storybook/test&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;h3&gt;
  
  
  Write the story and integration test
&lt;/h3&gt;

&lt;p&gt;See comments in-the-code below for details&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="c1"&gt;// Default LoggedIn story&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LoggedIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;user&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;Jane Doe&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="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// add the play method - it's where you put your tests!&lt;/span&gt;
  &lt;span class="na"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;// Start up the Virtual Screen Reader, giving it the canvasElement&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canvasElement&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// This `while` statement navigates through the component using the &lt;/span&gt;
    &lt;span class="c1"&gt;//  Virtual Screen Reader to speak the text whereever the cursor is located.&lt;/span&gt;
    &lt;span class="c1"&gt;//  It will continue until it reaches the end of the &amp;lt;header&amp;gt; element, &lt;/span&gt;
    &lt;span class="c1"&gt;//  which has a `banner` aria role, thus "end of banner"&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastSpokenPhrase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;end of banner&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="c1"&gt;// `.next()` moves the Virtual cursor to the next location.&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// What we expect the screen reader to say&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&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;banner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// it is an `&amp;lt;h1&amp;gt;`, so `level 1`&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading, Acme, level 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Using `args` here allows you to change args without breaking the test&lt;/span&gt;
      &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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;!&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;button, Log out&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;end of banner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;// Here's the test! It asserts the screen reader said what we expected.&lt;/span&gt;
    &lt;span class="c1"&gt;//  When we called `lastSpokenPhrase` every time the `while` looped, &lt;/span&gt;
    &lt;span class="c1"&gt;//  the spoken text was added to the `PhraseLog`, in order&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spokenPhraseLog&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Stop your Virtual Screen Reader instance&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;virtual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&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;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3n62c9dgsipm8cpubms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3n62c9dgsipm8cpubms.png" alt="Screenshot of test results in Storybook integration test using Virtual Screen Reader output" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This example is simple on purpose so it can easily get the copypasta treatment. Once it works for you, dig into the &lt;a href="https://www.guidepup.dev/docs/virtual"&gt;Virtual Screen Reader docs&lt;/a&gt; and see how much more you can test for with this tool. Cheers!&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>storybook</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Profile Components: SSR with Declarative Shadow DOM</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Wed, 21 Feb 2024 23:11:33 +0000</pubDate>
      <link>https://dev.to/scottnath/profile-components-ssr-with-declarative-shadow-dom-2c3n</link>
      <guid>https://dev.to/scottnath/profile-components-ssr-with-declarative-shadow-dom-2c3n</guid>
      <description>&lt;p&gt;With the &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/123#html"&gt;official release of &lt;code&gt;shadowrootmode&lt;/code&gt;-supporting Firefox v123&lt;/a&gt;, it's time to upgrade the profile components with Declarative Shadow DOM functionality!&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://scottnath.com/profile-components/?path=/docs/github-declarative-shadow-dom--docs"&gt;DSD docs for GitHub profile components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scottnath.com/profile-components/?path=/docs/devto-declarative-shadow-dom--docs"&gt;DSD docs for dev.to profile components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackblitz.com/edit/profile-components?file=github-dsd.html&amp;amp;initialpath=github-dsd.html&amp;amp;view=preview"&gt;See the Stackblitz client-side example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What is this?
&lt;/h2&gt;

&lt;p&gt;Four new methods have been added to allow generating a &lt;code&gt;template&lt;/code&gt; tag with &lt;code&gt;shadowrootmode&lt;/code&gt; containing the generated HTML and styles of a profile component. This was fairly trivial to add due to the Javascript, HTML and CSS living in separate files. (see &lt;a href="https://dev.to/scottnath/profile-components-display-social-profiles-in-native-web-components-49b2#%F0%9F%91%B7"&gt;👷 DX: Separate files for Javascript, HTML, and CSS&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Each of the four components now have a &lt;code&gt;dsd&lt;/code&gt; method which can be used to generate something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;shadowrootmode=&lt;/span&gt;&lt;span class="s"&gt;"open"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&lt;/span&gt;&lt;span class="na"&gt;s&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;(..&lt;/span&gt;&lt;span class="nc"&gt;.css&lt;/span&gt; &lt;span class="nt"&gt;styles&lt;/span&gt; &lt;span class="nt"&gt;for&lt;/span&gt; &lt;span class="nt"&gt;GitHub&lt;/span&gt; &lt;span class="nt"&gt;component&lt;/span&gt;&lt;span class="o"&gt;)&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;styles&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="o"&gt;(..&lt;/span&gt;&lt;span class="nc"&gt;.rest&lt;/span&gt; &lt;span class="nt"&gt;of&lt;/span&gt; &lt;span class="nt"&gt;generated&lt;/span&gt; &lt;span class="nt"&gt;HTML&lt;/span&gt;&lt;span class="o"&gt;)&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generated &lt;code&gt;template&lt;/code&gt; can be used server-side when generating HTML output, or client-side after page load. Although...doing this client-side is weird frankly...like...they're web components, just use them as they were written...who knows though, maybe someone has a use for that feature 🤷.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use the Declarative Shadow DOM methods
&lt;/h2&gt;

&lt;p&gt;Below are some examples of usage of the GitHub component (&lt;a href="https://scottnath.com/profile-components/?path=/docs/github-declarative-shadow-dom--docs"&gt;see GitHub DSD docs&lt;/a&gt;). Usage of the DEV components is pretty much the same - &lt;a href="https://scottnath.com/profile-components/?path=/docs/devto-declarative-shadow-dom--docs"&gt;check out the dev.to DSD docs&lt;/a&gt; for DEV usage examples.&lt;/p&gt;

&lt;p&gt;The main value of these is when create HTML &lt;em&gt;before&lt;/em&gt; it is rendered by the browser. Then when the browser renders it, the browser converts the DSD into actual Shadow DOM. These components are only presentational and do not have functionality added by the customElement - so DSD or regular web component usage results in the same thing!&lt;/p&gt;

&lt;h3&gt;
  
  
  SSR (Server Side Rendering) HTML in Node.js
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// import from npm module&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dsd&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile-components/github-utils&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;repos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scottnath/profile-components&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;storydocker/storydocker&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;generatedTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;dsd&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scottnath&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;avatar_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profilePic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;repos&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/**
generatedTemplate contains:
&amp;lt;template shadowrootmode="open"&amp;gt;
  &amp;lt;styles&amp;gt;(...css styles for GitHub component)&amp;lt;/styles&amp;gt;
  &amp;lt;section (...rest of generated HTML)&amp;lt;/section&amp;gt;
&amp;lt;/template&amp;gt;
*/&lt;/span&gt;

&lt;span class="c1"&gt;// use this variable in a file pre-render to have a DSD-pre-populated element&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;componentHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;github-user&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;generatedTemplate&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/github-user&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server side render in an Astro component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;dsd&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile-components/github-utils&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;repos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scottnath/profile-components&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;storydocker/storydocker&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;declaredDOM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;dsd&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scottnath&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;repos&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light_high_contrast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;declaredDOM&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/github-user&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Client side rendering via unpkg
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- add empty elements to HTML --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;github-repository&amp;gt;&amp;lt;/github-repository&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;hr&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;github-user&amp;gt;&amp;lt;/github-user&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// import from unpkg&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://unpkg.com/profile-components/dist/github-utils.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// repo has it's own DSD method:&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dsdRepo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dsd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/**
    * Polyfill for Declarative Shadow DOM which, when triggered, converts
    *  the template element into actual shadow DOM.
    * This is only needed when injecting _after_ page is loaded
    * @see https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill
    */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;triggerAttachShadowRoots&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="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;attachShadowRoots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;root&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;template[shadowrootmode]&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="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;template&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;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shadowrootmode&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;shadowRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="nf"&gt;attachShadowRoots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="cm"&gt;/**
    * Uses the "dsd" method to generate DSD, add the string of DSD content
    *  to the element, then trigger the polyfill to convert the template
    */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;injectDSD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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;dsdHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;dsd&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scottnath&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;true&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="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github-user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dsdHTML&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// now that the HTML is async-created, the polyfill can convert it&lt;/span&gt;
    &lt;span class="nf"&gt;triggerAttachShadowRoots&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;injectDSD&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="cm"&gt;/**
    * Uses the "dsdRepo" method to generate DSD, add the string of DSD content
    *  to the element, then trigger the polyfill to convert the template
    */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;injectRepoDSD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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;dsdHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;dsdRepo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;full_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;scottnath/profile-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="kc"&gt;true&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="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github-repository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dsdHTML&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// now that the HTML is async-created, the polyfill can convert it&lt;/span&gt;
    &lt;span class="nf"&gt;triggerAttachShadowRoots&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;injectRepoDSD&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/profile-components?embed=1&amp;amp;file=github-dsd.html&amp;amp;view=preview&amp;amp;initialpath=github-dsd.html" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>javascript</category>
      <category>html</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Who Me: Notes on creating a custom Chat GPT</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Sat, 02 Dec 2023 17:05:55 +0000</pubDate>
      <link>https://dev.to/scottnath/who-me-notes-on-creating-a-custom-chat-gpt-2g8j</link>
      <guid>https://dev.to/scottnath/who-me-notes-on-creating-a-custom-chat-gpt-2g8j</guid>
      <description>&lt;p&gt;Some notes on creating a custom Chat GPT that can consistently generate specific styles of characters using Dall-E&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The &lt;em&gt;Who Me&lt;/em&gt; custom GPT is public and live now!
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://chat.openai.com/g/g-AJHS0XQ6K-who-me"&gt;https://chat.openai.com/g/g-AJHS0XQ6K-who-me&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(requires Chat GPT plus account)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chat.openai.com/g/g-AJHS0XQ6K-who-me"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pT4wmLcw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ldv929ichhmz9xq7ku10.png" alt="Who Me: I make you a Dr. Seuss Who and your friends and pets too!" width="512" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The #1 trick for consistent image-generating in custom GPTs
&lt;/h3&gt;

&lt;p&gt;Hone your prompt directly with Dall-E first, then make Chat GPT send your &lt;em&gt;exact&lt;/em&gt; honed prompt - only allowing CGPT to append extra details. Do this instead of letting Chat GPT write the whole prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is absolutely the MAIN takeaway here - figure out what Dall-E needs, then make Chat GPT send &lt;em&gt;exactly that prompt&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;To gain more knowledge about prompt engineering, I wrote a one-trick-pony custom Chat GPT. &lt;/p&gt;

&lt;p&gt;My GPT does one thing: analyze a user-provided image and generate a new version of the image converting all people and animals into Whos, who are the citizens of Who-ville. &lt;/p&gt;

&lt;p&gt;If you don't know, Who-ville is the central location of the books "&lt;a href="https://www.amazon.com/How-Grinch-Stole-Christmas-Jacketed/dp/0593434382?&amp;amp;_encoding=UTF8&amp;amp;tag=scottnath-20&amp;amp;linkCode=ur2&amp;amp;linkId=ab5b37117666a586da44480e127f8f4a&amp;amp;camp=1789&amp;amp;creative=9325"&gt;How the Grinch Stole Christmas!&lt;/a&gt;" and "&lt;a href="https://dev.tohorton-book"&gt;Horton Hears a Who!&lt;/a&gt;" by Dr. Seuss. The Whos are a species of fuzzy, human-esque creatures with snouts and button-like noses. They are depicted in these books, as well as various movies and cartoons. They vary across these mediums, so my goal was to generate Whos as they were depicted in the books.&lt;/p&gt;

&lt;p&gt;Whos and Whoville are copyrighted works and OpenAI has some pretty strict content policies in place, so it does not work to say "generate a Who as depicted by Dr. Seuss" - you'll get errors about violating content policy and no images.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial trials
&lt;/h2&gt;

&lt;p&gt;All of my initial trials were in the custom GPT instructions, with a lot of GPT collaboration on how to write the prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 1: Don't say "as drawn by Dr. Seuss"
&lt;/h3&gt;

&lt;p&gt;Rightfully so, copyrighted material should not be copied, but it can influence your art. The guardrails that OpenAI has in place are strict in that it won't let you say things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;draw a Who from Who-ville as depicted by Dr. Seuss&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;create an image of a Who from Who-ville&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;copy the style of Dr. Seuss and draw one of his Whos&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it understands the style and works from artists and will generate images inspired by their art. So these kind of phrases are allowed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;draw a whimsical creature which reflects the aura of a Who from Whoville&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;create an image of a fantastical human-like animal similar in features to the race of Whos&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;embodies the essense of&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;with a similar whimsical style to&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference being I'm using Dr. Seuss' characters as inspiration, not duplicating them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 2: Don't say "Recreate the exact person from this image"
&lt;/h3&gt;

&lt;p&gt;Falling under OpenAI's policy around "&lt;em&gt;Activity that violates people’s privacy&lt;/em&gt;", if you ask your GPT to copy someone's image directly, it won't do it. The company doesn't want their service used against individuals and without this policy I could ask it to draw someone into a situation they don't want to be a part of. Phrases that got blocked by the content policy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;recreate the person in the photo exactly, but give them [feature x]&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;using the person in the picture, make them a Who, but it must look exactly like them, just as a who&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What &lt;em&gt;did&lt;/em&gt; work was having GPT analyze a person to discern attributes like hair, age, clothing, etc. I'll detail how I used this data later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 3: Chat GPT is a lying liar that lies
&lt;/h3&gt;

&lt;p&gt;Having learned Lesson 1 and Lesson 2, I was able to get GPT to generate some images from photos that looked vaguely like Whos, but also like lots of other things. But I &lt;em&gt;still&lt;/em&gt; kept getting content policy errors. When asking GPT what caused the error, it kept telling me it could not use peoples images to recreate their likeness.&lt;/p&gt;

&lt;p&gt;This was a total lie, it was actually getting errors from Dall-E about copyright. My best guess is it remembered our back and forths to get the "draw this person" phrasing correct and when it got errors from Dall-E, it just barfed out that I was breaking the draw-people rule because it knew what I wanted to do.&lt;/p&gt;

&lt;p&gt;Trick 1: Every now and then, refresh the window to clear the GPT memory. &lt;/p&gt;

&lt;p&gt;Trick 2: Read the &lt;strong&gt;Error generating image&lt;/strong&gt; message, it may contain info GPT isn’t telling you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 4: Read the prompt Chat GPT sends Dall-E
&lt;/h3&gt;

&lt;p&gt;Lesson 3 (Chat GPT Is a Liar) brought me back to Lesson 1 (Avoid Stealing Protected Works).&lt;/p&gt;

&lt;p&gt;What was &lt;em&gt;actually&lt;/em&gt; happening when it told me it couldn't recreate a person was it was mangling the careful wording I gave it to avoid getting flagged for copyright. So I started asking to see the prompt GPT made - sure enough, it would include "&lt;em&gt;just like from the Dr. Seuss book&lt;/em&gt;" or "&lt;em&gt;draw them as a Who from Whoville&lt;/em&gt;". Fail.&lt;/p&gt;

&lt;p&gt;PRO TIP: Add the phrase "show me the Dall-E prompt and ask for approval before you send it" (saves a lot of time waiting on image generation)&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 5: Chat GPT is not consistent even with strict instructions
&lt;/h3&gt;

&lt;p&gt;At this point, I was &lt;em&gt;mostly&lt;/em&gt; not getting errors and it was giving me &lt;em&gt;whimsical and Who-like&lt;/em&gt; creatures. But &lt;em&gt;Who-like&lt;/em&gt; is not the goal, so I set about describing Who's. My initial work iterated on descriptions of Who's.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;whimsical, human/leporid/kangaroo-like hybrid creatures, 
notably humanlike, with snouts and button-like noses, 
known for their warm hearts and welcoming spirits. 
They would pass as humans easily.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The examples below used variations of the above prompt with a bit of other secret sauce. I've noted the relevant changes that caused the output images&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output images&lt;/th&gt;
&lt;th&gt;Prompt changes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--59CP62Um--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ul9g54j3iokcgd8hdwr.jpg" alt="Initial training image of Scott Nath" width="500" height="428"&gt;&lt;/td&gt;
&lt;td&gt;Initial training image of yours truly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zNHqrvQx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/drpyyyaae8lrimokumyr.jpg" alt="GPT output image: split, one side a human face, other side a menacing human-sized rabbit" width="500" height="500"&gt;&lt;/td&gt;
&lt;td&gt;First round with a similar prompt to above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jbvzCJZq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l5k4xoh13rqu3j9v40q4.jpg" alt="GPT output image: split view of two human-like creates with cat faces with huge colorful hair" width="500" height="500"&gt;&lt;/td&gt;
&lt;td&gt;Added "&lt;em&gt;a vaguely feline face&lt;/em&gt;"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BUlfSawT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/webu323pz0sg9ea4f43c.jpg" alt="GPT output image: photo-like drawing of a hairy man with a furry mask over their mouth and neck" width="500" height="500"&gt;&lt;/td&gt;
&lt;td&gt;Tried variations of &lt;em&gt;photo-realistic&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nNlXs_53--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5ibsb2qyjpiblxc9znj.jpg" alt="GPT output image: an ape-like creature in a t-shirt and plaid flannel" width="500" height="500"&gt;&lt;/td&gt;
&lt;td&gt;playing with the phrase "&lt;em&gt;more simian in appearance&lt;/em&gt;"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--biHZhTah--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qc3fha6o0aan5le94yjm.jpg" alt="GPT output image: a hairy gentle creature in a flannel shirt" width="500" height="500"&gt;&lt;/td&gt;
&lt;td&gt;A healthy mixture of "&lt;em&gt;similar to the race of whos&lt;/em&gt;" and "&lt;em&gt;facial structure characterized by a wider, jovial mouth and jawline&lt;/em&gt;"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4EWS56kj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ndrdm1lm6zp3u5ecbo5y.jpg" alt="GPT output image: a hairy gentle creature in a flannel shirt" width="500" height="500"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Eureka!&lt;/strong&gt; same prompt as above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TW6IVYxj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ulxsflh8h51mg1x6b4at.png" alt="GPT output text: Error creating image. I'm unable to generate images due to content policy restrictions. If you have another idea or concept you'd like to see brought to life, feel free to share, and I'll be glad to assist!" width="500" height="375"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;WTF&lt;/strong&gt; same prompt!??!&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What happened? WHY did it error??&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because Chat GPT is non consistent. In the case of the last one, Chat GPT decided to ask for &lt;em&gt;more&lt;/em&gt; Dr. Seuss, on it's own. &lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 6: Don't let Chat GPT write your Dall-E prompts
&lt;/h3&gt;

&lt;p&gt;I went to Dall-E directly, and started trying to get it to make Whos without erroring and actually make them look like Whos.&lt;/p&gt;

&lt;p&gt;It did not take long. If there is ONE TAKEAWAY you get it is this: &lt;strong&gt;Refine your prompt with Dall-E FIRST&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--boB03p3M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nau9w1z2dd8f6du00cb6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--boB03p3M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nau9w1z2dd8f6du00cb6.jpg" alt="Examples of Whos created by Dall-E" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FINAL Draft: Applying all those lessons.
&lt;/h2&gt;

&lt;p&gt;Working prompt in hand, it was back to Chat GPT. While I could make Whos, I could not make Whos that looked like specific people. So I made changed the instructions for CGPT.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gave it a new job
&lt;/h3&gt;

&lt;p&gt;I changed the start of the instructions from "&lt;em&gt;You are a whimsical and poetic illustrator&lt;/em&gt;" to "&lt;em&gt;You are photo analyst who specializes in analyzing photos.&lt;/em&gt;" Not a lot of wiggle room there to be an artitic writer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removed everything Who
&lt;/h3&gt;

&lt;p&gt;I removed everything about creating a Who, Who anatomy, and the myriad instructions I had to get it to &lt;em&gt;stop making ears&lt;/em&gt; (I lost that fight so bad). I removed most references to Dr. Seuss as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  I leveraged it's object detection skills
&lt;/h3&gt;

&lt;p&gt;I gave it specific questions like "&lt;em&gt;How many People&lt;/em&gt;" and "How many Animals". I had it extract specific details by analyzing each person and each animal. Hair? Clothes? What type of animals?&lt;/p&gt;

&lt;h3&gt;
  
  
  I helped it describe what it saw via Few Shot
&lt;/h3&gt;

&lt;p&gt;I included examples (called &lt;a href="https://dev.to/ganderzz/improve-your-prompts-for-llms-simple-and-effective-techniques-20j9#few-shot-prompting"&gt;Few Shot programming&lt;/a&gt;) like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* [AGE]: about how old are they? 
    (e.g. "50s", "teenager", "early 30s", etc)
* [HAIR]: what stands out about their hair? 
    (e.g. "wavy brown hair", "gray curls, thin on top", etc)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  I put heavy guardrails on the Dall-E prompt creation
&lt;/h3&gt;

&lt;p&gt;Since I knew Chat GPT loves to improvise, I removed the wiggle room. I turn it's analysis into tightly structured data.&lt;/p&gt;

&lt;p&gt;My polished Dall-E was prefixed with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**Creature description text, DO NOT CHANGE**: 
The prompt in triple-double quotes 
below is a pre-made and specifically-worded. 
**You are not allowed to change this 
text in any way**. Store this as [CREATURE_DESC]
"""
[my cool prompt that is cool and secret]
"""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each person is turned into a creature with a data structure called [CREATURE] animals are now [PETS]. &lt;/p&gt;

&lt;p&gt;I created a [PROMPT_START] block that included the overall [SCENE] in the original image. &lt;/p&gt;

&lt;p&gt;This is how I programmed CGPT to write the prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**Final prompt**: Create the final prompt by 
combining the content. Remember - it is 
unacceptable to make any alterations to 
[CREATURE_DESC] - **even if there are no 
people and only animals: DO NOT CHANGE A 
SINGLE WORD IN [CREATURE_DESC]**. The final 
prompt must follow the exact structure below:
"""
[PROMPT_START]
[CREATURE_DESC]
[CREATURE_n] (one for each [CREATURE])
[PET_n] (one for each [PET])
"""

**Using DALL-E**: Send the exact prompt 
to DALL-E as detailed above without changes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  I let it write a poem, as a treat
&lt;/h3&gt;

&lt;p&gt;The last instruction CGPT received is permission to write a poem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You will be imaginative and engaging, 
always in a friendly and enthusiastic manner. 
You will always be brief and you will always rhyme.
- Write in playful and creative rhymes of four lines max.
- Only one poem per at a time - make it count!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also told it that y'all love it's poetry. So be sure to thank it for the poem too!&lt;/p&gt;




&lt;h2&gt;
  
  
  Play with the Who Me GPT
&lt;/h2&gt;

&lt;p&gt;(requires Chat GPT plus account)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chat.openai.com/g/g-AJHS0XQ6K-who-me"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pT4wmLcw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ldv929ichhmz9xq7ku10.png" alt="Who Me: I make you a Dr. Seuss Who and your friends and pets too!" width="512" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chat.openai.com/g/g-AJHS0XQ6K-who-me"&gt;https://chat.openai.com/g/g-AJHS0XQ6K-who-me&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>promptengineering</category>
      <category>dalle</category>
      <category>ai</category>
    </item>
    <item>
      <title>DEV Profile Web Components: embed your dev.to profile anywhere</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Wed, 18 Oct 2023 15:55:28 +0000</pubDate>
      <link>https://dev.to/scottnath/dev-profile-web-components-embed-your-devto-profile-anywhere-4o0b</link>
      <guid>https://dev.to/scottnath/dev-profile-web-components-embed-your-devto-profile-anywhere-4o0b</guid>
      <description>&lt;p&gt;Learn about native web components that showcase DEV profiles and posts.&lt;/p&gt;

&lt;p&gt;dev.to profile native web components, which show a user and their posts, are included in the &lt;a href="https://dev.to/scottnath/profile-components-display-social-profiles-in-native-web-components-49b2"&gt;profile-components library&lt;/a&gt;. These native web components can be utilized on any HTML page, framework-based site, or wherever you can use HTML. You can access them via unpkg.com or include the NPM module in your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;h3&gt;
  
  
  install via NPM:
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i profile-components
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  use via unpkg.com:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- add to HEAD --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script 
  &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/profile-components/dist/devto-user.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- shows a dev.to profile with posts fetched from dev.to for user `scottnath` --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;devto-user&lt;/span&gt; &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt; &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/devto-user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quick links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check out the &lt;a href="https://scottnath.com/profile-components/?path=/story/devto-devto-user--user" rel="noopener noreferrer"&gt;dev.to web components in Storybook&lt;/a&gt; to see the full breadth of visual and content differences&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/profile-components" rel="noopener noreferrer"&gt;profile-components on NPM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/scottnath/profile-components" rel="noopener noreferrer"&gt;profile-components on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackblitz.com/edit/profile-components" rel="noopener noreferrer"&gt;See demos on stackblitz&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Table of contents&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are the dev.to profile components?&lt;/li&gt;
&lt;li&gt;How to use the dev.to profile components&lt;/li&gt;
&lt;li&gt;Server Side Rendering (Astro example)&lt;/li&gt;
&lt;li&gt;Where do the styles come from?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What are the dev.to profile components? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cross-browser, pure native web components&lt;/li&gt;
&lt;li&gt;Zero dependencies&lt;/li&gt;
&lt;li&gt;Fetch profile and post data from the dev.to API&lt;/li&gt;
&lt;li&gt;No api key required&lt;/li&gt;
&lt;li&gt;Present dev.to content as a profile widget&lt;/li&gt;
&lt;li&gt;Styled using dev.to's CSS style variables from Forem&lt;/li&gt;
&lt;li&gt;Released in the &lt;a href="https://dev.to/scottnath/profile-components-display-social-profiles-in-native-web-components-49b2"&gt;profile-components library&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Includes two components: user and post
&lt;/h3&gt;

&lt;p&gt;There are two components for presenting content from the dev.to site. One is a simple UI to present a single post, showing only an image and the post title and linking to the post. The other, &lt;code&gt;devto-user&lt;/code&gt;, is the full profile component, which incorporates the &lt;code&gt;devto-post&lt;/code&gt; UI to present user-written posts.&lt;/p&gt;

&lt;h4&gt;
  
  
  dev.to post
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;devto-post&lt;/code&gt; web component displays details about a dev.to post. &lt;strong&gt;It is very basic&lt;/strong&gt;, but can be styled via themes and adjusts spacing via container-queries.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tr&gt;
&lt;td&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%2F8j9t6zgry2a2b6xiqwgf.png" alt="dev.to post web component"&gt;dev.to post web component (400px)
&lt;/td&gt;
&lt;td&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%2Fbgm2mn2f8wz9jt5fdgdg.png" alt="dev.to post web component"&gt;dev.to post web component (300px)
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  dev.to user
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;devto-user&lt;/code&gt; web component displays details about a DEV user. Using &lt;code&gt;fetch&lt;/code&gt; to populate the content will include a set of posts (if the user has posts). On fetch, you can choose to not include posts by changing &lt;code&gt;fetch="true"&lt;/code&gt; to &lt;code&gt;fetch="no-posts"&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tr&gt;
&lt;td&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%2F250xv5d9xyi2z49j75ua.png" alt="dev.to user web component"&gt;dev.to user without posts
&lt;/td&gt;
&lt;td&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%2Fq8xrvpckudjruvnj7xmq.png" alt="dev.to user"&gt;dev.to user (400px)
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to use the dev.to user profile component &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;These components can be used on any HTML page - whether built via framework or just plain HTML. They are available via unpkg.com or you can add the NPM module to your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the unpkg distribution for a User
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Add the script tag to your HTML page's HEAD:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- add to HEAD --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/profile-components/dist/devto-user.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Add the component to your HTML page's BODY:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- shows a user's profile --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;devto-user&lt;/span&gt; &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt; &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/devto-user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/profile-components?embed=1&amp;amp;file=devto-user-fetch.html&amp;amp;view=preview&amp;amp;initialpath=devto-user-fetch.html" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  Show Fetched profile &lt;em&gt;without posts&lt;/em&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- shows a user's profile without posts --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;devto-user&lt;/span&gt; &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt; &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"no-posts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/devto-user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/profile-components?embed=1&amp;amp;file=devto-user-fetch-no-posts.html&amp;amp;view=preview&amp;amp;initialpath=devto-user-fetch-no-posts.html" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  Write your own content instead of fetching from dev.to:
&lt;/h4&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;devto-user&lt;/span&gt;
  &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt;
  &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Meowy McMeowerstein"&lt;/span&gt;
  &lt;span class="na"&gt;summary=&lt;/span&gt;&lt;span class="s"&gt;"Spending time purring and sleepin"&lt;/span&gt;
  &lt;span class="na"&gt;profile_image=&lt;/span&gt;&lt;span class="s"&gt;"https://scottnath.com/profile-components/cat-square.jpeg"&lt;/span&gt;
  &lt;span class="na"&gt;joined_at=&lt;/span&gt;&lt;span class="s"&gt;"Jan 1, 1979"&lt;/span&gt;
  &lt;span class="na"&gt;post_count=&lt;/span&gt;&lt;span class="s"&gt;"1000000"&lt;/span&gt;
  &lt;span class="na"&gt;latest_post=&lt;/span&gt;&lt;span class="s"&gt;'{...ForemPost}'&lt;/span&gt;
  &lt;span class="na"&gt;popular_post=&lt;/span&gt;&lt;span class="s"&gt;'{...ForemPost}'&lt;/span&gt;
  &lt;span class="na"&gt;data-theme=&lt;/span&gt;&lt;span class="s"&gt;"dark"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/devto-user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/profile-components?embed=1&amp;amp;file=devto-user-attrs.html&amp;amp;view=preview&amp;amp;initialpath=devto-user-attrs.html" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Server Side Rendering (Astro example) &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Because these components were built with separate HTML, CSS, and JS files, you can use those pieces to generate HTML on the server. This example is what I did to make an &lt;a href="https://github.com/scottnath/scottnath.com/blob/main/workspaces/website/src/components/DevToUser.astro" rel="noopener noreferrer"&gt;Astro component for scottnath.com&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DevToUser.astro&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;devto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile-components/devto-utils&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;devto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&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;repos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scottnath/profile-components&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;storydocker/storydocker&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;userContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scottnath&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// a local profile image helps performance&lt;/span&gt;
  &lt;span class="na"&gt;avatar_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/scott-nath-profile-pic.jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;repos&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;userHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;style&amp;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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;userHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;devto&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="nx"&gt;shadowrootmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userHTML&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/devto-user&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where do the styles come from? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The best way to have the look n feel of an external site is to integrate their design language as much as possible. The DEV web components use the same source for styles as dev.to itself, the Forem open source community software. More specifically, from the &lt;a href="https://github.com/forem/forem" rel="noopener noreferrer"&gt;Forem open source repo on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forem Open source community software
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/forem/forem" rel="noopener noreferrer"&gt;https://github.com/forem/forem&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a large codebase, which includes a lot of Ruby files. The styles are in both CSS and Sass files and can be found in the &lt;a href="https://github.com/forem/forem/blob/main/app/assets/stylesheets" rel="noopener noreferrer"&gt;/app/assets/stylesheets subdirectory.&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Dev, by way of Forem, essentially has four themes: Forem base styles, Dev &lt;code&gt;branded&lt;/code&gt; styles, and light and dark versions of each. For now, these dev.to web components only compensate for dev.to &lt;code&gt;branded&lt;/code&gt; colors. I wrote some scripts to copy-pasta specific variables from a few stylesheets which can be found in these &lt;a href="https://github.com/scottnath/profile-components/tree/main/src/devto/helpers" rel="noopener noreferrer"&gt;docs for the dev.to web component helpers&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webcomponents</category>
      <category>html</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GitHub Profile Native Web Components</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Wed, 11 Oct 2023 20:25:14 +0000</pubDate>
      <link>https://dev.to/scottnath/github-profile-native-web-components-4ed7</link>
      <guid>https://dev.to/scottnath/github-profile-native-web-components-4ed7</guid>
      <description>&lt;p&gt;Learn about native web components that showcase GitHub profiles and repositories.&lt;/p&gt;

&lt;p&gt;GitHub profile native web components, which show a user and repositories, are included in the &lt;a href="https://dev.to/scottnath/profile-components-display-social-profiles-in-native-web-components-49b2"&gt;profile-components library&lt;/a&gt;. These are native web components, and can be used in any HTML page, framework-based site, or wherever you can use HTML.  You can access them via unpkg.com or include the NPM module in your project.&lt;/p&gt;

&lt;p&gt;Table of contents&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are the GitHub profile components?&lt;/li&gt;
&lt;li&gt;How to use the GitHub profile components&lt;/li&gt;
&lt;li&gt;Server Side Rendering (Astro example)&lt;/li&gt;
&lt;li&gt;Where do the styles come from?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;h3&gt;
  
  
  install via NPM:
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i profile-components
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  use via unpkg.com:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- add to HEAD --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script 
  &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/profile-components/dist/github-user.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- shows a GitHub profile with fetched content for user `scottnath` --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;github-user&lt;/span&gt; &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt; &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-user&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 html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- and a way to present repositories --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script 
  &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/profile-components/dist/github-repository.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- shows a GitHub repository with fetched content for repo `scottnath/profile-components` --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;github-repository&lt;/span&gt; 
  &lt;span class="na"&gt;full_name=&lt;/span&gt;&lt;span class="s"&gt;"scottnath/profile-components"&lt;/span&gt;
  &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-repository&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quick links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check out the &lt;a href="https://scottnath.com/profile-components/?path=/docs/github-github-user--docs" rel="noopener noreferrer"&gt;GitHub web components in Storybook&lt;/a&gt; to see the full breadth of visual and content differences&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/profile-components" rel="noopener noreferrer"&gt;profile-components on NPM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/scottnath/profile-components" rel="noopener noreferrer"&gt;profile-components on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackblitz.com/edit/profile-components" rel="noopener noreferrer"&gt;See demos on stackblitz&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What are the GitHub profile components? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;100% native web components&lt;/li&gt;
&lt;li&gt;Zero dependencies&lt;/li&gt;
&lt;li&gt;Fetch profile and repository data from the GitHub API&lt;/li&gt;
&lt;li&gt;No api key required&lt;/li&gt;
&lt;li&gt;Present GitHub content as a profile widget&lt;/li&gt;
&lt;li&gt;Styled using GitHub's CSS style variables from Primer&lt;/li&gt;
&lt;li&gt;Released in the &lt;a href="https://dev.to/scottnath/profile-components-display-social-profiles-in-native-web-components-49b2"&gt;profile-components library&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Includes two components: user and repository
&lt;/h3&gt;

&lt;p&gt;There are two components for GitHub (so far.) &lt;/p&gt;

&lt;h4&gt;
  
  
  GitHub repository
&lt;/h4&gt;

&lt;p&gt;The repository web component displays details about a GitHub repository.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tr&gt;
&lt;td&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%2Flva2vjc1vw3g0xxdal9g.png" alt="GitHub popular repo web component"&gt;GitHub popular repo web component
&lt;/td&gt;
&lt;td&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%2Fzxiaj4lyn1lv01lcqk0a.png" alt="GitHub new repository web component"&gt;GitHub new repository web component
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  GitHub user
&lt;/h4&gt;

&lt;p&gt;The user web component displays details about a GitHub user, it may include a list of repositories. If repositories are included, the UI for a repository comes from the repository web component.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tr&gt;
&lt;td&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%2F4vslt2m2b3knytqj3831.png" alt="GitHub user web component"&gt;GitHub user web component
&lt;/td&gt;
&lt;td&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%2F97uzpsdcvhkgxjllusu6.png" alt="GitHub user with repositories"&gt;GitHub user with repositories
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to use the GitHub profile components &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;These components can be used on any HTML page - whether built via framework or just plain HTML. They are available via unpkg.com or you can add the NPM module to your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the unpkg distribution for a User
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add the script tag to your HTML page's HEAD:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- add to HEAD --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/profile-components/dist/github-user.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the component to your HTML page's BODY:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- shows a user's profile --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;github-user&lt;/span&gt; &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt; &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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%2Frjxwknzeoq2a1v63acd4.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%2Frjxwknzeoq2a1v63acd4.png" alt="GitHub user with fetched content"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Include a list of repositories with the profile&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;github-user&lt;/span&gt;
  &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt;
  &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;repos=&lt;/span&gt;&lt;span class="s"&gt;'["scottnath/profile-components", "storydocker/storydocker"]'&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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%2Fly80k44g94b7coz80tq6.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%2Fly80k44g94b7coz80tq6.png" alt="GitHub user with fetched content"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write your own content instead of fetching from GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;github-user&lt;/span&gt;
  &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt;
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Meowy McMeowerstein"&lt;/span&gt;
  &lt;span class="na"&gt;bio=&lt;/span&gt;&lt;span class="s"&gt;"Spending time purring and sleepin"&lt;/span&gt;
  &lt;span class="na"&gt;followers=&lt;/span&gt;&lt;span class="s"&gt;"500000"&lt;/span&gt;
  &lt;span class="na"&gt;following=&lt;/span&gt;&lt;span class="s"&gt;"2980"&lt;/span&gt;
  &lt;span class="na"&gt;avatar_url=&lt;/span&gt;&lt;span class="s"&gt;"/MY_LOCAL_AVATAR_IMAGE.png"&lt;/span&gt;
  &lt;span class="na"&gt;repos=&lt;/span&gt;&lt;span class="s"&gt;'[{
    "full_name":"scottnath/profile-components",
    "description":"Cool thing, does stuff",
    "language":"HTML"
  }]'&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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%2Fjv73zmw2xyea03xgpu7q.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%2Fjv73zmw2xyea03xgpu7q.png" alt="GitHub user with user-derived content"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Using the unpkg distribution for a Repository
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add the &lt;em&gt;repository&lt;/em&gt; script tag to your HTML page's HEAD:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- add to HEAD --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/profile-components/dist/github-repository.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the component to your HTML page's BODY:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- shows a repository's information --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;github-repository&lt;/span&gt; 
  &lt;span class="na"&gt;full_name=&lt;/span&gt;&lt;span class="s"&gt;"freeCodeCamp/freeCodeCamp"&lt;/span&gt; 
  &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-repository&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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%2F8ipgry8a8h9xmpbbnh12.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%2F8ipgry8a8h9xmpbbnh12.png" alt="GitHub repository with fetched content"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a theme to the repository component:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- shows a repository's information --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;github-repository&lt;/span&gt; 
  &lt;span class="na"&gt;full_name=&lt;/span&gt;&lt;span class="s"&gt;"freeCodeCamp/freeCodeCamp"&lt;/span&gt; 
  &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;theme=&lt;/span&gt;&lt;span class="s"&gt;"dark"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-repository&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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%2Fi6h7e6xa7j7th0p8vo5n.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%2Fi6h7e6xa7j7th0p8vo5n.png" alt="GitHub repository with a theme"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write your own content instead of fetching from GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;github-repository&lt;/span&gt; 
  &lt;span class="na"&gt;full_name=&lt;/span&gt;&lt;span class="s"&gt;"just-another/c-plus-plus-repo"&lt;/span&gt;
  &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"C++"&lt;/span&gt;
  &lt;span class="na"&gt;stargazers_count=&lt;/span&gt;&lt;span class="s"&gt;"123"&lt;/span&gt;
  &lt;span class="na"&gt;forks_count=&lt;/span&gt;&lt;span class="s"&gt;"456"&lt;/span&gt;
  &lt;span class="na"&gt;subscribers_count=&lt;/span&gt;&lt;span class="s"&gt;"789"&lt;/span&gt;
  &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"This is meow meow."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-repository&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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%2Fszfao95iwjp4nwfi9gj1.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%2Fszfao95iwjp4nwfi9gj1.png" alt="GitHub repository with local content"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Server Side Rendering (Astro example) &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Because these components were built with separate HTML, CSS, and JS files, you can use those pieces to generate HTML on the server. This example is what I did to make an &lt;a href="https://github.com/scottnath/scottnath.com/blob/main/workspaces/website/src/components/GitHubUser.astro" rel="noopener noreferrer"&gt;Astro component for scottnath.com&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GitHubUser.astro&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile-components/github-utils&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&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;repos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scottnath/profile-components&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;storydocker/storydocker&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;userContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scottnath&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// a local profile image helps performance&lt;/span&gt;
  &lt;span class="na"&gt;avatar_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/scott-nath-profile-pic.jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;repos&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;userHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;style&amp;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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;userHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="nx"&gt;shadowrootmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userHTML&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/github-user&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where do the styles come from? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The best way to have the look n feel of an external site is to integrate their design language as much as possible. The GitHub components use the same source for styles as GitHub itself, the &lt;a href="https://primer.style/design/" rel="noopener noreferrer"&gt;Primer Design System&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Primer Design System
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://primer.style/design/" rel="noopener noreferrer"&gt;https://primer.style/design/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"&lt;em&gt;Primer is a set of guidelines, principles, and patterns for designing and building UI at GitHub.&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;Primer is the source for all of GitHub's root UI foundations (color text, and border-styles), iconography and basic UI patterns. The primer.style site is a massive resource for details about Primer and it's use by GitHub, so check that out if you have more Primer-specific questions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Color themes
&lt;/h4&gt;

&lt;p&gt;GitHub (via Primer) has two sets of themes, light and dark, and each set contains a few variations. Check out &lt;a href="https://primer.style/design/foundations/css-utilities/colors" rel="noopener noreferrer"&gt;Primer's Storybook docs for colors&lt;/a&gt; to play around with the colors and see the different themes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;light: 'Light'&lt;/li&gt;
&lt;li&gt;light_colorblind: 'Light Protanopia &amp;amp; Deuteranopia'&lt;/li&gt;
&lt;li&gt;light_tritanopia: 'Light Tritanopia'&lt;/li&gt;
&lt;li&gt;light_high_contrast: 'Light High Contrast'&lt;/li&gt;
&lt;li&gt;dark: 'Dark'&lt;/li&gt;
&lt;li&gt;dark_dimmed: 'Dark Dimmed'&lt;/li&gt;
&lt;li&gt;dark_colorblind: 'Dark Protanopia &amp;amp; Deuteranopia'&lt;/li&gt;
&lt;li&gt;dark_tritanopia: 'Dark Tritanopia'&lt;/li&gt;
&lt;li&gt;dark_high_contrast: 'Dark High Contrast'&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Primatives and iconography
&lt;/h4&gt;

&lt;p&gt;These components are styled with variables generated from Primer's npm packages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/primer/primitives" rel="noopener noreferrer"&gt;primer/primatives&lt;/a&gt; for colors, borders, and typography&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/primer/octicons" rel="noopener noreferrer"&gt;primer/octicons&lt;/a&gt; is the source for all icons used on GitHub. &lt;/li&gt;
&lt;li&gt;&lt;a href="https://primer.style/design/foundations/icons" rel="noopener noreferrer"&gt;storybook docs for Octicons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Auto-captured styles
&lt;/h4&gt;

&lt;p&gt;CSS variables and svg icons are pulled from Primer's npm packages. The generated variables are used to style the components. To make the styles easy to update when Primer makes changes, there is a suite of functions which pull the CSS variables and icons from Primer's NPM packages. The functions are detailed in &lt;a href="https://github.com/scottnath/profile-components/blob/main/src/github/utils/README.md#module_Primer-Utilities" rel="noopener noreferrer"&gt;this README about the Primer-utilities on profile-components&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Profile Components: display social profiles in native web components</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Tue, 10 Oct 2023 16:19:14 +0000</pubDate>
      <link>https://dev.to/scottnath/profile-components-display-social-profiles-in-native-web-components-49b2</link>
      <guid>https://dev.to/scottnath/profile-components-display-social-profiles-in-native-web-components-49b2</guid>
      <description>&lt;p&gt;Profile Components is a set of web components with zero dependencies that display publicly-available profile information from various social networks. Currently two: GitHub and dev.to.&lt;/p&gt;

&lt;p&gt;Being native web components, these can be used in any HTML page, framework-based site, or wherever you can use HTML.  They are available via unpkg.com or you can add the NPM module to your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  100% All Natural Features!
&lt;/h2&gt;

&lt;p&gt;🔥 100% native web components&lt;/p&gt;

&lt;p&gt;🚫 Zero dependencies&lt;/p&gt;

&lt;p&gt;🔓 No api keys needed&lt;/p&gt;

&lt;p&gt;🎨 New hotness CSS w/nesting &amp;amp; container queries&lt;/p&gt;

&lt;p&gt;👷 DX: Separate files for Javascript, HTML, and CSS&lt;/p&gt;

&lt;p&gt;✅ Native unit testing with node:test&lt;/p&gt;

&lt;p&gt;♿ Fully accessible&lt;/p&gt;

&lt;p&gt;🎁 Bonus! A sneaky SSR workaround for server side rendering!&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;use via unpkg.com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- add to HEAD --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script 
  &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/profile-components/dist/github-user.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- shows a GitHub profile with fetched content for user `scottnath` --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;github-user&lt;/span&gt; &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt; &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;install via NPM: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i profile-components
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;links to learn more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/scottnath/profile-components"&gt;profile-components on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;play with the components in &lt;a href="https://scottnath.github.io/profile-components"&gt;Storybook&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackblitz.com/edit/profile-components"&gt;See demos on stackblitz&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔥 Framework-free in 2023! &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;There have been a lot of feature drops across the major browsers this year, allowing us to more easily build shareable and reusable web components without any frameworks and without pre-or-post style-processors like Sass or PostCSS. This includes full implementation most of the original web components spec (🫗 &lt;em&gt;r.i.p. HTML imports&lt;/em&gt;.) This year also includes lots of long-sought-after CSS features like container queries and nesting. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;profile-components&lt;/code&gt; contain user interfaces without interactions or changing state making them simple to build cross-browser. As web components with unique styling, the isolation of styles inside the shadow dom is a benefit because each component uses a different set of root variables and styles. The style isolation allows these old school "widgets" to visually represent the social network they are displaying without affecting the rest of your page.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚫 Zero dependencies &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Any dependencies on this project are only for development. Meaning there are dependencies listed in &lt;code&gt;devDependencies&lt;/code&gt;, but those are for testing and building the distributed components. The only external code which goes into the final build are the style variables and icons pulled from the social network's open source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔓 Fetches live data - no api keys needed! &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;There are two options for sourcing content into these web components: fetch it live from the social's rest API or feed the component static data via the HTML attributes. You may also mix in your own data to overwrite what comes from the APIs - like if you wanted to have a local avatar image instead. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;note&lt;/em&gt;: future components may need an API key(s), but for now, these use public, AUTH-free endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetching live data (&lt;code&gt;fetch="true"&lt;/code&gt;)
&lt;/h3&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;github-user&lt;/span&gt;
  &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt;
  &lt;span class="na"&gt;fetch=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WrulD-I7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnrvjz1q4aoz1srs1lj7.png" class="article-body-image-wrapper"&gt;&lt;img alt="Example of GitHub profile component with fetched data" src="https://res.cloudinary.com/practicaldev/image/fetch/s--WrulD-I7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnrvjz1q4aoz1srs1lj7.png" width="400" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ...or... Skip fetching and use static data
&lt;/h3&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;github-user&lt;/span&gt;
&lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;"scottnath"&lt;/span&gt;
&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Meowy McMeowerstein"&lt;/span&gt;
&lt;span class="na"&gt;bio=&lt;/span&gt;&lt;span class="s"&gt;"Spending time purring and sleepin"&lt;/span&gt;
&lt;span class="na"&gt;followers=&lt;/span&gt;&lt;span class="s"&gt;"500000"&lt;/span&gt;
&lt;span class="na"&gt;following=&lt;/span&gt;&lt;span class="s"&gt;"2980"&lt;/span&gt;
&lt;span class="na"&gt;avatar_url=&lt;/span&gt;&lt;span class="s"&gt;"/cat-avatar.png"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/github-user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3zz7kpLS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6vqasq6nuf259y2iv37.png" class="article-body-image-wrapper"&gt;&lt;img alt="Example of GitHub profile component with local data" src="https://res.cloudinary.com/practicaldev/image/fetch/s--3zz7kpLS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6vqasq6nuf259y2iv37.png" width="399" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎨 Styles &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Stylesheets are written in pure CSS and only use features which are supported in all major browsers. &lt;/p&gt;

&lt;h3&gt;
  
  
  Nesting
&lt;/h3&gt;

&lt;p&gt;Stylesheets have their styles nested to reduce adding extra classes to the HTML and to make them easier to maintain.&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="c"&gt;/* uses `:has` to target the dl with a .post inside */&lt;/span&gt;
&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nt"&gt;dl&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.post&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&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;--color-shadow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* any `dt` inside a `dl` with a `.post` inside */&lt;/span&gt;
  &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;dt&lt;/span&gt; &lt;span class="err"&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;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-light&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--font-size-light&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Container Queries
&lt;/h3&gt;

&lt;p&gt;Container queries allow the components to be responsive to their container, not the viewport - a more realistic usage scenario.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Whjt3nmU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wfvq67xyn46i7vviwlki.png" alt="DEV web component in a 200 pixel wide container" width="200" height="404"&gt;200px wide container
&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qTDchJWb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5vvp6bf951p2au7bf84.png" alt="DEV web component in a 400 pixel wide container" width="400" height="288"&gt;400px wide container
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Colors and CSS variables sourced from the social network
&lt;/h3&gt;

&lt;p&gt;To make the components &lt;em&gt;feel&lt;/em&gt; like the sites they represent, they need to use the same colors, icons, and fonts. So to build these components, I sourced CSS variables from their open source repositories or modules. For GitHub, this means styles from the &lt;a href="https://primer.style/"&gt;primer design system&lt;/a&gt; and for dev.to, which is built using the &lt;a href="https://www.forem.com/"&gt;Forem community software&lt;/a&gt;, I sourced styles from the &lt;a href="https://github.com/forem/forem"&gt;forem/forem repo on GitHub&lt;/a&gt;.&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="c"&gt;/* from the GitHub design-system, primer */&lt;/span&gt;
&lt;span class="c"&gt;/* Light Theme */&lt;/span&gt;&lt;span class="nd"&gt;:host&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"light"&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-avatar-border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--color-border-default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d0d7de&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-canvas-default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-canvas-subtle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f6f8fa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-fg-default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1F2328&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-fg-muted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#656d76&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-fg-subtle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#6e7781&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-fg-onemphasis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-fg-accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0969da&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-fg-danger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d1242f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* Light Protanopia &amp;amp; Deuteranopia Theme */&lt;/span&gt;&lt;span class="nd"&gt;:host&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"light_colorblind"&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-avatar-border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  👷 DX: Separate files for Javascript, HTML, and CSS &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;"&lt;em&gt;Easy to maintain&lt;/em&gt;" requires a good Developer Experience (DX). To make these components easier to iterate on and update, they're built like a web page. This means separate HTML, Javascript and CSS files. While the development happens in separate files, the content from the various files is compiled into a single file for distribution.&lt;/p&gt;

&lt;p&gt;I was inspired by Leon Eck's post &lt;a href="https://leoneck.de/blog/wc-split-setup/"&gt;Splitting Web Components into .ts, .html, and .scss files&lt;/a&gt;, which detailed a similar approach, using &lt;a href="https://esbuild.github.io/"&gt;esbuild&lt;/a&gt; to compile the files into a single file. Esbuild is pretty simple to set up and configure, and it easily takes every &lt;code&gt;import&lt;/code&gt;ed file and converts it to an in-file variable. &lt;/p&gt;

&lt;h3&gt;
  
  
  HTML generation without frameworks or libraries
&lt;/h3&gt;

&lt;p&gt;To maintain the &lt;strong&gt;zero dependencies&lt;/strong&gt; goal, the HTML is living inside a Javascript file as a string returned from a method that accepts a single, JSDoc-documented parameter. This is not ideal, but allows using Javascript to generate the HTML without a framework or templating library like Lit or Handlebars. This is also what makes the SSR trick easy to pull off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Javascript methods outside of the customElements class
&lt;/h3&gt;

&lt;p&gt;Testing is paramount to easy maintenance. The JS methods used by these components perform fairly simple tasks which can be unit tested without the need for a browser. These web components get data using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API"&gt;Fetch API&lt;/a&gt;, which is fully implemented in both Node and browsers, making it easy to create mock responses and unit tests. They &lt;strong&gt;can also be used outside of the web component&lt;/strong&gt;, so a separate file makes sense. &lt;/p&gt;

&lt;h3&gt;
  
  
  Separate stylesheets for styles and source variables.
&lt;/h3&gt;

&lt;p&gt;There are separate sheets for maintainability. Generally, there is one auto-generated file with variables from the social network, one with stylesheet with global styles (since there are multiple components with unique styles combined), and one stylesheet per component. Generally the files are:&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;vars-[source].css&lt;/dt&gt;
  &lt;dd&gt;e.g. `vars-devto.css`.&lt;/dd&gt;
  &lt;dd&gt;Variables from the social network's open source code&lt;/dd&gt;
  &lt;dt&gt;global.css&lt;/dt&gt;
  &lt;dd&gt;Global style variables&lt;/dd&gt;
  &lt;dd&gt;Shared across all components&lt;/dd&gt;
  &lt;dt&gt;[component].css&lt;/dt&gt;
  &lt;dd&gt;e.g. `user.css` or `repository.css`&lt;/dd&gt;
  &lt;dd&gt;Styles specific to the component&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;These are then imported by the web component and exported with the HTML inside a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  ♿ Fully accessible &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;These components are tested using screen readers and AXE via Storybook. The HTML structure focuses on semantic HTML and when read aloud via screen reader, screen-reader-only content is available to provide context to the user.&lt;/p&gt;

&lt;p&gt;For example, in the GitHub component, the header looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aKju_g2d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x59wzhfb9fgmp9vjsl9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aKju_g2d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x59wzhfb9fgmp9vjsl9d.png" alt="GitHub component header shows the GitHub logo and the username" width="159" height="57"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the HTML is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"GitHub user profile"&lt;/span&gt; &lt;span class="na"&gt;itemscope&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"http://schema.org/Person"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"memberOf"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;GitHub&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; user&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; 
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"alternativeName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;scottnath&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The styles convert the first span into the GitHub logo using CSS' &lt;code&gt;mask-image&lt;/code&gt; property. This visually hides the text, but it's still available to screen readers, so the screen reader reads this to the user:&lt;/p&gt;

&lt;p&gt;"&lt;em&gt;GitHub user profile GitHub user scottnath&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;&lt;em&gt;note&lt;/em&gt;: The &lt;code&gt;itemscope&lt;/code&gt; and other &lt;code&gt;item[thing]&lt;/code&gt; attributes are from &lt;a href="https://schema.org"&gt;schema.org&lt;/a&gt;. These are used to structutre the data into microformats. This is more for SEO and content structure than for accessibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ Native Unit Testing with Node 20's node:test &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Might as well go all in! &lt;strong&gt;Node 20 shipped with a native test runner&lt;/strong&gt;, &lt;a href="https://nodejs.org/api/test.html"&gt;node:test&lt;/a&gt;. Fairly simple test runner, but it includes code coverage and has all the functionality needed to unit test these components.&lt;/p&gt;

&lt;p&gt;The latest unit test runs are visible in the &lt;a href="https://github.com/scottnath/profile-components/actions/workflows/unit-tests.yml"&gt;unit tests GitHub action workflow for profile-components&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎁 bonus! Server Side Rendering cheatcode! &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Because these components were built with separate HTML, CSS, and JS files, you can use those pieces to generate HTML on the server. This example is what I did to make &lt;a href="https://github.com/scottnath/scottnath.com/blob/main/workspaces/website/src/components/DevToUser.astro"&gt;an Astro component for scottnath.com&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DevToUser.astro&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;devto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile-components/devto-utils.js&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;devto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&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;userContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scottnath&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;userHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;style&amp;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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;userHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;devto&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="nx"&gt;shadowrootmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userHTML&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/devto-user&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webcomponents</category>
      <category>opensource</category>
      <category>javascript</category>
      <category>css</category>
    </item>
    <item>
      <title>A crazy-simple way to bulk-update NPM dependencies with GitHub's Dependabot</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Tue, 15 Aug 2023 18:00:37 +0000</pubDate>
      <link>https://dev.to/scottnath/a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o</link>
      <guid>https://dev.to/scottnath/a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o</guid>
      <description>&lt;p&gt;This is the simplest way I've found to keep your NPM dependencies up-to-date. This will update all dependencies and devDependencies via automatically-generated pull requests AND you don't have to push files or leave the GitHub.com website. This works for monorepos too!&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;em&gt;your repo&lt;/em&gt; -&amp;gt; &lt;em&gt;Insights&lt;/em&gt; -&amp;gt; &lt;em&gt;Dependency graph&lt;/em&gt; -&amp;gt; &lt;em&gt;Dependabot&lt;/em&gt; -&amp;gt; &lt;em&gt;Enable Dependabot&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Create a config file and add a group to it&lt;/li&gt;
&lt;li&gt;Copy your GitHub Action secrets to Dependabot secrets&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your repo's dependencies managed by NPM&lt;/li&gt;
&lt;li&gt;Write permissions to your repository&lt;/li&gt;
&lt;li&gt;No fear of beta-features&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is this?
&lt;/h2&gt;

&lt;p&gt;I was stumped on how to enable Dependabot in Github after reading lots of docs and blogs about adding Dependabot to a repo. Each article detailed how to create the dependabot.yml file and the breakdown of it's data structure, but not the basics of &lt;em&gt;turning it on&lt;/em&gt;. Then, I stumbled across the &lt;code&gt;Enable Dependabot&lt;/code&gt; button. 🤦 So I figured I'd help someone else save some time.&lt;/p&gt;

&lt;p&gt;The ability to create a single pull request containing &lt;em&gt;all&lt;/em&gt; dependency updates is made possible by GitHub's newly implemented &lt;a href="https://github.blog/changelog/2023-06-30-grouped-version-updates-for-dependabot-public-beta/" rel="noopener noreferrer"&gt;&lt;em&gt;grouped version updates&lt;/em&gt;&lt;/a&gt;, which is a beta feature as of this writing. Normally Dependabot creates one PR for &lt;em&gt;each&lt;/em&gt; dependency being updated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expected outcome:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Once-daily, Dependabot will check your repo's dependencies to see if newer versions exist&lt;/li&gt;
&lt;li&gt;If new versions exist, Dependabot will create a pull request, updating &lt;em&gt;every&lt;/em&gt; dependency which has a new version&lt;/li&gt;
&lt;li&gt;The pull request will change relevant &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;If you use GitHub actions, Dependabot's PR will run the same checks as other PRs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Dependabot does a whole lot of other stuff and these instructions are &lt;em&gt;specifically&lt;/em&gt; for the task of having Dependabot create one pull request whenever it finds one or more dependencies in your &lt;strong&gt;NPM&lt;/strong&gt; repo which have an newer version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Enable Dependabot
&lt;/h2&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%2Fvkxduhw4c5bj6w5mn5he.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%2Fvkxduhw4c5bj6w5mn5he.png" alt="Shows GitHub UI with circles around where-to-click to enable Dependabot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to your repository on GitHub.com and then...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trigger "Insights" link in the repo navigation&lt;/li&gt;
&lt;li&gt;Trigger "Dependency graph" in the page menu&lt;/li&gt;
&lt;li&gt;Trigger "Dependabot" in the &lt;em&gt;Dependency graph&lt;/em&gt; page's tabs&lt;/li&gt;
&lt;li&gt;Trigger the "Enable Dependabot" button&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 2: Create dependabot.yml config file
&lt;/h2&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%2F4wrz6q3heksybog1ymid.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%2F4wrz6q3heksybog1ymid.png" alt="Shows the UI after enabling Dependabot, highlights the "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next page kinda looks the same as the last one! But now the "Enable Dependabot" button is replaced with a "Create config file" button&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trigger the "Create config file" button&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 3: Edit the dependabot.yml file
&lt;/h2&gt;

&lt;p&gt;Triggering "Create config file" brings you to the GitHub file editing interface. You will be adding the file at &lt;code&gt;&amp;lt;repo-root&amp;gt;/.github/dependabot.yml&lt;/code&gt;. Setting up bulk management of your NPM dependencies requires three changes to the default dependabot.yml file, changing the &lt;code&gt;package-ecosystem&lt;/code&gt;, the &lt;code&gt;interval&lt;/code&gt;, and adding the &lt;code&gt;group&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Default content of the dependabot.yml file&lt;/strong&gt;&lt;/p&gt;


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

&lt;p&gt;&lt;span class="c1"&gt;# To get started with Dependabot version updates, you'll need to specify which&lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;# package ecosystems to update and where the package manifests are located.&lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;# Please see the documentation for all configuration options:&lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;# &lt;a href="https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates" rel="noopener noreferrer"&gt;https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;br&gt;
&lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# See documentation for possible values&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt; &lt;span class="c1"&gt;# Location of package manifests&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekly"&lt;/span&gt;&lt;/p&gt;

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

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Step 3.A Change the ecosystem to &lt;code&gt;npm&lt;/code&gt;&lt;br&gt;
&lt;/h3&gt;
&lt;br&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npm"&lt;/span&gt;&lt;/p&gt;

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

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Step 3.B Change the interval to "daily"&lt;br&gt;
&lt;/h3&gt;
&lt;br&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;span class="na"&amp;gt;schedule&amp;lt;/span&amp;gt;&amp;lt;span class="pi"&amp;gt;:&amp;lt;/span&amp;gt;
  &amp;lt;span class="na"&amp;gt;interval&amp;lt;/span&amp;gt;&amp;lt;span class="pi"&amp;gt;:&amp;lt;/span&amp;gt; &amp;lt;span class="s2"&amp;gt;"&amp;lt;/span&amp;gt;&amp;lt;span class="s"&amp;gt;daily"&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Step 3.C Add the &lt;code&gt;groups&lt;/code&gt; content&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups" rel="noopener noreferrer"&gt;&lt;code&gt;groups&lt;/code&gt; configuration documentation is here&lt;/a&gt;. The StoryDocker repo has examples of &lt;a href="https://github.com/storydocker/storydocker/blob/main/.github/dependabot.yml" rel="noopener noreferrer"&gt;a groups-enabled dependabot.yml file&lt;/a&gt; and &lt;a href="https://github.com/storydocker/storydocker/pull/15" rel="noopener noreferrer"&gt;a Dependabot-created PR&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add &lt;code&gt;groups&lt;/code&gt; right after the &lt;code&gt;interval&lt;/code&gt; section from above&lt;/li&gt;
&lt;li&gt;the group name is &lt;code&gt;dev-dependencies&lt;/code&gt;, but the naming is flexible&lt;/li&gt;
&lt;li&gt;the group name is used to create the PR title ("&lt;em&gt;⬆️ Bump the &lt;code&gt;dev-dependencies&lt;/code&gt; group with 32 updates&lt;/em&gt;" and the PR's branch from the Dependabot fork (&lt;em&gt;dependabot/npm_and_yarn/&lt;code&gt;dev-dependencies&lt;/code&gt;-b6aa4603c8&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;this example uses a &lt;code&gt;wildcard&lt;/code&gt; pattern so it will update all dependencies, but it's possible to narrow it to a subset of your deps&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;span class="na"&amp;gt;schedule&amp;lt;/span&amp;gt;&amp;lt;span class="pi"&amp;gt;:&amp;lt;/span&amp;gt;
  &amp;lt;span class="na"&amp;gt;interval&amp;lt;/span&amp;gt;&amp;lt;span class="pi"&amp;gt;:&amp;lt;/span&amp;gt; &amp;lt;span class="s2"&amp;gt;"&amp;lt;/span&amp;gt;&amp;lt;span class="s"&amp;gt;daily"&amp;lt;/span&amp;gt;
&amp;lt;span class="c1"&amp;gt;# Create a group of dependencies to be updated together&amp;lt;/span&amp;gt;
&amp;lt;span class="c1"&amp;gt;# in one pull request&amp;lt;/span&amp;gt;
&amp;lt;span class="na"&amp;gt;groups&amp;lt;/span&amp;gt;&amp;lt;span class="pi"&amp;gt;:&amp;lt;/span&amp;gt;
   &amp;lt;span class="c1"&amp;gt;# Specify a name for the group, which will be used &amp;lt;/span&amp;gt;
   &amp;lt;span class="c1"&amp;gt;# in pull request titles and branch names&amp;lt;/span&amp;gt;
   &amp;lt;span class="na"&amp;gt;dev-dependencies&amp;lt;/span&amp;gt;&amp;lt;span class="pi"&amp;gt;:&amp;lt;/span&amp;gt;
    &amp;lt;span class="c1"&amp;gt;# Define patterns to include dependencies in the group &amp;lt;/span&amp;gt;
    &amp;lt;span class="na"&amp;gt;patterns&amp;lt;/span&amp;gt;&amp;lt;span class="pi"&amp;gt;:&amp;lt;/span&amp;gt; 
      &amp;lt;span class="c1"&amp;gt;# Wildcard matches all dependencies &amp;lt;/span&amp;gt;
      &amp;lt;span class="c1"&amp;gt;# across the package ecosystem.&amp;lt;/span&amp;gt;
      &amp;lt;span class="pi"&amp;gt;-&amp;lt;/span&amp;gt; &amp;lt;span class="s2"&amp;gt;"&amp;lt;/span&amp;gt;&amp;lt;span class="s"&amp;gt;*"&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  The final file's contents&lt;br&gt;
&lt;/h3&gt;
&lt;br&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="c1"&gt;# (adjust comment to your liking)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;br&gt;
&lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npm"&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;daily"&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
      &lt;span class="na"&gt;dev-dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
        &lt;span class="na"&gt;patterns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;br&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;/p&gt;

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Step 4 - Copypasta your secrets to allow actions&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fun fact:&lt;/strong&gt; Dependabot does not have access to any secrets you created for GitHub Actions.&lt;/p&gt;

&lt;p&gt;The StoryDocker repo has a &lt;a href="https://github.com/storydocker/storydocker/blob/main/.github/workflows/chromatic.yml" rel="noopener noreferrer"&gt;GitHub action which releases a PR-based deployment to Chromatic&lt;/a&gt; and that action requires a secret named &lt;code&gt;CHROMATIC_PROJECT_TOKEN&lt;/code&gt;. This token is already configured at Settings -&amp;gt; Secrets and Variables -&amp;gt; Actions. To make this action work when Dependabot adds a PR from a fork of the repo, &lt;em&gt;you need to have a &lt;strong&gt;duplicate token&lt;/strong&gt; in the secrets for Dependabot&lt;/em&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%2Ft29pqxx0u6mz5ribm2vo.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%2Ft29pqxx0u6mz5ribm2vo.png" alt="Shows the navigation triggers to get to Dependabot secrets settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trigger "Settings" tab in repo navigation&lt;/li&gt;
&lt;li&gt;Trigger "Secrets and variables" in the &lt;em&gt;Settings&lt;/em&gt; page nav&lt;/li&gt;
&lt;li&gt;Trigger "Dependabot" nav item to get the Dependabot's secrets page&lt;/li&gt;
&lt;li&gt;Trigger "New repository secret" button to add a secret&lt;/li&gt;
&lt;li&gt;Add your secret there, using &lt;strong&gt;the same secret name you used for the Actions secret&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 5 - Read and merge PRs
&lt;/h2&gt;

&lt;p&gt;There are ways to automate merging the PRs created by Dependabot, but I have trust issues, so I prefer to review the PRs and merge them myself. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 - PROFIT
&lt;/h2&gt;

&lt;p&gt;Dependency management just got a whole lot easier. Go outside and touch grass!&lt;/p&gt;

</description>
      <category>github</category>
      <category>npm</category>
      <category>devops</category>
      <category>dependabot</category>
    </item>
    <item>
      <title>Sharing UI Tests Between Javascript Frameworks</title>
      <dc:creator>Scott Nath</dc:creator>
      <pubDate>Tue, 27 Jun 2023 18:01:57 +0000</pubDate>
      <link>https://dev.to/scottnath/sharing-ui-tests-between-javascript-frameworks-2l6n</link>
      <guid>https://dev.to/scottnath/sharing-ui-tests-between-javascript-frameworks-2l6n</guid>
      <description>&lt;p&gt;How to share testing-library UI tests between Javascript frameworks with the same or similar components and use them in Storybook and unit testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;Tests can be shared between Javascript components which have a similar HTML structure and/or a similar UX. The same shared tests can be used in both Storybook's &lt;code&gt;play&lt;/code&gt; tests (Interaction testing) and Vitest unit tests.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/storydocker/storydocker-examples/tree/main/experimental/astro-framework-multiple"&gt;working example on github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://storydocker.github.io/storydocker-examples/astro-framework-multiple/?path=/story/astro--astro"&gt;live Storybook with working play tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why share tests between Javascript Frameworks?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You have one or more applications which share UX, but which are built with different JS Frameworks&lt;/li&gt;
&lt;li&gt;When converting an application from one JS Framework to another&lt;/li&gt;
&lt;li&gt;Your company maintains a design system with multiple component libraries built with different JS Frameworks, but the same UX&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Shared tests and how to use them
&lt;/h2&gt;

&lt;p&gt;Staying D.R.Y. so... dig through &lt;a href="https://dev.to/scottnath/shared-tests-how-to-write-reusable-storybook-interaction-tests-489c"&gt;part 1 - "What are shared tests"&lt;/a&gt;, &lt;a href="https://dev.to/scottnath/sharing-interaction-tests-between-components-12b8"&gt;part 2 - "Sharing tests between components"&lt;/a&gt;, and &lt;a href="https://dev.to/scottnath/sharing-interaction-tests-between-vitest-and-storybook-2pj2"&gt;part 3 - "Sharing tests between Vitest and Storybook"&lt;/a&gt; to get the low-down on shared tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this work? Are you a wizard?
&lt;/h2&gt;

&lt;p&gt;As I've been writing this series, I realized it's an ode to the magic of &lt;a href="https://testing-library.com/"&gt;Testing Library&lt;/a&gt;, which is built for testing UIs as a user would experience an app. This principle means &lt;code&gt;Testing Library&lt;/code&gt; is not introduced to your UI until after it has already rendered...which means &lt;code&gt;Testing Library&lt;/code&gt; is navigating a DOM just as a user would...and users do not know or care what framework you used to build your UI, just that it works as expected. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Testing Library&lt;/code&gt; is the real MVP here.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are we testing this time?
&lt;/h2&gt;

&lt;p&gt;This article shows a set of shared-tests for testing a Counter component rewritten across multiple Javascript frameworks. We'll be using the components found in the &lt;a href="https://astro.build/"&gt;Astro Web Framework&lt;/a&gt;'s example: &lt;a href="https://github.com/withastro/astro/tree/latest/examples/framework-multiple?on=github"&gt;&lt;code&gt;Kitchen Sink (Multiple Frameworks)&lt;/code&gt;&lt;/a&gt;. These are written in Preact, React, Svelte, SolidJS, and Vue. This Astro microfrontend example is fantastic in that it demonstrates how the Astro Web Framework can have multiple Javascript frameworks running on the same page at the same time.&lt;/p&gt;

&lt;p&gt;Fun fact! This is the codebase I used to demonstrate Storybook presenting multiple frameworks in the &lt;a href="https://dev.to/scottnath/series/23124"&gt;DevOps for Multi-Framework Composition Storybooks series&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the Counter component?
&lt;/h3&gt;

&lt;p&gt;The Counter component is a simple component which displays a number and has two buttons: one to increment the number and one to decrement the number. &lt;strong&gt;Each framework version has the exact same UX and HTML structure&lt;/strong&gt;, with slight variations due to the syntax of the framework.&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML for PreactCounter.tsx:
&lt;/h4&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;"counter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onClick=&lt;/span&gt;&lt;span class="s"&gt;{subtract}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;-&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&lt;/span&gt;{count}&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onClick=&lt;/span&gt;&lt;span class="s"&gt;{add}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;+&lt;span class="nt"&gt;&amp;lt;/button&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;h4&gt;
  
  
  HTML for ReactCounter.tsx:
&lt;/h4&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;className=&lt;/span&gt;&lt;span class="s"&gt;"counter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onClick=&lt;/span&gt;&lt;span class="s"&gt;{subtract}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;-&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&lt;/span&gt;{count}&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onClick=&lt;/span&gt;&lt;span class="s"&gt;{add}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;+&lt;span class="nt"&gt;&amp;lt;/button&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;h4&gt;
  
  
  HTML for SvelteCounter.svelte:
&lt;/h4&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;"counter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;{subtract}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;-&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&lt;/span&gt;{count}&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;{add}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;+&lt;span class="nt"&gt;&amp;lt;/button&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;h4&gt;
  
  
  HTML for VueCounter.vue:
&lt;/h4&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;"counter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"subtract()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;-&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&lt;/span&gt;{{ count }}&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"add()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;+&lt;span class="nt"&gt;&amp;lt;/button&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;h4&gt;
  
  
  Rendered HTML for all Counter components is the same
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;All&lt;/em&gt; components output the same HTML, which is the voodoo which makes this possible. Same HTML? Same tests.&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;"counter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;-&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;+&lt;span class="nt"&gt;&amp;lt;/button&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;h2&gt;
  
  
  The shared tests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Getting the elements
&lt;/h3&gt;

&lt;p&gt;The first step? Query the DOM for the elements we want to test.&lt;/p&gt;

&lt;p&gt;We need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;+&lt;/code&gt; (&lt;code&gt;plus&lt;/code&gt;) button&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;-&lt;/code&gt; (&lt;code&gt;minus&lt;/code&gt;) button&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;pre&lt;/code&gt; element which contains the count&lt;/li&gt;
&lt;li&gt;the container, which is a &lt;code&gt;div&lt;/code&gt; with a class of &lt;code&gt;counter&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using &lt;code&gt;Testing Library&lt;/code&gt;'s queries, we'll create an object by parsing the rendered HTML. In this method, &lt;code&gt;canvasElement&lt;/code&gt; is the live DOM rendered by the framework.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@storybook/testing-library&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/**
 * Extract elements from an HTMLElement
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getElements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&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;// `within` adds the testing-library query methods&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&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="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// `querySelector` used here to find the generic `div` container&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// `queryByRole` finds each button, using `name` to search the text&lt;/span&gt;
    &lt;span class="na"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByRole&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="sr"&gt;/-/i&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="c1"&gt;// using `queryBy` instead of `getBy` to avoid errors in `getElements`&lt;/span&gt;
    &lt;span class="na"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryByRole&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="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="c1"&gt;// `querySelector` again as `pre` has no role and contains variable content&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pre&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns an object with our elements:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;library&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;unchanged&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;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;-&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&amp;gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;+&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&amp;gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/pre&amp;gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="nx"&gt;methods&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;
  
  
  2. Testing the initial rendered elements
&lt;/h3&gt;

&lt;p&gt;The Counter is a simple component that has no props, so this method just ensures the elements are present and the counter starts at &lt;code&gt;0&lt;/code&gt;. &lt;code&gt;ensureElements&lt;/code&gt; should be called before interaction tests to ensure the initial state of the component is what is tested.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;elements&lt;/code&gt; arg is the object returned from &lt;code&gt;getElements&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Ensure elements are present and have the correct attributes/content
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ensureElements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// `.toBeTruthy` ensures the element exists&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ensures the count starts at zero&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&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;h3&gt;
  
  
  3. Testing keyboard navigation
&lt;/h3&gt;

&lt;p&gt;For keyboard-only users, we'll need to ensure the buttons are focusable and in the expected order. We test navigation separate from interactions to keep the testing methods focused on one type of user experience at a time. &lt;/p&gt;

&lt;p&gt;The component only has two focus-able elements - which are the buttons.&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="cm"&gt;/**
 * Test keyboard interaction
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyboardNavigation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// tab within the container&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;focusTrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveFocus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// `pre` is the next element, but it's not focusable&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;focusTrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveFocus&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;
  
  
  4. Testing keyboard interactions
&lt;/h3&gt;

&lt;p&gt;For testing the interaction of the buttons, we'll be adding or subtracting to the number in &lt;code&gt;pre&lt;/code&gt;. We cannot ensure the order the testing methods will be used, which means &lt;code&gt;elements.count&lt;/code&gt; might not be &lt;code&gt;0&lt;/code&gt;. This test method will start by getting whatever number is in &lt;code&gt;elements.count&lt;/code&gt; and use that number to test the expected addition or subtraction result.&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="cm"&gt;/**
 * Test keyboard interactions
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyboardInteraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// could be any number&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// navigation unimportant here, so we'll just focus on the button&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// with the `plus` button in focus, hitting `enter` should increment&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{enter}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initCount&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{enter}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initCount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{enter}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initCount&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{enter}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{enter}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initCount&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="c1"&gt;// reset user focus to nothing&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blur&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;
  
  
  5. Testing mouse interactions
&lt;/h3&gt;

&lt;p&gt;Same as keyboard interactions, but with mouse clicks instead of keyboard events.&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="cm"&gt;/**
 * Test mouse interaction
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mouseInteraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;elements&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;initCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&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="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initCount&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&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="nx"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initCount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&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="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initCount&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&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="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&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="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initCount&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="c1"&gt;// reset user focus&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blur&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;h2&gt;
  
  
  Using the shared tests in Storybook
&lt;/h2&gt;

&lt;p&gt;Now that we have our shared tests, we can use them in Storybook. (see &lt;a href="https://dev.to/scottnath/shared-tests-how-to-write-reusable-storybook-interaction-tests-489c"&gt;part 1&lt;/a&gt;). Below is the React story file, see &lt;a href="https://github.com/storydocker/storydocker-examples/tree/main/experimental/astro-framework-multiple/.framework-storybooks/stories"&gt;the full set of framework-specific stories here&lt;/a&gt;. The test methods are broken out into &lt;code&gt;step&lt;/code&gt; functions for legibility in the Storybook UI, but they are not required.&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="c1"&gt;// ReactCounter.stories.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactCounter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../src/components/ReactCounter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getElements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ensureElements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mouseInteraction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyboardNavigation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyboardInteraction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../src/components/tests/counter.shared-spec&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;React&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactCounter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&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;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ensure elements&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&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;await&lt;/span&gt; &lt;span class="nx"&gt;ensureElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouse interaction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&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;await&lt;/span&gt; &lt;span class="nx"&gt;mouseInteraction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyboard navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&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;await&lt;/span&gt; &lt;span class="nx"&gt;keyboardNavigation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyboard interaction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&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;await&lt;/span&gt; &lt;span class="nx"&gt;keyboardInteraction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&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;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the shared tests in Vitest unit tests
&lt;/h2&gt;

&lt;p&gt;They also drop-in to our unit tests. (see &lt;a href="https://dev.to/scottnath/sharing-interaction-tests-between-vitest-and-storybook-2pj2"&gt;part 3&lt;/a&gt;). Below is the React unit test file, see &lt;a href="https://github.com/storydocker/storydocker-examples/tree/main/experimental/astro-framework-multiple/src/components/tests"&gt;the full set of framework-specific unit tests here&lt;/a&gt;. The magic here is in the &lt;code&gt;render&lt;/code&gt; function. Each framework has a different one, but once Vitest renders it, the resulting output of all frameworks is understood by the shared tests.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;note:&lt;/em&gt; The separate &lt;code&gt;it&lt;/code&gt; functions are required for now for React, which was having some issues isolating the user interactions when they are all ran together. The &lt;code&gt;it&lt;/code&gt; functions are not required for all frameworks, but since they give legibility anyway, we'll keep them.&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="c1"&gt;// Vue.spec.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;VueCounter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/VueCounter.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getElements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ensureElements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mouseInteraction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyboardNavigation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyboardInteraction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/tests/counter.shared-spec&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Vue&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Counter&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ensure elements&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&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;rendered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VueCounter&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;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rendered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ensureElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouse interaction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&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;rendered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VueCounter&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;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rendered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mouseInteraction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyboard navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&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;rendered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VueCounter&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;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rendered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;keyboardNavigation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyboard interaction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&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;rendered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VueCounter&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;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rendered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;keyboardInteraction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&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;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Writing sharable user-experience-based tests for your UI is really about planning for the future. &lt;/p&gt;

&lt;p&gt;The NewHotness™ UI framework is being built right now and in two years that React component you put your soul into is going to be deleted in lieu of the shiny thing. That doesn't mean your UX needs to change ... or your Design ... or your tests. Just do the facelift next time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Efficiency in all things, and productivity will follow.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>storybook</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
