fix(a11y): label form controls, add a main landmark, fix light-mode contrast - #10
Merged
Conversation
…ontrast Lighthouse accessibility was 88 on the landing page and 96 on the paste page. SEO is already 100 since the sitemap work, so nothing was needed there. Form controls had no accessible name. The visible text lives in <div class="option-label">, while <label class="toggle"> wraps only the switch graphic, so screen readers announced eight controls as unlabelled checkboxes and selects. They now point at that visible text with aria-labelledby: one source of truth, and assistive tech reads the same words sighted users do. The password, confirm and webhook fields relied on placeholders, which are not accessible names and vanish on input; they get explicit aria-labels. Lighthouse never reached those three because they sit behind toggles. Added a <main> landmark. body is a flex column and .page-bg/.page-plain claim flex: 1 as its children, so main forwards both the growth and the axis. Layout is unchanged, verified against before/after screenshots of both page types. Light-mode contrast. The action chips on the paste page used raw Dracula hues, which are designed for dark backgrounds: copy at 1.04:1, raw 1.18:1, download 1.23:1 — invisible rather than merely low-contrast. The paste id stacked opacity 0.7 on an already-muted colour for 2.53:1. All four now resolve through semantic tokens that darken in light mode and fall through to the raw palette in dark mode, which is left byte-identical. Two more contrast bugs Lighthouse structurally cannot see, because both notices render conditionally: .notice-info and .owner-notice.just-created sat at ~1.3:1 in light mode. Fixed the same way. And the cause of one of them: var(--muted) was never defined. An undefined var() is invalid at computed-value time, so the declaration is dropped and the element inherits its parent's colour — which is why the "save this link" sub-text rendered in Dracula cyan, in both themes. It now uses var(--text-muted), and a test asserts no stylesheet declaration references an undefined custom property. landing page a11y 88 -> 100 seo 100 paste page a11y 96 -> 100
Both branches appended tests to test_api.py; kept both sets.
Merged
ClaraVnk
added a commit
that referenced
this pull request
Jul 20, 2026
Nothing has shipped since v1.5.0: production still serves that build, so the SEO, privacy, performance and accessibility work merged in #3 and #7-#10 is present on main and absent from ghostbit.dev. Pushes to main only publish the `edge` and `sha-*` image tags. The semver and `latest` tags — which is what a pinned deployment follows — are produced only by a v*.*.* git tag, and the last one predates all of it. Server changes in this release: - meta description, canonical link, /sitemap.xml, noindex on paste pages - star count fetched server-side; no third-party call from the browser and connect-src back to 'self' - landing page 928 KB -> 336 KB, LCP 6.0s -> 3.0s; paste page 515 KB -> 307 KB - form controls labelled, <main> landmark, light-mode contrast fixed - dead static assets removed, including a source map misnamed .sha256 Note the CLI has no changes since v1.5.0, yet this publishes ghostbit-cli 1.6.0 to PyPI with identical code: release.yml reads its version from cli/pyproject.toml, so a server-only release cannot be cut without it. Worth decoupling.
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.
Contexte
Lighthouse accessibility was 88 on the landing page and 96 on the paste page. SEO is already 100 since the sitemap work in #3, so the 92 in the original report is gone — nothing needed there.
Changements
Form controls had no accessible name. The visible text lives in
<div class="option-label">, while<label class="toggle">wraps only the switch graphic — so screen readers announced eight controls as unlabelled checkboxes and selects. They now reference that visible text witharia-labelledbyrather than duplicating it intoaria-label: one source of truth, and assistive tech reads the same words sighted users see.The password, confirm-password and webhook fields relied on placeholders, which are not accessible names and disappear on input. They get explicit
aria-labels. Lighthouse never flagged these three because they sit behind toggles and it only audits the default state.Added a
<main>landmark.bodyis a flex column and.page-bg/.page-plainclaimflex: 1as its children, somainforwards both the growth and the axis. Layout is unchanged — verified against before/after screenshots of both page types.Light-mode contrast. The paste page action chips used raw Dracula hues, which are built for dark backgrounds:
1.04:1 is not "low contrast", it is invisible text. The paste id compounded the problem by stacking
opacity: 0.7on an already-muted colour. All four now resolve through semantic tokens that darken under[data-theme="light"]and fall through to the raw palette in dark mode — dark theme computes byte-identical, verified in the browser.Two more contrast bugs Lighthouse structurally cannot see, because both notices render conditionally (owner-only, just-created):
.notice-infoand.owner-notice.just-createdsat at ~1.3:1 in light mode. Found by reading the CSS after noticing the "save this link" line looked washed out in a screenshot.var(--muted)was never defined. An undefinedvar()is invalid at computed-value time, so the declaration is dropped and the element inherits its parent's colour. That is why the "save this link" sub-text rendered in Dracula cyan — in both themes, not just light. It now usesvar(--text-muted).Tests
116 pass, including a new guard asserting no stylesheet declaration references an undefined custom property. Confirmed it catches the original bug:
Browser-verified: the owner notice was rendered by actually creating a paste — the state Lighthouse cannot reach — and its computed colours checked in both themes. Dark mode returns
rgb(80, 250, 123)for both the notice and the copy button, identical to before.Risques
The
<main>wrapper is the only structural change. It needed a CSS rule to forward the flex behaviour; without it the page would stop filling the viewport. Screenshots of both page types confirm no shift, but it is worth a look on a narrow viewport if you have one handy.The light-mode colour values are hand-picked to clear AA with margin (all ≥5.4:1) while keeping each action's hue identity. They are darker than a designer might choose — if any look muddy to you, they can move lighter as long as they stay above 4.5:1.