mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Detect side-tab borders on modern color formats and label cards
- Fix isNeutralColor to handle oklch, oklab, lch, lab, hsl, and hwb with format-specific chroma/saturation thresholds. jsdom returns these formats literally, so the previous rgb-only regex caused every modern-format border color to be silently treated as neutral and skipped by checkBorders. - Flip the unknown-format fallback from neutral to colored, so unrecognized color strings err on the side of detection. - Introduce a narrower BORDER_SAFE_TAGS set (SAFE_TAGS minus 'label') used only by the border checks. Card-shaped clickable labels with thick colored side borders are now detected, while colors, motion, and nested-card checks continue to skip labels to avoid false positives on real form labels. - Add tests/fixtures/antipatterns/modern-color-borders.html with 8 flag cases (oklch x3, oklab, lch, lab, plus 2 label cards) and 10 pass cases (neutrals across formats, plain inline labels, thin/neutral-bordered labels, colored-on-all-sides). Reproducer (preop-portal demo): side-tab findings rise from 0 to 12.
This commit is contained in:
@@ -48,6 +48,18 @@ const SAFE_TAGS = new Set([
|
||||
'rect', 'line', 'polyline', 'polygon', 'g', 'defs', 'use',
|
||||
]);
|
||||
|
||||
// Per-check safe-tags override for the border (side-tab / border-accent)
|
||||
// rule. We intentionally re-allow <label> here because card-shaped clickable
|
||||
// labels (e.g. .checklist-item wrapping a checkbox + content) are one of the
|
||||
// canonical side-tab anti-pattern shapes and must be detected. The rule's
|
||||
// other preconditions (non-neutral color, width >= 2px on a single side,
|
||||
// radius > 0 or width >= 3, element size >= 20x20 in the browser path)
|
||||
// already filter out plain inline form labels so this does not introduce
|
||||
// false positives. See modern-color-borders.html for the test matrix.
|
||||
const BORDER_SAFE_TAGS = new Set(
|
||||
[...SAFE_TAGS].filter(t => t !== 'label')
|
||||
);
|
||||
|
||||
const OVERUSED_FONTS = new Set([
|
||||
'inter', 'roboto', 'open sans', 'lato', 'montserrat', 'arial', 'helvetica',
|
||||
]);
|
||||
@@ -300,9 +312,53 @@ const ANTIPATTERNS = [
|
||||
|
||||
function isNeutralColor(color) {
|
||||
if (!color || color === 'transparent') return true;
|
||||
const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
||||
if (!m) return true;
|
||||
return (Math.max(+m[1], +m[2], +m[3]) - Math.min(+m[1], +m[2], +m[3])) < 30;
|
||||
|
||||
// rgb/rgba — use channel spread. Threshold 30 ≈ 11.7% of the 0–255 range.
|
||||
const rgb = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
||||
if (rgb) {
|
||||
return (Math.max(+rgb[1], +rgb[2], +rgb[3]) - Math.min(+rgb[1], +rgb[2], +rgb[3])) < 30;
|
||||
}
|
||||
|
||||
// oklch()/lch() — chroma is the second numeric component.
|
||||
// oklch chroma is ~0–0.4 in sRGB gamut; >= 0.02 reads as tinted, not gray.
|
||||
// lch chroma is ~0–150; >= 3 reads as tinted. jsdom emits both formats
|
||||
// literally (it does NOT convert them to rgb).
|
||||
const oklch = color.match(/oklch\(\s*[\d.%-]+\s+([\d.-]+)/i);
|
||||
if (oklch) return parseFloat(oklch[1]) < 0.02;
|
||||
const lch = color.match(/lch\(\s*[\d.%-]+\s+([\d.-]+)/i);
|
||||
if (lch) return parseFloat(lch[1]) < 3;
|
||||
|
||||
// oklab()/lab() — a and b are signed axes; chroma = sqrt(a² + b²).
|
||||
// oklab a/b are ~-0.4..0.4, threshold 0.02. lab a/b are ~-128..127, threshold 3.
|
||||
const oklab = color.match(/oklab\(\s*[\d.%-]+\s+([\d.-]+)\s+([\d.-]+)/i);
|
||||
if (oklab) {
|
||||
const a = parseFloat(oklab[1]), b = parseFloat(oklab[2]);
|
||||
return Math.hypot(a, b) < 0.02;
|
||||
}
|
||||
const lab = color.match(/lab\(\s*[\d.%-]+\s+([\d.-]+)\s+([\d.-]+)/i);
|
||||
if (lab) {
|
||||
const a = parseFloat(lab[1]), b = parseFloat(lab[2]);
|
||||
return Math.hypot(a, b) < 3;
|
||||
}
|
||||
|
||||
// hsl/hsla — saturation is the second numeric component (percent).
|
||||
// Modern jsdom usually converts hsl() to rgb, but handle it directly for
|
||||
// safety across versions and for any engine that preserves the format.
|
||||
const hsl = color.match(/hsla?\(\s*[\d.-]+\s*,?\s*([\d.]+)%/i);
|
||||
if (hsl) return parseFloat(hsl[1]) < 10;
|
||||
|
||||
// hwb(hue whiteness% blackness%) — a pixel is fully gray when
|
||||
// whiteness + blackness >= 100; chroma-like saturation = 1 - (w+b)/100.
|
||||
const hwb = color.match(/hwb\(\s*[\d.-]+\s+([\d.]+)%\s+([\d.]+)%/i);
|
||||
if (hwb) {
|
||||
const w = parseFloat(hwb[1]), b = parseFloat(hwb[2]);
|
||||
return (1 - Math.min(100, w + b) / 100) < 0.1;
|
||||
}
|
||||
|
||||
// Unknown / unrecognized format — err on the side of DETECTING rather
|
||||
// than silently skipping. This is the opposite of the previous default,
|
||||
// which was the root cause of the oklch bug.
|
||||
return false;
|
||||
}
|
||||
|
||||
function parseRgb(color) {
|
||||
@@ -369,7 +425,7 @@ function colorToHex(c) {
|
||||
// ─── Section 3: Pure Detection ──────────────────────────────────────────────
|
||||
|
||||
function checkBorders(tag, widths, colors, radius) {
|
||||
if (SAFE_TAGS.has(tag)) return [];
|
||||
if (BORDER_SAFE_TAGS.has(tag)) return [];
|
||||
const findings = [];
|
||||
const sides = ['Top', 'Right', 'Bottom', 'Left'];
|
||||
|
||||
@@ -833,7 +889,7 @@ function resolveGradientStops(el, win) {
|
||||
|
||||
function checkElementBordersDOM(el) {
|
||||
const tag = el.tagName.toLowerCase();
|
||||
if (SAFE_TAGS.has(tag)) return [];
|
||||
if (BORDER_SAFE_TAGS.has(tag)) return [];
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width < 20 || rect.height < 20) return [];
|
||||
const style = getComputedStyle(el);
|
||||
|
||||
@@ -43,6 +43,18 @@ const SAFE_TAGS = new Set([
|
||||
'rect', 'line', 'polyline', 'polygon', 'g', 'defs', 'use',
|
||||
]);
|
||||
|
||||
// Per-check safe-tags override for the border (side-tab / border-accent)
|
||||
// rule. We intentionally re-allow <label> here because card-shaped clickable
|
||||
// labels (e.g. .checklist-item wrapping a checkbox + content) are one of the
|
||||
// canonical side-tab anti-pattern shapes and must be detected. The rule's
|
||||
// other preconditions (non-neutral color, width >= 2px on a single side,
|
||||
// radius > 0 or width >= 3, element size >= 20x20 in the browser path)
|
||||
// already filter out plain inline form labels so this does not introduce
|
||||
// false positives. See modern-color-borders.html for the test matrix.
|
||||
const BORDER_SAFE_TAGS = new Set(
|
||||
[...SAFE_TAGS].filter(t => t !== 'label')
|
||||
);
|
||||
|
||||
const OVERUSED_FONTS = new Set([
|
||||
'inter', 'roboto', 'open sans', 'lato', 'montserrat', 'arial', 'helvetica',
|
||||
]);
|
||||
@@ -295,9 +307,53 @@ const ANTIPATTERNS = [
|
||||
|
||||
function isNeutralColor(color) {
|
||||
if (!color || color === 'transparent') return true;
|
||||
const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
||||
if (!m) return true;
|
||||
return (Math.max(+m[1], +m[2], +m[3]) - Math.min(+m[1], +m[2], +m[3])) < 30;
|
||||
|
||||
// rgb/rgba — use channel spread. Threshold 30 ≈ 11.7% of the 0–255 range.
|
||||
const rgb = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
||||
if (rgb) {
|
||||
return (Math.max(+rgb[1], +rgb[2], +rgb[3]) - Math.min(+rgb[1], +rgb[2], +rgb[3])) < 30;
|
||||
}
|
||||
|
||||
// oklch()/lch() — chroma is the second numeric component.
|
||||
// oklch chroma is ~0–0.4 in sRGB gamut; >= 0.02 reads as tinted, not gray.
|
||||
// lch chroma is ~0–150; >= 3 reads as tinted. jsdom emits both formats
|
||||
// literally (it does NOT convert them to rgb).
|
||||
const oklch = color.match(/oklch\(\s*[\d.%-]+\s+([\d.-]+)/i);
|
||||
if (oklch) return parseFloat(oklch[1]) < 0.02;
|
||||
const lch = color.match(/lch\(\s*[\d.%-]+\s+([\d.-]+)/i);
|
||||
if (lch) return parseFloat(lch[1]) < 3;
|
||||
|
||||
// oklab()/lab() — a and b are signed axes; chroma = sqrt(a² + b²).
|
||||
// oklab a/b are ~-0.4..0.4, threshold 0.02. lab a/b are ~-128..127, threshold 3.
|
||||
const oklab = color.match(/oklab\(\s*[\d.%-]+\s+([\d.-]+)\s+([\d.-]+)/i);
|
||||
if (oklab) {
|
||||
const a = parseFloat(oklab[1]), b = parseFloat(oklab[2]);
|
||||
return Math.hypot(a, b) < 0.02;
|
||||
}
|
||||
const lab = color.match(/lab\(\s*[\d.%-]+\s+([\d.-]+)\s+([\d.-]+)/i);
|
||||
if (lab) {
|
||||
const a = parseFloat(lab[1]), b = parseFloat(lab[2]);
|
||||
return Math.hypot(a, b) < 3;
|
||||
}
|
||||
|
||||
// hsl/hsla — saturation is the second numeric component (percent).
|
||||
// Modern jsdom usually converts hsl() to rgb, but handle it directly for
|
||||
// safety across versions and for any engine that preserves the format.
|
||||
const hsl = color.match(/hsla?\(\s*[\d.-]+\s*,?\s*([\d.]+)%/i);
|
||||
if (hsl) return parseFloat(hsl[1]) < 10;
|
||||
|
||||
// hwb(hue whiteness% blackness%) — a pixel is fully gray when
|
||||
// whiteness + blackness >= 100; chroma-like saturation = 1 - (w+b)/100.
|
||||
const hwb = color.match(/hwb\(\s*[\d.-]+\s+([\d.]+)%\s+([\d.]+)%/i);
|
||||
if (hwb) {
|
||||
const w = parseFloat(hwb[1]), b = parseFloat(hwb[2]);
|
||||
return (1 - Math.min(100, w + b) / 100) < 0.1;
|
||||
}
|
||||
|
||||
// Unknown / unrecognized format — err on the side of DETECTING rather
|
||||
// than silently skipping. This is the opposite of the previous default,
|
||||
// which was the root cause of the oklch bug.
|
||||
return false;
|
||||
}
|
||||
|
||||
function parseRgb(color) {
|
||||
@@ -364,7 +420,7 @@ function colorToHex(c) {
|
||||
// ─── Section 3: Pure Detection ──────────────────────────────────────────────
|
||||
|
||||
function checkBorders(tag, widths, colors, radius) {
|
||||
if (SAFE_TAGS.has(tag)) return [];
|
||||
if (BORDER_SAFE_TAGS.has(tag)) return [];
|
||||
const findings = [];
|
||||
const sides = ['Top', 'Right', 'Bottom', 'Left'];
|
||||
|
||||
@@ -828,7 +884,7 @@ function resolveGradientStops(el, win) {
|
||||
|
||||
function checkElementBordersDOM(el) {
|
||||
const tag = el.tagName.toLowerCase();
|
||||
if (SAFE_TAGS.has(tag)) return [];
|
||||
if (BORDER_SAFE_TAGS.has(tag)) return [];
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width < 20 || rect.height < 20) return [];
|
||||
const style = getComputedStyle(el);
|
||||
|
||||
@@ -82,6 +82,43 @@ describe('detectHtml — jsdom fixtures', () => {
|
||||
assert.ok(borderFindings.length <= 1);
|
||||
});
|
||||
|
||||
it('modern-color-borders: oklch/oklab/lch/lab side-tabs are flagged, neutrals pass', async () => {
|
||||
// Regression for the isNeutralColor bug where any non-rgb() color format
|
||||
// (oklch, oklab, lch, lab — which jsdom does NOT normalize to rgb) was
|
||||
// misclassified as neutral, causing checkBorders() to silently skip
|
||||
// every element with a modern-color side border.
|
||||
//
|
||||
// Also regression for the SAFE_TAGS/label bug: card-shaped <label>
|
||||
// elements (clickable checklist rows with padding + radius + colored
|
||||
// side border) used to be silently skipped because checkBorders'
|
||||
// SAFE_TAGS gate excluded <label>. The fix narrows that gate so card-
|
||||
// shaped labels are checked while plain inline form labels still pass.
|
||||
const f = await detectHtml(path.join(FIXTURES, 'modern-color-borders.html'));
|
||||
const sideTabs = f.filter(r => r.antipattern === 'side-tab');
|
||||
// Eight FLAG cases: oklch x3, oklab, lch, lab — all colored border-left
|
||||
// with a non-zero border-radius — plus two card-shaped <label> cases
|
||||
// (one oklch, one rgb). Each must produce exactly one side-tab.
|
||||
assert.equal(
|
||||
sideTabs.length, 8,
|
||||
`expected 8 side-tab findings from the FLAG column, got ${sideTabs.length}: ${sideTabs.map(r => r.snippet).join('; ')}`
|
||||
);
|
||||
// Every finding must be a border-left (never right/top/bottom) since
|
||||
// that's the only side the fixture decorates.
|
||||
for (const r of sideTabs) {
|
||||
assert.match(r.snippet || '', /border-left:/, `expected border-left, got ${r.snippet}`);
|
||||
}
|
||||
// PASS column must contribute zero border findings of either flavor.
|
||||
// There are 10 pass cases: 6 structural neutrals plus 4 labels (plain
|
||||
// inline form label, label with a neutral gray border, label in a form
|
||||
// row, and a label with a thin 1px colored left border). If any leaks
|
||||
// through, the label exception is over-broad.
|
||||
const borderAccent = f.filter(r => r.antipattern === 'border-accent-on-rounded');
|
||||
assert.equal(
|
||||
borderAccent.length, 0,
|
||||
`expected 0 border-accent-on-rounded, got ${borderAccent.length}: ${borderAccent.map(r => r.snippet).join('; ')}`
|
||||
);
|
||||
});
|
||||
|
||||
it('typography-should-flag: detects all three issues', async () => {
|
||||
const f = await detectHtml(path.join(FIXTURES, 'typography-should-flag.html'));
|
||||
assert.ok(f.some(r => r.antipattern === 'overused-font'));
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Side-Tab with Modern Color Formats (oklch/lab/lch/hsl/hwb)</title>
|
||||
<style>
|
||||
/* Two-column fixture: left col = should-flag, right col = should-pass.
|
||||
The side-tab snippet doesn't embed identifying text, so the test
|
||||
asserts finding counts against the structural ids (#flag-N / #pass-N)
|
||||
by reading the element via data attributes in the check path. Since
|
||||
that isn't available to the Node fixture runner, we instead place a
|
||||
unique number of FLAG elements (each a distinct side-tab) and verify
|
||||
the side-tab count. */
|
||||
|
||||
body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; background: #fafafa; }
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1120px; margin: 0 auto; }
|
||||
.col h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }
|
||||
.case { padding: 12px 16px; margin-bottom: 16px; }
|
||||
.case h3 { font-size: 14px; margin: 0 0 4px; }
|
||||
.case p { font-size: 13px; margin: 0; color: #64748b; }
|
||||
|
||||
/* ── FLAG cases: colored side-tab borders using modern color formats ── */
|
||||
|
||||
/* 1: oklch border-left + border-radius (the canonical real-page bug) */
|
||||
#flag-oklch-1 {
|
||||
width: 400px;
|
||||
background: oklch(100% 0 0);
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid oklch(65% 0.12 250);
|
||||
}
|
||||
|
||||
/* 2: oklch mixed — neutral 1px border all around + colored 4px left +
|
||||
border-radius (mirrors .medication-card from the reproducer) */
|
||||
#flag-oklch-2 {
|
||||
width: 400px;
|
||||
background: oklch(100% 0 0);
|
||||
border: 1px solid oklch(80% 0.05 250);
|
||||
border-left: 4px solid oklch(75% 0.15 45);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 3: oklch alert pattern — colored 4px border-left + border-radius */
|
||||
#flag-oklch-3 {
|
||||
width: 400px;
|
||||
background: oklch(98% 0.01 60);
|
||||
border-left: 4px solid oklch(75% 0.18 45);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 4: oklab border-left + border-radius */
|
||||
#flag-oklab-1 {
|
||||
width: 400px;
|
||||
background: #ffffff;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid oklab(60% 0.1 0.15);
|
||||
}
|
||||
|
||||
/* 5: lch border-left + border-radius */
|
||||
#flag-lch-1 {
|
||||
width: 400px;
|
||||
background: #ffffff;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid lch(55% 60 250);
|
||||
}
|
||||
|
||||
/* 6: lab border-left + border-radius */
|
||||
#flag-lab-1 {
|
||||
width: 400px;
|
||||
background: #ffffff;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid lab(50% 40 -30);
|
||||
}
|
||||
|
||||
/* 7: card-shaped <label> with colored oklch side border + radius
|
||||
(the checklist-item pattern from preop-portal reproducer) */
|
||||
label#flag-label-oklch {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
width: 400px;
|
||||
padding: 16px;
|
||||
background: oklch(100% 0 0);
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid oklch(65% 0.12 250);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 8: card-shaped <label> with colored rgb side border + radius —
|
||||
proves the fix is not oklch-specific */
|
||||
label#flag-label-rgb {
|
||||
display: block;
|
||||
width: 400px;
|
||||
padding: 16px;
|
||||
background: #ffffff;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #3b82f6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ── PASS cases: legitimate or neutral borders, must NOT fire ── */
|
||||
|
||||
/* 1: oklch chroma ~0 — true neutral gray border should not fire */
|
||||
#pass-oklch-neutral {
|
||||
width: 400px;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid oklch(80% 0 0);
|
||||
}
|
||||
|
||||
/* 2: oklch low chroma (0.01) — still perceptually neutral */
|
||||
#pass-oklch-nearneutral {
|
||||
width: 400px;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid oklch(75% 0.01 250);
|
||||
}
|
||||
|
||||
/* 3: oklch colored border all around (not one-sided) — no side-tab */
|
||||
#pass-oklch-allsides {
|
||||
width: 400px;
|
||||
background: #ffffff;
|
||||
border: 3px solid oklch(65% 0.12 250);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 4: oklch colored 1px border-left (too thin to qualify) */
|
||||
#pass-oklch-thin {
|
||||
width: 400px;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border-left: 1px solid oklch(65% 0.12 250);
|
||||
}
|
||||
|
||||
/* 5: lab neutral (chroma near zero) — must not fire */
|
||||
#pass-lab-neutral {
|
||||
width: 400px;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid lab(50% 0 0);
|
||||
}
|
||||
|
||||
/* 6: lch neutral (chroma 0) */
|
||||
#pass-lch-neutral {
|
||||
width: 400px;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid lch(50% 0 0);
|
||||
}
|
||||
|
||||
/* 7: plain inline form label — no border, no padding, no radius.
|
||||
Must NOT be flagged for anything. */
|
||||
label#pass-label-plain {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
/* 8: form label with a small neutral gray left border — neutral color
|
||||
should not fire the colored side-tab rule. */
|
||||
label#pass-label-neutral-border {
|
||||
display: block;
|
||||
width: 400px;
|
||||
padding: 8px;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #e5e7eb;
|
||||
}
|
||||
|
||||
/* 9: inline label inside a form row — default inline layout,
|
||||
no borders, must not fire. */
|
||||
label#pass-label-inline {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* 10: label with 1px colored left border — too thin to qualify
|
||||
(matches the existing 1px thin rule for divs). */
|
||||
label#pass-label-thin {
|
||||
display: block;
|
||||
width: 400px;
|
||||
padding: 8px;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
border-left: 1px solid #3b82f6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="grid">
|
||||
<div class="col" data-col="flag">
|
||||
<h2>Should flag</h2>
|
||||
<div class="case" id="flag-oklch-1"><h3>oklch checklist</h3><p>oklch border-left 3px + radius</p></div>
|
||||
<div class="case" id="flag-oklch-2"><h3>oklch medication card</h3><p>neutral all + colored left 4px</p></div>
|
||||
<div class="case" id="flag-oklch-3"><h3>oklch alert</h3><p>warm border-left 4px + radius</p></div>
|
||||
<div class="case" id="flag-oklab-1"><h3>oklab card</h3><p>oklab border-left 4px + radius</p></div>
|
||||
<div class="case" id="flag-lch-1"><h3>lch card</h3><p>lch border-left 4px + radius</p></div>
|
||||
<div class="case" id="flag-lab-1"><h3>lab card</h3><p>lab border-left 4px + radius</p></div>
|
||||
<label class="case" id="flag-label-oklch">
|
||||
<input type="checkbox">
|
||||
<span>Card-shaped label with oklch side border</span>
|
||||
</label>
|
||||
<label class="case" id="flag-label-rgb">
|
||||
<input type="checkbox">
|
||||
<span>Card-shaped label with rgb side border</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col" data-col="pass">
|
||||
<h2>Should pass</h2>
|
||||
<div class="case" id="pass-oklch-neutral"><h3>oklch neutral</h3><p>chroma 0 gray</p></div>
|
||||
<div class="case" id="pass-oklch-nearneutral"><h3>oklch near-neutral</h3><p>chroma 0.01</p></div>
|
||||
<div class="case" id="pass-oklch-allsides"><h3>oklch all sides</h3><p>uniform border</p></div>
|
||||
<div class="case" id="pass-oklch-thin"><h3>oklch thin</h3><p>1px too thin</p></div>
|
||||
<div class="case" id="pass-lab-neutral"><h3>lab neutral</h3><p>chroma 0</p></div>
|
||||
<div class="case" id="pass-lch-neutral"><h3>lch neutral</h3><p>chroma 0</p></div>
|
||||
<div>
|
||||
<label for="pass-email" id="pass-label-plain">Email address</label>
|
||||
<input type="email" id="pass-email">
|
||||
</div>
|
||||
<label id="pass-label-neutral-border">
|
||||
<input type="checkbox">
|
||||
<span>Label with neutral gray left border</span>
|
||||
</label>
|
||||
<form>
|
||||
<label for="pass-name" id="pass-label-inline">Name:</label>
|
||||
<input type="text" id="pass-name">
|
||||
</form>
|
||||
<label id="pass-label-thin">
|
||||
<input type="checkbox">
|
||||
<span>Label with 1px colored left border (too thin)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/detect-antipatterns-browser.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user