1
0
Fork 0
mirror of https://git.sr.ht/~seirdy/seirdy.one synced 2024-09-19 20:02:10 +00:00

Web best practices: add lots of new content

- Add detailed information on targeting old browsers
- Move section on the Tor Browser after the aforementioned older-browser
  section.
- Add cryptcheck, check-your-website
- Add document-policy to image-compression section to describe how to
  enforce image compression.
- Link to ticket for opena11y eval library
- Minor clarification regarding Firefox a11y inspector: scrollable
  elements in any direction will be interactive
- Update note on seirdy.one cache-control directives
- Add missing "Future users" section to gemtext version
- Updated outdated info on full-text feed sizes
- Spelling/typos
This commit is contained in:
Rohan Kumar 2022-08-22 21:15:13 -07:00
parent 0bbf28c41a
commit 7e386b0aa9
No known key found for this signature in database
GPG key ID: 1E892DB2A5F84479
2 changed files with 277 additions and 126 deletions

View file

@ -249,7 +249,7 @@ Avoid layout shifts by including dimensions in HTML attributes. The simplest way
In-depth server configuration is a bit out of scope, so I'll keep each improvement brief.
Compression--especially static compression--dramatically reduces download sizes. My full-text RSS feed is about a quarter of a megabyte, but the Brotli-compressed version is about 70 kilobytes. Caddy supports this with a "precompressed" directive; Nginx requires the ngx_brotli module for Brotli compression:
Compression---especially static compression---dramatically reduces download sizes. My full-text Atom feed is almost a megabyte, but the Brotli-compressed version is under one-quarter of that size. Caddy supports this with a "precompressed" directive; Nginx requires the ngx_brotli module for Brotli compression:
=> https://github.com/google/ngx_brotli The ngx_brotli module
@ -263,42 +263,6 @@ Consider the trade-offs involved in enabling 0-RTT for TLS 1.3. On one hand, it
=> https://www.rfc-editor.org/rfc/rfc8446.html#appendix-E.5 RFC-8446, Appendix E.5.
## The Tor Browser
Many people use Tor out of necessity. On Tor, additional constraints apply.
## Constrainsts of the Tor Browser
Tor users are encouraged to set the Tor Browser's (TBB) security settings to "safest".
=> https://tb-manual.torproject.org/en-US/security-settings/ TBB Security Settings
This disables scripts, MathML, some fonts, SVG images, and other unsafe Firefox features:
=> https://gitweb.torproject.org/torbutton.git/tree/modules/security-prefs.js Torbutton security-prefs source code
If your site has any SVG images, the Tor browser will download these just like Firefox would (to avoid fingerprinting) but will not render them.
Additionally, hopping between nodes in Tor circuits incurs latency, worsening the impacts of requiring multiple requests and round-trips. Try to minimise the number of requests to view a page.
If you use a CDN or some overcomplicated website security stack, make sure it doesn't block Tor users or require them to enable JavaScript to complete a CAPTCHA. Tor Browser users are supposed to avoid fingerprinting vectors like JS and browser extensions, so requiring a JavaScript-based CAPTCHA will effectively block many Tor users.
Tor users are unable to leverage media queries or client-hints to signal special needs. Pages need to be as accessible as possible by default, as per the "inclusive by default" directive outlined in the introduction. This should be a given, but it's doubly important when serving fingerprinting-averse readers.
### Hidden services
To go above and beyond, try mirroring your site to a Tor hidden service to reduce the need for exit nodes. Mirroring allows you to keep a separate version of your site optimized for the Tor browser.
Normally, optimizing specifically for a given user agent's quirks (especially in a separate version of a website) is a bad practice; however, the Tor Browser is a special case because there's no alternative available: Tor users should all use the same browser to avoid standing out. Sometimes, the Tor Browser pretends to have Firefox's capabilities: progressive enhancement and graceful degradation won't work when a browser lies about its functionality.
For example, my website's clearnet version uses some SVG images. Some browsers can't handle a given image format. The typical solution is to use a <picture> element containing <source> children of varying formats and a fallback <img> element using a legacy image format.
The Tor browser will download whichever format Firefox would, rather than whichever formats it actually supports. A <picture> element containing an SVG and a raster fallback won't help: the Tor browser will avoid fingerprinting by selecting the SVG format, not a fallback format. The image will not be rendered, so users will have downloaded the image only to see a white box.
I address the issue by not using any SVG images on my hidden service:
=> http://wgq3bd2kqoybhstp77i3wrzbfnsyd27wt34psaja4grqiezqircorkyd.onion/ My Tor hidden service (HTTP+HTML)
## Against lazy loading
Lazy loading may or may not work. Some browsers, including Firefox and the Tor Browser, disable lazy-loading when the user turns off JavaScript. Turning it off makes sense because lazy-loading, like JavaScript, is a fingerprinting vector. Specifically, it identifies idiosyncratic scrolling patterns:
@ -402,6 +366,83 @@ When pages grow long, keep performance in check by doing the following:
* Remember that CSS can impact the accessibility tree. Avoid using scripts to alter properties such as "display" and "visibility".
* Test with screen readers on underpowered hardware. Examples include old entry-level Android phones and netbooks with aggressive thermal throttling.
## Old browsers
People do not use your site exclusively with the latest stable versions of Chrome, Firefox, or Safari. If you use metrics, they will be biased against users who avoid your site due to incompatibilities.
### Old browsers in use
Plenty of reasons exist for using older versions of each of the mainstream browsers:
* Firefox: In addition to Firefox's "Stable", "Beta", and "Nightly" channels, Firefox includes an "Extended Support Release" (ESR). Firefox ESR receives only bugfixes and major security patches for over one year. It's the default browser in Debian and the basis for the Tor Browser. In other words: if you want people to be able to browse your site anonymously (i.e. with the Tor Browser), you need to support Firefox ESR versions for at least 13 months after their release. If that wasn't enough: the latest version of KaiOS uses Firefox 84, while older versions (still being sold in 2022!) use Firefox 49.
* Safari and WebKit: On Linux, WebKitGTK powers various browsers, RSS readers, and in-app browsers. WebKitGTK versions are limited by a distribution's release model; LTS distributions will not have the shiniest new WebKit features. Safari versions are similarly limited by iOS and macOS versions. For instance, the iPhone 7 will not be able to run iOS 16 or Safari 16, just four years after it was discontinued; it'll only receive occasional security patches for Safari 15.x. The only way for iPhone-7-and-earlier users to use Safari 16+ features is to buy a new phone. Expecting users to buy a new device every few years just to display a webpage needlessly contributes to the rampant consumerism and throw-away culture in the consumer electronics industry.
* Chromium: Google Chrome's "Extended stable" channel releases half as often as its "Stable" channel. Moreover, Chromium is the basis for QtWebEngine, the Web engine of the Qt UI toolkit. QtWebEngine powers a host of Web browsers: Qutebrowser, Falkon, Nyxt, Angelfish, Otter Browser, and others. Like WebKit2GTK, QtWebEngine powers various RSS readers and in-app browsers. The latest version of Qt typically includes a QtWebEngine several versions behind upstream Chromium, and most distributions don't ship the latest version of Qt. Finally, Android's Chromium-based WebView implementations are still sometimes locked to a vendor's abandoned version of Android and woefully out of date; in-app browsers are often even worse, sometimes shipping a version of Chromium that's *years* old.
* Opera: Opera switched away from its in-house Presto browser engine a few years ago, in favor of becoming a Chromium fork. However, Opera Presto is still being kept alive by Opera Mini. Opera Mini's "extreme" mode uses a proxy server to render pages; that server runs Opera's Presto rendering engine.
### How to support old browsers
I recommend testing with a pre-2013 version of WebKit, as those WebKit versions are ancestors to both Chromium and Safari. Old versions of Opera and Firefox are still available for download. Obviously, I wouldn't recommend using these browsers outside of a heavily sandboxed environment (e.g. a confined VM) unless you disable unnecessary features and limit their use to testing.
Always use progressive enhancement: everything besides your semantic markup and the occasional legacy image format should be an optional enhancement. Test with CSS, scripts, new image formats, etc. disabled.
Restrict markup to the subset of the WHATWG's HTML Living Standard that also appears in the W3C's HTML 5.1 standard. This should provide a slower-moving base than the raw Living Standard that's also friendlier to older browsers. I don't recommend referencing the W3C's HTML 5.1 standard directly, since the Living Standard has since made several important clarifications, corrections, and removals. The most recent such removal was the Document Outline algorithm. For years after it was standardized, the Document Outline was ignored by every browser engine. Finally, the WHATWG replaced the Document Outline algorithm with a revised version that involved multiple heading levels. The revised version matches what user-agents and good authors have been doing for decades.
=> https://www.tpgi.com/html5-document-outline/ TPGi: "The HTML5 Document Outline is a dangerous fiction"
=> https://github.com/whatwg/html/pull/7829 GitHub pull-request that replaced the Document Outline
CSS offers multiple equivalent ways to do the same thing; prefer older versions when possible. A non-exhaustive list of ways to make stylesheets work in older browsers:
* Use `em` units rather than the more convenient `rem`
* Avoid CSS variables and custom properties; keep track of values by using increments, or use a post-processor to replace references to variables.
* Use the `@supports` at-rule to progressively replace an older feature with a newer one.
* Explicitly define the `display` property for the `hidden` attribute, for browsers that don't support `hidden`.
When in doubt: "Can I Use" and MDN are excellent resources to track feature support across all the mainstream browser engines.
=> https://caniuse.com/ Can I Use
=> https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Page_structures/Compatibility_tables MDN's browser compatibility data
Feel free to go wild when adding strictly-optional features.
## The Tor Browser
Many people use Tor out of necessity. On Tor, additional constraints apply.
### Constrainsts of the Tor Browser
Tor users are encouraged to set the Tor Browser's (TBB) security settings to "safest".
=> https://tb-manual.torproject.org/en-US/security-settings/ TBB Security Settings
This disables scripts, MathML, some fonts, SVG images, and other unsafe Firefox features:
=> https://gitweb.torproject.org/torbutton.git/tree/modules/security-prefs.js Torbutton security-prefs source code
If your site has any SVG images, the Tor browser will download these just like Firefox would (to avoid fingerprinting) but will not render them.
Additionally, hopping between nodes in Tor circuits incurs latency, worsening the impacts of requiring multiple requests and round-trips. Try to minimise the number of requests to view a page.
If you use a CDN or some overcomplicated website security stack, make sure it doesn't block Tor users or require them to enable JavaScript to complete a CAPTCHA. Tor Browser users are supposed to avoid fingerprinting vectors like JS and browser extensions, so requiring a JavaScript-based CAPTCHA will effectively block many Tor users.
Tor users are unable to leverage media queries or client-hints to signal special needs. Pages need to be as accessible as possible by default, as per the "inclusive by default" directive outlined in the introduction. This should be a given, but it's doubly important when serving fingerprinting-averse readers.
### Hidden services
To go above and beyond, try mirroring your site to a Tor hidden service to reduce the need for exit nodes. Mirroring allows you to keep a separate version of your site optimized for the Tor browser.
Normally, optimizing specifically for a given user agent's quirks (especially in a separate version of a website) is a bad practice; however, the Tor Browser is a special case because there's no alternative available: Tor users should all use the same browser to avoid standing out. Sometimes, the Tor Browser pretends to have Firefox's capabilities: progressive enhancement and graceful degradation won't work when a browser lies about its functionality.
For example, my website's clearnet version uses some SVG images. Some browsers can't handle a given image format. The typical solution is to use a <picture> element containing <source> children of varying formats and a fallback <img> element using a legacy image format.
The Tor browser will download whichever format Firefox would, rather than whichever formats it actually supports. A <picture> element containing an SVG and a raster fallback won't help: the Tor browser will avoid fingerprinting by selecting the SVG format, not a fallback format. The image will not be rendered, so users will have downloaded the image only to see a white box.
I address the issue by not using any SVG images on my hidden service:
=> http://wgq3bd2kqoybhstp77i3wrzbfnsyd27wt34psaja4grqiezqircorkyd.onion/ My Tor hidden service (HTTP+HTML)
## In-page search
In-page search (e.g., using "Ctrl + f") has been a basic feature in document readers well before browsers, and continues to be an essential feature today.
@ -539,7 +580,7 @@ Consider using a <figure> element when employing the previous section's two-part
Figures aren't just for images; they're for any self-contained referenced content that's closer to the surrounding body than an <aside>. Where appropriate, use figures for:
* Blockquotes, captioned with citations
* Code snippets, captioned with their purpose or links to a the larger files from which the snippets were borrowed.
* Code snippets, captioned with their purpose or links to the larger files from which the snippets were borrowed.
* Some mathematical notation, captioned with brief explanations of their behavior, purpose, or significance (equations should also have alt-text!)
Figures and captions have loose guidelines, and nearly everything I said on the matter is full of exceptions. A figure need not have a caption, but the majority benefit from one. It need not contain a single main element, but most probably should.
@ -766,7 +807,7 @@ Color schemes should also look good to users who apply gamma adjustments. Most o
> Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.
=> https://www.w3.org/TR/WCAG22/#distinguishable WCAG 2.2, section 1.4.1
A basic WCAG Level A requirement is for information to not be conveyed solely through color. Bothe the presence and type of interactivity need to be visually communicated by other means. Links should not look like buttons, and vice-versa.
A basic WCAG Level A requirement is for information to not be conveyed solely through color. Both the presence and type of interactivity need to be visually communicated by other means. Links should not look like buttons, and vice-versa.
### In defense of link underlines
@ -883,7 +924,7 @@ Most of my images will probably be screenshots that start as PNGs. My typical fl
In general, avoid loading images just for decoration. Only use an image if it has a clear purpose that significantly adds to the content in a way that text can't replace, and provide alt-text as a fallback. Any level of detail that isn't necessary for getting the point across should be removed by lossy compression and cropping.
If you want to include a profile photo (e.g., if your website is part of the IndieWeb and uses an [h-card](https://microformats.org/wiki/h-card)), I recommend re-using one of your favicons. Doing so should be harmless since most browsers will fetch and cache favicons anyway.
If you want to include a profile photo (e.g., if your website is part of the IndieWeb and uses an h-card microformat, I recommend re-using one of your favicons. Doing so should be harmless since most browsers will fetch and cache favicons anyway.
If you really want to take PNG optimization to the next level, try Efficient Compression Tool:
@ -914,6 +955,16 @@ Rather than create separate lanes for different users, I prefer making the defau
It might seem odd to create a lossless WebP from a lossy PNG, but I've found that it's often the best way to get the smallest possible image at the minimum acceptable quality for screenshots containing solid backgrounds.
### Enforce image compression
The "Document Policy" HTTP header has experimental directives `lossless-images-max-bpp` and `lossy-images-max-bpp`.
=> https://github.com/wicg/document-policy/blob/main/document-policy-explainer.md Document Policy explainer doc
In this context, "bpp" refers to "bytes per pixel"; a 256-by-256 pixel image that's 6.6 kilobytes large would have a "bpp" of 0.1. If it's a lossy image, then sending `lossy-images-max-bpp=0.1` would forbid the image from loading if it's over 6.6 kilobytes in size.
Currently, these directives are disabled-by-default in Chromium; you can enable them by navigating to `chrome://flags` and toggling "Experimental Web platform features". I don't think these directives are perfect, but they are quite useful.
=> https://seirdy.one/notes/2022/08/12/document-policy-and-image-compression/ Document policy and image compression
### Dark image variants
Bright images on an otherwise dark page distract readers, especially readers like me with ADHD. The human iris adjusts to average amounts of light; an object far brighter than its surroundings causes eye strain even among readers with healthy vision.
@ -1195,7 +1246,7 @@ Readers who double-tap to jump or zoom can't do so if there's no screen region t
=> gemini://seirdy.one/misc/touch_targets.png Phone screen with three "touch target" rectangles atop each other, separated by blank sections labeled "space". Image from Axess Lab.
Having clearly distinguished links also helps users decide safe places to tap the screen. See the "In defense of link underlines" section for more information. Always make sure one non-interactive region exists on the screen at a time, 48 CSS pixels in either dimension; that's the size of a [tap target](#tap-target).
Having clearly distinguished links also helps users decide safe places to tap the screen. See the "In defense of link underlines" section for more information. Always make sure one non-interactive region exists on the screen at a time, 48 CSS pixels in either dimension; that's the size of a tap target.
### Tap targets
@ -1458,6 +1509,41 @@ Beware of "display" and "visibility" CSS properties; they can interfere with con
> If what is represented in the accessibility tree does not represent the developers intended UI, its either (wittingly/unwittingly) the fault of the developer or the browser. But what we can be sure of, in these cases, is that it is not the fault of the screen reader.
=> https://www.tpgi.com/short-note-on-what-css-display-properties-do-to-table-semantics/ Short note on what CSS display properties do to table semantics
## Future users
The number of people using your site in the future is hopefully greater than the number of people using your site in the present. Accordingly, your pages need to work correctly in the future.
Much of this section borrows from an article by Jeff Huang:
=> https://jeffhuang.com/designed_to_last/ This Page is Designed to Last
### Third parties
I've already made a privacy and performance case against third-party resources in an earlier "Third-party content" subsection). Another reason to avoid third-party resources is longevity. Third-party scripts, styles, frames, and images all depend on someone else's host keeping the resource available at the same URL; over time, these tend to disappear. Sticking to first-party content reduces these points of failure.
Third-party content is especially problematic when it's hosted on modest hardware. If your site "goes viral", your traffic could take down the third-party site or prompt them to disable hotlinking. _Serve assets yourself._
### Dead links
On the same note: your internal and external links to other pages need to change with time
=> https://en.wikipedia.org/wiki/Link_rot Wikipedia: Link rot
Use a broken-link checker regularly to ensure that your content stays alive. I recommend checkers that can cache results. My site has a bit under two thousand links at the time of writing; checking all of them at once would be exhausting, but using a week-long cache allows me to split this over seven days. Examples of such broken-link checkers include:
=> https://github.com/lycheeverse/lychee lychee
=> https://github.com/wjdp/htmltest htmltest
Whenever you link to a page, try to archive a snapshot of it. The Wayback Machine, archive.today, and Ghostarchive are popular options. Archival is one of the few times I recommend using a third-party service; a service like the Wayback Machine will likely outlive your website. That being said, self-hosted solutions like ArchiveBox do exist.
If you link often enough, archival might be something worth automating. The Wayback Machine offers an API, and allows registered users to mass-archive archive all outlinks in a page.
### Reproducibility
Imagine your typical "modern" website's deployment pipeline. It requires thousands of dependencies to build. It uses bespoke tools to deploy to a service provider with a custom non-standard stack (e.g. Fly.io, Heroku, Cloudflare Workers, AWS Lambda).
Ten years from now, how much of this will still work?
Try to ensure that your website can be archived, and/or easily re-built and served on an ordinary server. This way, your work can still be made accessible after you're gone. For example: all my site requires to build is a tarball of statically-linked binaries, a POSIX shell, and a decent Make implementation (bmake and GNU make work) to build. To serve, it just needs a static web server.
=> https://git.sr.ht/~seirdy/seirdy.one/tree/master/item/.build.yml See my build manifest to see how my site is built
## Testing
If your site is simple enough, it should automatically handle the vast majority of edge-cases. Different devices and browsers all have their quirks, but they generally have one thing in common: they understand POSH.
@ -1490,7 +1576,7 @@ These are the tools I use regularly. I've deliberately excluded tools that would
6. AInspector: a Firefox addon that displays audits in the sidebar. In my experience, it does have many false-positives (especially regarding DPUB-ARIA); however, it has caught a few issues missed by all other tools.
=> https://ainspector.disability.illinois.edu/ AInspector website
7. Firefox's Accessibility Inspector: supports some very basic audits for contrast, keyboard access, and text labels. The accessibility tree is also a useful way to ensure that elements have accessible names. Horizontally-scrollable elements may only become interactive on narrow viewports, so I recommend running the keyboard audits after reducing the viewport width.
7. Firefox's Accessibility Inspector: supports some very basic audits for contrast, keyboard access, and text labels. The accessibility tree is also a useful way to ensure that elements have accessible names. The keyboard audits have false-positives on non-interactive elements with scrollable overflow, which need to be focusable.
=> https://firefox-source-docs.mozilla.org/devtools-user/accessibility_inspector/index.html#check-for-accessibility-issues Firefox Accessibility Inspector documentation
8. Chromium's CSS Overview: can show contrast violations. I recommend enabling the APCA-based contrast algorithm in the DevTools experimental settings first. Note that this uses an earlier version of APCA and does not account for contrast that is too high.
@ -1500,9 +1586,16 @@ These are the tools I use regularly. I've deliberately excluded tools that would
=> https://testssl.sh/ testssl.sh
=> https://www.ssllabs.com/ssltest/ Qualys SSL Labs' SSL Server Test
10. CryptCheck: Unlike TLS 1.3, not all TLS 1.2 ciphers are secure. CryptCheck goes a bit further than testssl.sh and SSL Labs when it comes to evaluating TLS 1.2 cipher suites' security properties
=> https://tls.imirhil.fr/ CryptCheck
10. Webbkoll: basic security checks, focusing on HTTP headers. I consider it a spiritual successor to Mozilla's HTTP Observatory.
=> https://webbkoll.dataskydd.net/ Webbkoll
11. Check Your Website: slower, more in-depth website checks with an emphasis on security. It covers name server configurations, DNSSEC, DANE, email DNS records, MTA-STS, well-known paths, redirects, certificate transparency, subresource integrity, caching, and well-known ports. If you find its reports too overwhelmingly detailed, Hardenize is an easier-to-understand option.
=> https://check-your-website.server-daten.de/ Check Your Website
=> https://www.hardenize.com/ Hardenize
I excluded Security Headers, since it tends to cargo-cult headers regardless of whether or not they are necessary. For instance, it penalizes forgoing the "Permissions-Policy" header even if the CSP blocks script loading and execution. I excluded PageSpeed Insights and GTMetrix since those are mostly covered by Lighthouse.
### Unorthodox tests

View file

@ -271,7 +271,7 @@ Loading content of unknown dimensions, such as images, can create layout shifts;
In-depth server configuration is a bit out of scope, so I'll keep each improvement brief.
Compression---especially static compression---dramatically reduces download sizes. My full-text RSS feed is about a quarter of a megabyte, but the Brotli-compressed version is about 70 kilobytes. Caddy supports this with a `precompressed` directive; Nginx requires the [ngx_brotli module](https://github.com/google/ngx_brotli) for Brotli compression.
Compression---especially static compression---dramatically reduces download sizes. My full-text Atom feed is almost a megabyte, but the Brotli-compressed version is under one-quarter of that size. Caddy supports this with a `precompressed` directive; Nginx requires the [ngx_brotli module](https://github.com/google/ngx_brotli) for Brotli compression.
When serving many resources at once (e.g., if a page has many images), HTTP/2 could offer a speed boost through multiplexing; use it if you can, but expect many clients to only support HTTP/1.1. HTTP/3 is unlikely to help textual websites much, so run a benchmark to see if it's worthwhile.
@ -281,33 +281,6 @@ Using [OCSP stapling](https://en.wikipedia.org/wiki/OCSP_stapling) eliminates th
Consider the trade-offs involved in enabling 0-<abbr title="Round-Trip Time">RTT</abbr> for TLS 1.3. On one hand, it shaves off a round-trip during session resumption; on the other hand, it can enable replay attacks. 0-RTT shouldn't be too unsafe for idempotent GET requests of static content. For dynamic content, evaluate whether your backend is vulnerable to replay attacks described in [appendix E.5](https://www.rfc-editor.org/rfc/rfc8446.html#appendix-E.5) of the spec.
The Tor Browser
---------------
Many people use Tor out of necessity. On Tor, additional constraints apply.
### Constraints of the Tor Browser
Tor users are encouraged to set the [Tor Browser's security settings](https://tb-manual.torproject.org/en-US/security-settings/) to "safest". This disables scripts, MathML, some fonts, SVG images, and [other unsafe Firefox features](https://gitweb.torproject.org/torbutton.git/tree/modules/security-prefs.js). If your site has any SVG images, the Tor browser will download these just like Firefox would (to avoid fingerprinting) but will not render them.
Additionally, hopping between nodes in Tor circuits incurs latency, worsening the impacts of requiring multiple requests and round-trips. Try to minimise the number of requests to view a page.
If you use a CDN or some overcomplicated website security stack, make sure it doesn't block Tor users or require them to enable JavaScript to complete a CAPTCHA. Tor Browser users are supposed to avoid fingerprinting vectors like JS and browser extensions, so requiring a JavaScript-<wbr />based CAPTCHA will effectively block many Tor users.
Tor users are unable to leverage media queries or client-hints to signal special needs. Pages need to be as accessible as possible by default, as per the ["inclusive by default" directive outlined earlier](#inc-by-default). This should be a given, but it's doubly important when serving fingerprinting-averse readers.
### Hidden services
To go above and beyond, try mirroring your site to a Tor hidden service to reduce the need for exit nodes. Mirroring allows you to keep a separate version of your site optimized for the Tor browser.
Normally, optimizing specifically for a given user agent's quirks (especially in a separate version of a website) is a bad practice; however, the Tor Browser is a special case because there's no alternative available: Tor users should all use the same browser to avoid standing out. Sometimes, the Tor Browser pretends to have Firefox's capabilities: progressive enhancement and graceful degradation won't work when a browser lies about its functionality.
For example, my website's clearnet version uses some SVG images. Some browsers can't handle a given image format. The typical solution is to use a `<picture>` element containing `<source>` children of varying formats and a fallback `<img>` element using a legacy image format.
The Tor browser will download whichever format Firefox would, rather than whichever formats it actually supports. A `<picture>` element containing an SVG and a raster fallback won't help: the Tor browser will avoid fingerprinting by selecting the SVG format, not a fallback format. The image will not be rendered, so users will have downloaded the image only to see a white box.
I address the issue by not using any SVG images on [my hidden service](http://wgq3bd2kqoybhstp77i3wrzbfnsyd27wt34psaja4grqiezqircorkyd.onion/).
Against lazy loading
--------------------
@ -443,6 +416,75 @@ When pages grow long, keep performance in check by doing the following:
* Test with screen readers on underpowered hardware. Examples include old entry-level Android phones and netbooks with aggressive thermal throttling.
Old browsers
------------
People do not use your site exclusively with the latest stable versions of Chrome, Firefox, or Safari. If you use metrics, they will be biased against users who avoid your site due to incompatibilities.
### Old browsers in use
Plenty of reasons exist for using older versions of each of the mainstream browsers:
Firefox
: In addition to Firefox's "Stable", "Beta", and "Nightly" channels, Firefox includes an "Extended Support Release" (<abbr>ESR</abbr>). Firefox <abbr>ESR</abbr> receives only bugfixes and major security patches for over one year. It's the default browser in Debian and the basis for the Tor Browser. In other words: if you want people to be able to browse your site anonymously (i.e. with the Tor Browser), you need to support Firefox <abbr>ESR</abbr> versions for at least 13 months after their release. If that wasn't enough: the latest version of KaiOS uses Firefox 84, while older versions (still being sold in 2022!) use Firefox 49.[^12]
Safari and WebKit
: On Linux, WebKitGTK powers various browsers, RSS readers, and in-app browsers. WebKitGTK versions are limited by a distribution's release model; LTS distributions will not have the shiniest new WebKit features. Safari versions are similarly limited by iOS and macOS versions. For instance, the iPhone 7 will not be able to run iOS 16 or Safari 16, just four years after it was discontinued; it'll only receive occasional security patches for Safari 15.x. The only way for iPhone-7-and-earlier users to use Safari 16+ features is to buy a new phone. Expecting users to buy a new device every few years just to display a webpage needlessly contributes to the rampant consumerism and throw-away culture in the consumer electronics industry.
Chromium
: [Google Chrome's "Extended stable" channel](https://support.google.com/chrome/a/answer/9027636?hl=en) releases half as often as its "Stable" channel. Moreover, Chromium is the basis for QtWebEngine, the Web engine of the Qt UI toolkit. QtWebEngine powers a host of Web browsers: Qutebrowser, Falkon, Nyxt, Angelfish, Otter Browser, and others. Like WebKit2GTK, QtWebEngine powers various RSS readers and in-app browsers. The latest version of Qt typically includes a QtWebEngine several versions behind upstream Chromium, and most distributions don't ship the latest version of Qt. Finally, Android's Chromium-based WebView implementations are still sometimes locked to a vendor's abandoned version of Android and woefully out of date; in-app browsers are often even worse, sometimes shipping a version of Chromium that's _years_ old.
Opera
: Opera switched away from its in-house Presto browser engine a few years ago, in favor of becoming a Chromium fork. However, Opera Presto is still being kept alive by Opera Mini. Opera Mini's "extreme" mode uses a proxy server to render pages; that server runs Opera's Presto rendering engine.
### How to support old browsers
I recommend testing with a pre-2013 version of WebKit, as those WebKit versions are ancestors to both Chromium and Safari. Old versions of Opera and Firefox are still available for download. Obviously, I wouldn't recommend using these browsers outside of a heavily sandboxed environment (e.g. a confined VM) unless you disable unnecessary features and limit their use to testing.
Always use progressive enhancement: everything besides your semantic markup and the occasional legacy image format should be an optional enhancement. Test with CSS, scripts, new image formats, etc. disabled.
Restrict markup to the subset of the WHATWG's HTML Living Standard that also appears in the W3C's HTML 5.1 standard. This should provide a slower-moving base than the raw Living Standard that's also friendlier to older browsers. I don't recommend referencing the W3C's HTML 5.1 standard directly, since the Living Standard has since made several important clarifications, corrections, and removals.[^13]
CSS offers multiple equivalent ways to do the same thing; prefer older versions when possible. A non-exhaustive list of ways to make stylesheets work in older browsers:
- Use `em` units rather than the more convenient `rem`
- Avoid CSS variables and custom properties; keep track of values by using increments, or use a post-processor to replace references to variables.
- Use the `@supports` at-rule to progressively replace an older feature with a newer one.
- Explicitly define the `display` property for the `hidden` attribute, for browsers that don't support `hidden`.
When in doubt: [Can I Use](https://caniuse.com/) and [MDN's browser compatibility data](https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Page_structures/Compatibility_tables) are excellent resources to track feature support across all the mainstream browser engines. Feel free to go wild when adding strictly-optional features.
The Tor Browser
---------------
Many people use Tor out of necessity. On Tor, additional constraints apply.
### Constraints of the Tor Browser
Tor users are encouraged to set the [Tor Browser's security settings](https://tb-manual.torproject.org/en-US/security-settings/) to "safest". This disables scripts, MathML, some fonts, SVG images, and [other unsafe Firefox features](https://gitweb.torproject.org/torbutton.git/tree/modules/security-prefs.js). If your site has any SVG images, the Tor browser will download these just like Firefox would (to avoid fingerprinting) but will not render them.
Additionally, hopping between nodes in Tor circuits incurs latency, worsening the impacts of requiring multiple requests and round-trips. Try to minimise the number of requests to view a page.
If you use a CDN or some overcomplicated website security stack, make sure it doesn't block Tor users or require them to enable JavaScript to complete a CAPTCHA. Tor Browser users are supposed to avoid fingerprinting vectors like JS and browser extensions, so requiring a JavaScript-<wbr />based CAPTCHA will effectively block many Tor users.
Tor users are unable to leverage media queries or client-hints to signal special needs. Pages need to be as accessible as possible by default, as per the ["inclusive by default" directive outlined earlier](#inc-by-default). This should be a given, but it's doubly important when serving fingerprinting-averse readers.
### Hidden services
To go above and beyond, try mirroring your site to a Tor hidden service to reduce the need for exit nodes. Mirroring allows you to keep a separate version of your site optimized for the Tor browser.
Normally, optimizing specifically for a given user agent's quirks (especially in a separate version of a website) is a bad practice; however, the Tor Browser is a special case because there's no alternative available: Tor users should all use the same browser to avoid standing out. Sometimes, the Tor Browser pretends to have Firefox's capabilities: progressive enhancement and graceful degradation won't work when a browser lies about its functionality.
For example, my website's clearnet version uses some SVG images. Some browsers can't handle a given image format. The typical solution is to use a `<picture>` element containing `<source>` children of varying formats and a fallback `<img>` element using a legacy image format.
The Tor browser will download whichever format Firefox would, rather than whichever formats it actually supports. A `<picture>` element containing an SVG and a raster fallback won't help: the Tor browser will avoid fingerprinting by selecting the SVG format, not a fallback format. The image will not be rendered, so users will have downloaded the image only to see a white box.
I address the issue by not using any SVG images on [my hidden service](http://wgq3bd2kqoybhstp77i3wrzbfnsyd27wt34psaja4grqiezqircorkyd.onion/).
In-page search
--------------
@ -471,7 +513,7 @@ Moreover, some search implementations (such as the one built into Firefox) suppo
### Problematic overrides {#problematic-overrides}
Search is so essential to some users' ability to navigate that some desktop users enable "type-ahead" search, to automatically begin a search upon typing multiple characters.[^12] If you ignored my advice to avoid JavaScript, at least think twice before using it to define custom keyboard shortcuts which interfere with this type of functionality. I singled out type-ahead search, but there are countless other examples of uncommon keyboard behavior that JavaScript overrides interfere with.
Search is so essential to some users' ability to navigate that some desktop users enable "type-ahead" search, to automatically begin a search upon typing multiple characters.[^14] If you ignored my advice to avoid JavaScript, at least think twice before using it to define custom keyboard shortcuts which interfere with this type of functionality. I singled out type-ahead search, but there are countless other examples of uncommon keyboard behavior that JavaScript overrides interfere with.
Another problematic override is scroll-behavior. Enforcing smooth-scrolling (e.g., with the `scroll-behavior` CSS property) can interfere with the use of in-page search by slowing down jumps between matches. Rapidly darting around the page with smooth scrolling can cause motion sickness. Simply relying on users to override default behaviors violates the "inclusive by default" directive I encourage, since user preferences are fingerprintable and shift responsibility away from developers.
@ -506,7 +548,7 @@ Some people raised fingerprinting concerns when I suggested using the default "s
You can't do font enumeration or accurately calculate font metrics without JavaScript. Since text-based websites that follow these best-practices don't send requests after the page loads and have no scripts, they shouldn't be able to fingerprint via font identification.
Other websites can still fingerprint via font enumeration using JavaScript. They don't need to stop at seeing what sans-serif maps to: they can see available fonts on a user's system,[^13] the user's canvas fingerprint, window dimensions, etc. Some of these can be mitigated by [Firefox's protections against fingerprinting](https://support.mozilla.org/en-US/kb/firefox-protection-against-fingerprinting), but these protections understandably override user font preferences.
Other websites can still fingerprint via font enumeration using JavaScript. They don't need to stop at seeing what sans-serif maps to: they can see available fonts on a user's system,[^15] the user's canvas fingerprint, window dimensions, etc. Some of these can be mitigated by [Firefox's protections against fingerprinting](https://support.mozilla.org/en-US/kb/firefox-protection-against-fingerprinting), but these protections understandably override user font preferences.
Ultimately, surveillance self-defense on the web is an arms race full of trade-offs. If you want both privacy and customizability, the web is not the place to look; try Gemini or Gopher instead.
@ -514,7 +556,7 @@ Ultimately, surveillance self-defense on the web is an arms race full of trade-o
Browsers allow users to zoom by adjusting size metrics. Additionally, most browsers allow users to specify a minimum font size. Minimum sizes don't always work; setting size values in `px` can override these settings.
In your stylesheets, avoid using `px` where possible. Define sizes and dimensions using relative units (preferably `em`). Exceptions exist for rare items that that shouldn't scale with zoom (e.g. decoration, minimum margins).[^14]
In your stylesheets, avoid using `px` where possible. Define sizes and dimensions using relative units (preferably `em`). Exceptions exist for rare items that that shouldn't scale with zoom (e.g. decoration, minimum margins).[^16]
{{<codefigure>}} {{< codecaption lang="CSS" >}}
@ -535,7 +577,7 @@ Expect some readers to have images disabled or unloaded. Examples include:
* Blind readers.
* Users with metered connections: sometimes they disable all images, and other times they only disable images surpassing a size.[^15]
* Users with metered connections: sometimes they disable all images, and other times they only disable images surpassing a size.[^17]
* People experiencing packet loss who fail to download some images.
@ -545,13 +587,13 @@ Accordingly, follow good practices for alt-text:
* Concisely summarize the image content the best you can, without repeating the surrounding content.
* Images should usually have alt-text under 100 ch.[^16] Save longer descriptions for a caption or <code>aria-<wbr />describedby</code>. Exceptions exist; this is just a weak norm.
* Images should usually have alt-text under 100 ch.[^18] Save longer descriptions for a caption or <code>aria-<wbr />describedby</code>. Exceptions exist; this is just a weak norm.
* Don't include significant information that isn't present in the image; I'll cover how to handle supplementary information in the next subsections.
The <abbr title="Web Accessibility Initiative">WAI</abbr> provides some guidelines in <cite>[An `alt` Decision Tree](https://www.w3.org/WAI/tutorials/images/decision-tree/)</cite>. It's a little lacking in nuance, but makes for a good starting point. Remember that guidelines and "good practices" always have exceptions.
Alt text isn't just for blind readers; sighted readers who can't load an image will see alt-text in its place. This alt text might be confined to the image container, so small images should have shorter alt text.[^17]
Alt text isn't just for blind readers; sighted readers who can't load an image will see alt-text in its place. This alt text might be confined to the image container, so small images should have shorter alt text.[^19]
### Putting images in context
@ -581,7 +623,7 @@ Blockquote
: Captioned with a citation
Code snippet
: Captioned with its purpose or a link to a the larger file from which the snippet was borrowed
: Captioned with its purpose or a link to the larger file from which the snippet was borrowed
Mathematical notation
: Sometimes captioned with a brief explanation of its behavior, purpose, or significance. Remember to add alt-text.
@ -621,7 +663,7 @@ A <dfn>`longdesc`</dfn> attribute used to be another way to reference an image t
The recommended way to link to a transcript is by hyperlinking the image (i.e., wrapping it with `<a>`) or semantically grouping the image with its transcript. Put a short summary in the alt-text, and mention the availability of a transcript in a visible caption.
A [StackOverflow thread about comic transcripts](https://stackoverflow.com/questions/65564539/what-is-the-semantically-correct-way-to-include-transcript-from-a-comic) outlines a good approach to semantically grouping images and transcripts, and my approach is similar. I group an image, alt-text, and caption in a `<figure>` element and follow it with a transcript in a `<details>` element. I use <code>aria-<wbr />describedby</code> to semantically link the figure and the transcript.[^18]
A [StackOverflow thread about comic transcripts](https://stackoverflow.com/questions/65564539/what-is-the-semantically-correct-way-to-include-transcript-from-a-comic) outlines a good approach to semantically grouping images and transcripts, and my approach is similar. I group an image, alt-text, and caption in a `<figure>` element and follow it with a transcript in a `<details>` element. I use <code>aria-<wbr />describedby</code> to semantically link the figure and the transcript.[^20]
An image, alt-text, figure caption, and transcript combine to form a complex relationship that should be grouped together in a single landmark. I put all three inside a `<section>` with a heading, and give the group an `aria-label` that indicates the presence of the three sub-elements. Using a `section` landmark ensures that the figure and caption remain together as a single unit. The [html code](#xkcd-html) for the [xkcd comic earlier in the page](#infinite-scrolling) is a representative example.
@ -705,7 +747,7 @@ Even if you set custom colors, ensure that the page is compatible with color ove
[This page's canonical location](https://seirdy.one/posts/2020/11/23/website-best-practices/) is an example application of Technique C25 (and the related [Technique G148](https://www.w3.org/WAI/WCAG22/Techniques/general/G148)). It only uses non-default colors when a user agent requests a dark color scheme (using the `prefers-color-scheme` CSS media query; see the next subsection) and for lightening borders. Any image with a solid background may match the page background; to ensure that their dimensions are clear, I surrounded them with borders. Most browsers will render these borders with the default foreground color, which should be visually distinct from the background. I included borders and/or horizontal rules to break up some sections, since heading-based delineation is either unavailable or insufficient for them. When overriding color schemes, the page layout remains clear.
Color overrides go well beyond simple foreground and background color changes. Windows High Contrast Mode (<abbr title="Windows High Contrast Mode">WHCM</abbr>) is perhaps the best example. [WHCM makes advanced modifications to color palettes](#win-hcm): it colors elements with a user-specified palette, all according to semantic markup while ignoring ARIA overrides.[^19]
Color overrides go well beyond simple foreground and background color changes. Windows High Contrast Mode (<abbr title="Windows High Contrast Mode">WHCM</abbr>) is perhaps the best example. [WHCM makes advanced modifications to color palettes](#win-hcm): it colors elements with a user-specified palette, all according to semantic markup while ignoring ARIA overrides.[^21]
<abbr title="Windows High Contrast Mode">WHCM</abbr> leads the standardization process for the `forced-colors` CSS media feature, but it isn't the only implementation of the underlying idea. If you navigate to <samp>about:preferences</samp> in Firefox and activate the <samp translate="yes">Colors</samp> button in the "Language and Appearance" section, you'll be presented with the option to override website palettes with [your own default colors](#default-colors).
@ -761,7 +803,7 @@ This image is an approximation of what halation looks like, cropped from <a href
If you can't bear the thought of parting with your solid-black background, worry not: there exists a CSS media feature and client-hint for contrast preferences called `prefers-contrast`. It takes the parameters `no-preference`, `less`, and `more`. You can serve increased-contrast pages to those who request `more`, and vice versa. Check section 11.3 of the W3C {{<mention-work itemtype="TechArticle">}}{{<cited-work url="https://drafts.csswg.org/mediaqueries-5/#prefers-contrast" name="Media Queries Level 5" extraName="headline">}}{{</mention-work>}} specification for more information.
I personally like a foreground and background of `#E9E9E9` and `#191919`, respectively. These shades seem to be as far apart as possible without causing accessibility issues: `#191919` is barely bright enough to create a soft "glow" capable of minimizing halos among slightly astigmatic users, but won't ruin contrast on cheap displays. I also support a `prefers-contrast: less` media query which lightens the background to `#333`.[^20]
I personally like a foreground and background of `#E9E9E9` and `#191919`, respectively. These shades seem to be as far apart as possible without causing accessibility issues: `#191919` is barely bright enough to create a soft "glow" capable of minimizing halos among slightly astigmatic users, but won't ruin contrast on cheap displays. I also support a `prefers-contrast: less` media query which lightens the background to `#333`.[^22]
### Contrast is complex
@ -789,7 +831,7 @@ Accounting for halation, overstimulation, and high-contrast needs is hard to do
Color palettes need to be effective for different types of vision deficiencies (e.g. color blindnesses) and screens. Color blindness is a far more nuanced topic than "the inability to see some colors". {{<mention-work itemprop="citation" itemtype="BlogPosting">}}{{<indieweb-person itemprop="author" first-name="Rob" last-name="Pike" url="http://herpolhode.com/rob/">}} describes his experience in {{<cited-work name="Color blindness" extraName="headline" url="https://commandcenter.blogspot.com/2020/09/color-blindness-is-inaccurate-term.html">}}{{</mention-work>}}. Color blindness manifests in complex ways. Testing in grayscale is a great start, but it doesn't account for all kinds of color vision deficiencies.
Different screens and display-calibrations render color differently; what may look like a light-gray on a cheap monitor could look nearly black on a high-end OLED screen. Try to test on both high- and low-end displays, especially when designing a dark color scheme.[^21]
Different screens and display-calibrations render color differently; what may look like a light-gray on a cheap monitor could look nearly black on a high-end OLED screen. Try to test on both high- and low-end displays, especially when designing a dark color scheme.[^23]
Color schemes should also look good to users who apply gamma adjustments. Most operating systems and desktop environments bundle a feature to reduce the screen color temperature at night, while some individuals may select a higher one in the morning.
@ -807,11 +849,11 @@ Visible interactive semantics
{{< /quotecaption >}}
{{</quotation>}}
A basic WCAG Level A requirement is for information to not be conveyed solely through color. Bothe the presence and type of interactivity need to be visually communicated by other means. Links should not look like buttons, and vice-versa.
A basic WCAG Level A requirement is for information to not be conveyed solely through color. Both the presence and type of interactivity need to be visually communicated by other means. Links should not look like buttons, and vice-versa.
### In defense of link underlines
Some typographers insist that [underlined on-screen text is obsolete](https://practicaltypography.com/underlining.html),[^22] and that hyperlinks are no exception. I disagree.
Some typographers insist that [underlined on-screen text is obsolete](https://practicaltypography.com/underlining.html),[^24] and that hyperlinks are no exception. I disagree.
Readers already expect underlined text to signify a hyperlink. Don't break fundamental affordances for aesthetics. Underlines are also necessary to distinguish the beginnings and ends of multiple consecutive links, especially among color-blind users.
@ -894,9 +936,9 @@ Someone using the GitHub issues interface for the first time will struggle to id
### Against focusable containers
Screen readers like iOS VoiceOver[^23] fail to follow in-page links whose targets are not focusable. Designers often work around this by annotating link targets with the `tabindex="-1"` HTML attribute. This is a good idea when a link target is a heading or piece of [phrasing content](https://html.spec.whatwg.org/dev/dom.html#phrasing-content). Unfortunately, making large containers focusable ruins keyboard-navigability.
Screen readers like iOS VoiceOver[^25] fail to follow in-page links whose targets are not focusable. Designers often work around this by annotating link targets with the `tabindex="-1"` HTML attribute. This is a good idea when a link target is a heading or piece of [phrasing content](https://html.spec.whatwg.org/dev/dom.html#phrasing-content). Unfortunately, making large containers focusable ruins keyboard-navigability.
Normally, if you select some text in a page and press <kbd>Tab</kbd>, the tab-focusable element _after_ the selected text will receive focus. However, if the selectable text is inside a focusable container---even a container with a negative `tabindex`---pressing <kbd>Tab</kbd> will move focus to the _start_ of the container. If you're reading this on a desktop browser, you can experience this first-hand: select some text in this paragraph and press <kbd>Tab</kbd>. Then, do the same in [this snapshot of an excellent _Smashing Magazine_ article](https://web.archive.org/web/20220808163715/https://www.smashingmagazine.com/2022/06/voice-control-usability-considerations-partially-visually-hidden-link-names/).[^24]
Normally, if you select some text in a page and press <kbd>Tab</kbd>, the tab-focusable element _after_ the selected text will receive focus. However, if the selectable text is inside a focusable container---even a container with a negative `tabindex`---pressing <kbd>Tab</kbd> will move focus to the _start_ of the container. If you're reading this on a desktop browser, you can experience this first-hand: select some text in this paragraph and press <kbd>Tab</kbd>. Then, do the same in [this snapshot of an excellent _Smashing Magazine_ article](https://web.archive.org/web/20220808163715/https://www.smashingmagazine.com/2022/06/voice-control-usability-considerations-partially-visually-hidden-link-names/).[^26]
{{<image-figure id="focus-container">}} {{<picture name="focus-container" alt="Two screenshots of an article byline above a title. First, the title has selected text; then, the byline is focused.">}}
@ -928,7 +970,7 @@ Some image optimization tools I use:
: The reference WebP encoder; has dedicated lossless and lossy modes. Lossy WebP compression isn't always better than JPEG, but lossless WebP consistently beats PNG.
`avifenc`
: The reference AVIF encoder, included in [libavif](https://github.com/AOMediaCodec/libavif).[^25] AVIF lossless compression is typically useless, but its lossy compression is pretty unique in that it leans towards detail removal rather than introducing compression artifacts. Note that AVIF is not supported by Safari or most WebKit-based browsers. It also seems unsupported in Microsoft Edge.[^26]
: The reference AVIF encoder, included in [libavif](https://github.com/AOMediaCodec/libavif).[^27] AVIF lossless compression is typically useless, but its lossy compression is pretty unique in that it leans towards detail removal rather than introducing compression artifacts. Note that AVIF is not supported by Safari or most WebKit-based browsers. It also seems unsupported in Microsoft Edge.[^28]
I put together [a quick script](https://git.sr.ht/~seirdy/dotfiles/tree/3b722a843f3945a1bdf98672e09786f0213ec6f6/Executables/shell-scripts/bin/optimize-image) to losslessly optimize images using these programs. For lossy compression, I typically use [GNU Parallel](https://www.gnu.org/software/parallel/) to mass-generate images using different options before selecting the smallest image at the minimum acceptable quality. Users who'd rather avoid the command line while performing lossy compression can instead check out [Squoosh](https://squoosh.app/), a JavaScript app that bundles Web&shy;Assembly-compiled encoders; I've heard good things about it.
@ -989,6 +1031,12 @@ Rather than create separate lanes for different users, I prefer making the defau
It might seem odd to create a lossless WebP from a lossy PNG, but I've found that it's often the best way to get the smallest possible image at the minimum acceptable quality for screenshots containing solid backgrounds.
### Enforce image compression
[The "Document Policy" HTTP header](https://github.com/wicg/document-policy/blob/main/document-policy-explainer.md) has experimental directives `lossless-images-max-bpp` and `lossy-images-max-bpp`. In this context, "bpp" refers to "bytes per pixel"; a 256-by-256 pixel image that's 6.6 kilobytes large would have a "bpp" of 0.1. If it's a lossy image, then sending `lossy-images-max-bpp=0.1` would forbid the image from loading if it's over 6.6 kilobytes in size.
Currently, these directives are disabled-by-default in Chromium; you can enable them by navigating to `chrome://flags` and toggling "Experimental Web platform features". [I don't think these directives are perfect]({{<relref "/notes/document-policy-and-image-compression.md">}}), but they are quite useful.
### Dark image variants
Bright images on an otherwise dark page distract readers, especially readers like me with ADHD. The human iris adjusts to average amounts of light; an object far brighter than its surroundings causes eye strain even among readers with healthy vision.
@ -1015,7 +1063,7 @@ Light and dark variants of legacy formats (PNG, JPG, GIF), WebP, and AVIF can ca
### SVG images
I only recommend using SVG in images; avoid using them in embeds, objects, or directly in the body. Remember that users may save images, and open them in a non-browser image viewer with reduced SVG compatibility. To maintain maximum compatibility, stick a the subset of the [secure static processing mode](https://www.w3.org/TR/SVG/conform.html#secure-static-mode) of [SVG Static](https://www.w3.org/TR/SVG11/feature#SVG-static). Specifically, the subset that appears in the [SVG Tiny Portable<wbr />/Secure (<abbr title="Portable/Secure">PS</abbr>) spec](https://datatracker.ietf.org/doc/draft-svg-tiny-ps-abrotman/). SVG Tiny PS is a subset of [SVG Tiny&nbsp;1.2](https://www.w3.org/TR/SVGTiny12/intro.html), which is a supported export format in most vector drawing programs. Ignore the elements specifically required for SVG Tiny PS; your image can be a standard SVG that only utilizes a tiny subset of the full SVG spec.
I only recommend using SVG in images; avoid using them in embeds, objects, or directly in the body. Remember that users may save images, and open them in a non-browser image viewer with reduced SVG compatibility. To maintain maximum compatibility, stick the subset of the [secure static processing mode](https://www.w3.org/TR/SVG/conform.html#secure-static-mode) of [SVG Static](https://www.w3.org/TR/SVG11/feature#SVG-static). Specifically, the subset that appears in the [SVG Tiny Portable<wbr />/Secure (<abbr title="Portable/Secure">PS</abbr>) spec](https://datatracker.ietf.org/doc/draft-svg-tiny-ps-abrotman/). SVG Tiny PS is a subset of [SVG Tiny&nbsp;1.2](https://www.w3.org/TR/SVGTiny12/intro.html), which is a supported export format in most vector drawing programs. Ignore the elements specifically required for SVG Tiny PS; your image can be a standard SVG that only utilizes a tiny subset of the full SVG spec.
This advice might seem daunting, but its usually easy to use existing tools to generate an SVG Tiny file and manually edit it to support the SVG secure static mode. SVGs that conform to this subset should be compatible with Qt5's SVG implementation, librsvg (used by Wikipedia and GNOME), and most operating systems' icon renderers. Moreover, tools like [usvg](https://github.com/RazrFalcon/resvg/tree/master/usvg) can simplify complex SVGs to [a tiny subset of the SVG spec](https://github.com/RazrFalcon/resvg/blob/master/docs/usvg_spec.adoc).
@ -1046,7 +1094,7 @@ The first or second heading in the DOM, and the highest heading level, should be
Remember that not all landmarks are announced by screen readers; for instance, many screen readers don't announce the ending of a `<header>` element in an article. An `<hr>` element is a good way to force the ending of a landmark to be visible: it introduces a thematic break between sections that is visible to assistive technologies and user-agents that don't support CSS.
Consider adding a "skip link" if some pages require many <kbd>Tab</kbd> keystrokes to reach the main content.[^27] Visually-impaired users generally prefer navigating by headings or landmarks, but screen reader beginners and motor-impaired users still benefit from a skip link. Skip links are especially helpful when pure heading- and landmark-based navigation isn't optimal.
Consider adding a "skip link" if some pages require many <kbd>Tab</kbd> keystrokes to reach the main content.[^29] Visually-impaired users generally prefer navigating by headings or landmarks, but screen reader beginners and motor-impaired users still benefit from a skip link. Skip links are especially helpful when pure heading- and landmark-based navigation isn't optimal.
If your skip link toggles visibility states when focused, ensure that it doesn't move any existing content; see [the "Layout shifts" section](#layout-shifts) for more details. If it appears over existing content, it needs to have a solid background; if you set the background color, set a foreground color too as described in [the "About custom colors" section](#about-custom-colors).
@ -1056,7 +1104,7 @@ Users of <abbr title="assistive technology">AT</abbr>s such as screen readers pr
Try using a tool to view a list of all your link names. Just about every screen reader and some browser extensions should offer this functionality. Minimize links with ambiguous names, and ensure that identical link names have identical destinations.
Think twice before placing important content immediately after skippable content such as nested landmarks, long code snippets, figures, and large lists. AT users who wish to skip content may jump directly to the next heading, glossing over anything between the skippable content and subsequent heading; this is especially common on mobile devices.[^28] When it makes sense to do so, place skippable content in its own sections and/or at the end of its parent section.
Think twice before placing important content immediately after skippable content such as nested landmarks, long code snippets, figures, and large lists. AT users who wish to skip content may jump directly to the next heading, glossing over anything between the skippable content and subsequent heading; this is especially common on mobile devices.[^30] When it makes sense to do so, place skippable content in its own sections and/or at the end of its parent section.
### Single-column layout
@ -1145,7 +1193,7 @@ Users employing machine translation will not benefit from your soft hyphens, so
Where long inline `<code>` elements can trigger horizontal scrolling, consider a scrollable `<pre>` element instead. Making a single element horizontally scrollable is far better than making the entire page scrollable in two dimensions. Hard-wrap code blocks so that they won't horizontally scroll in most widescreen desktop browsers.
Be sure to test your hyphens with NVDA or Windows Narrator: these screen readers' pronunciation of words can be disrupted by poorly-placed hyphens. Balancing the need to adapt to narrow screens against the need to sound correctly to a screen reader is a complex matter.[^29] The best place to insert a hyphen is between compound words. For example, splitting "Firefighter" into "Fire-fighter" is quite safe. Beyond that, try listening to hyphenated words in NVDA to ensure they remain clear.
Be sure to test your hyphens with NVDA or Windows Narrator: these screen readers' pronunciation of words can be disrupted by poorly-placed hyphens. Balancing the need to adapt to narrow screens against the need to sound correctly to a screen reader is a complex matter.[^31] The best place to insert a hyphen is between compound words. For example, splitting "Firefighter" into "Fire-fighter" is quite safe. Beyond that, try listening to hyphenated words in NVDA to ensure they remain clear.
### Keeping text together
@ -1405,7 +1453,7 @@ On one hand, users who need enhanced focus visibility may override the default f
The WCAG [Success Criterion 2.4.12](https://w3c.github.io/wcag/guidelines/22/#focus-appearance-enhanced) recommends making focus indicators 2&nbsp;px thick. While this success criterion is only AAA-level, it's easy enough to meet and beneficial enough to others that we should all meet it.
You can use `:focus` and `:focus-visible` to highlight selected and keyboard-focused elements, respectively. Take care to only alter styling, not behavior: only keyboard-focusable elements should receive outlines. Modern browser stylesheets use `:focus-visible` instead of `:focus`; old browsers only support `:focus` and re-style a subset of focusable elements. Your stylesheets should do the same, to match browser behavior.[^30]
You can use `:focus` and `:focus-visible` to highlight selected and keyboard-focused elements, respectively. Take care to only alter styling, not behavior: only keyboard-focusable elements should receive outlines. Modern browser stylesheets use `:focus-visible` instead of `:focus`; old browsers only support `:focus` and re-style a subset of focusable elements. Your stylesheets should do the same, to match browser behavior.[^32]
{{<codefigure>}}
@ -1485,7 +1533,7 @@ Screen readers on touch screen devices are also quite different from their deskt
Screen reader implementations often skip punctuation marks like the exclamation point ("!"). Ensure that meaning doesn't rely too heavily on such punctuation.
Screen readers have varying levels of verbosity. The default verbosity level doesn't always convey inline emphasis, such as `<em>`, `<code>`, or `<strong>`. Ensure that your meaning carries through without these semantics.[^31]
Screen readers have varying levels of verbosity. The default verbosity level doesn't always convey inline emphasis, such as `<em>`, `<code>`, or `<strong>`. Ensure that your meaning carries through without these semantics.[^33]
Default verbosity does, however, convey symbols and emoji. Use symbols and emoji judiciously, since they can get pretty noisy if you aren't careful. Use `aria-labelledby` on symbols when appropriate; I used labels to mark my footnote backlinks, which would otherwise be read as <samp>right arrow curving left</samp>. If you have to use a symbol or emoji, first test how assistive technologies announce it; the emoji name may not communicate what you expect.
@ -1543,7 +1591,7 @@ No matter how simple a page is, I don't think simplicity eliminates the need for
Automated tests---especially accessibility tests---are a supplement to manual tests, not a replacement for them. Think of them as time-savers that bring up issues for further research, containing both false positives and false negatives.
These are the tools I use regularly. I've deliberately excluded tools that would be redundant.[^32]
These are the tools I use regularly. I've deliberately excluded tools that would be redundant.[^34]
[Nu HTML checker](https://validator.nu/)
@ -1556,27 +1604,33 @@ These are the tools I use regularly. I've deliberately excluded tools that would
: An auditing tool by Google that uses the DevTools protocol in any Chromium-based browser. Skip the "Access&shy;ibility" category, since it just runs a subset of axe-core's audits. The most useful audit is the tap target size check in its "SEO" category. It's also convenient for measuring performance with CPU throttling, to simulate low-end mobile devices. Note that your `sandbox` CSP directive will need to include `allow-scripts` for it to function.
[Webhint](https://webhint.io/)
: Similar to Lighthouse. Again, you can ignore the accessibility audits if you already use axe-core. I personally disagree with some of its hints: the "unneeded HTTP headers" hint ignores the fact that the CSP can have an effect on non-hypertext assets, the "HTTP cache" hint has an unreasonable bias against caching HTML, and the "Correct `Content-Type` header" recommends charset attributes a bit too agg&shy;ressively.[^33]
: Similar to Lighthouse. Again, you can ignore the accessibility audits if you already use axe-core. I personally disagree with some of its hints: the "unneeded HTTP headers" hint ignores the fact that the CSP can have an effect on non-hypertext assets, the "HTTP cache" hint has an unreasonable bias against caching HTML, and the "Correct `Content-Type` header" recommends charset attributes a bit too agg&shy;ressively.[^35]
[IBM Equal Access Accessibility Checker](https://www.ibm.com/able/toolkit/verify/automated/)
: Has a scope similar to axe-core. Its "Sensory Characteristics" audit seems unique.
[AInspector](https://ainspector.disability.illinois.edu/)
: a Firefox addon that displays audits in the sidebar. In my experience, it does have many false-positives (especially regarding DPUB-ARIA); however, it has caught a few issues missed by all other tools.
: A Firefox addon that displays audits in the sidebar. In my experience, it does have many false-positives (especially regarding DPUB-ARIA; see [issue 3 for the OpenA11y Evaluation Library](https://github.com/opena11y/evaluation-library/issues/3)); however, it has caught a few issues missed by all other tools.
[Firefox's Accessibility Inspector](https://firefox-source-docs.mozilla.org/devtools-user/accessibility_inspector/index.html#check-for-accessibility-issues)
: supports some very basic audits for contrast, keyboard access, and text labels. The accessibility tree is also a useful way to ensure that elements have accessible names. The keyboard audits have false-positives on non-interactive horizontally-scrollable elements, which need to be focusable.
: Supports some very basic audits for contrast, keyboard access, and text labels. The accessibility tree is also a useful way to ensure that elements have accessible names. The keyboard audits have false-positives on non-interactive elements with scrollable overflow, which need to be focusable.
[Chromium's CSS Overview](https://developer.chrome.com/docs/devtools/css-overview/)
: can show some basic accessibility violations, including contrast violations. I recommend enabling the APCA-based contrast algorithm in the DevTools experimental settings first. Note that this uses an earlier version of APCA and does not account for contrast that is too high.
: Can show some basic accessibility violations, including contrast violations. I recommend enabling the APCA-based contrast algorithm in the DevTools experimental settings first. Note that this uses an earlier version of APCA and does not account for contrast that is too high.
[testssl.sh (cli)](https://testssl.sh/) OR [SSL Labs' SSL Server Test (web, proprietary)](https://www.ssllabs.com/ssltest/)
: basically equivalent tools for auditing your TLS setup. I prefer testssl.sh.
: Basically equivalent tools for auditing your TLS setup. I prefer testssl.sh.
[CryptCheck](https://tls.imirhil.fr/)
: Unlike TLS 1.3, not all TLS 1.2 ciphers are secure. CryptCheck goes a bit further than testssl.sh and SSL Labs when it comes to evaluating TLS 1.2 cipher suites' security properties
[Webbkoll](https://webbkoll.dataskydd.net/)
: basic security checks, focusing on HTTP headers. I consider it a spiritual successor to Mozilla's HTTP Observatory.
: Basic security checks, focusing on HTTP headers. I consider it a spiritual successor to Mozilla's HTTP Observatory.
### Unorthodox tests {#unorthodox-tests}
[Check Your Website](https://check-your-website.server-daten.de/)
: Slower, more in-depth website checks with an emphasis on security. It covers name server configurations, DNSSEC, DANE, email DNS records, MTA-STS, well-known paths, redirects, certificate transparency, subresource integrity, caching, and well-known ports. If you find its reports too overwhelmingly detailed, [Hardenize](https://www.hardenize.com/) is an easier-to-understand option.
### Unorthodox tests
In addition to standard testing, I recommend testing with unorthodox setups that are unlikely to be found in the wild. If a website doesn't work well in one of these tests, there's a good chance that it uses an advanced Web feature that can serve as a point of failure in other cases. Simple sites should be able to look good in a variety of situations out of the box.
@ -1586,7 +1640,7 @@ These tests begin reasonably, but gradually grow absurd. Once again, use your ju
1. Test in all three major browser engines: Blink, Gecko, and WebKit.
2. Evaluate the heaviness and complexity of your scripts (if any) by testing with your browser's <abbr title="just-in-time">JIT</abbr> compilation disabled.[^34]
2. Evaluate the heaviness and complexity of your scripts (if any) by testing with your browser's <abbr title="just-in-time">JIT</abbr> compilation disabled.[^36]
3. Test using the Tor Browser's safest security level enabled (disables JS and other features).
@ -1614,7 +1668,7 @@ These tests begin reasonably, but gradually grow absurd. Once again, use your ju
15. Try printing out your page in black-and-white from an unorthodox graphical browser.
16. Download your webpage and test how multiple word processors render and generate PDFs from it.[^35]
16. Download your webpage and test how multiple word processors render and generate PDFs from it.[^37]
17. Combine conversion tools. Combine an HTML-<wbr />to-<wbr />EPUB converter and an EPUB-<wbr />to-<wbr />PDF converter, or stack multiple article-extraction utilities. Be creative and enjoy breaking your site. When something breaks, examine the breakage and see if it's caused by an issue in your markup, or a CSS feature with an equivalent alternative.
@ -1800,55 +1854,59 @@ A special thanks goes out to GothAlice for the questions she answered in <samp>#
[^11]: [See the "Lighthouse" entry in the "Automated tests" section](#lighthouse). Lighthouse benchmarks my machine with a score of around 1320; with that score, it recommends [throttling my machine by just under 3.1x](https://github.com/patrickhulce/lighthouse-cpu-throttling-calculator/blob/a9c67dc1c58c972a0673bef05756290105334af1/pages/index.js#L11) to simulate a target mobile device. The Chromium team came up with the throttling formula under idealized conditions (the phone isn't overheating, battery-saver mode is off, etc); I go further and throttle between 12x and 13x, still shooting for a perfect performance score. This article is the largest page on my site; it often gets a perfect performance score (100) with my stylesheet enabled, but never reaches 100 when I disable CSS containment or remove my stylesheet.
[^12]: Firefox users [can enable "find as you type"](https://website-archive.mozilla.org/www.mozilla.org/access/access/type-ahead/) by toggling a preference in <samp>about:<wbr />config</samp>. Chromium (and derivatives) users can [install an extension like Type-ahead-find](https://github.com/Foxy/chrome-type-ahead); note that it requires full-page access and performs script injection to work.
[^12]: KaiOS is the third most popular mobile OS, after Android and iOS; [it's the second-most popular mobile OS in India](https://economictimes.indiatimes.com/tech/software/after-mauling-telcos-mukesh-ambani-now-fires-at-operating-systems/articleshow/64958877.cms). Firefox updates are tied to operating-system updates in KaiOS. Since most manufacturers don't support KaiOS devices for long (they're budget feature phones for emerging markets), don't expect KaiOS users to mass-upgrade their ancient Firefox versions anytime soon. The last KaiOS Firefox update was from version 49 to 84, almost one year after Firefox 84 came out.
[^13]: Iterating through a list of font names to see if each one is available on a user's system is a slow but effective way to determine installed fonts without being granted permission to use the Font Access API. [Browser&shy;Leaks has a demo](https://browserleaks.com/fonts) of this approach. Warning: the page might hog your CPU for a while.
[^13]: The most recent such removal was the Document Outline algorithm. For years after it was standardized, [the Document Outline was ignored by every browser engine](https://www.tpgi.com/html5-document-outline/). Finally, [the WHATWG replaced the Document Outline algorithm](https://github.com/whatwg/html/pull/7829) with a revised version that involved multiple heading levels. The revised version matches what user-agents and good authors have been doing for decades.
[^14]: Decoration is more than cosmetic. The [color overrides and accessibility](#color-overrides-and-accessibility) sub-section describes how some decorations, like borders, improve access&shy;ibility.
[^14]: Firefox users [can enable "find as you type"](https://website-archive.mozilla.org/www.mozilla.org/access/access/type-ahead/) by toggling a preference in <samp>about:<wbr />config</samp>. Chromium (and derivatives) users can [install an extension like Type-ahead-find](https://github.com/Foxy/chrome-type-ahead); note that it requires full-page access and performs script injection to work.
[^15]: uBlock Origin is a popular browser extension for content filtering; it's the most popular Firefox add-on. It includes a built-in feature to block all media elements exceeding a user-configurable size threshold.
[^15]: Iterating through a list of font names to see if each one is available on a user's system is a slow but effective way to determine installed fonts without being granted permission to use the Font Access API. [Browser&shy;Leaks has a demo](https://browserleaks.com/fonts) of this approach. Warning: the page might hog your CPU for a while.
[^16]: [WebAIM](https://wave.webaim.org/api/docs?format=html) and the [University of Illinois](https://fae.disability.illinois.edu/rulesets/IMAGE_4_EN/) recommend 100 characters; [Tangaru](https://www.tanaguru.com/en/) recommends an even smaller limit of 80 characters. I sometimes exceed 100 characters for detailed images but usually stay below 80.
[^16]: Decoration is more than cosmetic. The [color overrides and accessibility](#color-overrides-and-accessibility) sub-section describes how some decorations, like borders, improve access&shy;ibility.
[^17]: Browser support for displaying alt-text in place of broken images seems good. More information about support for alt-text exposure can be found on {{<mention-work itemtype="BlogPosting">}}{{<cited-work name="Alternative Text for CSS Generated Content" extraName="headline" url="https://adrianroselli.com/2020/10/alternative-text-for-css-generated-content.html">}}{{</mention-work>}}. That post seems to indicate that Firefox 81 on macOS 10.15 didn't display alt-text, but [users report correct alt-text display](https://cybre.space/@nleigh/108688070682694730) in more recent Firefox versions.
[^17]: uBlock Origin is a popular browser extension for content filtering; it's the most popular Firefox add-on. It includes a built-in feature to block all media elements exceeding a user-configurable size threshold.
[^18]: Once it gains basic support across all browsers and screen readers, I might recommend using `aria-details` instead of <code>aria-<wbr />describedby</code> for more complex descriptions. At the time of writing, [`aria-details` is only supported by JAWS](https://a11ysupport.io/tech/aria/aria-details_attribute). {{<mention-work itemtype="TechArticle">}}{{<cited-work name="WAI-ARIA 1.2" url="https://www.w3.org/TR/wai-aria-1.2/#aria-details">}}{{</mention-work>}} describes `aria-details` with an example similar to the one I gave in [code snippet 5](#xkcd-html).
[^18]: [WebAIM](https://wave.webaim.org/api/docs?format=html) and the [University of Illinois](https://fae.disability.illinois.edu/rulesets/IMAGE_4_EN/) recommend 100 characters; [Tangaru](https://www.tanaguru.com/en/) recommends an even smaller limit of 80 characters. I sometimes exceed 100 characters for detailed images but usually stay below 80.
[^19]: Since <abbr title="Windows High Contrast Mode">WHCM</abbr> sets colors independently of explicitly-defined ARIA roles, it's a good way to test adherence to [the First Rule of ARIA](#first-rule-of-aria).
[^19]: Browser support for displaying alt-text in place of broken images seems good. More information about support for alt-text exposure can be found on {{<mention-work itemtype="BlogPosting">}}{{<cited-work name="Alternative Text for CSS Generated Content" extraName="headline" url="https://adrianroselli.com/2020/10/alternative-text-for-css-generated-content.html">}}{{</mention-work>}}. That post seems to indicate that Firefox 81 on macOS 10.15 didn't display alt-text, but [users report correct alt-text display](https://cybre.space/@nleigh/108688070682694730) in more recent Firefox versions.
[^20]: An earlier version of this article recommended a background of `#111`, but two helpful readers sensitive to overstimulation and halation found `#191919` preferable.
[^20]: Once it gains basic support across all browsers and screen readers, I might recommend using `aria-details` instead of <code>aria-<wbr />describedby</code> for more complex descriptions. At the time of writing, [`aria-details` is only supported by JAWS](https://a11ysupport.io/tech/aria/aria-details_attribute). {{<mention-work itemtype="TechArticle">}}{{<cited-work name="WAI-ARIA 1.2" url="https://www.w3.org/TR/wai-aria-1.2/#aria-details">}}{{</mention-work>}} describes `aria-details` with an example similar to the one I gave in [code snippet 5](#xkcd-html).
[^21]: When making an earlier version of this site's dark-mode color palette, I made the mistake of exclusively testing in cheap or poorly-calibrated displays with bright black points. I mistakenly thought that my `#0b0b0b` background was bright enough to [prevent halation](#halation). Only after testing on a better screen did I realize that it would look almost completely black; I subsequently lightened the background to `#111` to strike a good balance.
[^21]: Since <abbr title="Windows High Contrast Mode">WHCM</abbr> sets colors independently of explicitly-defined ARIA roles, it's a good way to test adherence to [the First Rule of ARIA](#first-rule-of-aria).
[^22]: {{<mention-work itemtype="Book">}}{{<cited-work name="Practical Typography" url="https://practicaltypography.com/">}}{{</mention-work>}} only renders invisible text without JavaScript. You can use a textual browser, screen reader, copy-paste the page contents elsewhere, use a reader-mode implementation, or "view source" to read it without enabling scripts. All of these options will ironically override the carefully-crafted typography of this website about typography.
[^22]: An earlier version of this article recommended a background of `#111`, but two helpful readers sensitive to overstimulation and halation found `#191919` preferable.
[^23]: When making an earlier version of this site's dark-mode color palette, I made the mistake of exclusively testing in cheap or poorly-calibrated displays with bright black points. I mistakenly thought that my `#0b0b0b` background was bright enough to [prevent halation](#halation). Only after testing on a better screen did I realize that it would look almost completely black; I subsequently lightened the background to `#111` to strike a good balance.
[^24]: {{<mention-work itemtype="Book">}}{{<cited-work name="Practical Typography" url="https://practicaltypography.com/">}}{{</mention-work>}} only renders invisible text without JavaScript. You can use a textual browser, screen reader, copy-paste the page contents elsewhere, use a reader-mode implementation, or "view source" to read it without enabling scripts. All of these options will ironically override the carefully-crafted typography of this website about typography.
I find <cite>Practical Typography</cite> quite useful for printed works, and incorporated a more moderate version of its advice on soft-hyphens into this page. With a few such exceptions, I generally find it to be poor advice for Web content.
[^23]: I can't confirm if this is also an issue on VoiceOver for macOS, because I haven't borrowed the hardware required to test it.
[^25]: I can't confirm if this is also an issue on VoiceOver for macOS, because I haven't borrowed the hardware required to test it.
[^24]: I linked to a snapshot from the Wayback Machine in hopes that the live version of the Smashing Magazine site will get fixed.
[^26]: I linked to a snapshot from the Wayback Machine in hopes that the live version of the Smashing Magazine site will get fixed.
[^25]: libavif links against libaom, librav1e, and/or libsvtav1 to perform AVIF encoding and decoding. libaom is best for this use-case, particularly since libaom can link against libjxl to use its Butteraugli distortion metric. This lets libaom optimize the perceptual quality of lossy encodes much more accurately.
[^27]: libavif links against libaom, librav1e, and/or libsvtav1 to perform AVIF encoding and decoding. libaom is best for this use-case, particularly since libaom can link against libjxl to use its Butteraugli distortion metric. This lets libaom optimize the perceptual quality of lossy encodes much more accurately.
[^26]: I find it quite odd that Microsoft Edge doesn't support AVIF. Chromium has supported AVIF for a long time, and Edge is based on Chromium. AVIF is a royalty-free format; I don't know why Microsoft would remove support for it, especially since Microsoft allows adding AVIF support to Windows. I think Edge also removed Chromium's experimental, off-by-default support for JPEG-XL.
[^28]: I find it quite odd that Microsoft Edge doesn't support AVIF. Chromium has supported AVIF for a long time, and Edge is based on Chromium. AVIF is a royalty-free format; I don't know why Microsoft would remove support for it, especially since Microsoft allows adding AVIF support to Windows. I think Edge also removed Chromium's experimental, off-by-default support for JPEG-XL.
[^27]: I say that a skip link is useful to reduce the amount of <kbd>Tab</kbd> keystrokes required, but I don't know a good "threshold number" to signify "too many keystrokes". If it takes ten keystrokes to reach the main content, it's probably time to add a skip-link.
[^29]: I say that a skip link is useful to reduce the amount of <kbd>Tab</kbd> keystrokes required, but I don't know a good "threshold number" to signify "too many keystrokes". If it takes ten keystrokes to reach the main content, it's probably time to add a skip-link.
[^28]: ATs typically let users navigate by headings, landmarks, paragraphs, and links. Most users prefer skipping article content with heading-based navigation. Keyboard users can bind different keys to different modes of navigation, but mobile users can only access one navigation mode at a time.
[^30]: ATs typically let users navigate by headings, landmarks, paragraphs, and links. Most users prefer skipping article content with heading-based navigation. Keyboard users can bind different keys to different modes of navigation, but mobile users can only access one navigation mode at a time.
Mobile users wishing to temporarily switch modes have to stop, change their navigation mode, perform a navigation gesture, and switch back. Mobile users trying to skim an article don't always find this worth the effort and sometimes stick to heading-based navigation even when a different mode would be optimal.
[^29]: At least, it will be until [NVDA bug 9343](https://github.com/nvaccess/nvda/issues/9343) gets resolved.
[^31]: At least, it will be until [NVDA bug 9343](https://github.com/nvaccess/nvda/issues/9343) gets resolved.
[^30]: If you'd like to learn more, {{<mention-work itemtype="BlogPosting">}}{{< cited-work name="A guide to designing accessible, WCAG-compliant focus indicators" url="https://www.sarasoueidan.com/blog/focus-indicators/" extraName="headline" >}} by {{< indieweb-person url="https://www.sarasoueidan.com/" first-name="Sara" last-name="Soueidan" url="https://www.sarasoueidan.com/" itemprop="author">}}{{</mention-work>}} has far more details on making accessible focus indicators.
[^32]: If you'd like to learn more, {{<mention-work itemtype="BlogPosting">}}{{< cited-work name="A guide to designing accessible, WCAG-compliant focus indicators" url="https://www.sarasoueidan.com/blog/focus-indicators/" extraName="headline" >}} by {{< indieweb-person url="https://www.sarasoueidan.com/" first-name="Sara" last-name="Soueidan" url="https://www.sarasoueidan.com/" itemprop="author">}}{{</mention-work>}} has far more details on making accessible focus indicators.
[^31]: Screen readers aren't alone here. Several programs strip inline formatting: certain feed readers, search result snippets, and textual browsers invoked with the `-dump` flag are some examples I use every day.
[^33]: Screen readers aren't alone here. Several programs strip inline formatting: certain feed readers, search result snippets, and textual browsers invoked with the `-dump` flag are some examples I use every day.
[^32]: I excluded PageSpeed Insights and GTMetrix since those are mostly covered by Lighthouse. I excluded Security Headers, since its approach seems to be recommending headers regardless of whether or not they are necessary. It penalizes forgoing the <code>Permissions-<wbr />Policy</code> header even if the CSP blocks script loading and execution; see [Security Headers issue #103](https://github.com/securityheaders/securityheaders-bugs/issues/103). I personally find the <code>Permissions-<wbr />Policy</code> header quite problematic, as I noted in August 2021 on [webappsec-permissions-policy issue #189](https://github.com/w3c/webappsec-permissions-policy/issues/189#issuecomment-904783021).
[^34]: I excluded PageSpeed Insights and GTMetrix since those are mostly covered by Lighthouse. I excluded Security Headers, since its approach seems to be recommending headers regardless of whether or not they are necessary. It penalizes forgoing the <code>Permissions-<wbr />Policy</code> header even if the CSP blocks script loading and execution; see [Security Headers issue #103](https://github.com/securityheaders/securityheaders-bugs/issues/103). I personally find the <code>Permissions-<wbr />Policy</code> header quite problematic, as I noted in August 2021 on [webappsec-permissions-policy issue #189](https://github.com/w3c/webappsec-permissions-policy/issues/189#issuecomment-904783021).
[^33]: My site caches HTML documents for ten minutes and caches the RSS feed for several hours. I disagree with webhint's recommendations against this: cache durations should be based on request rates and how often a resource is updated. I also disagree with some of its `content-type` recommendations: you don't need to declare UTF-8 charsets for SVG content-type headers if the SVG is ASCII-only and called from a UTF-8 HTML document. You gain nothing but header bloat by doing so.
[^35]: My site caches HTML and RSS feed for a few hours. I disagree with webhint's recommendations against this: cache durations should be based on request rates and how often a resource is updated. I also disagree with some of its `content-type` recommendations: you don't need to declare UTF-8 charsets for SVG content-type headers if the SVG is ASCII-only and called from a UTF-8 HTML document. You gain nothing but header bloat by doing so.
[^34]: Consider disabling the JIT for your normal browsing too; doing so removes whole classes of vulnerabilities. In Firefox, navigate to <samp>about:<wbr />config</samp> and toggle some flags under <code>javascript<wbr />.options</code>.
[^36]: Consider disabling the JIT for your normal browsing too; doing so removes whole classes of vulnerabilities. In Firefox, navigate to <samp>about:<wbr />config</samp> and toggle some flags under <code>javascript<wbr />.options</code>.
<figure itemprop="hasPart" itemscope="" itemtype="https://schema.org/SoftwareSourceCode">
<figcaption>
@ -1864,6 +1922,6 @@ A special thanks goes out to GothAlice for the questions she answered in <samp>#
In Chromium and derivatives, run the browser with `--js-flags='--jitless'`; in the Tor Browser, set the security level to "Safer".
[^35]: LibreOffice can also render HTML but has extremely limited support for CSS. OnlyOffice seems to work best, but doesn't load images. If your page is CSS-optional, it should look fine in both.
[^37]: LibreOffice can also render HTML but has extremely limited support for CSS. OnlyOffice seems to work best, but doesn't load images. If your page is CSS-optional, it should look fine in both.