Today I was creating an element for a website that had an inline icon at the end of a string of text. It was a little off-eye icon to show if an item in the list was unpublished.
And it looks pretty good when the icon displays next to the title.
But, the item titles would be created by users, and the width was also responsive. That meant that, in some cases, titles would wrap onto more than one line.
And at a particular breakpoint, depending on the title length, the icon would wrap on a new line all by itself.
And when this happened, it looked awful.
I mean, it doesn't just look bad. It makes it hard to understand.
When the icon was at the end of the text, you could see clearly which title the icon was referring to, it looked attached. But when the icon wrapped to a new line on its own, well, it looked like it was on its own.
What is this eye thingy, I could imagine people wondering to themselves, as they stared at the screen, unsure if it was another title or a glitch.
The only way I could be happy is if I prevented the icon from wrapping to a new line in JavaScript by itself. I always want it to stay next to the last word of the title.
Keeping the icon attached to the last word
To solve my problem, I needed the icon to stay with the last word. The icon could only move a new line when the last word of the title did. That's the rule!
I considered a purely CSS solution, but I could only really find answers if I was to change my icon to an :after
CSS element. I didn't want to do that since it was a dynamic element, and in the future, I might want to add click events or other actions to it (or even add more icons).
And I also didn't want to have to leave extra padding at the right side of my list with the icon, as it would reduce the space available for all rows (including the ones without the icon).
So instead my plan was to:
- Get the last word in the title string with JavaScript
- Put the last word in a
span
with the icon and prevent wrapping within the span with CSS
This way, the icon will always stay next to the last word in the title.
The code
Ok, we are ready to write some JavaScript!
For this example, the title string is in a variable called title
.
I started by splitting the title into an array by split()
ing it at the spaces (" "
). And then using .pop()
to remove the last word.
// example title
let title = "This is a title";
// turn the title into an array
let titleArr = title.trim().split(" ");
// get the last word for attaching with the icon
let lastWord = titleArr.pop();
I can join()
the title array back together, and add back in the spaces. It will already be without the last word since pop()
removes it from the array.
let restOfTitle = titleArr.join(" ");
console.log(restOfTitle); // "This is a"
console.log(lastWord); // "title"
Perfect, now I have the last word and the rest of the title separate, I can put it in my HTML, something like this.
let html = `<div>${restOfTitle.length > 0 ? `<span>${restOfTitle}</span> ` : ""}${`<span class="whitespace-nowrap">${lastWord}${!isPublished ? eyeOffIcon : ""}</span>`}</div>`;
Let me break that down. First I check if the restOfTitle
has any length (restOfTitle.length
), because if it's a one-word title, then lastWord
will have taken it and restOfTitle
will be empty.
If it does, we'll display that <span>${restOfTitle}</span>
. This is just normal text, nothing special is going on here, but we have added a none-breaking space to create a space between before the last word (since .join
doesn't add a space at the end of the string).
For the next bit <span class="whitespace-nowrap">${lastWord}${!isPublished ? eyeOffIcon : ""}</span>
, we display the last word and the icon, but we wrap both in a span with the class whitespace-nowrap
- that's a class for the following CSS:
.whitespace-nowrap {
white-space: nowrap;
}
If you are using the TailwindCSS framework, this CSS class is already built-in.
Now my icon will always stay next to the last word in the string, and only move onto a new line when the last word does. Yay!
In ReactJS
I was actually building this in ReactJS, so here is how the JSX markup looks instead of HTML.
return (
<div>
{ restOfTitle.length > 0 && <span>{ restOfTitle } </span> }
<span className="whitespace-nowrap">
{ lastWord }
{ !isPublished && <EyeOffIcon width="16" height="16" title="Unpublished" /> }
</span>
</div>
);