Web Platform News

W3C Wiki has a page on SVG security (last updated in 2014) that lists the restrictions applied to SVG images. An SVG loaded with an <img> element cannot fetch any external resources (including fonts) and cannot execute scripts. These restrictions don’t apply to inline SVG (<svg> element in HTML document).


When a hyperlink contains both an icon and text (e.g., “← Back”), wrap both parts in a single <a> element. Avoid using two redundant <a> elements for the icon and text. Redundant links are a problem for keyboard users (they have to press Tab twice) and screen reader users (the link is announced twice).


The CSS ::marker pseudo-element1 for styling list item markers (bullets, numbers, etc.) only accepts a small set of text-related properties2, such as font-size and color, and the content property, which means that it’s currently not possible to increase the spacing between the marker and the text via padding or margin properties3. As a workaround, you can add spaces (e.g., content: "• "), hide the marker and create a custom marker using the list item’s ::before element, or add padding to the list item in some cases4.

  1. developer.mozilla.org/docs/Web/CSS/::marker
  2. drafts.csswg.org/css-lists-3/#marker-properties
  3. github.com/w3c/csswg-drafts/issues/4571
  4. twitter.com/simevidas/status/1591208191983255553

An event’s target property1 references the node to which the event was dispatched (event’s origin), whereas the event’s currentTarget property2 references the node to which the event listener was added (object of addEventListener call). You can use CSS pointer-events: none3 to ensure that click and other pointer events4 aren’t dispatched to an element’s descendants (useful for buttons).

  1. developer.mozilla.org/docs/Web/API/Event/target
  2. developer.mozilla.org/docs/Web/API/Event/currentTarget
  3. developer.mozilla.org/docs/Web/CSS/pointer-events
  4. svgwg.org/svg2-draft/interact.html#PointerEvents

When the user focuses an initially empty, required form field (<input> with required attribute), some screen readers will announce this field as invalid (“invalid data” in Firefox/VoiceOver, “invalid entry” in Chrome and Firefox/NVDA, etc.). This information is misleading and can confuse screen reader users. Website authors can avoid this issue by using aria-required="true"1 instead of required, and implementing client-side validation (error message on attempted from submit) with JavaScript.

  1. developer.mozilla.org/docs/Web/Accessibility/ARIA/Attributes/aria-required

Facebook, Gmail, GitHub, and other popular websites allow users to navigate the site and perform actions using single-character keyboard shortcuts (e.g., pressing the C key on Gmail’s website opens the “New message” dialog). This feature can be frustrating for speech input and keyboard users1, so websites should allow users to remap or disable single-character shortcuts (e.g., Facebook has an option for this2).

  1. w3c.github.io/wcag/understanding/character-key-shortcuts.html
  2. twitter.com/simevidas/status/1573747791448399872

Automatic hyphenation (CSS hyphens: auto) is widely supported in modern browsers1. However, the CSS hyphenate-limit-chars property2, which would allow websites to set the minimum number of characters before/after the hyphenation break, is still not supported. Browsers default to 2 characters, but that may be too low for some people who would like to turn on automatic hyphenation. You can voice your support for this CSS property in Chromium issue 9240693 and Mozilla bug 15217234.

  1. caniuse.com/css-hyphens
  2. drafts.csswg.org/css-text-4/#hyphenate-char-limits
  3. bugs.chromium.org/p/chromium/issues/detail?id=924069
  4. bugzilla.mozilla.org/show_bug.cgi?id=1521723

When using the CSS calc() function to add (or subtract) a variable to a length or percentage, e.g., calc(100% - var(‑‑margin)), be aware that the calc() value will become invalid if the custom property is set to a unitless zero (‑‑margin: 0). To avoid this error, set the custom property to 0px instead. Note that your CSS linter may automatically strip the unit from this value (turn 0px into 0).


Chrome shipped (inline) import maps for JavaScript modules (<script type="importmap">) in 20211. This feature can be used to improve the cacheability of versioned module dependencies: The mutable hashed file names are only declared in the import map, while the modules use “bare import specifiers” (e.g., import moment from "moment"). Safari has not implemented import maps yet because this feature currently does not integrate CSP subresource integrity2.

  1. groups.google.com/a/chromium.org/g/blink-dev/c/rVX_dJAJ-eI/m/17r-6-eiAgAJ
  2. lists.webkit.org/pipermail/webkit-dev/2020-October/031556.html

Šime’s newsletter #1

Hello, everyone. I posted 23 news items on webplatform.news in the past month. The best way to subscribe to that content is via the website’s RSS feed.

In this newsletter, I’d like to share other information that I found interesting. Here’s the first batch of content:

  1. I checked which older browser versions (more than one year old) still have a significant usage share on Wikimedia’s analytics. The one that stands out is the previous (major) version of Safari (Safari 14). For this reason, I think it makes sense to focus on this browser version in particular when implementing fallbacks (e.g., for :focus-visible).
  2. Jeremy Keith asked why so many websites avoid the <button> element. This could be (in part) due to the <button> element’s layout limitations. For example, it’s difficult to make buttons flexible (like <div> elements).
  3. :focus, :focus-visible, and :focus:not(:focus-visible). How do these three CSS selectors relate to each other? I was confused about this, so I made a diagram. My main takeaway is that :focus-visible is a proper subset of :focus (an element that is :focus-visible is also :focus).
  4. I somehow never learned that href="/page" is a path-absolute URL string. For comparison, href="page" (which is the same as href="./page") is a path-relative URL string. See my example.
  5. I agree with Chris Heilmann (and Bramus, and probably many others) that light/dark mode switches should be a browser feature instead of cluttering the web.
  6. The CSS Working Group added the scrollbar-width property (in 2018), and Firefox shipped this feature shortly after. The scrollbar-width: thin value makes the scrollbar thinner, which can make sense ”for certain small or cramped elements”. I suggested a thin scrollbar for the table of contents sidebar in Wikipedia’s new design, but there is a concern that thin scrollbars may be difficult to use for people with mobility impairments.
  7. The two ways in CSS to truncate a single line of text with an ellipsis are text-overflow: ellipsis and -webkit-line-clamp: 1, but they behave differently, and -webkit-line-clamp: 1 is the safer method because text-overflow: ellipsis splits words, which can have disastrous results.

Until next time,
Šime


Browsers allow websites to write text to the clipboard (Async Clipboard writeText method1) without requesting permission from the user. In Safari and Firefox, this operation requires a user gesture2 (mouse click, key press, etc.), but in Chromium-based browsers, a web page can freely write text to the clipboard without any interaction from the user3 ('clipboard-write' access is auto-granted4).

  1. developer.mozilla.org/docs/Web/API/Clipboard/writeText
  2. github.com/w3c/clipboard-apis/issues/182
  3. twitter.com/simevidas/status/1565813868475080704
  4. groups.google.com/a/chromium.org/g/blink-dev/c/epeaao7l13M/m/b8ewAH5uBwAJ

If an image on the page has a caption (<figcaption> element), it should also have a text alternative (alt attribute). Repeating the same text in <figcaption> and alt is incorrect. Marking an image that has a caption as decorative (alt="") is also incorrect. The Guardian1 and The Verge2 are some of the websites that get this wrong.

  1. twitter.com/simevidas/status/1558682035484590080
  2. twitter.com/simevidas/status/1564959853076008961

A better name for third-party cookies is cross-site cookies1 (“third party” is ambiguous2). E.g., if a web page includes an image from a different website, then the cookie that comes with that image is a cross-site cookie. The web page cannot access this cookie. If multiple websites load this image, then the image’s web server can track the user across these sites via that cookie. Some browsers protect the user’s privacy by blocking or partitioning cross-site cookies3.

  1. httpwg.org/http-extensions/draft-ietf-httpbis-rfc6265bis.html#name-third-party-cookies
  2. tess.oconnor.cx/2020/10/parties
  3. educatedguesswork.org/posts/private-browsing/#cookies

Two nested requestAnimationFrame1 function calls (“double rAF”) is the generic way to run code after the browser has painted the next frame. Waiting for the next frame is necessary when applying a CSS transition to an element that has just been added to the DOM. If there isn’t a frame between the initial and final states, the browser won’t animate the transition and just render the final state2.

  1. developer.mozilla.org/docs/Web/API/window/requestAnimationFrame
  2. twitter.com/simevidas/status/1563222845546586114

During page load, if you need to run a script as soon as the initial HTML document has finished parsing, use a <script defer> element (type=module scripts are defer by default). The problem with the DOMContentLoaded event is that it fires after all deferred scripts have loaded and executed1. If the initial HTML document contains lots of content, and you need to run a script before the entire document has finished parsing, use a preloaded <script async> element2.

  1. youtu.be/_iq1fPjeqMQ?t=378
  2. youtu.be/_iq1fPjeqMQ?t=732

To ensure that an emoticon1 is correctly and consistently announced by screen readers, wrap it in a <span role=img> element with an aria-label attribute, e.g., <span role=img aria-label="tears of happiness emoticon">:’‑)</span>. This solution can also be used for emoji2.

  1. en.wikipedia.org/wiki/List_of_emoticons
  2. tink.uk/accessible-emoji/

Firefox shipped two CSS properties that were already supported in other browsers: (1) Apple introduced the backdrop-filter property in 20151 to enable the frosted glass effect on the web. The filter effect is applied to the area behind the element, so the element’s background needs to be (semi-)transparent for the effect to be visible. (2) When scroll-snap-stop: always is added to a scroll snap container2, each scroll operation (e.g., swipe gesture) always stops at the next element (snap position) and cannot scroll past it3.

  1. webkit.org/blog/3632/introducing-backdrop-filters/
  2. twitter.com/argyleink/status/1456979682344128517
  3. groups.google.com/a/chromium.org/g/blink-dev/c/bkUwigYHJDM/m/Bzvm8tkHAgAJ

The CSS :focus-visible pseudo-class enables websites to change the appearance of the browser’s default focus outline, which is shown on text inputs and elements focused via the keyboard (see “The :focus-visible heuristic”1). You may need to do this if the default outline isn’t visible against your design’s background2 (WCAG requires a minimum contrast of 3:1 between outline and background3). If your focus indicator is based on background instead of outline, add a transparent outline as a fallback for Windows High Contrast Mode4. Also consider falling back to :focus (via @supports selector()) for last year’s version of Safari5.

  1. blog.chromium.org/2020/09/giving-users-and-developers-more.html
  2. twitter.com/simevidas/status/1560223916999720961
  3. www.tpgi.com/new-success-criteria-in-wcag22/#focus-appearance
  4. www.matuzo.at/blog/2022/focus-outline/
  5. twitter.com/simevidas/status/1560611832473145345

For legacy reasons, the CSS url() function does not accept CSS variables, e.g., background-image: url(var(‑‑foo)) is invalid. The CSS Working Group discussed1 this limitation and (in 2020) decided to propose a src() function2 as a modern alternative to url() that does accept variables.

  1. github.com/w3c/csswg-drafts/issues/541
  2. drafts.csswg.org/css-values-4/#urls

You can use a subclass of EventTarget to dispatch and handle custom (synthetic) events independently of the DOM. When dispatching a custom event via the dispatchEvent method1, execution moves synchronously to event listeners that have been registered for events of that type: “When you call dispatchEvent(), all registered listeners are invoked before the next line of your code runs.” Event handlers that are async functions won’t block other event handlers.

  1. developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent

When using the scrollIntoView method1 to scroll an element on the page into view, browsers will by default scroll the element to the top of the viewport. This will happen even if the element was already fully visible in the viewport. If you don’t want the page to scroll in this case, add the block: 'nearest' option to the method call, e.g., input.scrollIntoView({ block: 'nearest' }). This option behaves similarly to the non-standard scrollIntoViewIfNeeded method2, which isn’t supported in Firefox.

  1. developer.mozilla.org/docs/Web/API/Element/scrollIntoView
  2. developer.mozilla.org/docs/Web/API/Element/scrollIntoViewIfNeeded

The HTML Standard has added a focusVisible option1 for the focus method, e.g., button.focus({ focusVisible: true }). This new option will enable websites to forcibly show the browser’s focus indicator (focus ring) when programmatically focusing an element (e.g., to show a focus ring on the default button when opening a dialog2). This proposed feature is supported by Gecko and Chromium3.

  1. html.spec.whatwg.org/multipage/interaction.html#focus-management-apis
  2. github.com/WICG/focus-visible/issues/268
  3. github.com/whatwg/html/pull/8087

In today’s browsers, the CSS :empty selector1 matches <p></p> and <p><!-- comment --></p> but not <p> </p>. This behavior is not consistent with the CSS Selectors Level 4 module, which (since 20182) states that :empty should also match elements that contain only white space (spaces, newlines, etc.). Whether browsers will implement this change remains an open question.

  1. www.w3.org/TR/selectors-4/#the-empty-pseudo
  2. github.com/w3c/csswg-drafts/issues/1967#issuecomment-424788796

Unlike with images, defining the aspect ratio of a video via the width and height attributes will not prevent a <video> layout shift in browsers. This is the result of an oversight in the HTML Standard1. As a temporary workaround, you can directly set the CSS aspect-ratio property on the <video> element to avoid the layout shift2.

  1. github.com/w3c/csswg-drafts/issues/7524
  2. twitter.com/simevidas/status/1557648172549390336

A good way to implement a mutually exclusive button group is via toggle buttons. A toggle button is a <button> element with an aria-pressed state1. JavaScript is necessary to update the aria-pressed attributes when the user clicks the buttons (aria-pressed="true" on the clicked button and aria-pressed="false" on all other buttons). You can use the CSS [aria-pressed="true"] selector to style the pressed button.

  1. w3c.github.io/aria/#aria-pressed

Accurate alternative text for images (<img alt> attribute) is necessary for people with vision disabilities, but it also makes it easier to find the image on Google Search1. In general, alternative text is the text that you would have written if you hadn’t been able to include the image2.

  1. twitter.com/simevidas/status/1556877948942155777
  2. html.spec.whatwg.org/multipage/images.html#alt

As of today, a total of 29 policy-controlled features have been standardized1. Websites can use the HTTP Permissions-Policy (Chromium) and Feature-Policy (Safari and Firefox) headers to, among other things, prevent its cross-origin iframes from accessing certain features2, such as geolocation and camera.

  1. github.com/w3c/webappsec-permissions-policy/blob/main/features.md
  2. developer.chrome.com/docs/privacy-sandbox/permissions-policy/

If some text on your website is in a different language, and your primary font doesn’t fully support that language’s diacritics (e.g., the popular Lato font doesn’t include the letter Đ1, which is used in some South Slavic languages), you can switch to a more suitable font by marking the text with the HTML lang attribute (e.g., lang="hr") and then using the CSS :lang() selector (e.g., :lang(hr)) to match it and apply a different font-family.

  1. twitter.com/simevidas/status/1556228230893473793

Since 2020, Firefox has an implementation of masonry layout, which can be enabled on the about:config page (search for “masonry”). This feature is defined in the CSS Grid Layout 3 spec. Mozilla is now waiting for other implementations before masonry layout can be shipped in Firefox1. To switch a grid to masonry layout, add grid-template-rows: masonry to the container, and the items will “effectively move up to plug any gaps”2.

  1. bugzilla.mozilla.org/show_bug.cgi?id=1757446#c0
  2. twitter.com/simevidas/status/1555034351456423936

If you use an image’s error event to set src to a fallback image (e.g., <img onerror="this.src='fallback.png'">), and that fallback image for whatever reason also triggers an error, this will result in an infinite recursion in browsers.


You can use CSS Grid to create a full-width component that breaks out of a central wrapper. The grid layout consists of a central column for components, and two flexible columns on each side (e.g., grid-template-columns: 1fr minmax(0, 70rem) 1fr). The full-width component then simply spans all three columns.


The web standards community is retiring the term “browser intervention” because spec changes cannot be cleanly categorized into interventions and non-interventions. One of the biggest interventions that shipped in browsers was improving scrolling performance by moving scrolling to the compositor thread, where it can’t be interrupted by JavaScript.


You can use the standard <hgroup> element1 to group a heading and a subheading for styling purposes, but this element doesn’t have any special semantics2 and is not included in the browser’s accessibility tree, which means that <hgroup> is no more accessible than a <div>.

  1. html.spec.whatwg.org/multipage/sections.html#the-hgroup-element
  2. twitter.com/aardrian/status/1543952473693605888