Building a Reaction Component

We're all familiar with dev.to's “Reaction Component” (although I'm still not sure, what the unicorn is for!) Here's a short tutorial on how to create a “Reaction Component” – both with and without JavaScipt.

Let's start with the CSS version!

Markup

We're going to wrap each reaction in a <label>, and add the <svg> and an empty <span> after a <input type="checkbox">:

<label aria-label="React with heart">
  <input type="checkbox" name="reaction-heart" value="75" style="--c:75" />
  <svg></svg>
  <span></span>
</label>

The <input type="checkbox"> is what we'll use to control both state and value.

Icon

On dev.to, two different icons are used, when you “react” to something. For the “like“-action, there's an unfilled heart and a filled heart. Same story for the “unicorn” and “bookmark”-reactions.

One could argue, that with slight design changes, the icons could simply toggle SVG's fill, stroke or stroke-width – but let's leave it at two icons. We'll <g>roup them within a single SVG:

<svg viewBox="0 0 24 24">
  <g><path d="M21.179 12.794l.013.014L12 22l-9.192-9.192.013-.014A6.5 6.5 0 0112 3.64a6.5 6.5 0 019.179 9.154zM4.575 5.383a4.5 4.5 0 000 6.364L12 19.172l7.425-7.425a4.5 4.5 0 10-6.364-6.364L8.818 9.626 7.404 8.21l3.162-3.162a4.5 4.5 0 00-5.99.334l-.001.001z"></path></g>
  <g><path d="M2.821 12.794a6.5 6.5 0 017.413-10.24h-.002L5.99 6.798l1.414 1.414 4.242-4.242a6.5 6.5 0 019.193 9.192L12 22l-9.192-9.192.013-.014z"></path></g>
</svg>

In CSS, we an use the :checked pseudo-selector to toggle between the two icons (in <g>-tags):

[name*="reaction-"]:checked + svg g:first-of-type,
[name*="reaction-"]:not(:checked) + svg g:last-of-type {
  opacity: 0;
}
[name*="reaction-"]:checked + svg g:last-of-type {
  opacity: 1;
}

Cool, now we can toggle between the two icons using the checkbox, let's add a counter! Did you notice the style="--c:75" in the markup?

We'll use that for a CSS counter:

counter-reset: reaction var(--c);

Unfortunately, we can't use the value-attribute, as in:

counter-reset: reaction attr(value);

– so we have to use that extra custom property, --c, for the initial value.

Then, we'll hook into the :checked-selector again:

[name*="reaction-"]:checked {
  counter-increment: reaction;
}

And that empty <span> in the markup will now play it's part:

span::after {
  content: counter(reaction);
}

But why the empty <span>? That's because we have to add the counter as pseudo-element content (::before or ::after).

Unfortunately, we can't add a pseudo-element to the <input type="checkbox">, as <input>-tags are part of the group of tags, that can't have children (aka “self-closing” tags) or pseudo-content (actually they can in Chrome and Safari, but it's not part of the spec!).

The rest is just bits of styling. Here's the CSS-only example on Codepen:

JavaScript

Even though the CSS-only version is cool, it's not very practical. You'll probably want to store the reaction somewhere!

Let's remove the counter-related stuff from the CSS, and the style="--c"-part from the markup. We'll wrap the reactions in a <form id="react">, and listen for changes using the onchange-eventListener:

react.addEventListener('change', (e) => {
  const t = e.target;
  t.parentNode.lastElementChild.innerText = t.value = t.value - 0 + (t.checked ? 1 : -1);
});

This small snippet will add or subtract 1(one) from the value of the current reaction, then set the innerText of the <span> to that.

It's within this snippet, you can add a fetch() (with POST) to store the current reaction.

On dev.to, for instance, a small JSON-object is POSTed:

{
  result: "create",
  category: "like"
}

Example, using JavaScript:

If you want to set the text of all the <span>-elements to the value of the <input>s, use this small snippet to iterate the elements-collection of the <form>:

[...react.elements].forEach(t => t.parentNode.lastElementChild.innerText = t.value);

That's it! Recently, there was a “Star Rating”-challenge here on dev.to (my entries were Star Rating Using a Single Input and Mood Selector).

It's always interesting to see how other developers solve problems, so please share a link in the comments, if you modify my example, or – event better – make your own “Reaction Component”!

19