Repository navigation
Round 4: notice, toast and loading - #19
Merged
Merged
Conversation
…visit One file, src/library/notice/Notice.astro. kind info | success | warning | danger, each with its own ground, text and accent and a Font Awesome Free solid icon read at build (circle-info, circle-check, triangle-exclamation, circle-exclamation; `icon` overrides, false drops it). Optional title; text or a default slot. role="status" for info and success, role="alert" for warning and danger, and the kind is said in words too: a visually hidden "Warning:" (kindLabel) leads the message. dismissible adds a 44px close button named "Dismiss", remembered in sessionStorage as key = version (both default to a hash of the content, so new words show again); after a dismiss focus moves to the next focusable thing. The runtime is emitted once per page and only when a notice is dismissible; each dismissible notice is mounted by a one-line script straight after it, so one closed earlier in the visit never flashes. window.__superheroNotice.reset() is for reviewers. Without JavaScript the notice shows and the close button stays hidden. Fallback contrast, text / accent on ground: info 10.79 / 5.83, success 8.57 / 4.86, warning 9.82 / 4.95, danger 9.35 / 5.74. Search: "inline alert" (SE Ranking US 260/mo, difficulty 13). The pre-assigned "alert banner component" has no US volume; "alert banner" (210/12) goes in alsoRanks. scripts/check-feedback.mjs joins npm run check: roles per kind, the no-JS render, mount order and a single runtime on the demo page and the gallery index, contrast per kind from the CSS, the 44px close, and 10 planted faults it must catch. Catalogue license: MIT. Pattern from Rocketbelt (Pier 1 Imports, 2020, MIT); reimplemented, no code copied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One file, src/library/toast/Toast.astro. Put one <Toast /> on the page; it renders the
region (role="region", aria-live="polite", "Notifications", fixed at `position`), hidden
until its script mounts it. A second <Toast /> renders nothing and warns at build.
window.__superheroToast.show({ text, kind?, action?, timeout? }) returns { element,
dismiss(), open }; data-toast buttons do the same without a script of their own. Text is
set as text. kind info | success | warning | danger picks the Font Awesome Free icon
(read at build into <template>s) and a hidden word ("Error: "). action is a button
(onClick(event, handle)) or a link; an href with a scheme other than http(s), mailto or
tel is refused. Timers pause on hover, on focus and in a hidden tab and resume with what
they had left; a toast with an action never leaves by itself; 0 keeps it; under 4 s is
raised to 4 s. At most three: a fourth pushes the oldest out. Escape closes the focused
toast, and focus moves to the next toast or back to where it was. Under reduced motion
toasts appear and leave at once. A bottom corner rises above the consent bar.
Without JavaScript nothing renders, and the catalogue says so: use a notice for anything
that must exist without it.
One copy of the runtime: toastRuntime() between the <tst-runtime> markers is serialised
into the inline script, and check-feedback.mjs runs it on a DOM stand-in with a fake
clock (text, timeouts and the floor, pauses, actions, the cap, Escape and focus, refused
hrefs, data-toast, motion), pins the page (one hidden region, empty, templates
aria-hidden, the runtime once), measures the fallbacks (text 14.75, action 8.01, icons
7.74 to 10.23 to 1) and plants 15 faults it must catch.
Search: "toast notifications" (SE Ranking US 320/mo, difficulty 32). The pre-assigned
"toast notification html" has no US volume.
Catalogue license: MIT. Pattern from Rocketbelt (Pier 1 Imports, 2020, MIT);
reimplemented, no code copied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One file, src/library/loading/Loading.astro, three shapes: - spinner: an inline SVG ring, role="status" with a visually hidden name (`label`, default "Loading", or shown with showLabel), sizes sm / md / lg. - skeleton: lines, avatar or card placeholders drawn with CSS gradients and a shimmer. The shapes are aria-hidden; with `label` a hidden role="status" says it instead. - busy: a wrapper that, while it has data-busy, makes its content aria-busy="true" and inert, covers it with the ring (after 300 ms, so fast actions never flicker) and says `label` in a status line outside the busy content, because screen readers hold back changes inside an aria-busy region. Focus inside the content moves to the wrapper and back to the same control afterwards. slowText after slowAfter ms, doneLabel when done. Hosts toggle data-busy or call window.__superheroLoading.busy(el, on) (el, anything in it, or a selector). The runtime is emitted once per page, only with a busy wrapper. Reduced motion: the ring stops and fades gently, the skeleton is flat, the overlay does not fade. Without JavaScript a spinner and a skeleton render as given and a busy wrapper renders its content, usable: overlay, aria-busy and inert only come from the script. check-feedback.mjs pins the no-JS render of every shape on the demo page and the gallery index, runs the busy runtime (between the <ld-runtime> markers) on the DOM stand-in, now with a MutationObserver so the bare attribute path is tested, confines animation to the motion blocks (the reduce branch may only fade), keeps the overlay behind .ld--live, and measures the slow message (14.75:1); 15 planted faults for loading, 40 in all. Search: "css loading animation" (SE Ranking US 480/mo, difficulty 18). The pre-assigned "skeleton loader css" has no US volume. Catalogue license: MIT. Pattern from Rocketbelt (Pier 1 Imports, 2020, MIT); reimplemented, no code copied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--nt-* and data-nt are news-ticker's, already on main: a host theming one themed the other, and each element's script could find the other's root on a page that has both. Following the library's rule when two letters are taken (--anb-, --ofc-), notice is now ntc in its custom properties (including the private --_ntc-accent), classes, data attributes, the element expando and the demo, and check-feedback follows. 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.
Round 4, chip E (feedback): three elements, one commit each. The patterns come from Rocketbelt's messages, notices, notifications and busy indicators. The markup, CSS and script are new, and nothing named Rocketbelt appears in an id or class.
scripts/check-feedback.mjsjoins thenpm run checkchain and covers all three.notice
What it does. An inline message in four kinds:
info,success,warning,danger. Each kind has its own ground, text and accent colours and a Font Awesome Free solid icon, read at build. It takes an optionaltitle, andtextor a slot. Theiconprop overrides the icon, andfalseremoves it. Withdismissibleit gets a close button that stays closed for the rest of the visit.Accessibility. Info and success are
role="status"; warning and danger arerole="alert". The kind is also said in words: a visually hidden "Warning:" (kindLabel) comes before the message, and the icon isaria-hidden. The close button is a real 44px<button>named "Dismiss". The dismissal is stored in sessionStorage askey=version, and both default to a hash of the content, so new words show again. After a dismiss, focus moves to the next focusable element. Fallback contrast, text on ground and then accent on ground:Without JavaScript. The notice shows and cannot be closed. The close button stays
hidden. The runtime is emitted once per page, and only when a notice is dismissible. Each dismissible notice is mounted by a one-line script straight after it, so a notice closed earlier never flashes.window.__superheroNotice.reset()is there for reviewers.What the check pins.
hidden, the hidden kind word, and the iconaria-hidden.type="button"andhidden, a key/version config, the mount script straight after, and the runtime once per page.Search.
inline alert: 260/mo, difficulty 13 (SE Ranking US, 2026-09-26). It also ranks foralert banner(210/12),css alert box(260/38) andcallout box(330/17). The pre-assigned "alert banner component" has no US volume, and the banner across the top of a site isannouncement-bar.Theming prefix.
--ntc-*, with classes.ntcand.ntc__*and the attributesdata-ntcanddata-ntc-close. Commit e488031 renamed these from--nt-*anddata-nt, which news-ticker already owned on main, so theming leaked between the two elements. Toast uses--ts-*and.tst, and loading uses--ld-*and.ld. Neither is shared with any other element.Attribution.
license: MIT. Pattern from Rocketbelt (Pier 1 Imports, 2020, MIT); reimplemented, no code copied.toast
What it does. Put one
<Toast />on the page. It renders the region, fixed atposition(bottom-right by default). A second<Toast />renders nothing and warns at build.window.__superheroToast.show({ text, kind?, action?, timeout? })returns{ element, dismiss(), open }.data-toastbuttons do the same with no script of their own; the demo uses them.Accessibility.
role="region"witharia-live="polite", named "Notifications". Success, warning and danger toasts say their kind in words; icons arearia-hidden.actionnever leaves by itself.timeout: 0keeps a toast until it is closed. A timeout under 4 s is raised to 4 s.<body>.javascript:, is refused.Without JavaScript. Nothing renders. The region is
hiddenuntil its script mounts it, and a toast only exists because a script made one. The catalogue says so plainly and points tonoticefor anything that must exist without JavaScript.What the check pins.
hiddenand empty, witharia-hiddenicon templates, the runtime straight after it and emitted once.toastRuntime()between the<tst-runtime>markers is the same code serialised into the page. The check runs it on a DOM stand-in with a fake clock and covers:onClick, the link, and refused hrefsdata-toastbuttonsno-preference.Search.
toast notifications: 320/mo, difficulty 32. It also ranks fortoast notification(1,100/67),toast popup(210/33) andhtml toast(70/26). The pre-assigned "toast notification html" has no US volume.Attribution.
license: MIT. Pattern from Rocketbelt (Pier 1 Imports, 2020, MIT); reimplemented, no code copied.loading
What it does. One element with three shapes:
spinner: an inline SVG ring withrole="status"and a visually hiddenlabel(default "Loading").showLabelshows the label instead, andsizeis sm, md or lg.skeleton:lines,avatarorcardplaceholders drawn from CSS gradients, with a shimmer.busy: a wrapper for real content. While it hasdata-busy, its content isaria-busy="true"andinert, and the spinner covers it after 300 ms, so fast actions never flicker.slowTextappears afterslowAfterms, anddoneLabelis said when the wait ends. Hosts toggle the attribute or callwindow.__superheroLoading.busy(el, on), whereelis the wrapper, anything inside it, or a selector.Accessibility.
aria-busyregion, so a message inside it might never be read.aria-hidden. Withlabel, a visually hidden status speaks for the skeleton instead.Without JavaScript. A spinner and a skeleton render as given. A busy wrapper renders its content, usable. The overlay,
aria-busyandinertonly ever come from the script, which is emitted once per page and only when a busy wrapper is on it.What the check pins.
role="status"with anaria-hiddenring.aria-hiddenor carries a named status, and its shapes arearia-hidden.inertand noaria-busy, anaria-hiddenoverlay, and an empty status outside the content.MutationObserverso the bare-attribute path is tested too. It covers:aria-busyplaced on the content, not the wrapperinerton the contentdoneLabelfalsefor a target outside any wrapper.ld--live; the slow message at 14.75:1.Search.
css loading animation: 480/mo, difficulty 18. It also ranks forcss skeleton(390/27),skeleton ui(480/23) andloading spinner(720/33). The pre-assigned "skeleton loader css" has no US volume, and "skeleton loader" is 20/45.Attribution.
license: MIT. Pattern from Rocketbelt (Pier 1 Imports, 2020, MIT); reimplemented, no code copied.Verification
npm run checkpasses after each commit, andnpm run buildpasses.ELEMENTS_SOURCE=<this checkout> npm run check:types. Its build produces/elements/notice/,/elements/toast/and/elements/loading/, andcheck:elementspasses. Every executable inline script on those pages is covered by the CSP hashes.queryoralsoRanks, andcheck-catalogenforces that.Not in this PR
The three sections for the
superhero-ui-libraryskill in ecropolis/agent-skills, with the plugin version bump. The README counts that as part of "finished". Chips D, F and G bump the same plugin version, so the skill update is best done once, after the round merges.🤖 Generated with Claude Code