60 stories
·
0 followers

Wayland, Remote Desktop Sharing and Ubuntu 22.04

1 Share

Desktop sharing is one of the essential things I do every day. For almost 10 years now, I’m using x11vnc to get GUI access on remote Ubuntu desktops. There are many VNC servers, but x11vnc is the best I have found so far, as it works well even under extremely severe bandwidth constraints, i.e., it can easily live with 1 Mbit/s in the uplink when using the highest compression scheme. And if nothing happens on the desktop, there is almost no data transmitted. As the name implies, x11vnc is based on the x11 display server. That’s a bit of a problem these days, as all Linux distributions that use the Gnome desktop, including Ubuntu, are trying very hard for years now to switch to the newer Wayland display server. One of the problems that have plagued all attempts so far was the lack of a usable remote desktop solution. But now, it looks like Ubuntu will make Wayland the default compositor in the upcoming 22.04 Long Term Support (LTS) version. X11 is still supported, but once Wayland becomes the default, I wonder how much love x11 will still get!? So long story short, I was wondering what kind of remote desktop sharing solution Gnome and Ubuntu are proposing for 22.04 and whether it works as well as x11vnc. The answer was not as bad as I thought, but also not as good as I hoped.

Broken in the Daily Build

It’s still early April 2022 so Ubuntu LTS 22.04 has not yet been released. However, there are daily builds available, so I download the latest one to have a closer look at the built-in desktop sharing. To my surprise, the functionality is totally broken in my daily build, but I’m not the first to notice. More about that later. So I assume for now that in whatever way the current problem will be fixed before 22.04 is released, the result will pretty much look like what is currently built into Ubuntu 21.10. So I got the latest image of that release and continued there.

Wayland, Desktop Sharing and Ubuntu 21.10

In Ubuntu 21.10, desktop sharing works in Wayland mode and I could activate it in the settings. I then used Remmina and its VNC plugin to connect remotely. In the local network, this works very nicely and the remote UI is responsive to mouse clicks and key presses. A positive surprise!

However, I could already see there and then that the bandwidth requirements are huge. Double digit Mbit/s datarates were not out of the ordinary. While this is no problem in the local network, such data rates are a total showstopper when connecting to a device over a slow Internet connection with 1-2 Mbit/s. Also, connecting to moving devices and quickly changing backhaul speeds, e.g. in trains (yes, I do that) is just out of the question at such speeds.

Minimum Data Rates

So what’s the slowest Internet connection Ubuntu’s Wayland based remote desktop sharing is still usable with? I experimented for quite a while and in the highest compression setting, I could get down as low as 2 Mbit/s. Anything less and the session becomes unresponsive. Even at 2 Mbit/s, responsiveness is considerably worse compared to x11vnc. In addition, desktop sharing takes the full 2 Mbit/s of bandwidth at all times, even if nothing changes on the screen. This significantly slows down other applications running over the slow remote line at the same time. So while I’m glad it works at all, I’m not so happy that some of my major use cases can’t be covered with that solution.

Other Tidbits

In addition, there are a number of other things that would make my live difficult with Ubuntu’s Wayland desktop sharing. Fortunately, I found a workaround for each of them:

No Localhost Option

The first thing that bugged me was that when desktop sharing is activated, it is open to all incoming IP addresses. That’s a problem, because VNC only uses 8 character passwords. That’s not strong enough by far. X11vnc shares the same problem, but I usually run the vnc server in ‘localhost’ mode, i.e. it only accepts incoming connections from 127.0.0.1. To access desktop sharing, I establish an ssh session and activate port forwarding. The way around this with Ubuntu’s built in Wayland desktop sharing is to use the Universal Firewall (ufw) and limit incoming access to port 5900 to localhost. I haven’t tried if this works, but I’m pretty confident that this can be made to work.

Network Dependencies

In Ubuntu’s settings menu, Remote Desktop Sharing can and has to be activated per Wi-Fi SSID. That’s nice but many devices I support frequently change Wi-Fi networks and I can’t get to the GUI remotely to activate desktop sharing in a new Wi-Fi. Fortunately, I usually do have SSH connections to those machines, even if they change networks frequently, and running

systemctl --user start gnome-remote-desktop

on the shell activates the remote desktop server if it is not running.

Restarting Hung Sessions

You might have noticed that nothing is perfect. Internet connectivity on the move in particular. If a VNC connection is interrupted, the vnc server hangs, and it’s not possible to log-in again. Also, there’s no way on the GUI to get things going again. But once the ssh link is up again, killing the remote desktop server and starting it again on the shell gets things running again.

Repeating Keys

Over slow connections, VNC suffers from characters that keep repeating. I didn’t have much of a problem with that, as x11vnc can handle very slow connections. With gnome-remote-desktop with the VNC backend, this is much more of a problem over slow connections. The fix: On the remote host, disable ‘repeat keys’ in the ‘Universal Access’ settings. Cool, I can use this for my current x11 setup as well in special situations.

Summary and Way Forward

Over relatively fast and un-metered Internet connections, Ubuntu’s Wayland desktop sharing with gnome-remote-desktop and the VNC backend works quite well. Unfortunately, my low bandwidth use-case is the norm rather than the exception for me, so it’s not a solution for me. Fortunately, it’s also possible to switch from Wayland to x11 for a user when logging in, and Ubuntu remembers this decision across reboots. In the next episode I’ll therefore have a closer look how x11vnc behaves in Ubuntu 22.04 LTS. Also, there is still the possibility that Ubuntu will switch to the RDP backend of the gnome-remote-desktop package they use for remote desktop sharing, which might change things again. If that happens, I’ll have a closer again.

P.S.

And to check if the current GUI session runs over X11 or Wayland, the following shell command comes in handy:

echo $XDG_SESSION_TYPE

Read the whole story
bernhardbock
17 days ago
reply
Share this story
Delete

The Connector Zoo: I2C Ecosystems

1 Share
Four jumper wires with white heatshrink on them, labelled VCC, SCL, SDA and GND

I2C is a wonderful interface. With four wires and only two GPIOs, you can connect a whole lot of sensors and devices – in parallel, at that! You will see I2C used basically everywhere, in every phone, laptop, desktop, and any device with more than a few ICs inside of it – and most microcontrollers have I2C support baked into their hardware. As a result, there’s a myriad of interesting and useful devices you can use I2C with. Occasionally, maker-facing companies create plug-and-play interfaces for the I2C device breakouts they produce, with standardized pinouts and connectors.

Following a standard pinout is way better than inventing your own, and your experience with inconsistent pin header pinouts on generic I2C modules from China will surely reflect that. Wouldn’t it be wonderful if you could just plug a single I2C-carrying connector into an MPU9050, MLX90614 or HMC5883L breakout you bought for a few dollars, as opposed to the usual hurdle of looking at the module’s silkscreen, soldering pin headers onto it and carefully arranging female headers onto the correct pins?

As with any standard, when it comes to I2C-on-a-connector conventions, you would correctly guess that there’s more than one, and they all have their pros and cons. There aren’t quite fifteen, but there’s definitely six-and-a-half! They’re mostly inter-compatible, and making use of them means that you can access some pretty powerful peripherals easily. Let’s start with the two ecosystems that only have minor differences, and that you’ll encounter the most!

The JST-SH buddies

QWIIC and STEMMA QT JST-SH footprint and symbol
KiCad symbol: Conn_01x04_MountingPin; footprint: JST_SH_SM04B-SRSS-TB_1x04-1MP_P1.00mm_Horizontal

There’s two ecosystems of I2C modules that are based on four-pin JST-SH (1mm pitch) connectors, and they’re very interchangeable! One of them is Sparkfun’s QWIIC, and other is Adafruit’s STEMMA QT (pronounced ‘cutie’). Both of them are straightforward to add to your PCB, as long as you have a few JST-SH four-pin connectors ready. What’s more – Adafruit and Sparkfun connectors have the same pinout!

The connectors being used are JST-SH, surface-mount, with 1 mm pitch. Their JST family is SR/SH and the part number for the original JST part is SM04B-SRSS-TB, but you can find cheap third-party connectors with the same dimensions on LCSC using “1x4P SH 1mm” search terms. Both QWIIC and STEMMA have pages to refer to when doing your own designs. Now, what are the differences between the two?

QWIIC limits itself to 3.3 V on both host (i.e. MCU board, providing power) and device (i.e. sensor, consuming power) side – a reasonable decision, simplifying things a lot. An overwhelming majority of devices we work with nowadays are 3.3 V, to the point where level-shifting problems are mostly unheard of. Perhaps, the eventual move to 1.8 V will shake that up, but we are not there yet, and factors like LED forward voltages will necessitate some kind of higher-than-1.8 V reference in our project when we get there, anyway. So, 3.3 V power and two 3.3 V logic level I2C signals on a single connector – plain and simple. Chances are, you can already add QWIIC to your sensor or MCU board – without any additional components required but the connector itself!

STEMMA QT sensor with a cable plugged into it
STEMMA QT sensor; photo by Adafruit

In contrast, STEMMA QT is built to expand on the possible educational and convenience value, in line with other Adafruit offerings. As such, it allows for 5 V hosts – with devices designed to work in the 3.3 V-5 V power and logic level range, making sure that your Arduino Uno is not left out of the game. This is possible because every module has a low-dropout voltage regulator like the AP2112K or the MIC5219, which helps keep things almost-3.3 V when you feed the board with 3.3 V. The reasoning is simple – apart from 5 V hosts like Arduino Uno, you might also want to chain your STEMMA devices with some power hungry-devices, like I2C-savvy servos or RGB strips. In short, plugging anything into anything, with any kind of chain, shouldn’t result in magic smoke escaping – an event rarely on the TODO list of a maker’s project. One more upside of STEMMA QT is standardization of device board sizes, letting you easily mechanically integrate new sensors into the project before they even arrive to you, and spawning nifty hacks like this STEMMA Qt 3D-printable hotswap socket!

To sum up the voltage compatibility situation – all STEMMA QT devices will work with QWIIC hosts; and all QWIIC devices will work with 3.3 V STEMMA Qt hosts; as a result, any QWIIC host is also technically a STEMMA QT host. QWIIC devices will be fiercely incompatible with 5 V STEMMA QT hosts, but such hosts are rare, and you just have to be on the lookout when interfacing QWIIC devices with STEMMA hosts. Another minor hiccup with both of these standards is lack of interrupt signals – that, I will expand on below, in the “Breakout Garden” section. For now, let’s take a look at STEMMA QT’s bigger sister!

Chaining QWIIC And STEMMA modules

Three rotary encoders from STEMMA QT ecosystem being chained
Three I2C QT Rotary Encoders chained; photo by Adafruit

Oh, right, chaining! Both QWIIC and STEMMA QT are chaining-friendly – many modules offering a second JST-SH connector on the opposite side of the board, with the same signals passed through. This lets you wire up your projects in a comfortable way, not having to locate a spot in your project where you can put your MCU while not having it too far from all of your sensors, or not resorting to breadboards for splitting your I2C bus into multiple stubs. Many host boards also offer multiple sockets wired in parallel, and there’s “splitter” boards available that turn a single JST-SH cable with I2C into, say, three extra sockets.

As long as your addresses don’t conflict, you will generally be fine wiring an I2C bus for a project in such a way. Oh, and make sure to not overload your I2C bus with all the pullups added in parallel – they tend to be 10 kΩ for STEMMA QT and 2.2 kΩ for QWIIC, and at least in case of QWIIC, it seems you’re typically able to cut two trace jumpers with an xacto knife to disconnect the pullups on any module. With all non-Pico-based Raspberry Pi boards having 1.8 kΩ onboard pullups on their I2C ports, you might find yourself needing to do that quite early on as you chain devices.

The Bigger, Blacker Connector

STEMMA footprint and symbol
KiCad symbol: Conn_01x04_MountingPin; footprint: JST_PH_S4B-PH-SM4-TB_1x04-1MP_P2.00mm_Horizontal

STEMMA has the same pinout as STEMMA QT and QWIIC, but a different, larger connector. It, too, is flexible when it comes to voltages you can output on the host end, and in turn, must be able to accept on the device end. Most of the advantages in STEMMA QT section apply to STEMMA, save for QWIIC compatibility and reduced physical size.

STEMMA connectors are JST PH connectors with 2 mm pitch, with JST part number being S4B-PH-SM4-TB, and cheap third-party connectors available with “1x4P PH 2mm” search terms. They’re a bit easier to tackle with a soldering iron than the 1 mm pitch SH connectors, and for larger boards, they’re a good fit.

JST-PH is one of those cases where surface-mount sockets turn out to be way more resilient than the through-hole ones. With a tougher retention mechanism, your fingernails will no longer suffice – you’ll likely want to use your trusty pair of blue flush cutters for unplugging these! I2C-carrying STEMMA also has an GPIO and analog STEMMA counterpart, using 3-pin JST PH connectors for things like WS2812 strips – where 5 V compatibility becomes exceptionally handy. However, 4-pin connectors are firmly reserved for I2C, and such consistency is hard to not appreciate in educational and prototyping environments – checking the dozens of tutorials Adafruit has involving STEMMA devices, the “Wiring” sections in those are extra straightforward! On some STEMMA hosts, you will also be able to rewire the port to either 3.3 V or 5 V through a solder jumper.

Just Can’t Get Into The Grove

Grove connector on some digital microphone breakoutThe Grove connector standard, oldest of them all, is now somewhat of a black sheep among the I2C connector ecosystems, and is here half as a piece of history, half as a cautionary tale. Contrary to principles of open-source ecosystems, Grove uses a proprietary connector, which has makers scramble for its proper identification as they try to find a source that is not SeeedStudio. Unlike all the other standards listed here, when you see a 4-pin Grove connector, you don’t know if it’s for I2C, UART, two digital GPIOs or something analog, which ruins the whole “plug and play” idea.

To those of you unfortunate to have to interface with Grove, it also uses 3.3 V – 5 V range of possible voltages, but is less interested in explicitly advertising which one is used, or letting you change that – both things that STEMMA does without breaking a sweat. It uses the same pinout for I2C as QWIIC/STEMMA do. If you have a STEMMA-compatible (JST-PH) cable, you can sand it down a bit to make it fit into a Grove connector.

In other words, there’s a good reason you don’t see Grove connectors used more often. I couldn’t help but notice that Tom’s Hardware, when writing the conclusion section of their own I2C connector ecosystem overview article, failed to find an advantage of Grove that wasn’t generic to every other ecosystem they talked about. Unless you want to resign to paying SeeedStudio for every connector and cable you ever need, and are okay never being compatible with connector ecosystems worth investing effort into, I strongly recommend you avoid using Grove on your boards. Let’s spend time on the next – underappreciated – option instead.

A Stroll In The Breakout Garden

"Breakout Garden" footprint and symbol
KiCad symbol: Conn_01x05; footprint: PinHeader_1x05_P2.54mm_Vertical

The Breakout Garden ecosystem by Pimoroni uses an elegant pinout – taking a row of five pins off the Raspberry Pi GPIO header, pins 1 to 9, including 3.3 V, SDA, SCL, a GPIO pin and, of course, GND – in this order. They sure don’t hold a patent to this pinout – a whole lot of hackers, myself included, have been using this pinout for ages on our I2C-equipped boards. Such a pinout, for a start, means that you can plug any Breakout Garden board onto a Raspberry Pi directly.

You’re not limited to that, either – Pimoroni also offers nice slide-in connectors that let you hotswap Breakout Garden modules. And, if you don’t want to use the slide-in sockets, just solder angled male pin headers and treat them like any other module. Breakout Garden modules typically have a 3.3 V – 5 V input voltage range. They also claim reverse polarity protection on every module, so, inevitably plugging a module backwards shouldn’t delay your project.

A Breakout Garden BME688 moduleWith the Breakout Garden pinout, you also get an extra GPIO, which is most commonly either NC or used as an interrupt pin. Interrupt pins are under-appreciated when working with I2C devices – they let you offload your CPU and your I2C bus, avoiding polling and letting your I2C peripheral signal you when it wants your attention, something not possible through the I2C bus alone. For a few modules, like this haptic actuator driver, the GPIO is used as a “trigger” pin instead, for action synchronisation. That does hurt the concept of chaining somewhat, of course – to be fair, though, so does the bulky hotswap socket. Not that you can’t connect Breakout Garden boards in parallel – after all, INT pin is typically disable-able for I2C devices, which will absolutely come handy, given the the puzzling decision of wiring all the INT pins together on their 6-socket Raspberry Pi HAT.

Adding basic support for “Breakout Garden”-like connection in your project is as simple as adding a five-pin 2.54mm (0.1″) pin header – and with that, you instantly get Raspberry Pi GPIO header compatibility. When it comes to creating your own modules, I couldn’t find any dimensions or an official “how to create” modules, so you’re probably not meant to do that – which doesn’t mean that you can’t reverse-engineer the schematics and the dimensions and then try anyway, but it sure is quite discouraging. If you want to add a Breakout Garden socket, they have two rows of pins that are exactly 5.08mm (0.2″) apart, and provide convenient plug & play connectivity, though with the cost of £1 per socket (not including shipping), the slide-in sockets are the most expensive to use of these all.

Conveniently Crimped Cables

Picture of a miswired JST-SH 4-pin cable. On one end, red wire is wired to pin 1, but on the other end, it's wired to pin 4 - and so on.In the market for some JST-SH accessories? You really can’t go wrong with the SMD connectors; however, you absolutely can go wrong when it comes to pre-crimped cables. Crimping JST-SH with its 1 mm pitch is far from easy, and buying your own cables is your best bet – except for when they come miswired. A year ago, I was preparing for a project and bought a bundle of JST-SH cables, pictured to the right. Upon closer inspection, something felt off.

Turned out that they had their cable ends wired in the opposite direction, one’s pinout reverse to that of the other – with likely disastrous consequences when used as an interconnect. The cables I bought will require some careful rewiring with tweezers, and next time you are shopping for third-party JST-SH cables so that you can wire up your next project, you will know to inspect the pictures before pressing the “Buy Now” button.

Mentions Of Variable Honorableness

Some DFRobot module, with a JST-PH connector, miswired compared to STEMMA/QWIIC as described in the articleIf you’ve ever worked with DFRobot parts, you might’ve seen JST-PH 4-pin (or 3-pin) connectors on their boards, too – they’re from their ecosystem called Gravity. Puzzlingly enough, Adafruit claims that STEMMA is compatible with Gravity, apart from their non-I2C devices – since Gravity does the same thing that Grove does, where a 4-pin connector doesn’t guarantee I2C. However, checking the pinout for Gravity I2C devices, it seems that every single pin is in a different spot, in particular, ground and power being reversed. Just like STEMMA, they use 3-pin connectors for digital and analog devices. The fun part is, they also managed to change the pinout for those at some point, also reversing the power pin polarity – while still using the same connector, a no-no for connector standards. Proceed with caution!

EasyC is basically QWIIC copied to a T, with the same pinout, connectors, chaining abilities and 3.3 V voltage limit – but not mentioning QWIIC in any way in their webpages and resources, which is a disappointment. Interoperability of different ecosystems is part of what makes them valuable, and arguably, you could be more inclined to buy from them if you knew that the standard they’re using is a widely accepted one – as opposed to “random pinout on some connector”. EasyC is driven by a Croatia-based e-radionica company, producing a medium-sized assortment of useful modules, including quite a few Chinese module clones and remixes – at European prices. Design files for their modules are not linked to from product files, but at least some of them seem to be on their GitHub! Interestingly enough, they also stock a 5cm “EasyC” cable which, upon closer inspection, has the same reversed wiring as the cables I told about in the previous section. Perhaps, putting a bit more thought into the EasyC ecosystem would be warranted.

A Lolin board pictured with a JST-SH 4-pin connector marked I2C. Sadly, that connector has its own custom pinout.Sometimes we see companies making an attempt at a JST-SH thing, but not quite getting there. An example of that is a somewhat recent board by Lolin, the Lolin D1 Mini Pro, with a JST-SH 4-pin connector on it labelled “I2C”. You could be forgiven for thinking that this is a QWIIC-like pinout; given that this connector is marked “I2C”, one could argue they’ve been tricked, backstabbed and quite possibly, bamboozled. Instead of GND-VCC-SDA-SCL pinout, this board is using GND-SDA-SCL-VCC, and it appears there’s even a few accessories like shields made with this pinout in mind. It’s as if someone sent Lolin a letter saying “hey, you should put I2C on a JST-SH connector” and then refused to elaborate further. Thankfully, with the GND pins matching, the likelihood of destroying things is not as extreme.

Building Your Own Things? Need Guidance?

Which one of the standards should you follow when designing your own boards? I have provided you with all the information you could ever need to make your own decisions, but if you’re looking for a recommendation or a guideline, I will happily provide one, too.

I personally don’t use full-sized STEMMA (JST-PH) connectors, as way too often, they’re bulky enough to make the PCB itself seem small, and can make small boards a bit unwieldy due to their height – plus, they can be harder to unplug. The JST-SH connectors, however, are way too enticing due to the prospect of being compatible with two ecosystems at once, as long as I avoid 5V hosts. And – a simple, standard pinout 5-pin header provides a surprising amount of benefits for how quickly you can add it!

The proposed solution for I2C connectors on boards - a JST-SH QWIIC-like connector next to a 5-pin pin header connector, with schematics shown.

In short, I recommend that you combine a Breakout Garden-like pin header and the QWIIC/STEMMA QT JST-SH connector on your boards. This way, you will always have compatibility with three out of the four ecosystems worth talking about, and connecting your I2C board to a Raspberry Pi will be as straightforward as getting five jumper cables. With this combination, you will never have to think about I2C header pinouts ever again, and you’ll have an interrupt signal handy for the times when you really could use one.

Do you have to add level shifting? Not if you don’t use 5 V in your projects, and especially not if you’re making a breakout for an IC that has a wide range of input voltages, like, say, I2C EEPROMs and RTCs. I personally work with mainly 3.3 V, and I don’t have reels of AP2112 regulators to sprinkle on every board I make – thankfully, I don’t have 5V I2C hosts, either. In case you do want to make your devices 5 V-compatible, you can’t go wrong with the classic, incredibly elegant, and inexpensive MOSFET solution for I2C level shifting!

Conclusion

Out of these, QWIIC, STEMMA and Breakout Garden have so far stood the test of time, with hobbyist electronics companies backing them all the way there. It is only fair that we benefit from the standards they created. Hopefully, the insights and the instructions provided get us closer to universal interoperability days, when one hacker’s MCU board can seamlessly interface with other hacker’s sensors. From there, one day, our favourite SSD1306 OLED breakouts might start arriving into our mailboxes equipped with a JST-SH connector and an extra cable. Today is not that day, but with every JST-SH footprint we add to our PCB, I believe we will see to it soon.

Read the whole story
bernhardbock
17 days ago
reply
Share this story
Delete

Building a loading bar component

1 Share

In this post I want to share thinking on how to build a color adaptive and accessible loading bar with the <progress> element. Try the demo and view the source!

Light and dark, indeterminate, increasing, and completion demoed on Chrome.

If you prefer video, here's a YouTube version of this post:

Overview #

The <progress> element provides visual and audible feedback to users about completion. This visual feedback is valuable for scenarios such as: progress through a form, displaying downloading or uploading information, or even showing that the progress amount is unknown but work is still active.

This GUI Challenge worked with the existing HTML <progress> element to save some effort in accessibility. The colors and layouts push the limits of customization for the built-in element, to modernize the component and have it fit better within design systems.

Light and dark tabs in each browser providing an      overview of the adaptive icon from top to bottom:      Safari, Firefox, Chrome.
Demo shown across Firefox, Safari, iOS Safari, Chrome, and Android Chrome in light and dark schemes.

Markup #

I chose to wrap the <progress> element in a <label> so I could skip the explicit relationship attributes in favor of an implicit relationship. I've also labeled a parent element affected by the loading state, so screen reader technologies can relay that information back to a user.

<progress></progress>

If there is no value, then the element's progress is indeterminate. The max attribute defaults to 1, so progress is between 0 and 1. Setting max to 100, for example, would set the range to 0-100. I chose to stay within the 0 and 1 limits, translating progress values to 0.5 or 50%.

Label-wrapped progress #

In an implicit relationship, a progress element is wrapped by a label like this:

<label>Loading progress<progress></progress></label>

In my demo I chose to include the label for screen readers only. This is done by wrapping the label text in a <span> and applying some styles to it so that it's effectively off screen:

<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>

With the following accompanying CSS from WebAIM:

.sr-only {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
Screenshot of the devtools revealing the screen ready only element.

Area affected by loading progress #

If you have healthy vision, it can be easy to associate a progress indicator with related elements and page areas, but for visually impaired users, it's not so clear. Improve this by assigning the aria-busy attribute to the top-most element that will change when loading is complete. Furthermore, indicate a relationship between the progress and the loading zone with aria-describedby.

<main id="loading-zone" aria-busy="true">

<progress aria-describedby="loading-zone"></progress>
</main>

From JavaScript, toggle aria-busy to true at the start of the task, and to false once finished.

Aria attribute additions #

While the implicit role of a <progress> element is progressbar, I've made it explicit for browsers that lack that implicit role. I've also added the attribute indeterminate to explicitly put the element into a state of unknown, which is clearer than observing the element has no value set.

<label>
Loading
<progress
indeterminate
role="progressbar"
aria-describedby="loading-zone"
tabindex="-1"
>
unknown</progress>
</label>

Use tabindex="-1" to make the progress element focusable from JavaScript. This is important for screen reader technology, since giving the progress focus as progress changes, will announce to the user how far the updated progress has reached.

Styles #

The progress element is a bit tricky when it comes to styling. Built-in HTML elements have special hidden parts that can be difficult to select and often only offer a limited set of properties to be set.

Layout #

The layout styles are intended to allow some flexibility in the progress element's size and label position. A special completion state is added that can be a useful, but not required, additional visual cue.

<progress> Layout #

The width of the progress element is left untouched so it can shrink and grow with the space needed in the design. The built-in styles are stripped out by setting appearance and border to none. This is done so the element can be normalized across browsers, since each browser has its own styles for their element.

progress {
--_track-size: min(10px, 1ex);
--_radius: 1e3px;

/* reset */
appearance: none;
border: none;

position: relative;
height: var(--_track-size);
border-radius: var(--_radius);
overflow: hidden;
}

The value of 1e3px for _radius uses scientific number notation to express a large number so the border-radius is always rounded. It's equivalent to 1000px. I like to use this because my aim is to use a value large enough that I can set it and forget it (and it's shorter to write than 1000px). It is also easy to make it even larger if needed: just change the 3 to a 4, then 1e4px is equivalent to 10000px.

overflow: hidden is used and has been a contentious style. It made a few things easy, such as not needing to pass border-radius values down to the track, and track fill elements; but it also meant no children of the progress could live outside of the element. Another iteration on this custom progress element could be done without overflow: hidden and it may open up some opportunities for animations or better completion states.

Progress complete #

CSS selectors do the tough work here by comparing the maximum with the value, and if they match, then the progress is complete. When complete, a pseudo-element is generated and appended to the end of the progress element, providing a nice additional visual cue to the completion.

progress:not([max])[value="1"]::before,
progress[max="100"][value="100"]::before
{
content: "✓";

position: absolute;
inset-block: 0;
inset-inline: auto 0;
display: flex;
align-items: center;
padding-inline-end: max(calc(var(--_track-size) / 4), 3px);

color: white;
font-size: calc(var(--_track-size) / 1.25);
}
Screenshot of the loading bar at 100% and showing a checkmark at the end.

Color #

The browser brings its own colors for the progress element, and is adaptive to light and dark with just one CSS property. This can be built upon with some special browser-specific selectors.

Light and dark browser styles #

To opt your site into a dark and light adaptive <progress> element, color-scheme is all that is required.

progress {
color-scheme: light dark;
}

Single property progress filled color #

To tint a <progress> element, use accent-color.

progress {
accent-color: rebeccapurple;
}

Notice the track background color changes from light to dark depending on the accent-color. The browser is ensuring proper contrast: pretty neat.

Fully custom light and dark colors #

Set two custom properties on the <progress> element, one for the track color and the other for the track progress color. Inside the prefers-color-scheme media query, provide new color values for the track and track progress.

progress {
--_track: hsl(228 100% 90%);
--_progress: hsl(228 100% 50%);
}

@media (prefers-color-scheme: dark) {
progress {
--_track: hsl(228 20% 30%);
--_progress: hsl(228 100% 75%);
}
}

Focus styles #

Earlier we gave the element a negative tab index so it could be programmatically focused. Use :focus-visible to customize focus to opt into the smarter focus ring style. With this, a mouse click and focus won't show the focus ring, but keyboard clicks will. The YouTube video goes into this in more depth and is worth reviewing.

progress:focus-visible {
outline-color: var(--_progress);
outline-offset: 5px;
}
Screenshot of the loading bar with a focus ring around it. Colors all match.

Custom styles across browsers #

Customize the styles by selecting the parts of a <progress> element that each browser exposes. Using the progress element is a single tag, but it's made of a few child elements that are exposed via CSS pseudo selectors. Chrome DevTools will show these elements to you if you enable the setting:

  1. Right-click on your page and select Inspect Element to bring up DevTools.
  2. Click the Settings gear in the top-right corner of the DevTools window.
  3. Under the Elements heading, find and enable the Show user agent shadow DOM checkbox.
Screenshot of where in DevTools to enable exposing the user agent shadow DOM.
Safari and Chromium styles #

WebKit-based browsers such as Safari and Chromium expose ::-webkit-progress-bar and ::-webkit-progress-value, which allow a subset of CSS to be used. For now, set background-color using the custom properties created earlier, which adapt to light and dark.

/*  Safari/Chromium  */
progress[value]::-webkit-progress-bar {
background-color: var(--_track);
}

progress[value]::-webkit-progress-value {
background-color: var(--_progress);
}
Screenshot showing the inner elements of the progress element.
Firefox styles #

Firefox only exposes the ::-moz-progress-bar pseudo selector on the <progress> element. This also means we can't tint the track directly.

/*  Firefox  */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
Screenshot of Firefox and where to find the progress element parts. Screenshot of the Debugging Corner where Safari, iOS Safari,    Firefox, Chrome and Chrome on Android all have the loading bar shown working.

Notice that Firefox has a track color set from accent-color while iOS Safari has a light blue track. It's the same in dark mode: Firefox has a dark track but not the custom color we've set, and it works in Webkit-based browsers.

Animation #

While working with browser built-in pseudo selectors, it's often with a limited set of permitted CSS properties.

Animating the track filling up #

Adding a transition to the inline-size of the progress element works for Chromium but not for Safari. Firefox also does not use a transition property on it's ::-moz-progress-bar.

/*  Chromium Only 😢  */
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
transition: inline-size .25s ease-out;
}

Animating the :indeterminate state #

Here I get a bit more creative so I can provide an animation. A pseudo-element for Chromium is created and a gradient is applied that is animated back and forth for all three browsers.

The custom properties #

Custom properties are great for many things, but one of my favorites is simply giving a name to an otherwise magical looking CSS value. Following is a fairly complex linear-gradient, but with a nice name. Its purpose and use cases can be clearly understood.

progress {
--_indeterminate-track: linear-gradient(to right,
var(--_track) 45%,
var(--_progress) 0%,
var(--_progress) 55%,
var(--_track) 0%
);
--_indeterminate-track-size: 225% 100%;
--_indeterminate-track-animation: progress-loading 2s infinite ease;
}

Custom properties will also help the code stay DRY since once again, we can't group these browser-specific selectors together.

The keyframes #

The goal is an infinite animation that goes back and forth. The start and end keyframes will be set in CSS. Only one keyframe is needed, the middle keyframe at 50%, to create an animation that returns to where it started from, over and over again!

@keyframes progress-loading {
50% {
background-position: left;
}
}

Targeting each browser #

Not every browser allows the creation of pseudo-elements on the <progress> element itself or allows animating the progress bar. More browsers support animating the track than a pseudo-element, so I upgrade from pseudo-elements as a base and into animating bars.

Chromium pseudo-element #

Chromium does allow the pseudo-element: ::after used with a position to cover the element. The indeterminate custom properties are used, and the back and forth animation works very well.

progress:indeterminate::after {
content: "";
inset: 0;
position: absolute;
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
Safari progress bar #

For Safari, the custom properties and an animation are applied to the pseudo-element progress bar:

progress:indeterminate::-webkit-progress-bar {
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
Firefox progress bar #

For Firefox, the custom properties and an animation are also applied to the pseudo-element progress bar:

progress:indeterminate::-moz-progress-bar {
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}

JavaScript #

JavaScript plays an important role with the <progress> element. It controls the value sent to the element and ensures enough information is present in the document for screen readers.

const state = {
val: null
}

The demo offers buttons for controlling the progress; they update state.val and then call a function for updating the DOM.

document.querySelector('#complete').addEventListener('click', e => {
state.val = 1
setProgress()
})

setProgress() #

This function is where the UI/UX orchestration occurs. Get started by creating a setProgress() function. No parameters are needed because it has access to the state object, progress element, and <main> zone.

const setProgress = () => {

}

Setting the loading status on the <main> zone #

Depending on whether the progress is complete or not, the related <main> element needs an update to the aria-busy attribute:

const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
}

Clear attributes if loading amount is unknown #

If the value is unknown or unset, null in this usage, remove the value and aria-valuenow attributes. This will turn the <progress> to indeterminate.

const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)

if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
}

Fix JavaScript decimal math issues #

Since I chose to stick with the progress default maximum of 1, the demo increment and decrement functions use decimal math. JavaScript, and other languages, are not always great at that. Here's a roundDecimals() function that will trim the excess off the math result:

const roundDecimals = (val, places) =>
+(Math.round(val + "e+" + places) + "e-" + places)

Round the value so it can be presented and is legible:

const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)

if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}

const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"
}

Set value for screen readers and browser state #

The value is used in three locations in the DOM:

  1. The <progress> element's value attribute.
  2. The aria-valuenow attribute.
  3. The <progress> inner text content.
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)

if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}

const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"

progress.value = val
progress.setAttribute('aria-valuenow', valPercent)
progress.innerText = valPercent
}

Giving the progress focus #

With the values updated, sighted users will see the progress change, but screen reader users are not yet given the announcement of change. Focus the <progress> element and the browser will announce the update!

const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)

if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}

const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"

progress.value = val
progress.setAttribute('aria-valuenow', valPercent)
progress.innerText = valPercent

progress.focus()
}
Screenshot of the Mac OS Voice Over app    reading the progress of the loading bar to the user.

Conclusion #

Now that you know how I did it, how would you‽ 🙂

There are certainly a few changes I'd like to make if given another chance. I think there's room to clean up the current component, and room to try and build one without the <progress> element's pseudo-class style limitations. It's worth exploring!

Let's diversify our approaches and learn all the ways to build on the web.

Create a demo, tweet me links, and I'll add it to the community remixes section below!

Community remixes #

nothing to see here yet

Read the whole story
bernhardbock
55 days ago
reply
Share this story
Delete

flowdog: Application framework for AWS Gateway Load Balancers

1 Share

Page 2

You can’t perform that action at this time.

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.

Read the whole story
bernhardbock
126 days ago
reply
Share this story
Delete

Ghost in the ethernet optic

1 Comment

Ghost in the ethernet optic

A Smart SFP

A few months ago I stumbled on a tweet pointing out a kind of [SFP optic](https://en.wikiped

Read the whole story
bernhardbock
130 days ago
reply
if this is placed in your data center by an attacker, it will be very hard to spot
Share this story
Delete

Youtube: It's Time for Operating Systems to Rediscover Hardware

1 Share
Read the whole story
bernhardbock
192 days ago
reply
Share this story
Delete
Next Page of Stories