Elements hardcode a hex color that exactly equals an existing global variable. Zero visual change to fix; the fix replaces the hex with var(--name). You can pick a different variable in the fix dialog, picking a non-identical color switches the fix to review tier.
Rule reference
Every one of the 66 checks Bricks Health can raise, why it matters and what to do. Rule codes are stable across releases: ignore-lists, score config and support conversations all reference them.
Severity → penalty points: critical 10 · major 5 · minor 1. Each rule's contribution to a category is capped (a pattern repeated 74× is one decision, not 74 problems), points are density-normalized on larger sites so the issue rate is what's scored (criticals stay full-weight at any size) and the total feeds a diminishing-returns curve, so fixing issues always visibly moves the score.
Design, class and performance checks cover every Bricks page and every reusable template (headers, footers, section templates), and classes or variables used only inside a template or a component count as in-use. Responsive checks measure rendered pages only.
Design
Colors, spacing and typography consistency.
A hardcoded color is visually indistinguishable from an existing token (RGB distance ≤ 12), usually an accidental near-duplicate like #FEF5E0 next to a #FEF5E3 token. Fixing consolidates on the token but shifts the rendered color by a hair, so it is a review-tier fix.
A hex color that matches no variable. Repeated values are strong candidates for a design token. The fix turns the color into a new global variable, or lets you assign an existing one, and swaps every usage on the page to it (creating a new variable is a zero-visual-change refactor). Only *literal* hex values are flagged: a color picked through a variable or palette swatch emits var(--name), which is the design system in use, and is never counted as hardcoded.
Most sides of a padding/margin sit exactly on a spacing token while one or two drift off by 1–2px (24/24/24/26 next to a 24px token), usually a typo. The fix sets all sides to the token.
Odd pixel values (not divisible by 2) that match no spacing token, 13px, 37px. They accumulate into visual noise; snap them to the grid manually.
Literal font sizes set per-element instead of via theme styles or classes. Sizes referencing tokens (font-size: var(--text-xl)) are the typography scale being used and are never flagged.
Two global variables hold the same color value. Consolidate on one.
The same section has been copy-pasted onto more than one page, and the copies are identical. The fix converts them into a single Bricks component, which then appears in the builder's Components panel. Measured as no visual change: before and after screenshots of both fixture pages differed by zero pixels. One thing to know before you apply it: converting replaces each copy's inner elements, so their #brxe-... IDs change. Anchors or custom code that target those exact IDs need updating.
Sections that share a structure but differ in their content. These are listed only, with a link to each one in the builder, and there is deliberately no one-click fix: converting them automatically would overwrite what each page actually displays. Use the link to look at them and decide for yourself whether a component makes sense.
Classes
Reusable styling instead of copy-paste.
Three or more same-type elements share ≥75% of their per-element style declarations. Elements that declare different font sizes never group, whatever else they share. Elements styled through Theme Styles presets (a button set to "Primary") do not count, that is the design system working. Groups whose members already share a global class are never flagged, that reuse already exists. The fix extracts the shared declarations into a new global class, attaches it to every element in the group and strips the duplicates, so future changes become one edit.
Two or more global classes have byte-identical settings. Edits to one silently miss the others; merge them. When three or more match, the finding shows a count and lists the class names in its description rather than sprawling them across the title. BEM-structured classes are excluded, element/modifier classes and component roots are deliberately scoped per component, so identical styling is coincidental and merging would couple unrelated components.
A class no element uses. Delete it, or keep it deliberately for upcoming work. Automatic.css utility classes are excluded: where the framework is active they are recognised from its configuration, its generated stylesheets and the ACSS category in your class manager, and shown as managed rather than unused.
A class used by exactly one element. Sometimes intentional structure; often a reuse plan that never happened. Safe to leave, or fold the styling onto the element and drop the class. Two shapes are excluded, because single use is by design: BEM element/modifier classes (block__element, block--modifier), and component roots, a class (.card) that a family of BEM descendants (.card__title) hangs off. Framework/library templates are built almost entirely from these. Unused (zero-use) classes are still reported.
Element-level custom CSS. Invisible to the design system; prefer a class.
Two classes on the same element set the same property to different values, so the result depends on class order. Split the shared properties or drop one. Not flagged: element-level custom CSS (two custom-CSS blocks compose rather than override), and a BEM base plus its own modifier/element (.grid + .grid--horizontal), where the descendant is meant to win.
Performance
Lean structure, right-sized media, lightweight fonts and icons.
An element's type is not registered with Bricks, its provider plugin is missing or deactivated, so it renders nothing for visitors while the builder shows a "PHP class does not exist" placeholder. Reactivate the plugin that registers the element type, or remove the orphaned elements.
A component instance whose component definition no longer exists, the component was deleted or never imported, so the instance renders nothing for visitors (just like a missing element). Re-create or re-import the component, or delete the empty instances from the page. Manual: the plugin will not guess whether you meant to remove that content.
The source upload is far larger than it ever renders. When responsive results are available, the finding measures how wide the image actually displays at each of your breakpoints and recommends a source size (displayed width × 2 for retina, capped at the original), so you know exactly what to export. Background and URL-inserted images matter most, since those bypass WordPress’s srcset.
An image referenced by URL only, no attachment, so no width/height attributes and layout shift on load. The fix links the URL to its media-library attachment when one exists locally.
An element sits 7+ wrappers deep. Flatten pass-through wrappers. Required list semantics (ul/ol/li) are discounted from the count, a card grid built correctly as ul > li > card can’t be flattened, so those levels don’t count and what’s flagged is genuine wrapper depth.
Divs/blocks whose only child is another wrapper (canonical section > container pairs are excluded). Each is DOM weight with no layout role.
Wrappers with no child elements: editing leftovers, or spacer cells used to offset a layout (better done with spacing/grid than empty markup). A childless wrapper is excluded when something is plainly rendering through it: a background image, a data- attribute, an inline style declaring a CSS custom property, or a global class that paints or sizes the box. A class that sets only padding or margin does not count, that is the spacer case this rule exists to report.
Autoplaying media (video/audio elements, background video) competes with critical content for bandwidth and is an accessibility concern. The bare autoplay key is not counted on sliders/carousels, where it means auto-advancing slides.
An unbounded query loop (posts_per_page: -1) scales render time with content growth until the page times out, flagged critical. A *finite* cap is good practice: posts_per_page is a maximum, not the actual count (a loop set to 30 renders however many posts exist, up to 30), so a sensible bound is never flagged. Only a very high finite cap (≥100) with no pagination earns a gentle nudge.
Your site pulls icons from more than one library, Font Awesome, Ionicons, Themify. Bricks loads a separate webfont for every family a page uses (Font Awesome alone is ~1MB of font files), so mixing two or three multiplies that download for a handful of glyphs. The finding lists every family in use and where each is placed. Standardize on one library, or swap the odd icons out and drop the extra font entirely.
One or more Google Fonts are in use while Bricks’ Disable Google Fonts switch is off, so every visitor’s browser connects to Google before your text can render, an extra third-party request chain that delays first paint, and an IP disclosure to Google that EU courts have treated as a GDPR violation. Families are matched against the same list Bricks itself loads from. Host the fonts locally (upload them as Bricks custom fonts or use a local-fonts plugin), point your typography at the local versions, then enable *Bricks → Settings → General → Disable Google Fonts*. The finding clears the moment the switch is on.
More than three distinct webfont families are used across your pages, templates, classes, components and any Theme Style that has conditions: a Theme Style with no conditions never loads, so its fonts are not counted. Each family, and every weight of it, is another download before text settles; most designs need two (one for headings, one for body). Consolidate on fewer families and trim unused weights. Locally-hosted custom fonts and system stacks (system-ui, Arial…) don’t count.
Tokens defined but never referenced in your Bricks content. Adopt them (the Design tab shows where hardcoded values duplicate them) or delete them, the one-click fix re-checks usage and removes only the still-unreferenced ones. Variables organized into a category (e.g. a framework’s synced palette) count as kept-by-intent and are never flagged. It can’t see external or child-theme code, so keep any tokens you use there.
Responsive
Measured in real rendered pages per breakpoint, not from data.
The page scrolls sideways at this breakpoint, the classic broken-on-mobile symptom.
An element extends past the viewport edge. Fixed widths and missing max-width: 100% are the usual causes. Each finding shows the exact overflow, e.g. *“extends 37px past the right edge, spans 0→412px in a 375px viewport”*, so it’s checkable, and sub-pixel spills under 4px (invisible to the eye) are ignored.
An image extends past the viewport edge. Usually a missing max-width: 100% on the image.
Text needs more room than its container provides.
The element sits entirely outside the visible area, visitors never see it.
A flex/grid gap over 100px at this breakpoint usually reads as a mistake.
Interactive elements below ~44px are hard to tap. Measured only at sub-800px viewports.
Accessibility
WCAG 2.2 A and AA checks a machine can decide, cited criterion by criterion. Thirteen of the 55 criteria are decided outright; the rest need a person, and the plugin names which ones rather than hiding them.
An image with no alt text anywhere, neither on the element nor on its media-library attachment. Screen readers announce nothing and the image disappears entirely for anyone who cannot see it. The fix asks you what the image conveys and never invents the words: generated alt text that reads plausibly and describes the wrong thing is worse than none. If the image is purely decorative, marking it so is a real answer and the fix offers it. 1.1.1 Non-text Content (A)
A link or button with an icon but no text and no aria-label, so assistive technology announces it as unlabelled and a keyboard user hears nothing about where it goes. Read from your Bricks settings, so it points at the element in the builder. 4.1.2 Name, Role, Value (A), also 2.4.4 Link Purpose (A) for links.
A form field carrying only a placeholder. A placeholder is not a label: it disappears the moment someone types, and screen readers do not reliably announce it. Give the field a label in the Bricks form builder so its purpose survives before, during and after input. 3.3.2 Labels or Instructions (A)
Fields collecting the visitor's own details, name, email, phone, address, with no autocomplete token, so browsers and assistive tools cannot fill them in. It matters most for people with motor or memory impairments, who otherwise retype the same information on every site. The fix maps each field to the token the spec defines for it. 1.3.5 Identify Input Purpose (AA)
The heading outline jumps a level, h2 straight to h4, which a screen reader user navigating by headings reads as a missing section. Headings carry structure, not size: the fix changes the level and leaves the styling to you. 1.3.1 Info and Relationships (A)
An element with tabindex="3" or similar, pulling it out of the natural reading order and in front of everything else on the page, so keyboard users meet it before content that visually precedes it. The fix removes the attribute and lets DOM order do the work. tabindex="0" is left alone: that one is legitimate. 2.4.3 Focus Order (A)
An embedded frame with no title, announced only as "frame", so someone navigating by landmarks cannot tell a map from a video from an advert. The fix asks you for the title rather than guessing one from the URL. 4.1.2 Name, Role, Value (A)
An aria-* attribute or role on a Bricks element that ARIA does not define, role="buton" and friends. ARIA overrides what an element reports itself to be, so a wrong value is worse than none: it replaces a correct announcement with a broken one. 4.1.2 Name, Role, Value (A)
⚑ This one is not a failure. A video element was found, and no automated check can tell whether its captions are accurate, complete and in sync, so this reaches the person who can watch it. Inaccurate captions are worse than none, which is exactly why no tool should claim to have checked. It will never offer a fix and never be marked as one. 1.2.2 Captions (Prerecorded) (A)
Text measured below 4.5:1 against its actual painted background (3:1 for large text). Measured in a real browser, because the background can come from an ancestor, a global class or a theme style, and no amount of reading builder data will tell you what the text really sits on. The fix offers only colours already in your palette that pass, each labelled with the ratio it reaches, and never invents a shade: a generated "accessible" colour would pass the checker and quietly redesign your site. 1.4.3 Contrast (Minimum) (AA)
A form control's own boundary measured below 3:1 against what surrounds it, so people with low vision cannot tell where the field is. Scoped deliberately to form controls, the one case that is unambiguous: deciding which of a card's four nested backgrounds counts as the adjacent colour is exactly why general tools decline this criterion. Controls carrying a shadow or a distinct fill are skipped, because those are boundaries too. 1.4.11 Non-text Contrast (AA)
Your CSS removes the focus outline and no :focus rule puts a visible indicator back, so keyboard users cannot see where they are. Decided by reading the stylesheets rather than by focusing elements and diffing styles: :focus-visible only matches when the browser judges focus to be keyboard-driven, so the obvious implementation reports every site doing the modern, correct thing as broken. A stylesheet that cannot be read is skipped rather than guessed at. 2.4.7 Focus Visible (AA)
As rendered, a link exposes no accessible name, so it is announced only as "link" with no indication of where it goes. The rendered counterpart to control-missing-name: this one catches links whose name is lost by CSS or markup the builder data cannot show. 2.4.4 Link Purpose (In Context) (A)
A button that renders with nothing to announce. 4.1.2 Name, Role, Value (A)
An input rendering as a button with no value and no label to announce. 4.1.2 Name, Role, Value (A)
A dropdown rendering with no name a screen reader can announce, so its purpose is unavailable to anyone not seeing the surrounding text. 4.1.2 Name, Role, Value (A)
The rendered input has no associated label, so assistive technology cannot say what it collects. Catches fields that markup or a third-party form plugin breaks after the builder has done its part. 4.1.2 Name, Role, Value (A)
A frame rendered without a title, including frames injected by shortcodes and embeds that never appear in your Bricks settings. 4.1.2 Name, Role, Value (A)
An aria-* attribute that is not part of ARIA, so it is ignored entirely. If it was meant to convey state or a name, that information never reaches assistive technology. 4.1.2 Name, Role, Value (A)
The ARIA attribute exists but its value is not one ARIA accepts, so it is discarded. A reference pointing at a missing id is the usual cause. 4.1.2 Name, Role, Value (A)
An element uses a role that does not exist, replacing a correct announcement with a broken one. 4.1.2 Name, Role, Value (A)
An element has a role that obliges certain ARIA attributes to be present, and they are missing, so the widget announces incompletely and its state is unavailable. 4.1.2 Name, Role, Value (A)
Two elements share an id that an ARIA attribute or a label points at. Only the first is ever resolved, so the relationship silently applies to the wrong element or to nothing. Scoped to ids something actually references: duplicated ids nothing points at are a hygiene issue, not an accessibility failure, and the criterion that used to cover them was withdrawn in WCAG 2.2. 4.1.2 Name, Role, Value (A)
An autocomplete attribute whose token is not one browsers recognise, so it cannot fill the field in. 1.3.5 Identify Input Purpose (AA)
The page does not declare its language, so screen readers guess pronunciation rules and often read it in the wrong accent, or unintelligibly. WordPress writes this from your site language, so a missing one means the page template is dropping it: look at whatever renders the opening html tag, usually the Bricks header template or the theme. Reported with that guidance and never offered a one-click fix, because there is no setting to write that would put it back. 3.1.1 Language of Page (A)
A lang attribute that is not a valid language tag, so assistive technology cannot act on it. WordPress derives a valid tag from Settings › General, so a broken value almost always comes from a template or plugin writing its own over the top. 3.1.1 Language of Page (A)
Nothing on the page is marked as the main content area, so people navigating by landmarks cannot jump past the header and navigation. Deliberately minor: structure can also be carried by headings, so this is a prompt rather than a decided failure and is never counted as one. 1.3.1 Info and Relationships (A)
Either no skip link was found, or one exists and points at an id that is not on the page, so the keyboard user who reaches for it is sent nowhere. Bricks adds a skip link by default, and has a setting to turn it off, so both states are reachable on a real site. Deliberately minor: a page can also satisfy the criterion with landmarks and headings. 2.4.1 Bypass Blocks (A)
A control smaller than 24 by 24 pixels, hard to hit accurately for anyone with a tremor, larger fingers, or a moving bus. Measured once by the Responsive audit and cross-referenced here rather than measured twice, so the two audits can never disagree about the same control. 2.5.8 Target Size (Minimum) (AA)
At 320px wide the page forces horizontal scrolling instead of reflowing into one column. Someone zoomed to 400% gets the same narrow viewport, and scrolling in two directions makes text very hard to follow. Cross-referenced from the Responsive audit's real browser measurements. 1.4.10 Reflow (AA)