Modern Web Weekly #2
Here we go with edition #2!
The modern web moves fast so let us make sure you stay up to date.
In this edition: a new <search>
element, hydrating a server-side rendered Web Component, displaying badges for push notifications, and how you can build adaptive context menus, tooltips, and popovers using only CSS.
Why we need a element
On March 24, the HTML specification added the <search>
element. This element will mainly be used to expose a search landmark in the browser's accessibility API. If you don't know what a landmark is, don't worry, this was the first time I heard about it as well...
A landmark is basically a navigational region in a webpage that helps authors structure their pages and helps people that use assistive technology to discover these regions and navigate to them.
Scott O'Hara explains how to use it and why you would use this instead of form role="search"
The search element by Scott O'Hara
Hydrating a Web Component
When a Web Component has been server-side rendered using Declarative Shadow DOM, it only has a Shadow Root and is not interactive yet. This enables very fast rendering but after that event handlers need to be registered and data needs to be loaded to make the Web Component interactive.
This process is called "hydration" and this detailed guide explains when and how to hydrate.
How to hydrate a server-side rendered Web Component by Danny Moerkerke
Web OTP
These days, most people in the world own a mobile device and developers are commonly using phone numbers to identify users of their services.
There are a variety of ways to verify phone numbers, but a randomly generated one-time password (OTP) sent by SMS is one of the most common. Sending this code back to the app's server proves the user own the phone number.
The Web OTP helps to makes this process smoother by autofilling the OTP in a annotated <input>
when a specially formatted SMS is sent.
Although Safari doesn't support the API, you can have the keyboard suggest to autofill the code. Eiji Kitamura explains how.
Verify phone numbers on the web with the Web OTP API by Eiji Kitamura
Displaying badges for push notifications
Now that the Web Push API is supported in all major browsers, you may want to display a count badge on your PWA icon to show the number of notifications it received while you were away.
Re-engage users with badges, notifications, and push messages by Microsoft.com
Creating adaptive context menus, tooltips and dropdowns using only CSS
If you have ever tried to build an adaptive context menu you know how challenging this can become very quickly.
You need to display the menu next to an element, let's say on the right of it. But if the element itself is close to the right edge of the screen the menu may be cut off so you need to display it on the left side. The same goes when you display it on any other side: there's always the possibility that (part of) the menu is outside of the viewport when it's displayed.
So you need to check the position and size of the menu each time it's displayed so you can make sure it's never outside the viewport. This requires some complex JavaScript and repeated querying of the size and position of the menu so this is definitely not something you can implement with CSS only.
Until now...
Popover API
In addition to the <dialog>
element we can now use the Popover API to display an element on top of everything else in the so-called top-layer of the browser window.
The nice part about this API is that a popover can be defined entirely declaratively in HTML and CSS so no JavaScript is needed. To define an element as a popover you simply add the popover
attribute:
<div id="tooltip" popover>
I am a tooltip
</div>
A popover is hidden by default and can be toggled by a <button>
that has a popovertarget
attribute that corresponds to the id
of the popover element:
<button popovertarget="tooltip">?</button>
<div id="tooltip" popover>
I am a tooltip
</div>
Now when you click the button repeatedly, the popover will be shown and hidden.
The popover
attribute is short for popover="auto"
. In this state, the popover can be closed by clicking outside it or pressing the Esc
key and only one popover can be shown at a time (unless popovers are nested).
The button now toggles the popover but you can also define a button that either only shows or hides the button using the popovertargetaction
attribute:
// shows the tooltip
<button popovertarget="tooltip" popovertargetaction="show">Show</button>
// hides the tooltip
<button popovertarget="tooltip" popovertargetaction="hide">Hide</button>
Here's a codepen to demonstrate this:
You will notice that by default, the popover is displayed in the center of the viewport. Of course, you can position the popover using CSS but that doesn't solve the problem we discussed earlier: we still need JavaScript to make sure we always position the tooltip fully inside the viewport.
This is where CSS anchor positioning comes in.
Tethering elements together using only CSS
Anchor positioning enables developers to tether elements together using only CSS in an adaptive way, meaning that the anchored element (tooltip, context menu etc.) will remain visible even when the user scrolls or resizes the viewport.
To anchor an element to another you need to give the anchored element the anchor
attribute with a value of the id
of the anchor element. To anchor the tooltip to the button from the previous examples:
You can also define the anchor in CSS using the anchor-name
property which accepts a "dashed-ident" value. We can replace the anchor="tooltip-button"
from the previous example with:
#tooltip {
anchor-name: --tooltip-button;
}
Since the anchored element is a popover it will be displayed in the center of the viewport. To position it we will use the anchor
function. It takes three arguments:
- Anchor element: the value of
anchor-name
of the anchor. When the anchor was defined with theanchor
attribute this may be omitted. - Anchor side: the position keyword, either
top
,right
,bottom
orleft
or a percentage (e.g. 50% would be equal tocenter
). - Fallback: an optional fallback that accepts a length or percentage.
You then use the anchor
function as the value for the top
, right
, bottom
or left
properties of the anchored element.
For example, if you want to display the anchored element on the right of the anchor you would position the left side of the anchored element on the right side of the anchor:
#tooltip {
left: anchor(right);
}
// or, if the anchor doesn't have an "anchor" attribute:
#tooltip {
left: anchor(--tooltip-button, right);
}
Similarly, you can align the top of the anchored element with the top of the anchor:
#tooltip {
left: anchor(right); // left of anchor aligned with right of button
top: anchor(top); // top of anchor aligned with top of button
}
This will enable you to position an element without any JavaScript but it still won't prevent that the tooltip may end up outside the viewport.
This is where position-fallback
comes in.
Adaptive positioning
position-fallback
requires developers to define a set of fallback positions that the browser will go through to find a set that positions the anchored element fully inside the viewport. If it cannot find one it will default to the first set.
These sets are defined with the @position-fallback
keyword, a name for the sets in "dashed-ident" format, and each set wrapped inside a @try
block. Let's go back to the previous example of the tooltip positioned on the right of the anchor and say we now want to position a context menu on the right.
We will name the sets --clockwise
and add the first set that positions the menu on the right:
@position-fallback --clockwise {
// right side, top aligned
@try {
left: anchor(right);
top: anchor(top);
}
}
If the anchor is positioned all the way on the right side of the viewport, the menu will need to be positioned on the left of the anchor. This will be the next set:
@position-fallback --clockwise {
// right side, top aligned
@try {
left: anchor(right);
top: anchor(top);
}
// left side, top aligned
@try {
right: anchor(left);
top: anchor(top);
}
}
Now what if the anchor is not only positioned all the way on the right side of the viewport but also at the bottom of it? In that case, the menu would need to be positioned on the left again but now the bottom of it will need to be aligned with the bottom of the anchor so it won't be cut off by the viewport at the bottom.
Let's add a third set for that:
@position-fallback --clockwise {
// right side, top aligned
@try {
left: anchor(right);
top: anchor(top);
}
// left side, top aligned
@try {
right: anchor(left);
top: anchor(top);
}
// left side, bottom aligned
@try {
right: anchor(left);
bottom: anchor(bottom);
}
}
Finally, the anchor could be positioned on the left side and at the bottom of the viewport. In that case, the menu will need to be positioned on the right and the bottom of the menu will need to be aligned with the bottom of the anchor again. This is the final set we need to add:
@position-fallback --clockwise {
// right side, top aligned
@try {
left: anchor(right);
top: anchor(top);
}
// left side, top aligned
@try {
right: anchor(left);
top: anchor(top);
}
// left side, bottom aligned
@try {
right: anchor(left);
bottom: anchor(bottom);
}
// right side, bottom aligned
@try {
left: anchor(right);
bottom: anchor(bottom);
}
}
With all position sets in place, we now apply them to the menu like this:
#menu {
position-fallback: --clockwise;
}
Now, whenever the menu is shown, the browser will go through the positions we defined and use the first one that makes sure the menu stays entirely inside the viewport.
No JavaScript needed, brilliant! 🎉
Here's a codepen that demonstrates fallback positioning. Use the radiobuttons to place the button in any corner of the viewport:
Send us a DM on Twitter to let us know!