19
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!
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
.
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:
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 POST
ed:
{
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