/***********/ /* LICENSE */ /***********/ /* Copyrights for code authored by Yahoo! Inc. is licensed under the following terms: MIT License Copyright (c) 2011 Yahoo! Inc. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*****************/ /* WHAT IS THIS? */ /*****************/ /* debugCSS is meant to be loaded on an existing page to highlight malformed, inaccessible or legacy HTML that is still worth fixing in modern codebases. The goal is signal over nostalgia: prefer checks that are actionable on current browser engines and app stacks. Error: #ff6b6b Warning: #ffd166 Notice: #8fd694 */ /************/ /* SETTINGS */ /************/ :root { --debugcss-error: #ff6b6b; --debugcss-warning: #ffd166; --debugcss-notice: #8fd694; --debugcss-text: #111111; --debugcss-panel: rgba(17, 24, 39, 0.94); --debugcss-panel-text: #f9fafb; --debugcss-outline-width: 3px; --debugcss-font: 600 12px/1.35 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --debugcss-shadow: 0 4px 14px rgba(0, 0, 0, 0.2); --debugcss-z: 2147483646; --debugcss-chip-opacity: 1; --debugcss-chip-transition: opacity 140ms ease, transform 140ms ease; --debugcss-alpha-bg: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAgMAAAAOFJJnAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRF/+Pj2tra2dnZ4Z9gVgAAAHxJREFUGBkFwUGKxDAQBDB9shqcuw2T/3TAc5+A95UruZv+IoNqvEWCavLFDB2M5vPgGpyFNOcB80cPJMxgHhLkQ4IxuBorpDA3Y+MazEfC2+gwgnOxHnSogb15F1JcGxmkcBb1AwlVqCIHadZGwt+NGmQhD1lIuB+scOUfnCk3BP0u5SsAAAAASUVORK5CYII=); } html[data-debugcss-compact] { --debugcss-chip-opacity: 0; } html[data-debugcss-compact]:is(:not([lang]), [lang=""]) { --debugcss-chip-opacity: 1; } html { counter-reset: debugcss-error debugcss-warning debugcss-notice debugcss-a11y debugcss-structure debugcss-legacy debugcss-security; } /*****************/ /* SHARED CHROME */ /*****************/ :where( html:not([lang]), html[lang=""], img:not([alt]), img:is(:not([src]), [src=""], [src="#"]), input[type="image"]:is(:not([alt]), [alt=""]), area[href]:is(:not([alt]), [alt=""]), a a, button:empty:not([aria-label]):not([aria-labelledby]), a[href]:empty:not([aria-label]):not([aria-labelledby]), :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] )[aria-hidden="true"], [aria-hidden="true"] :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] ), [aria-label=""], [aria-labelledby=""], [aria-describedby=""], iframe:not([title]):not([aria-label]):not([aria-labelledby]), iframe[title=""], a[href^="javascript:" i], a[target="_blank"]:not([rel~="noopener" i]):not([rel~="noreferrer" i]), a:not([href]), a[href=""], a[href="#"], main ~ main, :is(h1, h2, h3, h4, h5, h6):empty, :is(ul, ol) > :not(li), dl > :not(dt):not(dd), center, font, marquee, blink, table[summary], table thead td, table th:not([scope]):not([headers]):not([id]), img[alt=""], img[role="presentation"][alt]:not([alt=""]), :is(table, td, th, img)[align], :is(table, td, th)[bgcolor], :is(table, td, th)[valign], table[border], table[cellpadding], table[cellspacing], [id=""], [for=""], [role="slider"]:not([aria-valuemin]), [role="slider"]:not([aria-valuemax]), [role="slider"]:not([aria-valuenow]), [role="button"]:not(button):not(input):not(a[href]):not([tabindex]), [role="checkbox"]:not([aria-checked]), [role="radio"]:not([aria-checked]), [role="switch"]:not([aria-checked]), [role="tab"]:not([aria-selected]), [role="combobox"]:not([aria-expanded]), [role="dialog"]:not([aria-label]):not([aria-labelledby]), [role="progressbar"]:not([aria-valuenow]), [tabindex]:not([tabindex="-1"]):not([tabindex="0"]), [accesskey], input[type="radio"]:not([name]), form button:not([type]), input[pattern]:not([title]):not([aria-describedby]):not([aria-description]), summary:empty, caption:empty, video[autoplay]:not([muted]), audio[autoplay] ) { outline: var(--debugcss-outline-width) solid var(--debugcss-tone) !important; outline-offset: 2px !important; position: relative; } :where( html:not([lang]), html[lang=""], img:not([alt]), img:is(:not([src]), [src=""], [src="#"]), input[type="image"]:is(:not([alt]), [alt=""]), area[href]:is(:not([alt]), [alt=""]), a a, button:empty:not([aria-label]):not([aria-labelledby]), a[href]:empty:not([aria-label]):not([aria-labelledby]), :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] )[aria-hidden="true"], [aria-hidden="true"] :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] ), [aria-label=""], [aria-labelledby=""], [aria-describedby=""], iframe:not([title]):not([aria-label]):not([aria-labelledby]), iframe[title=""], a[href^="javascript:" i], a[target="_blank"]:not([rel~="noopener" i]):not([rel~="noreferrer" i]), a:not([href]), a[href=""], a[href="#"], main ~ main, :is(h1, h2, h3, h4, h5, h6):empty, :is(ul, ol) > :not(li), dl > :not(dt):not(dd), center, font, marquee, blink, table[summary], table thead td, table th:not([scope]):not([headers]):not([id]), img[alt=""], img[role="presentation"][alt]:not([alt=""]), :is(table, td, th, img)[align], :is(table, td, th)[bgcolor], :is(table, td, th)[valign], table[border], table[cellpadding], table[cellspacing], [id=""], [for=""], [role="slider"]:not([aria-valuemin]), [role="slider"]:not([aria-valuemax]), [role="slider"]:not([aria-valuenow]), [role="button"]:not(button):not(input):not(a[href]):not([tabindex]), [role="checkbox"]:not([aria-checked]), [role="radio"]:not([aria-checked]), [role="switch"]:not([aria-checked]), [role="tab"]:not([aria-selected]), [role="combobox"]:not([aria-expanded]), [role="dialog"]:not([aria-label]):not([aria-labelledby]), [role="progressbar"]:not([aria-valuenow]), [tabindex]:not([tabindex="-1"]):not([tabindex="0"]), [accesskey], input[type="radio"]:not([name]), form button:not([type]), input[pattern]:not([title]):not([aria-describedby]):not([aria-description]), summary:empty, caption:empty, video[autoplay]:not([muted]), audio[autoplay] )::after { all: initial !important; background: var(--debugcss-tone) !important; border: 1px solid rgba(17, 24, 39, 0.8) !important; border-radius: 4px !important; box-shadow: var(--debugcss-shadow) !important; box-sizing: border-box !important; color: var(--debugcss-text) !important; content: var(--debugcss-message) !important; display: block !important; font: var(--debugcss-font) !important; left: 0 !important; max-width: min(28rem, calc(100vw - 1rem)) !important; opacity: var(--debugcss-chip-opacity) !important; padding: 0.22rem 0.45rem !important; pointer-events: none !important; position: absolute !important; text-transform: none !important; top: 0 !important; transform: translate(0.25rem, 0.25rem) !important; transition: var(--debugcss-chip-transition) !important; white-space: normal !important; z-index: var(--debugcss-z) !important; } html[data-debugcss-compact] :where( img:not([alt]), img:is(:not([src]), [src=""], [src="#"]), input[type="image"]:is(:not([alt]), [alt=""]), area[href]:is(:not([alt]), [alt=""]), a a, button:empty:not([aria-label]):not([aria-labelledby]), a[href]:empty:not([aria-label]):not([aria-labelledby]), :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] )[aria-hidden="true"], [aria-hidden="true"] :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] ), [aria-label=""], [aria-labelledby=""], [aria-describedby=""], iframe:not([title]):not([aria-label]):not([aria-labelledby]), iframe[title=""], a[href^="javascript:" i], a[target="_blank"]:not([rel~="noopener" i]):not([rel~="noreferrer" i]), a:not([href]), a[href=""], a[href="#"], main ~ main, :is(h1, h2, h3, h4, h5, h6):empty, :is(ul, ol) > :not(li), dl > :not(dt):not(dd), center, font, marquee, blink, table[summary], table thead td, table th:not([scope]):not([headers]):not([id]), img[alt=""], img[role="presentation"][alt]:not([alt=""]), :is(table, td, th, img)[align], :is(table, td, th)[bgcolor], :is(table, td, th)[valign], table[border], table[cellpadding], table[cellspacing], [id=""], [for=""], [role="slider"]:not([aria-valuemin]), [role="slider"]:not([aria-valuemax]), [role="slider"]:not([aria-valuenow]), [role="button"]:not(button):not(input):not(a[href]):not([tabindex]), [role="checkbox"]:not([aria-checked]), [role="radio"]:not([aria-checked]), [role="switch"]:not([aria-checked]), [role="tab"]:not([aria-selected]), [role="combobox"]:not([aria-expanded]), [role="dialog"]:not([aria-label]):not([aria-labelledby]), [role="progressbar"]:not([aria-valuenow]), [tabindex]:not([tabindex="-1"]):not([tabindex="0"]), [accesskey], input[type="radio"]:not([name]), form button:not([type]), input[pattern]:not([title]):not([aria-describedby]):not([aria-description]), summary:empty, caption:empty, video[autoplay]:not([muted]), audio[autoplay] ):is(:hover, :focus, :focus-within) { --debugcss-chip-opacity: 1; } /********************/ /* SEVERITY BUCKETS */ /********************/ :where( table[summary], table th:not([scope]):not([headers]):not([id]), img[alt=""], img[role="presentation"][alt]:not([alt=""]), :is(table, td, th, img)[align], :is(table, td, th)[bgcolor], :is(table, td, th)[valign], table[border], table[cellpadding], table[cellspacing], [id=""], [for=""], [accesskey], form button:not([type]), input[pattern]:not([title]):not([aria-describedby]):not([aria-description]) ) { --debugcss-tone: var(--debugcss-notice) !important; counter-increment: debugcss-notice debugcss-a11y !important; } :where( [id=""], [for=""], form button:not([type]) ) { --debugcss-tone: var(--debugcss-notice) !important; counter-increment: debugcss-notice debugcss-structure !important; } :where( table[summary], :is(table, td, th, img)[align], :is(table, td, th)[bgcolor], :is(table, td, th)[valign], table[border], table[cellpadding], table[cellspacing] ) { --debugcss-tone: var(--debugcss-notice) !important; counter-increment: debugcss-notice debugcss-legacy !important; } :where( [aria-label=""], [aria-labelledby=""], [aria-describedby=""], iframe:not([title]):not([aria-label]):not([aria-labelledby]), iframe[title=""], [role="button"]:not(button):not(input):not(a[href]):not([tabindex]), [tabindex]:not([tabindex="-1"]):not([tabindex="0"]), summary:empty, caption:empty, video[autoplay]:not([muted]), audio[autoplay] ) { --debugcss-tone: var(--debugcss-warning) !important; counter-increment: debugcss-warning debugcss-a11y !important; } :where( a:not([href]), a[href=""], a[href="#"], main ~ main, :is(h1, h2, h3, h4, h5, h6):empty, :is(ul, ol) > :not(li), dl > :not(dt):not(dd), table thead td ) { --debugcss-tone: var(--debugcss-warning) !important; counter-increment: debugcss-warning debugcss-structure !important; } :where( a[href^="javascript:" i], a[target="_blank"]:not([rel~="noopener" i]):not([rel~="noreferrer" i]) ) { --debugcss-tone: var(--debugcss-warning) !important; counter-increment: debugcss-warning debugcss-security !important; } :where( center, font, marquee, blink ) { --debugcss-tone: var(--debugcss-warning) !important; counter-increment: debugcss-warning debugcss-legacy !important; } :where( html:not([lang]), html[lang=""], img:not([alt]), input[type="image"]:is(:not([alt]), [alt=""]), area[href]:is(:not([alt]), [alt=""]), button:empty:not([aria-label]):not([aria-labelledby]), a[href]:empty:not([aria-label]):not([aria-labelledby]), :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] )[aria-hidden="true"], [aria-hidden="true"] :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] ), [role="slider"]:not([aria-valuemin]), [role="slider"]:not([aria-valuemax]), [role="slider"]:not([aria-valuenow]), [role="checkbox"]:not([aria-checked]), [role="radio"]:not([aria-checked]), [role="switch"]:not([aria-checked]), [role="tab"]:not([aria-selected]), [role="combobox"]:not([aria-expanded]), [role="dialog"]:not([aria-label]):not([aria-labelledby]), [role="progressbar"]:not([aria-valuenow]), input[type="radio"]:not([name]) ) { --debugcss-tone: var(--debugcss-error) !important; counter-increment: debugcss-error debugcss-a11y !important; } :where( img:is(:not([src]), [src=""], [src="#"]), a a ) { --debugcss-tone: var(--debugcss-error) !important; counter-increment: debugcss-error debugcss-structure !important; } /*******************/ /* ERROR MESSAGES */ /*******************/ html:not([lang]), html[lang=""] { --debugcss-message: "Add a non-empty lang attribute to ." !important; } img:not([alt]) { --debugcss-message: "Image is missing alt text." !important; } img:is(:not([src]), [src=""], [src="#"]) { --debugcss-message: "Image has a missing or placeholder src." !important; } input[type="image"]:is(:not([alt]), [alt=""]) { --debugcss-message: "Image button needs meaningful alt text." !important; } area[href]:is(:not([alt]), [alt=""]) { --debugcss-message: "Image map area needs alt text." !important; } a a { --debugcss-message: "Nested links are invalid markup." !important; } button:empty:not([aria-label]):not([aria-labelledby]) { --debugcss-message: "Empty button has no accessible name." !important; } a[href]:empty:not([aria-label]):not([aria-labelledby]) { --debugcss-message: "Empty link has no accessible name." !important; } :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] )[aria-hidden="true"] { --debugcss-message: "Focusable element is hidden from assistive tech with aria-hidden." !important; } [aria-hidden="true"] :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] ) { --debugcss-message: "Focusable content sits inside an aria-hidden subtree." !important; } [role="slider"]:not([aria-valuemin]), [role="slider"]:not([aria-valuemax]), [role="slider"]:not([aria-valuenow]) { --debugcss-message: "Slider role is missing a required aria-value attribute." !important; } [role="checkbox"]:not([aria-checked]), [role="radio"]:not([aria-checked]), [role="switch"]:not([aria-checked]) { --debugcss-message: "Custom checkable control is missing aria-checked." !important; } [role="tab"]:not([aria-selected]) { --debugcss-message: "Tab role is missing aria-selected." !important; } [role="combobox"]:not([aria-expanded]) { --debugcss-message: "Combobox role is missing aria-expanded." !important; } [role="dialog"]:not([aria-label]):not([aria-labelledby]) { --debugcss-message: "Dialog role needs an accessible name." !important; } [role="progressbar"]:not([aria-valuenow]) { --debugcss-message: "Progressbar role is missing aria-valuenow." !important; } input[type="radio"]:not([name]) { --debugcss-message: "Radio input needs a name to participate in a group." !important; } /*********************/ /* WARNING MESSAGES */ /*********************/ [aria-label=""], [aria-labelledby=""], [aria-describedby=""] { --debugcss-message: "ARIA naming or description attribute is empty." !important; } iframe:not([title]):not([aria-label]):not([aria-labelledby]), iframe[title=""] { --debugcss-message: "Iframe needs a non-empty accessible name." !important; } a[href^="javascript:" i] { --debugcss-message: "javascript: URLs are fragile and inaccessible." !important; } a[target="_blank"]:not([rel~="noopener" i]):not([rel~="noreferrer" i]) { --debugcss-message: "target=\"_blank\" should include rel=\"noopener\" or rel=\"noreferrer\"." !important; } a:not([href]), a[href=""], a[href="#"] { --debugcss-message: "Anchor has no real destination." !important; } main ~ main { --debugcss-message: "Only one
landmark should appear per document." !important; } :is(h1, h2, h3, h4, h5, h6):empty { --debugcss-message: "Heading is empty." !important; } :is(ul, ol) > :not(li) { --debugcss-message: "Only
  • elements belong directly inside lists." !important; } dl > :not(dt):not(dd) { --debugcss-message: "Only
    and
    belong directly inside
    ." !important; } center, font, marquee, blink { --debugcss-message: "Deprecated element found." !important; } table thead td { --debugcss-message: "Header cells in should usually be ." !important; } [role="button"]:not(button):not(input):not(a[href]):not([tabindex]) { --debugcss-message: "role=\"button\" needs keyboard focus support." !important; } [tabindex]:not([tabindex="-1"]):not([tabindex="0"]) { --debugcss-message: "Positive tabindex can create a confusing focus order." !important; } summary:empty { --debugcss-message: " is empty." !important; } caption:empty { --debugcss-message: " is empty." !important; } video[autoplay]:not([muted]) { --debugcss-message: "Autoplaying video should usually be muted." !important; } audio[autoplay] { --debugcss-message: "Autoplaying audio is a strong accessibility smell." !important; } /********************/ /* NOTICE MESSAGES */ /********************/ table[summary] { --debugcss-message: "table[summary] is obsolete in modern HTML." !important; } table th:not([scope]):not([headers]):not([id]) { --debugcss-message: "Table header is missing scope or an explicit header relationship." !important; } img[alt=""] { --debugcss-message: "Image has empty alt text. Verify that it is decorative." !important; } img[role="presentation"][alt]:not([alt=""]) { --debugcss-message: "Presentational images should usually use alt=\"\"." !important; } :is(table, td, th, img)[align], :is(table, td, th)[bgcolor], :is(table, td, th)[valign], table[border], table[cellpadding], table[cellspacing] { --debugcss-message: "Legacy presentational HTML attribute found." !important; } [id=""] { --debugcss-message: "id attribute is present but empty." !important; } [for=""] { --debugcss-message: "for attribute is present but empty." !important; } [accesskey] { --debugcss-message: "accesskey can create hard-to-discover keyboard conflicts." !important; } form button:not([type]) { --debugcss-message: "Button inside a form should usually declare its type." !important; } input[pattern]:not([title]):not([aria-describedby]):not([aria-description]) { --debugcss-message: "Pattern-constrained input should explain the expected format." !important; } /**************************/ /* MODERN SELECTOR CHECKS */ /**************************/ @supports selector(:has(*)) { :where( html:not(:has(head > title)), html:not(:has(head > meta[charset])), html:not(:has(main, [role="main"])), label:not([for]):not(:has(:is(input, select, textarea, meter, progress))), :is(input:not([type="hidden"]), select, textarea, meter, progress):not([id]):not([aria-label]):not([aria-labelledby]):not(label :is(input, select, textarea, meter, progress)), [role="img"]:not(img):not(svg):not([aria-label]):not([aria-labelledby]), svg[role="img"]:not([aria-label]):not([aria-labelledby]):not(:has(> title)), svg[role="img"]:has(> title:empty), details:not(:has(> summary)), fieldset:not(:has(> legend)), table:not([role="presentation"]):not(:has(> caption)), video[controls]:not(:has(track[kind="captions"], track[kind="subtitles"])), section:not([aria-label]):not([aria-labelledby]):not(:has(:is(h1, h2, h3, h4, h5, h6))) ) { outline: var(--debugcss-outline-width) solid var(--debugcss-tone) !important; outline-offset: 2px !important; position: relative; } :where( html:not(:has(head > title)), html:not(:has(head > meta[charset])), html:not(:has(main, [role="main"])), label:not([for]):not(:has(:is(input, select, textarea, meter, progress))), :is(input:not([type="hidden"]), select, textarea, meter, progress):not([id]):not([aria-label]):not([aria-labelledby]):not(label :is(input, select, textarea, meter, progress)), [role="img"]:not(img):not(svg):not([aria-label]):not([aria-labelledby]), svg[role="img"]:not([aria-label]):not([aria-labelledby]):not(:has(> title)), svg[role="img"]:has(> title:empty), details:not(:has(> summary)), fieldset:not(:has(> legend)), table:not([role="presentation"]):not(:has(> caption)), video[controls]:not(:has(track[kind="captions"], track[kind="subtitles"])), section:not([aria-label]):not([aria-labelledby]):not(:has(:is(h1, h2, h3, h4, h5, h6))) )::after { all: initial !important; background: var(--debugcss-tone) !important; border: 1px solid rgba(17, 24, 39, 0.8) !important; border-radius: 4px !important; box-shadow: var(--debugcss-shadow) !important; box-sizing: border-box !important; color: var(--debugcss-text) !important; content: var(--debugcss-message) !important; display: block !important; font: var(--debugcss-font) !important; left: 0 !important; max-width: min(28rem, calc(100vw - 1rem)) !important; opacity: var(--debugcss-chip-opacity) !important; padding: 0.22rem 0.45rem !important; pointer-events: none !important; position: absolute !important; top: 0 !important; transform: translate(0.25rem, 0.25rem) !important; transition: var(--debugcss-chip-transition) !important; white-space: normal !important; z-index: var(--debugcss-z) !important; } html[data-debugcss-compact] :where( label:not([for]):not(:has(:is(input, select, textarea, meter, progress))), :is(input:not([type="hidden"]), select, textarea, meter, progress):not([id]):not([aria-label]):not([aria-labelledby]):not(label :is(input, select, textarea, meter, progress)), [role="img"]:not(img):not(svg):not([aria-label]):not([aria-labelledby]), svg[role="img"]:not([aria-label]):not([aria-labelledby]):not(:has(> title)), svg[role="img"]:has(> title:empty), details:not(:has(> summary)), fieldset:not(:has(> legend)), table:not([role="presentation"]):not(:has(> caption)), video[controls]:not(:has(track[kind="captions"], track[kind="subtitles"])), section:not([aria-label]):not([aria-labelledby]):not(:has(:is(h1, h2, h3, h4, h5, h6))) ):is(:hover, :focus, :focus-within) { --debugcss-chip-opacity: 1; } :where( table:not([role="presentation"]):not(:has(> caption)), section:not([aria-label]):not([aria-labelledby]):not(:has(:is(h1, h2, h3, h4, h5, h6))) ) { --debugcss-tone: var(--debugcss-notice) !important; counter-increment: debugcss-notice debugcss-a11y !important; } html:not(:has(head > meta[charset])) { --debugcss-tone: var(--debugcss-notice) !important; counter-increment: debugcss-notice debugcss-structure !important; } :where( label:not([for]):not(:has(:is(input, select, textarea, meter, progress))), :is(input:not([type="hidden"]), select, textarea, meter, progress):not([id]):not([aria-label]):not([aria-labelledby]):not(label :is(input, select, textarea, meter, progress)), [role="img"]:not(img):not(svg):not([aria-label]):not([aria-labelledby]), svg[role="img"]:not([aria-label]):not([aria-labelledby]):not(:has(> title)), svg[role="img"]:has(> title:empty), details:not(:has(> summary)), fieldset:not(:has(> legend)), video[controls]:not(:has(track[kind="captions"], track[kind="subtitles"])) ) { --debugcss-tone: var(--debugcss-warning) !important; counter-increment: debugcss-warning debugcss-a11y !important; } :where( html:not(:has(head > title)), html:not(:has(main, [role="main"])), details:not(:has(> summary)) ) { --debugcss-tone: var(--debugcss-warning) !important; counter-increment: debugcss-warning debugcss-structure !important; } html:not(:has(head > title)) { --debugcss-message: "Document is missing a element." !important; } html:not(:has(head > meta[charset])) { --debugcss-message: "Document is missing a declared character encoding." !important; } html:not(:has(main, [role="main"])) { --debugcss-message: "Document is missing a main landmark." !important; } label:not([for]):not(:has(:is(input, select, textarea, meter, progress))) { --debugcss-message: "Label has no for attribute and does not wrap a control." !important; } :is(input:not([type="hidden"]), select, textarea, meter, progress):not([id]):not([aria-label]):not([aria-labelledby]):not(label :is(input, select, textarea, meter, progress)) { --debugcss-message: "Form control has no id and no obvious built-in label path." !important; } [role="img"]:not(img):not(svg):not([aria-label]):not([aria-labelledby]) { --debugcss-message: "role=\"img\" element needs an accessible name." !important; } svg[role="img"]:not([aria-label]):not([aria-labelledby]):not(:has(> title)) { --debugcss-message: "Inline SVG used as an image needs a name or <title>." !important; } svg[role="img"]:has(> title:empty) { --debugcss-message: "Inline SVG title exists but is empty." !important; } details:not(:has(> summary)) { --debugcss-message: "<details> should contain a <summary>." !important; } fieldset:not(:has(> legend)) { --debugcss-message: "Fieldset is missing a legend." !important; } table:not([role="presentation"]):not(:has(> caption)) { --debugcss-message: "Data table has no caption." !important; } video[controls]:not(:has(track[kind="captions"], track[kind="subtitles"])) { --debugcss-message: "Video with controls has no captions or subtitles track." !important; } section:not([aria-label]):not([aria-labelledby]):not(:has(:is(h1, h2, h3, h4, h5, h6))) { --debugcss-message: "Section has no heading or accessible name." !important; } } /********************/ /* VISUAL FILTERING */ /********************/ html[data-debugcss-mode]:not([data-debugcss-mode~="a11y"]) :where( img:not([alt]), input[type="image"]:is(:not([alt]), [alt=""]), area[href]:is(:not([alt]), [alt=""]), button:empty:not([aria-label]):not([aria-labelledby]), a[href]:empty:not([aria-label]):not([aria-labelledby]), :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] )[aria-hidden="true"], [aria-hidden="true"] :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] ), [aria-label=""], [aria-labelledby=""], [aria-describedby=""], iframe:not([title]):not([aria-label]):not([aria-labelledby]), iframe[title=""], :is(h1, h2, h3, h4, h5, h6):empty, table th:not([scope]):not([headers]):not([id]), img[alt=""], img[role="presentation"][alt]:not([alt=""]), [role="slider"]:not([aria-valuemin]), [role="slider"]:not([aria-valuemax]), [role="slider"]:not([aria-valuenow]), [role="button"]:not(button):not(input):not(a[href]):not([tabindex]), [role="checkbox"]:not([aria-checked]), [role="radio"]:not([aria-checked]), [role="switch"]:not([aria-checked]), [role="tab"]:not([aria-selected]), [role="combobox"]:not([aria-expanded]), [role="dialog"]:not([aria-label]):not([aria-labelledby]), [role="progressbar"]:not([aria-valuenow]), [tabindex]:not([tabindex="-1"]):not([tabindex="0"]), [accesskey], input[type="radio"]:not([name]), input[pattern]:not([title]):not([aria-describedby]):not([aria-description]), summary:empty, caption:empty, video[autoplay]:not([muted]), audio[autoplay] ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-mode]:not([data-debugcss-mode~="a11y"]) :where( img:not([alt]), input[type="image"]:is(:not([alt]), [alt=""]), area[href]:is(:not([alt]), [alt=""]), button:empty:not([aria-label]):not([aria-labelledby]), a[href]:empty:not([aria-label]):not([aria-labelledby]), :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] )[aria-hidden="true"], [aria-hidden="true"] :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] ), [aria-label=""], [aria-labelledby=""], [aria-describedby=""], iframe:not([title]):not([aria-label]):not([aria-labelledby]), iframe[title=""], :is(h1, h2, h3, h4, h5, h6):empty, table th:not([scope]):not([headers]):not([id]), img[alt=""], img[role="presentation"][alt]:not([alt=""]), [role="slider"]:not([aria-valuemin]), [role="slider"]:not([aria-valuemax]), [role="slider"]:not([aria-valuenow]), [role="button"]:not(button):not(input):not(a[href]):not([tabindex]), [role="checkbox"]:not([aria-checked]), [role="radio"]:not([aria-checked]), [role="switch"]:not([aria-checked]), [role="tab"]:not([aria-selected]), [role="combobox"]:not([aria-expanded]), [role="dialog"]:not([aria-label]):not([aria-labelledby]), [role="progressbar"]:not([aria-valuenow]), [tabindex]:not([tabindex="-1"]):not([tabindex="0"]), [accesskey], input[type="radio"]:not([name]), input[pattern]:not([title]):not([aria-describedby]):not([aria-description]), summary:empty, caption:empty, video[autoplay]:not([muted]), audio[autoplay] )::after { content: none !important; } html[data-debugcss-mode]:not([data-debugcss-mode~="structure"]) :where( img:is(:not([src]), [src=""], [src="#"]), a a, a:not([href]), a[href=""], a[href="#"], main ~ main, :is(ul, ol) > :not(li), dl > :not(dt):not(dd), table thead td, [id=""], [for=""], form button:not([type]) ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-mode]:not([data-debugcss-mode~="structure"]) :where( img:is(:not([src]), [src=""], [src="#"]), a a, a:not([href]), a[href=""], a[href="#"], main ~ main, :is(ul, ol) > :not(li), dl > :not(dt):not(dd), table thead td, [id=""], [for=""], form button:not([type]) )::after { content: none !important; } html[data-debugcss-mode]:not([data-debugcss-mode~="legacy"]) :where( center, font, marquee, blink, table[summary], :is(table, td, th, img)[align], :is(table, td, th)[bgcolor], :is(table, td, th)[valign], table[border], table[cellpadding], table[cellspacing] ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-mode]:not([data-debugcss-mode~="legacy"]) :where( center, font, marquee, blink, table[summary], :is(table, td, th, img)[align], :is(table, td, th)[bgcolor], :is(table, td, th)[valign], table[border], table[cellpadding], table[cellspacing] )::after { content: none !important; } html[data-debugcss-mode]:not([data-debugcss-mode~="security"]) :where( a[href^="javascript:" i], a[target="_blank"]:not([rel~="noopener" i]):not([rel~="noreferrer" i]) ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-mode]:not([data-debugcss-mode~="security"]) :where( a[href^="javascript:" i], a[target="_blank"]:not([rel~="noopener" i]):not([rel~="noreferrer" i]) )::after { content: none !important; } html[data-debugcss-level]:not([data-debugcss-level~="error"]) :where( img:not([alt]), img:is(:not([src]), [src=""], [src="#"]), input[type="image"]:is(:not([alt]), [alt=""]), area[href]:is(:not([alt]), [alt=""]), a a, button:empty:not([aria-label]):not([aria-labelledby]), a[href]:empty:not([aria-label]):not([aria-labelledby]), :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] )[aria-hidden="true"], [aria-hidden="true"] :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] ), [role="slider"]:not([aria-valuemin]), [role="slider"]:not([aria-valuemax]), [role="slider"]:not([aria-valuenow]), [role="checkbox"]:not([aria-checked]), [role="radio"]:not([aria-checked]), [role="switch"]:not([aria-checked]), [role="tab"]:not([aria-selected]), [role="combobox"]:not([aria-expanded]), [role="dialog"]:not([aria-label]):not([aria-labelledby]), [role="progressbar"]:not([aria-valuenow]), input[type="radio"]:not([name]) ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-level]:not([data-debugcss-level~="error"]) :where( img:not([alt]), img:is(:not([src]), [src=""], [src="#"]), input[type="image"]:is(:not([alt]), [alt=""]), area[href]:is(:not([alt]), [alt=""]), a a, button:empty:not([aria-label]):not([aria-labelledby]), a[href]:empty:not([aria-label]):not([aria-labelledby]), :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] )[aria-hidden="true"], [aria-hidden="true"] :is( a[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, [tabindex]:not([tabindex="-1"]), audio[controls], video[controls], [contenteditable="true"], [contenteditable="plaintext-only"] ), [role="slider"]:not([aria-valuemin]), [role="slider"]:not([aria-valuemax]), [role="slider"]:not([aria-valuenow]), [role="checkbox"]:not([aria-checked]), [role="radio"]:not([aria-checked]), [role="switch"]:not([aria-checked]), [role="tab"]:not([aria-selected]), [role="combobox"]:not([aria-expanded]), [role="dialog"]:not([aria-label]):not([aria-labelledby]), [role="progressbar"]:not([aria-valuenow]), input[type="radio"]:not([name]) )::after { content: none !important; } html[data-debugcss-level]:not([data-debugcss-level~="warning"]) :where( [aria-label=""], [aria-labelledby=""], [aria-describedby=""], iframe:not([title]):not([aria-label]):not([aria-labelledby]), iframe[title=""], a[href^="javascript:" i], a[target="_blank"]:not([rel~="noopener" i]):not([rel~="noreferrer" i]), a:not([href]), a[href=""], a[href="#"], main ~ main, :is(h1, h2, h3, h4, h5, h6):empty, :is(ul, ol) > :not(li), dl > :not(dt):not(dd), center, font, marquee, blink, table thead td, [role="button"]:not(button):not(input):not(a[href]):not([tabindex]), [tabindex]:not([tabindex="-1"]):not([tabindex="0"]), summary:empty, caption:empty, video[autoplay]:not([muted]), audio[autoplay] ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-level]:not([data-debugcss-level~="warning"]) :where( [aria-label=""], [aria-labelledby=""], [aria-describedby=""], iframe:not([title]):not([aria-label]):not([aria-labelledby]), iframe[title=""], a[href^="javascript:" i], a[target="_blank"]:not([rel~="noopener" i]):not([rel~="noreferrer" i]), a:not([href]), a[href=""], a[href="#"], main ~ main, :is(h1, h2, h3, h4, h5, h6):empty, :is(ul, ol) > :not(li), dl > :not(dt):not(dd), center, font, marquee, blink, table thead td, [role="button"]:not(button):not(input):not(a[href]):not([tabindex]), [tabindex]:not([tabindex="-1"]):not([tabindex="0"]), summary:empty, caption:empty, video[autoplay]:not([muted]), audio[autoplay] )::after { content: none !important; } html[data-debugcss-level]:not([data-debugcss-level~="notice"]) :where( table[summary], table th:not([scope]):not([headers]):not([id]), img[alt=""], img[role="presentation"][alt]:not([alt=""]), :is(table, td, th, img)[align], :is(table, td, th)[bgcolor], :is(table, td, th)[valign], table[border], table[cellpadding], table[cellspacing], [id=""], [for=""], [accesskey], form button:not([type]), input[pattern]:not([title]):not([aria-describedby]):not([aria-description]) ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-level]:not([data-debugcss-level~="notice"]) :where( table[summary], table th:not([scope]):not([headers]):not([id]), img[alt=""], img[role="presentation"][alt]:not([alt=""]), :is(table, td, th, img)[align], :is(table, td, th)[bgcolor], :is(table, td, th)[valign], table[border], table[cellpadding], table[cellspacing], [id=""], [for=""], [accesskey], form button:not([type]), input[pattern]:not([title]):not([aria-describedby]):not([aria-description]) )::after { content: none !important; } @supports selector(:has(*)) { html[data-debugcss-compact]:is( :not(:has(head > title)), :not(:has(head > meta[charset])), :not(:has(main, [role="main"])) ) { --debugcss-chip-opacity: 1; } html[data-debugcss-mode]:not([data-debugcss-mode~="a11y"]) :where( label:not([for]):not(:has(:is(input, select, textarea, meter, progress))), :is(input:not([type="hidden"]), select, textarea, meter, progress):not([id]):not([aria-label]):not([aria-labelledby]):not(label :is(input, select, textarea, meter, progress)), [role="img"]:not(img):not(svg):not([aria-label]):not([aria-labelledby]), svg[role="img"]:not([aria-label]):not([aria-labelledby]):not(:has(> title)), svg[role="img"]:has(> title:empty), fieldset:not(:has(> legend)), table:not([role="presentation"]):not(:has(> caption)), video[controls]:not(:has(track[kind="captions"], track[kind="subtitles"])), section:not([aria-label]):not([aria-labelledby]):not(:has(:is(h1, h2, h3, h4, h5, h6))) ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-mode]:not([data-debugcss-mode~="a11y"]) :where( label:not([for]):not(:has(:is(input, select, textarea, meter, progress))), :is(input:not([type="hidden"]), select, textarea, meter, progress):not([id]):not([aria-label]):not([aria-labelledby]):not(label :is(input, select, textarea, meter, progress)), [role="img"]:not(img):not(svg):not([aria-label]):not([aria-labelledby]), svg[role="img"]:not([aria-label]):not([aria-labelledby]):not(:has(> title)), svg[role="img"]:has(> title:empty), fieldset:not(:has(> legend)), table:not([role="presentation"]):not(:has(> caption)), video[controls]:not(:has(track[kind="captions"], track[kind="subtitles"])), section:not([aria-label]):not([aria-labelledby]):not(:has(:is(h1, h2, h3, h4, h5, h6))) )::after { content: none !important; } html[data-debugcss-mode]:not([data-debugcss-mode~="structure"]) :where( details:not(:has(> summary)) ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-mode]:not([data-debugcss-mode~="structure"]) :where( details:not(:has(> summary)) )::after { content: none !important; } html[data-debugcss-level]:not([data-debugcss-level~="warning"]) :where( label:not([for]):not(:has(:is(input, select, textarea, meter, progress))), :is(input:not([type="hidden"]), select, textarea, meter, progress):not([id]):not([aria-label]):not([aria-labelledby]):not(label :is(input, select, textarea, meter, progress)), [role="img"]:not(img):not(svg):not([aria-label]):not([aria-labelledby]), svg[role="img"]:not([aria-label]):not([aria-labelledby]):not(:has(> title)), svg[role="img"]:has(> title:empty), details:not(:has(> summary)), fieldset:not(:has(> legend)), video[controls]:not(:has(track[kind="captions"], track[kind="subtitles"])) ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-level]:not([data-debugcss-level~="warning"]) :where( label:not([for]):not(:has(:is(input, select, textarea, meter, progress))), :is(input:not([type="hidden"]), select, textarea, meter, progress):not([id]):not([aria-label]):not([aria-labelledby]):not(label :is(input, select, textarea, meter, progress)), [role="img"]:not(img):not(svg):not([aria-label]):not([aria-labelledby]), svg[role="img"]:not([aria-label]):not([aria-labelledby]):not(:has(> title)), svg[role="img"]:has(> title:empty), details:not(:has(> summary)), fieldset:not(:has(> legend)), video[controls]:not(:has(track[kind="captions"], track[kind="subtitles"])) )::after { content: none !important; } html[data-debugcss-level]:not([data-debugcss-level~="notice"]) :where( table:not([role="presentation"]):not(:has(> caption)), section:not([aria-label]):not([aria-labelledby]):not(:has(:is(h1, h2, h3, h4, h5, h6))) ) { counter-increment: none !important; outline: none !important; } html[data-debugcss-level]:not([data-debugcss-level~="notice"]) :where( table:not([role="presentation"]):not(:has(> caption)), section:not([aria-label]):not([aria-labelledby]):not(:has(:is(h1, h2, h3, h4, h5, h6))) )::after { content: none !important; } } /****************/ /* DEBUG PANEL */ /****************/ body::after { all: initial !important; background: var(--debugcss-panel) !important; border: 1px solid rgba(255, 255, 255, 0.14) !important; border-radius: 8px !important; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35) !important; color: var(--debugcss-panel-text) !important; content: "Errors " counter(debugcss-error) " | Warnings " counter(debugcss-warning) " | Notices " counter(debugcss-notice) "\A" "A11y " counter(debugcss-a11y) " | Structure " counter(debugcss-structure) " | Legacy " counter(debugcss-legacy) " | Security " counter(debugcss-security) !important; display: block !important; font: 700 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important; padding: 0.55rem 0.75rem !important; pointer-events: none !important; position: fixed !important; right: 1rem !important; bottom: 1rem !important; white-space: pre !important; z-index: 2147483647 !important; } /*********/ /* EXTRA */ /*********/ :where([data-debugcss-ignore], [data-debugcss-ignore] *) { counter-increment: none !important; outline: none !important; } :where([data-debugcss-ignore], [data-debugcss-ignore] *)::after { content: none !important; } .debugcss-alpha, [data-debugcss-alpha] { background-color: #ffffff !important; background-image: var(--debugcss-alpha-bg) !important; background-repeat: repeat !important; background-size: 16px 16px !important; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12) !important; } body > img:only-child, body > img.debugcss-alpha:only-child, body > img[data-debugcss-alpha]:only-child { background-image: var(--debugcss-alpha-bg) !important; border: 32px solid rgba(0, 0, 0, 0.08) !important; box-shadow: 0 0 10px rgba(0, 0, 0, 0.3) !important; display: block !important; margin: 1rem auto !important; padding: 0 !important; }