The long story behind a tweet component
On a whimsical Sunday afternoon, I decided to brush up with React. I made the excellent choice of reviewing this talk from Kent C. Dodds, and planned out a relatively straightforward project to explore the different abstractions provided by the framework: build a reusable component to share a quote, or a tweet.
I sketched out the component and quickly realized I was going to practice with a lot that wasn’t React.
this project could sure use a few SVG icons…
uh, I seem to remember there’s a
<time>
element that would be perfect to display the timestamp...you know what, this layout is made to use grid properties...
I thought about finding a new idea to just practice with the framework, but luckily enough, I was reminded of this insightful article from Chris Coyier. I realized that my goal was to practice with React, sure, but React as an abstraction to what is ultimately shipped to the browser: HTML, CSS and just enough JavaScript to make the component reusable.
In light of this I was convinced to shift the focus of the project, from the JavaScript library to the building blocks behind the component.
Here’s what I came up with:
And here a few notes on the front-end concepts packed in the very same design.
SVG Syntax
Vector graphics are included repeatedly in the project:
the cheerful avatar at the beginning of the tweet;
the decorative icons below the tweet’s actual message;
the repeating background overlaid on top of a stylish purple-ish hue.
I’ll spare you a rambling on the d
attribute of the <path>
elements and instead focus on how the graphics are included on the page. If you’re interested, though, here’s a great tutorial from MDN.
Icon set
The icons are described at the very top of the markup, through a series of <symbol>
elements. These elements allow to create a template which can be then repeated through <use>
elements.
describe the assets in between the opening and closing tags.
<svg viewBox="..."> <symbol id="reference"> <!-- actual icon using path, circle and other drawing elements --> </symbol> </svg>
Be sure to add a reference through the
id
attribute.Include the graphic in the markup which follows.
<div> <svg viewBox="..."> <use href="#reference" /> </svg> </div>
Notice how the reference is repeated in the
href
attribute.
There are certainly benefits in adding SVG icons directly inline, but I find this approach to be rather convenient. There's a single <svg>
responsible for every possible graphic, and the <use>
elements pick up the syntax as much as needed.
Be aware of the following though: while the <symbol>
elements are not rendered on the screen, the <svg>
container occupies its default size of 300x100. To remove the element from the flow of the document you can set the display
property to none
and still benefit from the icons described in the element.
<svg viewBox="..." style="display: none;">
<symbol id="reference"></symbol>
</svg>
Repeating background
SVG syntax is present again in the stylesheet, and specifically the background
property of the body
. The idea is to have a basic drawing repeated on top of the solid background.
Starting from the vector graphic describing two lines intersecting each other in a zigzagging motif:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 50">
<g stroke="currentColor" stroke-linecap="square" stroke-linejoin="square" stroke-width="5" fill="none">
<path d="M2.5 47.5l47.5-45 47.5 45" />
<path d="M2.5 2.5l47.5 45 47.5-45" />
</g>
</svg>
The syntax is included in the url()
value in a fixed and unforgiving format, before the very color describing the unassuming background.
body {
background: url("data:image/svg+xml;utf8,ADD_SVG_SYNTAX_HERE"), BACKGROUND_UNDERNEATH;
}
The element needs to be included in a single line, and I highly recommend SVGOMG from Jake Archibald to automate this process. The tool optimizes vector graphics in more than just removing unnecessary whitespace, but I'll let your explore the GUI on your own.
Once included, you can adjust the size, repetition, position of the background through the matching properties. By default, the pattern is repeated both horizontally and vertically, which means that by describing a relatively small size, the zigzagging pattern can rapidly fill the entirety of the page with its pleasing rhythm.
body {
background: url("data:image/svg+xml;utf8,SVG_ZIGZAGGING_PATTERN"), hsl(0, 0%, 100%);
background-size: 100px;
}
Just be careful about the following:
always include the
xlmns
attribute. I presume this is included in most graphic editors, but if you're writing SVG syntax by hand, like a lunatic, you might forget about adding the value to the<svg>
element.<svg xmlns="http://www.w3.org/2000/svg"></svg>
escape any hash character,
#
, with the%23
string. This trips me up rather frequently, for instance when I use a hexadecimal color, like#C70586
, and is better explained in this thread.
Semantic Markup
This section started rather innocently, reading about the <time>
element on CSS Tricks, but grew to consider the structure of the component as a whole.
Hierarchy
The information baked in the tweet is provided through a series of elements describing their relative importance.
<h1>Pas</h1>
<h2>@paslepoulet</h2>
<h3>6th November</h3>
<p>Something witty I bet</p>
As if having a conversation with a screen reader, you can imagine how the component relates the name followed by the handle, timestamp and actual message.
aria-hidden
Below the paragraph element, a series of icons are included through the mentioned <use>
syntax. These are meant to be purely decorative, which explains the value of the aria-hidden
attribute.
<svg aria-hidden="true" ...>
<use href="#like"></use>
</svg>
Assistive technologies like screen readers are therefore instructed to ignore the elements.
You might have noticed the vector graphic describing the profile picture does not share the same attribute.
<svg ...>
<use href="#avatar"></use>
</svg>
This is a bit of a judgment call based on the fact that the avatar might actually be something of importance. The SVG is included as a placeholder, an extremely stylish one, which can be then substituted by a <figure>
element.
<time>
The date described in the <h3>
element is actually nested in a <time>
element.
<h3>
<time datetime="2019-12-31T23:59:59-02:00">
6th November
</time>
</h3>
Once again, I'll refer you to the article on CSS Tricks, but the gist of the element is as follows:
include a machine-readable format
datetime
attribute;display something more human-friendly to the screen.
For the component, you can think of the datetime
as describing an instance of the date object, for the moment in which the tweet is composed.
CSS Layout
Before diving into the CSS properties describing the appearance of the component, a minor note on the vector graphics included in the markup. You might have noticed each <svg>
element has a width
and height
attribute with a specific size. This size can be actually overwritten in the stylesheet, meaning the following:
<svg viewBox="0 0 100 100" width="30" height="30">
<use href="#like"></use>
</svg>
svg {
width: 100px;
height: 100px;
}
Would result in an icon 100 pixels wide and tall.
I still prefer to include the attributes as a fallback, a default which is applied when CSS doesn't intervene with a different value.
On to layout properties.
Block Layout
This is where things start to be more visual, so I'll add a few pens describing the ongoing design.
Without any CSS, the elements are displayed as if in a column, thanks to the block
value of the headings, paragraph and div containers.
This is already a satisfying layout, especially for screens with a smaller viewport width: following the order described in the markup, the quote displays the information in an easily digestible flow.
With a bit of style, it almost looks done.
Two important additions though:
the elements are spaced vertically by adding a
margin-top
to every element but the first. You can read more about this technique here on A List Apart, on how this is achieved with the owl selector;.tweet > * + * { margin-top: 1rem; }
the container nesting the decorative icon is made into a flex container, to have the icons spaced from side to side
.icons { display: flex; justify-content: space-between; }
Looking prettier by the minute.
Grid Layout
The single column layout is already a solid choice, but the project was planned out to display the information in a grid.
| | handle | name | time |
| avatar | message |
| | icons |
There are different ways to go about creating and positioning the elements, but for the project at hand, I chose to go with grid-template-areas
. In this declarative approach the grid is built out of four columns and three rows.
.tweet {
grid-template-areas:
"avatar handle name time"
"avatar message message message"
"avatar icons icons icons";
}
And each element is assigned a grid-area
value describing its position in the layout. For instance and for the main heading:
.tweet h1 {
grid-area: name;
}
This covers much of the layout. The avatar and headings are then aligned in the most pleasing manner I could think, and you can rest assured CSS tricks has a a handy reference on the properties being used.
Wrapping things up, we now have two different layouts for the tweet component. As mentioned, the first block-based layout might be preferable for smaller viewports, which means we can specify the grid layout in between a media query.
/* block layout */
@media (min-width: 550px) {
/* grid layout */
}
Of course grid properties might not be supported by the viewing device, but that won't stop us from shipping grid properties. In trying to be progressive and all, we can further nest the layout in a support query to make sure that the properties are applied as long as display: grid;
is supported.
/* block layout */
@media (min-width: 550px) {
@supports (display: grid) {
/* grid layout */
}
}
Add to that a couple of niceties, in the form of an accent color when highlighting the text or one of the icons, and the component is complete. A long, arduous journey, leading to a good-looking, hopefully robust design.
React
React ?!
You really didn't think I'd forget about the original goal of this project, right? It might no longer be the single subject of the project, but React still has its role in making the component actually reusable. What's great about adding React at this stage is that we now have a good starting point from which to expand.
Consider this pen, in which I used the library to display the values described by an object in the most befitting elements.
const data = {
name: "Pas",
handle: "paslepoulet",
time: "2019 10 4 15 30",
message: "Exploring React's own abstractions, starting with React.createElement()"
};
The design can be now populated with a varying set of values, just by changing the properties of an object. React not only makes the component data-driven though, but introduces a new set of challenges and opportunities.
Too long a paragraph
The length of the message forces the .tweet
container to be wider than one would hope. The previous markup used a <br/>
element to break the text in two lines, and perhaps we could use JavaScript to add the same element an arbitrary number of words. That's a possible route. Another option, which I personally chose, is actually take advantage of the max-width
property, and cap the width of the paragraph directly in CSS.
.tweet p {
max-width: 320px;
}
<time>
and <time>
again
The <time>
element has now the possibility to rely on an actual date, which makes the datetime
attribute incredibly more useful. How to create a date though? An instance of the date object can be created in a multitude of ways, and this article from Zell Liew has plenty to say about that.
const date = new Date(2019 10 4 15 30);
Once that's settled, which format to actually display on the screen? The Internationalization API sure looks promising.
const options = {
year: "numeric",
month: "short",
day: "numeric"
};
const format = new Intl.DateTimeFormat("en-US-u-ca-gregory", options);
Wrap Up
This post began as few notes on a very basic idea, but ended up being more of a novel. I hope you take something out of it, even if that something is adding an xlmns
attribute to that SVG background that just won't render.
I certainly discovered plenty, including a new perspective on the tools I use on a project. Without a doubt, it has been a laborious journey, overwhelming at times, but given enough time, rest and reflection, it might just become a rewarding one as well. To be sure, one in which I finally managed to practice with React as an abstraction.
Twice.