Skip to content

perf(SelectPanel v2): replace :has(input:placeholder-shown) with data-empty attribute#7904

Draft
mattcosta7 wants to merge 2 commits into
mainfrom
perf/selectpanel2-empty-input
Draft

perf(SelectPanel v2): replace :has(input:placeholder-shown) with data-empty attribute#7904
mattcosta7 wants to merge 2 commits into
mainfrom
perf/selectpanel2-empty-input

Conversation

@mattcosta7
Copy link
Copy Markdown
Contributor

Closes #

Replaces the &:has(input:placeholder-shown) :global(.TextInput-action) selector that hid the clear-action button when the search input is empty. The previous selector forced a descendant :has() evaluation on every keystroke (placeholder-shown toggles with each character typed/deleted), and the inner :global(.TextInput-action) selector required descendant matching across the whole TextInput subtree.

Approach: track the empty state in React state on the SelectPanelSearchInput component, derive an isEmpty value, and pass it through as data-empty on the rendered TextInput wrapper. The CSS now matches a plain attribute on the same .TextInput element.

The controlled/uncontrolled cases are both handled:

const [uncontrolledEmpty, setUncontrolledEmpty] = React.useState(
  () => !(props.value ?? props.defaultValue),
)
const isEmpty = props.value !== undefined ? props.value === '' : uncontrolledEmpty
  • For controlled inputs (value prop provided), isEmpty is derived from the prop directly so it stays in sync without an effect.
  • For uncontrolled inputs, the change handler and the clear-action click handler update the uncontrolledEmpty state.

Changelog

New

  • data-empty attribute on SelectPanel.SearchInput when the search input is empty.

Changed

  • Internal: SelectPanel.module.css clear-action visibility no longer uses :has(input:placeholder-shown).

Removed

  • The &:has(input:placeholder-shown) selector from SelectPanel.module.css.

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

  • All 16 packages/react/src/experimental/SelectPanel2/** unit tests pass.
  • tsc --noEmit on packages/react is clean.
  • Stylelint + Prettier + ESLint clean on touched files (only pre-existing content-visibility browser-compat warning at line 87, unrelated).

VRT expectations: None. The clear-action button is shown/hidden under identical conditions as before — when the input is empty, it's hidden; otherwise, it's shown. The only behavioral difference is timing: the previous :has(input:placeholder-shown) updated on the browser's :placeholder-shown flip (which happens on the same frame as the input event), while the new data-empty flips on the React commit that follows the change event. In practice both update in the same animation frame and the user can't observe the difference.

Part of the :has()-reduction series: #7901 (PageHeader), #7902 (ActionList SubGroup), #7903 (SegmentedControl divider).

Merge checklist

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 29, 2026

🦋 Changeset detected

Latest commit: edbd214

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label May 29, 2026
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Action required

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

TextInput spreads unknown props onto the inner <input> rather than the
.TextInput wrapper, so the data attribute landed on the input element.
Use a general-sibling combinator from the input to the clear-action span
(which sits later in the wrapper).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant