DEV Community

Cover image for Profile Components: display social profiles in native web components
Scott Nath
Scott Nath

Posted on • Edited on • Originally published at scottnath.com

Profile Components: display social profiles in native web components

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.

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.

100% All Natural Features!

🔥 100% native web components

🚫 Zero dependencies

🔓 No api keys needed

🎨 New hotness CSS w/nesting & container queries

👷 DX: Separate files for Javascript, HTML, and CSS

Native unit testing with node:test

Fully accessible

🎁 Bonus! A sneaky SSR workaround for server side rendering!

tl;dr

use via unpkg.com:

<!-- add to HEAD -->
<script 
  type="module" 
  src="https://unpkg.com/profile-components/dist/github-user.js"></script>

<!-- shows a GitHub profile with fetched content for user `scottnath` -->
<github-user username="scottnath" fetch="true"></github-user>
Enter fullscreen mode Exit fullscreen mode

install via NPM:

npm i profile-components
Enter fullscreen mode Exit fullscreen mode

links to learn more:

🔥 Framework-free in 2023!

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 (🫗 r.i.p. HTML imports.) This year also includes lots of long-sought-after CSS features like container queries and nesting.

profile-components 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.

🚫 Zero dependencies

Any dependencies on this project are only for development. Meaning there are dependencies listed in devDependencies, 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.

🔓 Fetches live data - no api keys needed!

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.

note: future components may need an API key(s), but for now, these use public, AUTH-free endpoints.

Fetching live data (fetch="true")

<github-user
  username="scottnath"
  fetch="true"
></github-user>
Enter fullscreen mode Exit fullscreen mode

Example of GitHub profile component with fetched data

...or... Skip fetching and use static data

<github-user
username="scottnath"
name="Meowy McMeowerstein"
bio="Spending time purring and sleepin"
followers="500000"
following="2980"
avatar_url="/cat-avatar.png"
></github-user>
Enter fullscreen mode Exit fullscreen mode

Example of GitHub profile component with local data

🎨 Styles

Stylesheets are written in pure CSS and only use features which are supported in all major browsers.

Nesting

Stylesheets have their styles nested to reduce adding extra classes to the HTML and to make them easier to maintain.

/* uses `:has` to target the dl with a .post inside */
& dl:has(.post) {
  border-bottom: 1px solid var(--color-shadow);
  padding-bottom: 1em;

  /* any `dt` inside a `dl` with a `.post` inside */
  & dt {
    color: var(--color-light);
    font-size: var(--font-size-light);
Enter fullscreen mode Exit fullscreen mode

Container Queries

Container queries allow the components to be responsive to their container, not the viewport - a more realistic usage scenario.

DEV web component in a 200 pixel wide container
200px wide container
DEV web component in a 400 pixel wide container
400px wide container

Colors and CSS variables sourced from the social network

To make the components feel 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 primer design system and for dev.to, which is built using the Forem community software, I sourced styles from the forem/forem repo on GitHub.

/* from the GitHub design-system, primer */
/* Light Theme */:host([data-theme="light"]) {
  --color-avatar-border: rgba(31,35,40,0.15);
  --color-border-default: #d0d7de;
  --color-canvas-default: #ffffff;
  --color-canvas-subtle: #f6f8fa;
  --color-fg-default: #1F2328;
  --color-fg-muted: #656d76;
  --color-fg-subtle: #6e7781;
  --color-fg-onemphasis: #ffffff;
  --color-fg-accent: #0969da;
  --color-fg-danger: #d1242f;
}
/* Light Protanopia & Deuteranopia Theme */:host([data-theme="light_colorblind"]) {
  --color-avatar-border: rgba(27,31,36,0.15);
  ...
Enter fullscreen mode Exit fullscreen mode

👷 DX: Separate files for Javascript, HTML, and CSS

"Easy to maintain" 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.

I was inspired by Leon Eck's post Splitting Web Components into .ts, .html, and .scss files, which detailed a similar approach, using esbuild to compile the files into a single file. Esbuild is pretty simple to set up and configure, and it easily takes every imported file and converts it to an in-file variable.

HTML generation without frameworks or libraries

To maintain the zero dependencies 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.

Javascript methods outside of the customElements class

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 Fetch API, which is fully implemented in both Node and browsers, making it easy to create mock responses and unit tests. They can also be used outside of the web component, so a separate file makes sense.

Separate stylesheets for styles and source variables.

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:

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

These are then imported by the web component and exported with the HTML inside a <style> tag.

♿ Fully accessible

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.

For example, in the GitHub component, the header looks like this:

GitHub component header shows the GitHub logo and the username

And the HTML is this:

<section aria-label="GitHub user profile" itemscope itemtype="http://schema.org/Person">
  <header>
    <span><span itemprop="memberOf">GitHub</span> user</span> 
    <span itemprop="alternativeName">scottnath</span>
  </header>
Enter fullscreen mode Exit fullscreen mode

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

"GitHub user profile GitHub user scottnath"

note: The itemscope and other item[thing] attributes are from schema.org. These are used to structutre the data into microformats. This is more for SEO and content structure than for accessibility.

✅ Native Unit Testing with Node 20's node:test

Might as well go all in! Node 20 shipped with a native test runner, node:test. Fairly simple test runner, but it includes code coverage and has all the functionality needed to unit test these components.

The latest unit test runs are visible in the unit tests GitHub action workflow for profile-components

🎁 bonus! Server Side Rendering cheatcode!

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 Astro component for scottnath.com.

// DevToUser.astro
---
import devto from 'profile-components/devto-utils.js';
const user = devto.user;

const userContent = await user.generateContent({
  username: 'scottnath',
},true);
let userHTML = '<style>' + user.styles + '</style>';
userHTML += user.html(userContent);
---

<devto-user>
  <template shadowrootmode="open" set:html={userHTML}>
  </template>
</devto-user>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)