Skip to content

Scope touch-target floor to class-less controls; add .sf-touch-target - #648

Merged
jackgranatowski merged 4 commits into
mainfrom
claude/hamburger-menu-css-conflicts-rwi9pe
Jul 22, 2026
Merged

Scope touch-target floor to class-less controls; add .sf-touch-target#648
jackgranatowski merged 4 commits into
mainfrom
claude/hamburger-menu-css-conflicts-rwi9pe

Conversation

@jackgranatowski

@jackgranatowski jackgranatowski commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Refactors the WCAG 2.5.5 44px touch-target minimum in core/accessibility.css to scope it only to genuinely bare, un-classed controls (button:not([class]), input[type="…"]:not([class]), etc.). Any control that carries a class is now treated as owned — by the framework, the author, or a third-party widget — and keeps whatever size its owner provides.

This fixes a long-standing issue where the blanket floor reached into third-party markup (e.g. page-builder hamburger toggles, plugin controls) and stretched narrow custom controls to 44px on both axes at a specificity higher than a single class, making them impossible to resize without !important.

Introduces .sf-touch-target — a new explicit opt-in class for controls you own that need the 44px hit area. Unlike the automatic floor, it is not gated to pointer: coarse, so it enforces the minimum everywhere, and it uses flexbox centering to keep short labels centred inside the enlarged box.

Type

  • feat
  • fix
  • docs
  • chore / tooling

Checklist

  • Conventional Commit messages (feat:, fix:, docs:, …) — enforced by commitlint
  • npm run lint:css passes (stylelint)
  • npm run build rebuilds dist/ (bundles are git-ignored; CI rebuilds and stamps headers)
  • npm test passes (unit + Playwright e2e)
  • Version references in sync if any version-related file changed (npm run check:version)
  • LLM guide reviewed/updated if core/*.css, optional/*.css, or token-registry.json changed (npm run check:llm-guide)
  • Generated artifacts regenerated, not hand-edited (npm run check:macros, check:registry, audit:check)
  • CHANGELOG.md updated under ## [Unreleased] (for user-facing changes)
  • Breaking changes include migration docs

Notes

Breaking change: Controls that carry any class no longer receive the automatic 44px floor on touch. This is intentional — the floor now only applies to bare, un-classed markup (the author's own quick <button>text</button>). Controls with classes are considered owned and keep their owner's sizing.

Migration path:

  • Bare <button> / native controls: unaffected, still get the 44px floor on touch
  • Classed controls that need the 44px hit area: add .sf-touch-target (or set min-block-size/min-inline-size yourself)
  • Classed controls that were being stretched and need to opt out: already fixed — just give them any class (no !important needed)

Tests added:

  • button-touch.spec.js: New regression guard verifying that a classed button (e.g. class="bricks-mobile-menu-toggle") is exempt from the floor on both axes
  • a11y-patterns.spec.js: Two new tests for .sf-touch-target — one verifying it enforces ≥44px on both axes on a classed control, one verifying it is overridable by a plain class rule without !important

Documentation:

  • docs/migration.md: Comprehensive breaking-change section explaining the rationale, the new .sf-touch-target class, and what changed for users
  • core/accessibility.css: Expanded comments explaining the scoping logic and the new opt-in class
  • tests/button-touch.spec.js: Updated test comments to reflect the new :not([class]) selector

Generated artifacts: docs/api-index.json, docs/api-index.md, docs/classes.md, docs/registry.json, configurator/src/data/classes.generated.json — all regenerated to include the new .sf-touch-target class (1 new PUBLIC class in

https://claude.ai/code/session_01GgL6mqSxe2ihjaUCxx78eR

Summary by CodeRabbit

  • New Features

    • Added the .sf-touch-target utility to provide WCAG-compliant 44px minimum touch areas for interactive controls.
    • Classless native controls automatically receive minimum touch-target sizing on coarse-pointer devices.
  • Bug Fixes

    • Controls with custom classes retain their existing sizing instead of being unexpectedly expanded.
  • Documentation

    • Updated accessibility guidance, migration notes, API references, and examples to explain the new behavior and opt-in utility.

claude added 2 commits July 21, 2026 19:13
…rols

The @media (pointer: coarse) minimum-touch-target floor in
core/accessibility.css targeted every bare <button> and native control
(only .sf-btn was carved out). It set both min-block-size and
min-inline-size to var(--sf-touch-target) at a specificity higher than a
single class, so it reached into markup the framework does not own —
most visibly third-party page-builder and plugin controls. A narrow
custom control such as a hamburger toggle
(<button class="...-menu-toggle">) was stretched to 44px on both axes and
could not be resized without !important.

Scope the floor to class-less controls only (button:not([class]), the
native inputs, select, summary, checkbox, radio). Any control carrying a
class is treated as owned — by SLASHED (.sf-btn), the author, or a
third-party widget — and keeps its owner's sizing. Genuinely bare,
un-classed controls keep the WCAG 2.5.5 44px floor on both axes. This
generalises the earlier .sf-btn carve-out into one rule: the framework
never re-sizes a control someone else has styled. Matches the existing
`a:not([class])` idiom in core/base.css and how Bootstrap/Tailwind keep
touch-target enforcement on component classes rather than bare elements.

- core/accessibility.css: rewrite selector + rationale comment
- tests/button-touch.spec.js: add regression guard that a classed button
  (hamburger toggle) is exempt; refresh header/test names
- docs/migration.md: breaking-change entry under Unreleased
- demo/index.html: correct the touch-target description

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgL6mqSxe2ihjaUCxx78eR
Scoping the automatic coarse-pointer floor to class-less controls (prev
commit) left classed controls — i.e. almost all real-world controls —
with no first-class way to request the 44px target beyond hand-rolling
min-sizes or the .sf-btn-only --sf-btn-min-height knob. Add the explicit
opt-in counterpart so the floor stays a safety net for bare markup while
the everyday mechanism is a class.

.sf-touch-target enforces min-block-size/min-inline-size:
var(--sf-touch-target) on both axes and centres a short label via
inline-flex (so it also works on inline elements like <a>). It lives in
core/accessibility.css alongside .sr-only / .skip-link / .sf-focus-shadow,
so it ships in every bundle, not just full. It is a single class
(specificity 0,1,0) with no !important, so a component rule can still opt a
specific control back out. Unlike the automatic floor it is not gated to a
coarse pointer — an explicit opt-in is predictable on every device.

- core/accessibility.css: add the .sf-touch-target helper + rationale
- tests/a11y-patterns.spec.js: enforce-on-classed-control and
  overridable-without-!important guards
- docs/migration.md: document .sf-touch-target as the primary remedy

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgL6mqSxe2ihjaUCxx78eR
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jackgranatowski, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 739fe257-1a76-413c-a1f0-f049a314449c

📥 Commits

Reviewing files that changed from the base of the PR and between e7c9652 and ccdadb3.

⛔ Files ignored due to path filters (2)
  • configurator/src/data/classes.generated.json is excluded by !**/*.generated.*
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • core/accessibility.css
  • demo/index.html
  • docs/api-index.json
  • docs/api-index.md
  • docs/llm-guide.md
  • docs/migration.md
  • tests/a11y-patterns.spec.js
  • tests/button-touch.spec.js
📝 Walkthrough

Walkthrough

The coarse-pointer touch-target fallback now applies only to classless native controls. A new .sf-touch-target utility provides explicit 44px minimum dimensions, with tests, documentation, demo content, and generated registries updated accordingly.

Changes

Touch target accessibility

Layer / File(s) Summary
Touch target CSS and regression coverage
core/accessibility.css, tests/*touch.spec.js, tests/a11y-patterns.spec.js
The automatic coarse-pointer floor targets classless controls, while .sf-touch-target enforces minimum inline and block sizes; tests cover enforcement, overrides, and classed-control exemptions.
Public class registration and reference updates
docs/api-index.*, docs/classes.md, docs/registry.json
Generated counts, inventories, and metadata include the new public accessibility class.
Migration and demo guidance
docs/migration.md, demo/index.html
Documentation and demo text describe classless automatic sizing, class-based exemptions, and explicit opt-in usage.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: codex

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main behavior change and the new opt-in utility class.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/hamburger-menu-css-conflicts-rwi9pe

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the codex label Jul 22, 2026
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR narrows the automatic touch-target floor and adds an explicit opt-in utility. The main changes are:

  • Limits coarse-pointer minimum sizing to controls without a class attribute.
  • Adds .sf-touch-target for explicit 44px minimum sizing.
  • Updates generated API data, migration guidance, demo copy, and tests.

Confidence Score: 4/5

The touch-target selector and utility need fixes before merging.

  • Empty class attributes can silently disable the automatic 44px floor.
  • The opt-in utility can remove native control affordances by replacing their display mode.
  • Generated API artifacts and the primary button behavior are otherwise consistent.

core/accessibility.css and touch-target tests

Important Files Changed

Filename Overview
core/accessibility.css Narrows the automatic floor and adds the new utility, but empty class attributes bypass the floor and the utility can replace native control layout.
tests/a11y-patterns.spec.js Covers sizing and overrides on buttons, but not native controls whose behavior depends on their default display.
tests/button-touch.spec.js Covers controls with a populated class and controls with no class attribute, but not an empty class attribute.
docs/migration.md Documents the breaking selector change and explicit opt-in path.
configurator/src/data/classes.generated.json Adds the new public accessibility class to the generated catalogue.

Reviews (1): Last reviewed commit: "feat(a11y): add .sf-touch-target opt-in ..." | Re-trigger Greptile

Comment thread core/accessibility.css Outdated
Comment thread core/accessibility.css Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/accessibility.css`:
- Around line 130-151: Update the `--sf-touch-target` documentation in
llm-guide.md to explain that the automatic minimum-size floor skips controls
carrying a class and that `.sf-touch-target` explicitly opts controls into the
44px hit-area sizing. Preserve the existing token guidance and describe that the
opt-in applies regardless of pointer type.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c913ea41-1d58-4e6c-8c7b-4338a939ac26

📥 Commits

Reviewing files that changed from the base of the PR and between 7eca696 and e7c9652.

⛔ Files ignored due to path filters (1)
  • configurator/src/data/classes.generated.json is excluded by !**/*.generated.*
📒 Files selected for processing (9)
  • core/accessibility.css
  • demo/index.html
  • docs/api-index.json
  • docs/api-index.md
  • docs/classes.md
  • docs/migration.md
  • docs/registry.json
  • tests/a11y-patterns.spec.js
  • tests/button-touch.spec.js

Comment thread core/accessibility.css
claude added 2 commits July 22, 2026 09:52
…gallery

Review feedback on #648:

- Empty class="" bypassed the floor (P1): renderers routinely emit
  class="" for an unstyled control, and :not([class]) excluded it, so a
  genuinely-unstyled control could sit below 44px on touch. Scope the floor
  to "no effective class" via
  :where(button, …):not([class]:not([class=""])) — matches no class
  attribute OR an empty one. :where() keeps the element list at zero
  specificity.
- .sf-touch-target replaced native display (P1): display: inline-flex
  stripped a classed <summary>'s list-item marker (and any control's
  native baseline). Drop display entirely — the class now sets only the two
  min-size properties, mirroring the automatic floor. Document that a purely
  inline target (<a>) needs its own display.
- Class gallery coverage test failed: add the .sf-touch-target preview card
  and the #cov-data entry so the demo gallery matches the API class set.
- llm-guide.md: document the class-less-only automatic floor and the
  .sf-touch-target opt-in (per CLAUDE.md llm-guide-sync mandate).
- Tests: guard the empty-class floor (button-touch) and that
  .sf-touch-target leaves a <summary> as list-item (a11y-patterns).
- migration.md: drop the centring claim; note sizing-only / inline caveat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgL6mqSxe2ihjaUCxx78eR
`npm audit fix` bumps fast-uri 3.1.2 → 3.1.4, resolving the two
high-severity advisories (GHSA-4c8g-83qw-93j6 host confusion via failed
IDN canonicalization; GHSA-v2hh-gcrm-f6hx literal backslash authority
delimiter). fast-uri is a deep devDependency transitive
(stylelint → table → ajv → fast-uri); the bump is in-major and touches
only package-lock.json. `npm audit` now reports 0 vulnerabilities;
stylelint (lint:css) and the unit suite (129/129) still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgL6mqSxe2ihjaUCxx78eR
@jackgranatowski
jackgranatowski merged commit 59b9e84 into main Jul 22, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants