Skip to content

Re-implement accessibility (at_rule colors, reduce-motion, prefers-contrast, forced-colors) - #136

Open
evnchn wants to merge 4 commits into
mainfrom
accessibility-accent-reimpl-2026-04-15
Open

Re-implement accessibility (at_rule colors, reduce-motion, prefers-contrast, forced-colors)#136
evnchn wants to merge 4 commits into
mainfrom
accessibility-accent-reimpl-2026-04-15

Conversation

@evnchn

@evnchn evnchn commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Re-implementation of what fork PR #133 was trying to do. After upstream zauberzeug#5910 redesigned the website, #133 is no longer rebaseable.

Status: Ready for review

Core (commit 30859c6)

  • nicegui/elements/colors.{js,py}at_rule parameter
  • nicegui/elements/sub_pages.{js,py} — reduce-motion (drop smooth scroll)
  • nicegui/static/nicegui.css — prefers-reduced-motion media query
  • tests/test_colors.py — test_at_rule
  • main.py — prefers-contrast color binding

Website wiring (commit 102e4e1)

  • website/components/hero_section.pyforced-colors:invert on mascot SVG
  • website/documentation/windows.pyforced-colors:outline on code/browser windows; forced-colors:opacity-100 on copy button
  • website/header.py — Tailwind arbitrary media variant media-[(prefers-reduced-transparency:reduce)]:[&.fade]:backdrop-blur-none to drop blur for users preferring reduced transparency

Dropped (no longer applicable after zauberzeug#5910 redesign)

  • ~website/components/footer_section.py — old svg.face/svg.discord/reddit/github replaced by Phosphor icons (text-based, inherit currentColor, which already respects forced-colors automatically)
  • star badge forced-colors:invertwebsite/star.py was deleted; replaced by Phosphor-icon GitHub badge in new header.py (no SVG to invert)
  • .bg-primary-alpha / .hover-opacity ports in examples_section.py, demo.py, overview.py — those CSS classes are gone from the redesign; the few remaining opacity-*/hover:opacity-* usages use Tailwind utilities directly
  • prefers-reduced-motion guards on scroll/reveal — already handled globally in website/static/style.css by the redesign

Closes #133 if accepted.

@evnchn
evnchn marked this pull request as ready for review April 15, 2026 07:45
@evnchn evnchn changed the title Re-implement accessibility (WIP: core ported, website wiring pending) Re-implement accessibility (at_rule colors, reduce-motion, prefers-contrast, forced-colors) Apr 15, 2026
evnchn added 2 commits April 15, 2026 16:16
…ed-motion

- nicegui/elements/colors.{js,py}: at_rule parameter to scope colors by media query
- nicegui/static/nicegui.css: prefers-reduced-motion kill-switch for transitions/animations/smooth-scroll
- tests/test_colors.py: test_at_rule
- main.py: ui.colors(primary='#317ABE', at_rule='@media (prefers-contrast: more)')

Previously 30859c6 inadvertently reverted recent upstream CSS and main.py changes
by wholesale-copying from a stale branch; this rewrites that commit cleanly.
- website/components/hero_section.py: forced-colors:invert on mascot SVG
- website/documentation/windows.py: forced-colors outline/opacity on code & browser windows
- website/header.py: Tailwind variant for prefers-reduced-transparency on .q-header.fade

Social icons (Phosphor/currentColor), logo wordmark (markdown), and star badge
targets dropped — redesign removed them.
@evnchn

evnchn commented Apr 15, 2026

Copy link
Copy Markdown
Owner Author

Force-pushed: rewrote history to fix regressions introduced by the re-impl sub-agent.

Previous version of commit 30859c6 wholesale-copied main.py, nicegui/static/nicegui.css, and nicegui/elements/sub_pages.{js,py} from the stale accessibility-accent branch, reverting:

  • Recent main.py refactor (design as d, footer_section, IntersectionObserver, ui.status_code) — restored
  • Recent nicegui.css additions (.q-parallax__media, .nicegui-log background, .nicegui-code-copy, etc.) — restored
  • Upstream fix Fix Cmd/Ctrl-click on sub-page links not opening new tab zauberzeug/nicegui#5928 (Cmd/Ctrl-click on sub-page links opens new tab) — restored by dropping our sub_pages changes entirely; the CSS @media (prefers-reduced-motion) block is the real protection
  • helpers.should_await — restored (was wrongly replaced with asyncio.iscoroutine)

Now a clean +63/-14 delta in 8 files across 2 commits.

@evnchn
evnchn force-pushed the accessibility-accent-reimpl-2026-04-15 branch from 102e4e1 to d005e7a Compare April 15, 2026 08:16
@evnchn
evnchn requested a review from Copilot April 15, 2026 08:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Re-implements a set of accessibility-focused styling improvements across NiceGUI core theming and the documentation website, adding support for conditional theming via CSS at-rules and improving behavior under reduced-motion / forced-colors / reduced-transparency user preferences.

Changes:

  • Add at_rule support to ui.colors() to scope theme variables (and custom colors) under a CSS at-rule (e.g. @media (...)).
  • Add global prefers-reduced-motion CSS to disable transitions/animations/scroll-behavior when requested.
  • Wire forced-colors and reduced-transparency adjustments into website components and docs UI.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
nicegui/elements/colors.py Adds at_rule parameter and forwards it as a component prop.
nicegui/elements/colors.js Implements at-rule–scoped CSS injection for theme variables and custom colors.
nicegui/static/nicegui.css Adds @media (prefers-reduced-motion: reduce) overrides.
tests/test_colors.py Adds a basic test covering the new at_rule code path.
main.py Uses at_rule to bind a higher-contrast primary color under prefers-contrast: more.
website/components/hero_section.py Adds forced-colors behavior for the mascot SVG.
website/documentation/windows.py Improves forced-colors visibility for windows and copy button.
website/header.py Drops header blur when prefers-reduced-transparency: reduce is set.

Comment on lines +30 to +39
for (const [color, value] of Object.entries(this.customColors || {})) {
const name = color.replaceAll("_", "-");
const varName = "--q-" + name;
css += `\n body { ${varName}: ${value} !important; }`;
css += `\n .text-${name} { color: var(${varName}) !important; }`;
css += `\n .bg-${name} { background-color: var(${varName}) !important; }`;
}
this.styleEl = document.createElement("style");
this.styleEl.innerHTML = `${this.atRule} {\n${css}\n}`;
document.head.appendChild(this.styleEl);

Copilot AI Apr 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When atRule is set, this code injects a <style> block for custom colors but does not remove any previously injected [data-nicegui-custom-colors] style blocks (created by applyColors). If a page (or earlier render) already called ui.colors(...) without at_rule, the old .text-*/.bg-* rules can remain globally active and defeat the intent of scoping custom colors to the at-rule. Consider removing/replacing the existing NiceGUI custom-colors style blocks (and, if necessary, previously set custom --q-* inline variables) before appending the at-rule style element.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged as a known design edge case. Each ui.colors(...) call mounts a separate Vue element; the at-rule branch injects a scoped <style> element which is cleaned up on unmounted. If a prior ui.colors() (no at_rule) ran applyColors globally within the same page render, those rules do persist — but that's the same behavior as chained ui.colors() calls today, and the at_rule's !important inside the scoped block still wins within the at-rule scope. Cleaning up prior global custom colors is a broader lifecycle change; leaving out of scope for this PR.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 5f2abd3. Followed the existing NiceGUI-removal-before-readd pattern from nicegui/static/nicegui.js (the applyColors function, which removes any existing [data-nicegui-custom-colors] style blocks before appending a new one). The at_rule branch now clears both [data-nicegui-custom-colors] and [data-nicegui-scoped-colors] style tags plus any --q-* inline properties set on document.body by prior plain ui.colors(...) calls, then tags its own scoped <style> with data-nicegui-scoped-colors for symmetrical cleanup by future calls. Added test_at_rule_supersedes_plain_colors covering the scenario.

Comment on lines +16 to +36
const colors = {
"--q-primary": this.primary,
"--q-secondary": this.secondary,
"--q-accent": this.accent,
"--q-dark": this.dark,
"--q-dark-page": this.darkPage,
"--q-positive": this.positive,
"--q-negative": this.negative,
"--q-info": this.info,
"--q-warning": this.warning,
};
let css = Object.entries(colors)
.map(([k, v]) => ` body { ${k}: ${v} !important; }`)
.join("\n");
for (const [color, value] of Object.entries(this.customColors || {})) {
const name = color.replaceAll("_", "-");
const varName = "--q-" + name;
css += `\n body { ${varName}: ${value} !important; }`;
css += `\n .text-${name} { color: var(${varName}) !important; }`;
css += `\n .bg-${name} { background-color: var(${varName}) !important; }`;
}

Copilot AI Apr 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at_rule is currently only covered by a test that verifies the media query branch can set the primary color, but it doesn't exercise custom colors inside an at-rule (i.e., scoped .text-<custom> / .bg-<custom> rules) or verify that custom color styles don’t leak outside the at-rule when previous ui.colors calls have injected global custom-color CSS. Adding tests for these behaviors would help prevent regressions in the new styling path.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. The existing test_at_rule covers the core at-rule branch (scoped --q-primary override applied to a button). Expanding coverage to custom-color scoping + leak-prevention is reasonable but, given Falko's limited review bandwidth, keeping the test surface minimal for this PR. Happy to add more in a follow-up if desired.

Comment on lines +345 to +351
@media (prefers-reduced-motion: reduce) {
html,
body * {
transition: none !important;
animation: none !important;
scroll-behavior: auto !important;
}

Copilot AI Apr 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reduced-motion rule targets html, body *, which excludes the body element itself and also does not cover pseudo-elements (e.g. ::before/::after) that often carry animations/transitions. To fully respect prefers-reduced-motion, consider expanding the selector to include body and pseudo-elements (commonly *, *::before, *::after scoped as needed).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 4b27a5c. Switched the selector to *, *::before, *::after so the body element and pseudo-element animations are also covered.

evnchn added 2 commits April 15, 2026 20:24
Previously the rule targeted 'html, body *' which excludes the body
element itself and pseudo-elements. Switch to '*, *::before, *::after'
so animations/transitions on body and generated content also honor
prefers-reduced-motion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants