feat(ui): add CSS variable theming system for fields, buttons, controls, and popups#16815
Merged
Conversation
…field theming Replace all hardcoded field color tokens across every field component with a consistent --field-color-* namespace. This makes the entire admin panel field appearance overridable via a single set of CSS custom properties. 27 vars defined in colors.css (non-themed :root block): - Base: --field-color-bg, --field-color-border, --field-color-text, --field-color-placeholder - Hover: --field-color-border-hover - Focus: --field-color-border-focus (aliases --accessibility-focus-color) - Disabled: --field-color-bg-disabled, --field-color-border-disabled, --field-color-text-disabled - Read-only: --field-color-bg-readonly, --field-color-border-readonly, --field-color-text-readonly - Error: --field-color-border-error, --field-color-error-bg, --field-color-error-text - Label: --field-color-label, --field-color-label-required - Description: --field-color-description - Toggle (checkbox/radio): --field-color-toggle-border, --field-color-toggle-border-hover, --field-color-toggle-bg-checked, --field-color-toggle-border-checked, --field-color-toggle-icon, --field-color-toggle-bg-disabled, --field-color-toggle-border-disabled - Upload card: --field-color-upload-bg, --field-color-upload-border Updated files: - css/colors.css, css/forms.css - elements/CodeEditor, elements/DatePicker, elements/ReactSelect - fields/Checkbox, Code, ConfirmPassword, DateTime, Email, FieldDescription, FieldError, FieldLabel, JSON, Number, Password, Point, RadioGroup(/Radio), Relationship, Select, Slug, Tabs/Tab, Text, Textarea, Upload/UploadCard Also fixes legacy undefined var bug in Upload/UploadCard (--bg-default / --border-default) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…der-radius sizing vars Extends the --field-color-* system with three sizing variables so users can create compact or spacious field themes with a single override each: --field-min-height: var(--spacer-5) /* 2rem — how tall inputs are */ --field-padding-inline: var(--spacer-2) /* horizontal inner padding */ --field-border-radius: var(--radius-medium) Updated: forms.css, ReactSelect, DatePicker input, CodeEditor, Code field, Textarea Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…, controls, and popups - Add theme.css with override vars: --button-height, --button-radius, --control-height, --field-min-height, --field-padding-inline, --field-border-radius, --popup-radius, --popup-item-radius, --popup-item-bg-hover, --popup-item-color-hover, --popup-item-bg-selected - Fix button box-sizing to content-box so border adds to height (Figma: 24px + 1px border = 26px total) - Fix large button height to match updated sizing model - Fix SearchBar and Hierarchy search to use border (not outline) + --control-height for correct height and consistent appearance with adjacent buttons - Fix popup background to use CSS var fallback evaluated in popup's own data-theme context, fixing white background regression in default Payload theme - Refactor Localizer to accept renderButton prop; AppHeader passes custom renderButton to own the localizer button appearance, removing CSS hack - Add global :focus-visible rule for consistent focus rings across interactive elements - Replace --radius-medium with --button-radius on ~25 interactive/focusable elements - Fix login form button to use Button component instead of Link with manual sizing - Fix GroupBy to translate select/radio option labels Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Toggler DocumentTogglerProps extends HTMLAttributes<HTMLButtonElement> so these are valid direct props, not extraButtonProps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ps types Allows extraButtonProps to be explicitly typed as it flows through DrawerToggler's ...rest spread down to the Button component. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
📦 esbuild Bundle Analysis for payloadThis analysis was generated by esbuild-bundle-analyzer. 🤖
Largest pathsThese visualization shows top 20 largest paths in the bundle.Meta file: packages/next/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js
Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js
DetailsNext to the size is how much the size has increased or decreased compared with the base branch of this PR.
|
…-to-top - Guard flip-to-top: only flip above trigger when there is actually enough space; if neither direction fits, prefer below (avoids off-screen top render) - Double rAF on initial open so position is recalculated after the browser finishes laying out the newly-visible popup content - Scroll toggler into view before opening columns popup in e2e helper so the popup renders below the button as expected - Consolidate scroll-before-open logic into openListColumns; remove duplicate scroll setup from reorderColumns
- Rename SearchBar (URL-syncing) → ListSearchFilter - Update all consumers: ListControls, HierarchyList, exports - Fix e2e test selectors from removed .search-filter__input to #search-filter-input Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PatrikKozak
previously requested changes
Jun 2, 2026
…tSearchFilter - Replace plain <button> with <Button buttonStyle="ghost" round> in SearchInput - Remove XIcon import (now handled by Button's built-in icon="x") - Position clear button absolutely to avoid flex layout shift on appear - Fix input padding to always reserve space for the clear button - Scope focus border to input only (not the clear button) - Add onClear prop to ListSearchFilter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PatrikKozak
approved these changes
Jun 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR introduces a comprehensive CSS variable theming system for the Payload admin panel, enabling users to deeply customize the UI through CSS custom properties — no
!importantneeded, since all Payload component styles are inside@layer payload-default.What's new
packages/ui/src/css/theme.css(new file)Central location for all component-level override variables. Imported by
styles.css.--field-min-height--field-padding-inline--field-border-radius--button-height--button-radius--control-height--popup-radius--popup-item-radius--popup-item-bg-hover--popup-item-color-hover--popup-item-bg-selectedField color variables (
colors.css)27
--field-color-*variables wired through all field CSS files for consistent field appearance customization.Button sizing fix
Buttons now use
box-sizing: content-boxso the Figma spec of 24px content + 1px border × 2 = 26px total is correct. Large button height also updated accordingly.SearchBar / Hierarchy search fix
outlinetoborder— so the border participates in height calculation like all other inputs--control-height+content-boxto SearchBar, Hierarchy search, and Pagination inputsPopup background fix
Root cause:
PopupusescreatePortaland setsdata-themedirectly on the.popup__contentelement itself. Descendant selectors like[data-theme='dark'] .popup__contentnever matched.Fix: Use a CSS variable fallback evaluated in the element's own context:
This correctly resolves
--color-bg-elevatedin the popup's owndata-themecontext.--popup-bgbecomes a pure user override hook (not defined in defaults).Localizer
renderButtonrefactorLocalizernow accepts arenderButtonprop (same signature asPopup.renderButton).AppHeaderpasses a customrenderButtonthat owns the button appearance, removing a CSS hack that was targeting.btninside.app-header__localizer.Focus rings
Added a global
:focus-visiblerule inutilities.cssand replaced--radius-mediumwith--button-radiuson ~25 interactive/focusable elements for consistency.How users can customize
All Payload styles are in
@layer payload-default. Plain (unlayered) CSS in:rootautomatically wins — no!importantrequired: