Repository navigation
countdown, content-toggle and off-canvas - #18
Merged
Merged
Conversation
Days, hours, minutes and seconds to `to` in the business's `timeZone` (announcement-bar's wall-clock parser, copied into its own `<cd-time>` block), or an `evergreen` run whose start instant is kept in localStorage, never a cookie, so a returning visitor sees the same deadline. Square, circle or plain; at zero a message, hide, or repeat to the next day's or week's occurrence across DST. role="img" named "Ends Oct 3, 5:00 PM CDT", digits aria-hidden and updated visually only. Without JavaScript the static "Ends <date>" line; the build-time digits stay hidden because they would be stale. No tick animation under reduced motion. scripts/check-time-and-toggles.mjs pins the time goldens (DST gap, half-hour zones, repeat next occurrence, evergreen start), the no-JS render and ARIA contract on the built pages, and runs mutation tests against its own rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Monthly or annual, homes or businesses: labels [a, b] on a <button role="switch"> named by label b (aria-checked = b shown), or two <button aria-pressed> in a named group when the labels are long. Panels in slots a and b, an optional badge that also describes the switch, the choice remembered for the session, and ?<param>=b (or the label) to preselect. Without JavaScript both panels render under their labels as headings and the switch is not shown; the script right after the element settles it before the first paint. The knob does not slide and the panel does not fade under reduced motion. notFor says why this is not tabs. check-time-and-toggles.mjs pins the no-JS render, the switch and pressed contracts, sessionStorage only, the fallback contrasts, and mutation-tests those rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A native <dialog> from left, right, top or bottom, opened by any data-offcanvas-open="<id>" trigger (aria-controls, aria-expanded, and aria-haspopup in modal mode). Modal by default: showModal(), backdrop, Escape, scroll lock that keeps the scrollbar width, a backdrop click that ignores a dragged selection. Push: show(), no backdrop, and --ofc-push / data-ofc-push published on <html> for the host to move its page. 44px close button, focus back to the opener, one panel open at a time, --ofc-z 110 under the consent bar. Without JavaScript the panel renders inline and visible where it sits, a link trigger to #<id> jumps to it, and the close button is not shown; the script right after the panel takes it off the page before the first paint. notFor sends main mobile navigation to mega-menu and says when to use which. check-time-and-toggles.mjs pins the no-JS render, the name, close button and trigger contract on the built page, the modal/push/lock/backdrop rules in the source, and mutation-tests them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
timer's implicit aria-live is "off", so it announces nothing as it ticks
without any aria-live attribute, and it is the role for this content. The
wrapper keeps the one absolute-end aria-label ("Ends Oct 3, 5:00 PM CDT").
role="img" turned real text into a picture; now the digits are reachable
for anyone who navigates into them.
Each unit reads as its number and its word: the zero-padded number is
aria-hidden and an unpadded copy is read instead ("3 days", not "zero
three"). The unit words use small capitals rather than text-transform,
which Chrome passes to screen readers as "DAYS".
The check now requires role="timer" and no aria-live attribute, readable
digits with an unpadded number and a unit word each; the mutants flip to
match (20 caught).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three elements from round 3, Chip B (time and toggles), one commit each. Each has its component in
src/library/<id>/, a demo, a catalogue entry and a check section in the newscripts/check-time-and-toggles.mjs, which is added to thenpm run checkchain.countdown
What it does. It shows days, hours, minutes and seconds to a moment. The moment is either
to, a wall-clock time in the business'stimeZone(required withto), orevergreen, a number of minutes that starts on each visitor's first view.key, never in a cookie. A returning visitor sees the same deadline, so the urgency stays true.square,circle(a ring arc per unit) andplain. Unit labels are overridable for other languages, along withends,endsIn,endedTextandlocale.onEndshowsendedText(the default), hides the countdown, or repeats. A repeat withtocounts to the same wall-clock time the next day or week (every), across DST. An evergreen repeat starts a fresh run.<anb-time>approach, copied into its own<cd-time>block. The element imports nothing.Accessibility contract. Nothing is announced as it ticks.
role="timer", whose implicitaria-liveisoff, and it carries noaria-liveattribute at all.aria-labelgives the absolute end, for example "Ends Oct 3, 5:00 PM CDT". Once ended, the label isendedText. The label changes only when the end itself does.aria-hidden, and screen readers get an unpadded copy, so it reads "3 days 4 hours".text-transform: uppercase, which Chrome would pass to screen readers as "DAYS".Without JavaScript. Visitors get the static "Ends Oct 3, 5:00 PM CDT" line in a
<time datetime>. An evergreen countdown reads "Ends in 30 minutes" instead.What the check pins.
<cd-time>source:everystaying passed;role="timer"with an "Ends …" label and noaria-liveattribute. Each unit has anaria-hiddenpadded number, a matching unpadded number and its unit word. The digits arehiddenwithout JavaScript. It also pins the end line with a parseabledatetime, the mount script straight after the element, and the runtime once per page.prefers-reduced-motion: no-preference, localStorage and neverdocument.cookie, and the square fallback pair at 14.75:1.>=in the repeat, weekly-as-daily, a floored smallest unit, a trusted future evergreen start,aria-live="polite", an explicitaria-live="off",role="img",aria-hiddendigits, a padded number read out, a missing unpadded number, a dropped unit word, visible digits without JavaScript, a missing label, a moved mount script, a doubled runtime, an ungated animation, a cookie, and a low-contrast fallback.Search. The page targets "countdown timer widget" (320/mo, difficulty 36). Its
alsoRanksare "countdown timer html" (320/53), "sales countdown timer" (40/44) and "evergreen countdown timer" (10/13). Two queries are left unclaimed: "countdown widget" (4,400/64) is out of reach, and "email countdown timer" (480/37) is a different product. All figures are SE Ranking US, 2026-09-26.content-toggle
What it does. It switches between two versions of one thing: monthly or annual prices, homes or businesses.
labels: [a, b]and two panels in slotsaandb.style="switch"(the default) puts the labels either side of a<button role="switch">.style="buttons"uses two<button aria-pressed>for long labels.default, and an optionalbadge("Save 20%") on one label viabadgeOn.rememberkeeps the choice in sessionStorage.paramlets?<param>=b, or the label text, preselect.content-toggleevent and setsdata-ct-state.Accessibility contract.
aria-checkedmeans "b is shown", so it reads as "Annual, switch, on". It carriesaria-controlsfor both panels, and the badge describes it.role="group".Without JavaScript. Both panels render, each under its label as a heading. The switch, which could not work, is not shown. With JavaScript, the script right after the element settles it before the first paint.
Not tabs. A toggle is two versions of one thing, switched in place. Three or more views, or different topics, are tabs. The catalogue's
notForsays so.What the check pins.
type="button"role="switch"whosearia-checkedmatchesdefaultand which is named by label b.aria-controlspointing at real panels, no tab roles, the mount script straight after, and the runtime once per page.Search. The page targets "toggle content" (110/mo, difficulty 27). Its
alsoRanksare "content switcher" (90/18), "css toggle switch" (170/16) and "html toggle switch" (90/9). "content toggle" and "pricing toggle" have no US volume, and "toggle switch" (22,400/64) is mostly hardware.off-canvas
What it does. It is a native
<dialog>that slides in fromside(left, right, top or bottom). Any element withdata-offcanvas-open="<id>"opens it.mode="modal"(the default) usesshowModal(): a backdrop, Escape to close, and a scroll lock that keeps the scrollbar width.mode="push"usesshow(): non-modal, with no backdrop and no lock. While open it publishes--ofc-push(the panel's size) anddata-ofc-push(its side) on<html>. The host moves its page withhtml[data-ofc-push="left"] .site { translate: var(--ofc-push) 0; }.--ofc-*, including--ofc-sizeand--ofc-z(110), which sits below the consent bar at 120. A modal panel lives in the top layer, like every modal dialog.Accessibility contract.
aria-controlsandaria-expanded, plusaria-haspopup="dialog"in modal mode. A trigger that is not a link or button getsrole="button"andtabindex="0", and opens on Enter or Space.closeLabel), a backdrop click, or Escape. A text selection dragged out of the panel does not close it. In push mode Escape works while focus is in the panel or on its trigger.Without JavaScript. The panel renders inline and visible where it sits, under its heading. A link trigger to
#<id>jumps to it, and the close button is not shown. The script right after the panel takes it off the page before the first paint.Off-canvas or mega-menu. mega-menu's own Menu drawer stays the site's main navigation on every screen size. Off-canvas is for what sits beside it: a cart, filters, an account menu. It is also for a secondary nav on a site whose main menu is plain links.
What the check pins.
open, is named by a heading with text, and has atype="button"close button with anaria-labelthat stayshiddenuntil the script runs.#<id>, both modes appear, and at least three sides are shown.showModal()plus the lock for modal mode, andshow()plus--ofc-pushfor push mode;--ofc-zunder 120, a 44px close button, and the panel pair at 14.75:1.Search. The page targets "off canvas" (140/mo, difficulty 35). Its
alsoRanksare "off canvas menu" (70/24), "slide out panel" (70/6) and "push menu" (110/13). "side drawer" (480/7) and "slide out drawer" (590/19) are furniture. "flyout menu" (210/14) is main navigation, which is mega-menu's ground.Verification
npm run checkandnpm run buildare green: 25 elements, 25 pages.ELEMENTS_SOURCE=<this checkout> npm run check:types). It reports 0 errors and 0 warnings. The only new hint is countdown's "Props declared but never used", the same one announcement-bar gets from its exported time function.ELEMENTS_SOURCE=… npm run build), itscheck:elementspasses, and CSP hashes are generated for the new inline scripts. On/elements/, where the website moves inline scripts to external files, all nine instances mount.?billing=annual,=aand nonsense; the pressed buttons; the event; reduced motion; no-JS.form method="dialog"; push publishing and clearing--ofc-push; one panel at a time; the scrollbar width kept (15px scrollbar, page unmoved); reduced motion; no-JS inline render and#idjump.licensenote. They use no Font Awesome icons; the close ×, like modal's, is a two-line SVG.Not in this PR: each element's section in the
superhero-ui-libraryskill (ecropolis/agent-skills), with its version bump. The README counts that as part of "finished".🤖 Generated with Claude Code