Opened 7 weeks ago
#64939 new enhancement
Add opt-out mechanism for global admin CSS on individual elements
| Reported by: |
|
Owned by: | |
|---|---|---|---|
| Milestone: | Awaiting Review | Priority: | normal |
| Severity: | normal | Version: | |
| Component: | General | Keywords: | |
| Focuses: | Cc: |
Description
Problem
WordPress admin stylesheets (forms.css, common.css) apply broad, bare-element selectors that set visual properties on elements like input, textarea, button, a, div, p, and headings. For example:
/* forms.css */ input[type="text"] { border: 1px solid #949494; min-height: 40px; /* ... */ } /* common.css */ a, div { outline: 0; } p { font-size: 13px; line-height: 1.5; margin: 1em 0; }
These rules work well for traditional wp-admin screens, but they conflict with component libraries that manage their own element styles — particularly those using CSS cascade layers, where unlayered admin styles unconditionally win regardless of specificity.
This has been causing inconveniences for years, but is now becoming a blocker for adopting modern CSS architectures like @layer. We have an interim workaround in place for the @wordpress/ui package, but a Core-level solution would benefit the broader ecosystem: any plugin or component library using layered CSS, or simply wanting a clean styling baseline for its UI, faces the same problem.
Proposal
Add a data-wp-no-global-css attribute that opts individual elements out of admin global styles. Each bare element selector in forms.css and common.css would be guarded with :where(:not([data-wp-no-global-css])):
/* Before */ input, select, textarea, button { box-sizing: border-box; font-family: inherit; font-size: inherit; font-weight: inherit; } /* After */ :is(input, select, textarea, button):where(:not([data-wp-no-global-css])) { box-sizing: border-box; font-family: inherit; font-size: inherit; font-weight: inherit; }
An element with the attribute is simply excluded from the rule. Elements without it are unaffected.
Design goals
- Specificity-neutral —
:where()adds zero specificity, so existing overrides of admin styles continue to work. - Per-element — the guard is on each element selector, not an ancestor wrapper, so there's no gap where the wrapper itself still receives admin styles.
- Package-agnostic — usable by
@wordpress/components,@wordpress/ui, or any third-party component library. - Not a styling hook — the attribute name describes an opt-out behavior, not a component identity, so it doesn't invite misuse as a selector by consumers.
- Backwards compatible — the default behavior (no attribute) is identical to today.
Scope
Only the bare/unscoped element selectors in forms.css and common.css need the guard — roughly the first ~335 lines of forms.css and the element resets in common.css. Class-scoped rules (.wp-admin ..., .form-table ..., etc.) are already sufficiently scoped and don't need changes.