Skip to content

Latest tips / 3

Let VoiceOver on iOS read a split element as a single element with the text role

September 22, 2022
#a11y #html

VoiceOver on iOS reads each element individually when a user swipes across the screen. If an element like a link is composed of several span elements, VoiceOver will not read the entire link text but only the text of the active span element (the one that the user has swiped on). Buttons are an exception - VoiceOver will read all child elements at once.

For cases where you want VoiceOver to read the whole text at once you can use the unofficial role text which causes VoiceOver to read the split element as one element. Note that this role is not an official role but just used by VoiceOver. In addition, the role should not be placed on the a tag directly but on a child element because it would overwrite the implicit link role of the a tag.

<a href="">
<!-- Let VoiceOver on iOS read the entire link text at once -->
<span role="text">
<span class="material-icons" aria-label="(external)">open_in_new</span>

VoiceOver reads the title of external SVGs even if the alt text is empty

September 22, 2022
#a11y #svg

For accessibility reasons every image on a page should have an alt attribute with a description of the image or, for decorative images, with an empty value. Images with an empty alt text will be ignored by screen readers.

However, there is one exception. If the src attribute points to an SVG file that has a title tag, VoiceOver (on macOS and iOS) will read the title and not ignore the image.

<img src="circle.svg" alt="" />
<!-- /circle.svg -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "">
<svg viewBox="0 0 20 10" xmlns="">
<circle cx="5" cy="5" r="5">
<!-- SVG image with a title tag -->

In the example above VoiceOver will read "Circle, image", while other screen readers like NVDA will ignore the image.

SVG files generated via design tools like Sketch often include a title. The title may be useless (like a description of a decorative icon) or even absolute nonsense (like an auto-generated UUID) and could also be in the wrong language, resulting in a confusing experience for screen reader users.

Either make sure that your images don't include a title tag or use the aria-hidden attribute to hide the image:

<img src="circle.svg" alt="" aria-hidden="true" />

RxJS combineLatest() accepts a selector function

July 12, 2022

The RxJS function combineLatest() accepts a selector function as last parameter. This selector function receives the latest value of each observable as argument and can be used to return a new value that will be emitted.

// Array of observables
[stream1$, stream2$, stream3$],

// Selector function
(val1, val2, val3) => val1 + val2 + val3 // Return value will be emitted

Import Markdown files in your Storybook MDX docs

June 23, 2022
#storybook #markdown

Storybook allows you to import existing Markdown files your MDX docs. You just need configure the transcludeMarkdown option in your storybook config:

// .storybook/main.js
module.exports = {
addons: [
name: "@storybook/addon-docs",
options: { transcludeMarkdown: true },
// ...

Then you can import your Markdown file in your MDX file and render it as a component:

import Example from "./";

# Hello World

<Example />

Use @at-root in SCSS to break out of nesting

June 23, 2022

In SCSS, selectors can be deeply nested. In some situations you may want to break out of the current selector hierarchy and emit some styles at the root of the document. SCSS provides the @at-root rule for this use case:

/* styles.scss */
@mixin some-mixin() {
color: black;

@at-root body {
background: lightgray;

.some-class {
@include some-mixin();

This will move body out of the parent selector:

/* styles.css */
.some-class {
color: black;

body {
background-color: lightgray;