Creating Global Components: A Checklist With Examples
Tags: html, css, javascript, webdev
Published: 2021-11-11 13:12:24

When working with components that need to work “across borders”, supporting multiple languages and writing-modes, you'll need a large “todo-checklist”, or you'll get lost sooner or later.

In this tutorial — which is one big todo-list —  we'll be creating a **Timeline-component**, that can be configured in multiple ways, supports `dir="ltr"` and `dir="rtl"` — and much, much more.

Let's dive in.

## Table Of Contents
* [Markup should be minimal and semantically correct](#c1)
* [Does it work with both `dir="ltr"` and `dir="rtl"`?](#c2)
* [Should the markup be enriched with microdata?](#c3)
* [Is it navigable by keyboard?](#c4)
* [Does it have focus-styles?](#c5)
* [What about `:hover`?](#c6)
* [Does it scale with longer content?](#c7)
* [Scrolling and snapping](#c8)
* [Testing with Dev Tools](#c9)
* [Checking Validity and Document Outline](#c10)
* [Bonus: A News Timeline](#c11)
* [Conclusion](#c12)
* [Code Examples](#code)

---

## Markup should be minimal and semantically correct<a name="c1"></a>

Timelines are typically *lists*, build with `<ul>` and `<li>`-tags. But **do** you always need a list? If the first thing you do, is adding `list-style: none;` to your CSS, are you using the correct tag?

I've worked on quite a few projects, where someone decided to “CSS reset” all list-styles:

```css
ul,
ol {
  list-style: none;
}
```

This is *really* annoying, because I typically want to show a *list* when I'm using `<ul>` or `<ol>`-tags, otherwise I'd chosen different tags!

Andy Bell's “CSS reset” is much nicer:

```css
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
ul[role="list"],
ol[role="list"] {
  list-style: none;
}
```

---

In our first *Timeline*-example, we'll be using plain `<a>`nchor-tags, since the timeline only contains `<a>`nchors pointing to locations within the same document. 

In CSS naming, we'll consider the `<a>`nchors as a type of list-items anyway, and use the class `tmln__item` — and `tmln__list` for the “list wrapper”:

```html
<nav class="tmln">
  <h2 class="tmln__header">Timeline</h2>
  <div class="tmln__list">
    <a class="tmln__item" href="#2021"><span data-title>2021</span></a>
    <a class="tmln__item" href="#2020"><span data-title>2020</span></a>
    <a class="tmln__item" href="#2019"><span data-title>2019</span></a>
    <a class="tmln__item" href="#2018"><span data-title>2018</span></a>
  /* etc. */
  </div>
</nav>
```

Here's the default, `dir="ltr"` version:
![Default left-to-right](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mxgetuaxg9ixbonoeamb.png)
 

---

## Does it work with both `dir="ltr"` and `dir="rtl"`?<a name="c2"></a>
Instead of writing unique CSS for both text-directions, use *CSS Logical Properties*. I've [written about them here](https://dev.to/madsstoumann/a-quick-guide-to-css-logical-properties-2fce). Here's the `dir="rtl"` version:

![Default right-to-left](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ml2e3ogg8txn2rzgm03b.png) 

---

Before we continue, let's add a *horizontal version*. We'll add a modifier to the main element, `tmln--hr`:

![03_tmln_hr_ltr](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w910646uyjg88xqlrrgt.png)
 
Remeber to check the `dir="rtl"` version:
![04_tmln_hr_ltr](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fb7bnm55vdw8avy4inxm.png)

---

## Should the markup be enriched with microdata?<a name="c3"></a>
While we're still working with the markup, let's consider whether we can enrich the markup by adding microdata, aka [schema.org](https://schema.org), telling search-engines in more detail about the content.

Is your content a list of [Events](https://schema.org/Event) or [News Articles](https://schema.org/NewsArticle)?

Google calls schemas for “Rich Results”, and have created a [testing-tool](https://search.google.com/test/rich-results), where you can either paste a url or markup.

---

## Is it navigable by keyboard?<a name="c4"></a>
In this case, because we used `<a>`-tags, it's navigable by keyboard by default. If you'd used a `<div>`-tag and added a click-handler with JavaScript, you'd have to add `tabindex="0"` for it to recieve keyboard-focus (but please: don't go there!)

---

## Does it have focus-styles?<a name="c5"></a>
To make it usable for keyboard-users, we'll add some styles using `focus-visible`, thus not triggering the style, when using a pointer-device (mouse or touch):

![09_focus-visible](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ruixfgutegf5qb5edku.png)

I'm gonna jump ahead to show an example from the next type of *Timeline*, we'll be creating — just to show you how `focus-within` can be used to target parent-elements of focusable elements.

In this case, a `box-shadow` is added to the bullet, a subtle `box-shadow` is added to the main box, and a `dotted outline` to the link itself:

![10_focus-within](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ht7wt24ht2nupsscldaw.png)

---

## What about `:hover`?<a name="c6"></a>
Should `:hover` work intentionally on mobile devices (it acts like a “pseudo-click”), or should it be disabled?

If you only want to show `:hover`-styles on devices that actually support them (recommended), use:

```css
@media (hover: hover) { ... }
```

---

## Does it scale with longer content?<a name="c7"></a>
Some languages take up [much more space](https://www.w3.org/International/articles/article-text-size) than others:

| Language	| Translation	  | Ratio |
| ------------- | --------------- | ----- |
| Korean	| 조회	          | 0.8   |
| English	| views           | 1     |
| Chinese	| 次檢視	          | 1.2   |
| Portuguese	| visualizações   | 2.6   |
| French	| consultations	  | 2.6   |
| German	| mal angesehen   | 2.8   |
| Italian	| visualizzazioni | 3     |

Check with various text-length (or use *Google Translate* live on your content) — depending on your layout, look into `min-width` (or `min-inline-size`), `fit-content` or similar.

---

## Scrolling and snapping<a name="c8"></a>
If your content *overflows* (like our *horizontal timeline*), do **not** hide the default scrollbar (it *will* be hidden on mobile devices, though — but that's expected). The browsers default scrollbar can be navigated by keyboard, using the arrow-keys. You're welcome to style it, though:

![15_scrollbar-button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/les3rss2sm5ibeslk4tp.png)

For a nicer design, you can set the scrollbar-buttons, `::-webkit-scrollbar-button` in WebKit, to the same color as the background:

![13_scrollbar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tgkp2j53d0357cl8dvmk.png)

As always, remember to check `rtl`:
![14_scrollbar_rtl](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zuwp0vx1f9g00vs377xw.png)

On mobile devices, add “scroll-snap” to the parent:

```css
.tmln__list {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}
```

On the items, add this:

```css
.tmln__item {
  scroll-snap-align: start;
  scroll-margin-inline-start: value;
}
```

---

## Testing with Dev Tools<a name="c9"></a>
Before we continue, let's check our component in *Lighthouse:*

![lighthouse](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ybnnxv75j5c6fpn0q8t4.png)

Wow - looks good so far!

Now is also a good time to check *CSS Coverage*. 

Open the *drawer* in *Chrome Dev Tools* by pressing *Escape* (if it's not already open). Add/Check the “Coverage”-tab:

![coverage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ljvg36bap7kyb5o0le8.png)
 
Hmm ... there's 9.9% unused CSS ... Let's check:

![coverage2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhji6lj62ft78ruu527l.png)

Ah! It's because the CSS is *unminified* and contains comments. 

When building your own components, look through the entire file, to check, whether you have unused CSS. 

---

## Checking Validity and Document Outline<a name="c10"></a>

Although *Lighthouse* finds most issues, I always [validate the markup](https://dev.to/madsstoumann/validating-markup-write-your-own-snippet-2p8d) — check the console for errors, and fix them. 

To see a visualization of the *Document Outline*, I use [HTML5 Outliner](https://chrome.google.com/webstore/detail/html5-outliner/afoibpobokebhgfnknfndkgemglggomo?hl=en-GB), a plugin for Chrome:

![outline]
(https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0u5tx1zcj2ci0mxqb344.png)

> **Tip!** Add a headline (`<h1>` to `<h6>`) to your `<nav>`-tags to prevent a bunch of `untitled NAV`-entries in the outliner. 

---

## Bonus: A News Timeline<a name="c11"></a>

Now, let's look into a **News Timeline**. In this case, we **do** need a list, so we'll replace the `<div>` and `<a>`nchors with `<ul>` and `<li>`-items:

```html
<nav class="tmln tmln--box tmln--hr">
  <h2 class="tmln__header">Latest News</h2>
  <ul class="tmln__list">
    <li class="tmln__item tmln__item--active">
      <span data-title>32 mins ago</span>
      <h3 class="tmln__item-headline">Someone, somewhere, did something stupid</h3>
      <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Molestias, ab? Sequi dolorem aspernatur ad earum! Eius nulla tempora pariatur temporibus.</p>
    </li>
  </ul>
</nav>
```

![New Timeline LTR](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c38xxp4555z03nyjhpp6.jpg)
 
 
And again, let's make sure the `dir="rtl"` version works as well:

![News Timeline RTL](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jvl6qoifjmavna420awe.jpg)
 
Now, let's **mix** the horizontal and the box-versions:

```html
<nav class="tmln tmln--box tmln--hr">
```

![07_tmln_boxhr_ltr](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0vltz4q44nhgtcednes.png)

And … let's check the `dir="rtl"` version:
![08_tmln_boxhr_rtl](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a5juh98tap6ltov0vnno.png)

---

### Re-check the list
At this point, we need to review all the steps from the regular **Timeline Component** again, add or edit focus-states etc.:

![11-focus-within-dark](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7dwom6vu79fskjnjezlh.png)

---

## Conclusion<a name="c12"></a>
Wow — you made it to the end! If you're primarily a JavaScript-developer, you might wonder why I:

1. Tagged this article with `JavaScript` †)
2. Chose the tags I did (instead of just using `<div>`s for everything). Here's a screenshot demonstrating why chosing the correct HTML-tags matters, if CSS for some reason fails:

![No CSS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yah5huaawlrwvhqevumi.png)

†) Because of **2** 😁
 
---

## Code Examples<a name="code"></a>

Here's a Codepen with examples:

{% codepen https://codepen.io/stoumann/pen/vYJavdK %}

---

Cover photo by Andrey Grushnikov from Pexels