Modern Web Weekly #61
The modern web, tested and explained in plain English
Taming the on-screen keyboard with interactive-widget
In Chromium-based browsers, you can now control how the layout of the page behaves when the on-screen keyboard is shown on mobile devices.
Different browsers behave differently when the on-screen keyboard is shown. They can resize the Layout Viewport, the Visual Viewport, or both.
Let’s look at what that means.
The Layout Viewport
When you view a webpage, usually, not the whole page is visible. To be able to view the whole page, you can scroll up and down. The part that is currently visible is viewed through the Layout Viewport. Any elements positioned with position: fixed are positioned relative to this viewport.
In addition to this, there is the Visual Viewport. By default, the Visual Viewport is the same size as the Layout Viewport. When you zoom in on a web page by pinch-zooming, you shrink the size of the Visual Viewport in relation to the Layout Viewport.
In the following image, the Layout Viewport is indicated by the red rectangle, and the Visual Viewport is indicated by the orange rectangle:
Both viewports can be resized when the on-screen keyboard is displayed, but this behaviour varies between browsers.
For example, Safari on iOS resizes the Visual Viewport but not the Layout Viewport. This means that content may be pushed up when the on-screen keyboard is visible, and that elements with position: fixed stay in place and may be hidden by the keyboard.
Chrome, Edge and Firefox on Android resize both the Layout Viewport and Visual Viewport. This means that content may be pushed up when the on-screen keyboard is visible, and that elements with position: fixed will also be pushed up.
Depending on your situation, both scenarios can be a problem. Content may be hidden when the on-screen keyboard overlaps it, but usually this will be expected. In particular, Safari’s behaviour has been regarded as problematic because elements with position: fixed don’t stay in place, so they suddenly disappear when the on-screen keyboard is visible.
To fix this, you can now choose the behaviour you want with the viewport meta tag. The behaviour can be configured by specifying the interactive-widget key in the content attribute.
For example:
<meta name=”viewport” content=”interactive-widget=resizes-visual”>
interactive-widget can take the following values:
resizes-visual: The keyboard resizes the Visual Viewport (default)resizes-content: The keyboard resizes both the Visual Viewport and the Layout Viewportoverlays-content: The keyboard doesn’t resize any viewport
The following image shows the effects for all three settings. Again, the Layout Viewport is indicated by the red rectangle, and the Visual Viewport is indicated by the orange rectangle:
The interactive-widget key is supported in Chrome on Android. It’s listed in the feature flags of Safari Tech Preview, so hopefully it will land in Safari soon as well.
Check the demo.
Firefox 144 now supports View Transitions, but…
Now that Firefox 144 joined the party, View Transitions are now supported in all major browsers. The release notes mention that they are supported for SPAs but for now without support for View Transition Types.
This is good news, but the implementation still seems buggy to me. I tested my Animated todo list demo in Firefox 144 I noticed that when document.startViewTransition is called for the first time, all subsequent calls fail.
I suspect that one or more elements of the View Transition pseudo-element tree remains present in the DOM, blocking interaction with any elements on the page.
You can see this issue in the screen recording below. When I hover the items in the lists, you can see the cursor changing to pointer. When I click an item and the view transition starts, you will see that the cursor no longer changes when I hover an item.
Then, when I navigate to another tab and back to the demo, everything works again until another view transition is started.
Slider with tooltip and color-changing background
I combined @function(), if(), progress() to , and and anchor-positioned pseudo-element to create a slider with a tooltip and background color that changes based on its value.
For this demo, I use the ::-webkit-slider-thumb and ::-webkit-slider-runnable-track pseudo elements that, despite their names, doesn’t work in Safari but only in Chromium-based browsers.
Support for @function() and if() is on its way in Safari so this demo currently only works in Chrome, but the slider with tooltip demo works in Safari as well.
We can change the background color of a slider (input type=”range”) using the ::-webkit-slider-runnable-track pseudo-element.
When we set a background color on it, its styling changes from the default styling, so we also need to set a height and border-radius and we also need to center the slider thumb element again with the pseudo-element ::-webkit-slider-thumb:
::-webkit-slider-runnable-track {
height: 10px;
border-radius: 10px;
}
::-webkit-slider-thumb {
margin-top: -3px;
}We’ll define the custom property ——value to hold the current value of the slider and set an input event handler to keep track of it:
range.addEventListener(’input’, e => {
range.style.setProperty(’--value’, range.value);
});Now I want the background color of the slider to change like this:
value < 20: green
value between 20 and < 60: yellow
value between 60 and < 80: orange
value between 80 and 100: red
We can use progress() to track when the current value passes the thresholds 20, 60, and 80 by using the same value for the start and end value. When the current value is below the threshold, it will be 0, and when it’s equal to or greater than the threshold, it will be 1 or higher:
/* returns 0 when --value < 20 and 1 or higher when value >= 20 */
progress(var(--value), 20, 20);If we combine this with min(), it will return 0 when the value is less than 20 and 1 when the value is greater than or equal to 20:
/* returns 0 when --value < 20 and 1 when value >= 20 */
min(1, progress(var(--value), 20, 20));Let’s wrap this in a function so we can reuse it:
@function --is-equal(--current, --threshold) {
result: min(1, progress(var(--value), var(--threshold),
var(--threshold)));
}And then we can use it like this:
/* returns 0 when --value < 20 and 1 when value >= 20 */
--is-equal(var(--value), 20);Now, let’s define three custom properties for the threshold values:
--after20: --is-equal(var(--value), 20);
--after60: --is-equal(var(--value), 60);
--after80: --is-equal(var(--value), 80);Now these properties will be 0 or 1, depending on the current value in var(—-value).
If we use style queries to read the values of these custom properties, we can use that with an if() statement to change the background-color based on the current value:
&::-webkit-slider-runnable-track {
background-color: if(
style(--after80: 1): red;
style(--after60: 1): orange;
style(--after20: 1): yellow;
else: green
);
}So this means that when the value of --after80 is 1, the if() statement will return true and the background-color will be red:
style(--after80: 1): red;and so on for the other two.
But… when I first tried this approach, it didn’t work, even though I asserted that the custom properties indeed contained the correct value 1.
After some debugging, I realized that the issue is in the min() function. While we use it to compare numbers, min() can compare all kinds of types like <number>, <length>, <percentage> etc., so the CSS engine doesn’t know what type of value the custom properties will hold, and the comparison will fail.
We can solve this by defining each custom property with @property, and then it works:
@property --after20 {
syntax: “<number>”; /* specifies the type of the property */
inherits: true;
initial-value: 0;
}But this is not very scalable, and you may not always be able to define each custom property you use like this, so a better solution is to use the optional return type declaration for @function so the CSS engine knows what type it returns:
/* the return type now specifies the type of the return value */
@function --is-equal(--current, --threshold) returns <number> {
result: min(1, progress(var(--value), var(--threshold),
var(--threshold)));
}And then everything works! 🎉
progress(), if() and @function are supported in Chrome, and progress() is supported in Safari Tech Preview as well.
Check the demo.
PWA Audit: on your way to a great PWA
Do you already have a PWA, but are you running into issues with performance, security, or functionality? Or are you not sure how to make your PWA better?
I can help you by running an audit of your PWA
I will evaluate it on more than 35 criteria and provide you with clear and actionable instructions on how to improve it. No generic stuff that you can get anywhere, but an in-depth quality checkup to get you on your way to a great PWA.
Some of the criteria I will evaluate it on are:
Installability
Cross-device and cross-platform compatibility
Offline support
Usability
Effective use of modern web APIs
Performance
Security
Your investment in the improvement of your PWA through the audit is €599, excluding VAT (where applicable).
If you want to request an audit or first would like to know more, you can
fill out the form or book a call with me.



