v4.0.0
Arrow keys did nothing. Enter did nothing. The panel had no role, the field never said it had
a popup, and results appearing in it announced nothing at all — a change a sighted reader
watches happen and a screen reader user is told nothing about, which is
WCAG 2.2 4.1.3 unmet. The
only way through a hit was the pointer.
The index behind it was fetched on every docs page whether or not anybody searched, and if
the file was not there the failure was swallowed: from then on every query answered
No results, which is a true sentence about a search that never happened.
The search index poops writes is every page's front matter copied out verbatim, and the
theme dropped a result's title, description and url straight into innerHTML. A <img src=x onerror=…> in one page's title: therefore ran on every page of the site, since the
search box is in the topbar.
The default footer also credited Poops and stopped there, so the layout, styles and script a
visitor is actually looking at had no name anywhere on the page. The 💩 trailed after the
sentence as a full stop of its own, and a screen reader read it out as "pile of poo".
Added
-
The search field is
<search-elemental>and<suggest-elemental>, which is the whole
keyboard the panel never had: ↓ and ↑ walk the results and wrap,
Home and End reach the ends once a row is under the cursor and stay
with the caret until then, Enter follows the row, Escape closes. The
cursor isaria-activedescendantrather than focus, so typing carries on while you look,
and the field now reports itself as a combobox with a popup instead of as a plain text box.
A settled search says5 results,No resultsorSearch failedin arole="status"
region — the announcement the panel filling itself never made.Two more things the reader gets: Escape empties the field rather than only
closing the panel, and focus leaving empties it too — this field is in the topbar of
every page, so a query left in it outlives the results it fetched, and on a phone a field
with something in it will not fold back into its icon.The DOM changes, and a site with its own rules for it needs them:
#search-resultsis gone.
The panel issuggest-elemental[open]holdingul > li > a[role="option"], the wrapper is
search-elemental.searchcarryingdata-state, and the row classes.sr-title/.sr-desc
are unchanged inside it..sr-emptystill exists but has moved out of the panel and is now
p.sr-note.sr-empty(see below). The panel's look is the element's optional theme with
--suggest-elemental-surface,--suggest-elemental-active,--suggest-elemental-radius,
--suggest-elemental-insetand--suggest-elemental-max-heightpointed at this theme's
tokens; anything that styled.search-resultsdirectly wants those instead.Measured against the same build before the change,
docs.min.jsgrows by 8.4KB and
docs.min.cssby 3.5KB — minified, before compression, and everything in this entry
included. -
The search field has a clear button a keyboard can reach.
<input type="search">gets a
cross of its own in Chromium and Safari, none in Firefox, and the one it gets is drawn by
the non-standard::-webkit-search-cancel-button— mouse-only, no tab stop, and absent from
the accessibility tree. It is switched off withappearance: noneand replaced by
button.search-clear: the octicon x at the magnifier's weight and distance from its end of
the field, a 24px target around a 16px glyph,aria-label="Clear the search", and a focus
ring, because a control you can tab to and cannot see you have reached is
2.4.7 Focus Visible unmet.
It isvisibility: hiddenwhile the field is empty, which takes it out of the tab order with
the look. Pressing it empties the field and hands the caret back.The field is 2rem of padding at both ends now, and the pending spinner moved one control
inward to--search-elemental-spinner-inset-inline: 2remso the two never share a corner. -
The empty and failed searches are drawn as well as spoken.
p.sr-note.sr-emptysays
No results andp.sr-note.sr-errorsays Search failed, in a box under the field,
shown bydata-statealone with no script involved. The words areempty-textand
error-texton the element, so what a reader sees and what the live region reads out is one
sentence rather than two that can drift.They sit outside the panel, which is the one place this theme departs from the element's own
advice: alistboxmay only ownoptions, so a "No results" row inside the panel is
aria-required-children— critical, andscript/a11yfails the build on it. Measured, not
assumed: the row was written that way first and the sweep caught it. -
/and ⌘K / Ctrl+K put the cursor in the search field. The field was reachable by
pointer or by tabbing the length of the topbar, and every docs site a reader arrives from
answers to one of these. The slash is ignored while an<input>,<textarea>,<select>
or acontenteditableholds focus — it is a character somebody may be mid-word in — and the
modifier pair works from inside a field, where it selects what is already typed. Shift is
where it stops —Ctrl+Shift+Kopens the web console and stays the browser's. What the two
do cost is Firefox's quick-find and itsCtrl+Ksearch bar, and Chrome'sCtrl+K
address-bar search, on a docs page. Nothing was added to the markup: no hint sits beside the
field. -
A grouped section in the mock site, and poops 2.3 to build it with.
Kitchen sinkand
Live samplesnow carrynavGroup: Samples, poops 2.3's front-matter grouping — the pages
stay where they are, urls and breadcrumbs unchanged, and only the sidebar gains the heading.
Filler with a job: a group is a section node with no page of its own, so it is the first
thing in the mock to render thenavtree.htmlbranch that omits the Overview link —
every other section there has an index page. The sweep and the unit suite cover it now
rather than by claim.poopsmoved to^2.3.0as a devDependency only; the peer range
stays>=2.0.0, because the theme reads the tree as data and an older poops just leaves the
field unread.
Changed
-
search-index.jsonis fetched on the first query, not on page load. The field is in the
topbar of every docs page and most visits never type in it, so the request went out for
everybody and paid off for a few. The first search of a visit now waits for the network and
gets a spinner while it does; every one after it answers from an index already in memory. -
The search field is 2.2rem tall, set rather than left to fall out of its padding and the
16px iOS floor, which came to 2.6rem — the tallest thing in a bar standing next to 2.25rem
icon buttons. -
book-of-elementalsmoves to^0.7.0from^0.5.0, which is where the two search
elements arrive. Nothing this theme already used changed shape: the hover tint the 0.6
release evened out across the book is re-pointed at--bg-althere and always was. -
The default footer names poops-docs-theme beside Poops, and the 💩 moved in front of
the Poops link witharia-hidden="true"on it, so it reads as decoration rather than as a
word. Both layouts change; a site settingsite.footerrenders its own html and is
untouched.
Fixed
-
A missing search index says so instead of reporting no results.
fetchresolves on a
404, and the load was wrapped in acatchthat threw the error away — so a site that never
generatedsearch-index.json, or one whose file was briefly unreachable, had a search box
that answered every query with No results for the rest of the visit. The response is
checked, the failure reaches the element as a rejection, and the field says Search failed
in the box and in the live region. The failed load is dropped rather than remembered, so the
next keystroke tries again. -
The mobile drawer no longer slides itself shut on page load. Closed is the state the
drawer arrives in —<disclosure-elemental>writes it at upgrade — but the transform
transition was live from the first frame, so on a load where the script landed after the
first paint (a cold cache, a slow phone) the browser animated the difference between the
rail the stylesheet had already drawn and the closed drawer the script asked for. The
transition rules now key off asidebar-nav-readyclass thatdocs.jsputs on
#sidebar-navwhen the toggle is first tapped, so a drawer nobody has touched cannot
travel. That first tap still slides — the class goes on before the element writes the state,
with a reflow between them. Counting animation frames instead was tried and measured: a
closed panel painted for two frames still slid in from nothing when the rule arrived.
Crossing the breakpoint before any tap now snaps instead of sliding, which is what the
element already does with the state itself. A site overriding
#sidebar-nav { transition: … }needs the class in its selector now. -
Escape no longer closes the sidebar rail on a wide screen. Escape and the scrim are the
light dismiss a drawer over a scrim wants and the disclosure pattern does not owe it — but
the keyboard is on the page at every width, and<disclosure-elemental>writesopenfrom
itsmediaquery only when the query changes. A query that still matches changes nothing,
so Escape above 60rem closed the rail and nothing put it back: the navigation was gone for
the rest of the visit, with the toggle that would reopen itdisplay: noneat that width.
The close now returns early while the element reportsdata-mode="pinned", which covers the
scrim as well as the key.
Security
- Search results are built as nodes, so front matter cannot become markup. The row is now
the one place the index is crossed from data into DOM:titleanddescriptiongo in as
textContent, and the url is resolved against the page and dropped unless its scheme is
http:orhttps:—hreftakes ajavascript:url as readily as a path. An entry that
fails that check is left out of the list rather than rendered as a dead link..sr-titleand
.sr-descstill name the two pieces of a row, though the box around them moved with the
panel — see the search entry under Added. Authoring markup in atitle:and expecting it
to render never worked in the sidebar or the<title>either; it now does not work here.
Full Changelog: v3.1.1...v4.0.0