DEV Community

OnePassApps
OnePassApps

Posted on

The Control Layer of app.onepassapps.com: 56 Pages, Zero Inputs in the Served HTML, and 33 Fallback Cards

I work on OnePassApps, the team behind app.onepassapps.com. This post is a census of our own served HTML: what the server sends before a single line of our app code runs, counted across all 56 URLs in our sitemap.

Everything below was measured on 9 November 2026 with plain HTTPS from one machine, outside the browser, signed out, no session, no cookies and no JavaScript executed. Facts file: content/form-layer-facts-2026-11-09.json. Tool: tools/form-layer-2026-11-09.py.

The headline: the served HTML contains no controls

Here is the census, per element, summed over all 56 documents.

<form>       0
<input>       0
<select>     0
<textarea>   0
<button>     0
<label>      0
<fieldset>   0
<legend>     0
<datalist>   0
<output>     0
<optgroup>   0
<meter>      0
<progress>   0
Enter fullscreen mode Exit fullscreen mode

Not one of the 56 pages carries a single one of them. There is no form to submit, no input to type into, no button to press and no label attached to anything, in the bytes the server sends. Every control you can click on this site is created by the bundle after it runs.

That is worth being precise about, because it is not the same as saying the site has no controls. It has plenty - it is nine apps with text boxes, dropdowns and buttons. The point is where they come from: the document that arrives carries the readable copy and the landmarks, and the controls arrive with the JavaScript.

What the served document does carry

The same census, counting the things that are there:

id="..."         561 across 56 pages
aria-labelledby  150 across 55 pages
aria-label       158 across 56 pages
tabindex          36 across 24 pages
Enter fullscreen mode Exit fullscreen mode

The aria-label attributes sit on nav elements (125) and on div elements (33). The tabindex attributes sit on main (23), on h2 (12) and on one p.

So the server does send the labelling machinery - and the screen-reader names - but it sends none of the things the names would point at, because the things themselves are not in the document yet. It is a document with all the signposts and none of the doors.

The one exception: 33 pages ship a fallback card

There is exactly one piece of markup that behaves like a control, and it is not a control. 33 of the 56 pages carry a <template> element, 858 bytes each, and the other 23 carry none. Those 23 are the guide pages; the 33 are the app and product pages.

The template holds the shell-failure card:

<div class="fallback fallback--shell" data-shell-fallback="" hidden>
  <section class="fallback__card" role="alert" aria-labelledby="shell-fallback-title">
    <img class="fallback__mark" src="/brand/sam-mark.svg" alt="" width="48" height="48" />
    <h1 class="fallback__title" id="shell-fallback-title">OnePassApps stopped working in this tab</h1>
    <p class="fallback__copy">Files and data saved on this device are unaffected. Reloading usually fixes it.</p>
Enter fullscreen mode Exit fullscreen mode

It is a heading, a paragraph and an image. It has a role="alert" on the card and an aria-labelledby pointing at the heading, so if it ever appears it is announced. And it says the useful thing: files and data saved on the device are unaffected.

It is inside a <template>, which means it is inert - the browser does not render it and does not expose it to the accessibility tree until it is cloned into the document.

The script that clones it

/shell-fallback.js is 4,104 bytes, served from the origin as a classic script before the module entry. Its own comment says what it is for, and the code underneath confirms it. The three lines that matter:

root.setAttribute("data-js", "");
root.setAttribute("data-shell-failed", "");
root.removeAttribute("data-shell-failed", "");
template.parentNode.insertBefore(template.content.cloneNode(true), template);
Enter fullscreen mode Exit fullscreen mode

The first line marks the document as scripted, which is what lets the stylesheet hide the static fallback from the first paint. The last two do the failure path: if the app bundle errors, or our own code throws before React replaces the startup shell, the script sets data-shell-failed on the root, hides #root, and clones the template's content into the document - so the card above is what a reader sees when the tab has failed.

Why this matters more than it sounds

Three consequences fall out of the census, and none of them are hypothetical:

A reader with JavaScript switched off gets the copy and nothing else. No input, no button. The page reads; it cannot be operated. That is a deliberate shape for an app that does its work in the browser tab - the work needs the tab.

The names arrive before the things they name. 150 aria-labelledby pointers and 158 aria-label attributes ship in the document, but the elements they describe ship later, with the bundle. Any tool that reads the served HTML alone sees a fully named document with nothing in it to name.

The failure path is pre-built and pre-styled. The fallback card is in 33 of the 56 documents, before anything has gone wrong, and the script that puts it on screen is 4,104 bytes of plain JavaScript served from our own origin. It does not depend on the bundle that just failed.

Limits of this measurement

One build, one day (9 November 2026), one network, plain HTTPS from one machine, signed out, one request per URL, no JavaScript executed - so this is what the server sends, not what a browser paints. The 56 URLs are the ones in our sitemap and are not an inventory of the site. The counts are of elements in the served HTML, so a control that only exists after the bundle runs is outside the census by construction. Byte lengths are the sizes as served on this request, not sizes on disk.

If you would rather see the surface than the plumbing, the app guides list what each of the nine apps actually does, and the home page is the one membership for all of them.

Top comments (0)