Skip to content

fix: inert react 19 support#12315

Open
logonoff wants to merge 1 commit intopatternfly:mainfrom
logonoff:12295-drawerpanel
Open

fix: inert react 19 support#12315
logonoff wants to merge 1 commit intopatternfly:mainfrom
logonoff:12295-drawerpanel

Conversation

@logonoff
Copy link
Copy Markdown
Member

@logonoff logonoff commented Apr 2, 2026

What: Closes #12295

It might be possible that just using inert: 'true' also fixes this problem but I have not verified if that works

Additional issues: issue due to https://stackoverflow.com/questions/72720469/error-when-using-inert-attribute-with-typescript

Summary by CodeRabbit

  • Bug Fixes
    • Improved inert/focus handling across Drawer, Compass, DualListSelector, Form Field Group, NavExpandable, SearchInput, TreeView items, and table rows to ensure consistent accessibility, focus trapping and keyboard navigation across React versions, preventing hidden or collapsed regions from receiving focus and improving screen reader behavior.
    • Harmonized collapsed/hidden behavior to reduce unexpected focus shifts.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 2, 2026

Walkthrough

Replaced empty-string inert assignments with a version-aware IS_INERT constant and added package-specific inert helpers that compute a boolean-compatible value from React's major version.

Changes

Cohort / File(s) Summary
New helpers
packages/react-core/src/helpers/inert.ts, packages/react-table/src/components/Table/utils/inert.ts
Added exported IS_INERT constants that derive a boolean-compatible value from react's major version (true for React 19+, legacy cast for older versions).
Core components (react-core)
packages/react-core/src/components/Drawer/DrawerPanelContent.tsx, packages/react-core/src/components/Compass/Compass.tsx, packages/react-core/src/components/DualListSelector/DualListSelectorTreeItem.tsx, packages/react-core/src/components/Form/InternalFormFieldGroup.tsx, packages/react-core/src/components/Nav/NavExpandable.tsx, packages/react-core/src/components/SearchInput/SearchInput.tsx, packages/react-core/src/components/TreeView/TreeViewListItem.tsx
Replaced inert: '' with inert: IS_INERT for collapsed/hidden regions and added corresponding imports. No control-flow or public API changes.
Table package
packages/react-table/src/components/Table/Tr.tsx
Replaced inert: '' with inert: IS_INERT for hidden rows when animations are enabled; imports new table util.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • kmcfaul
  • nicolethoen
  • mcoker
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: fixing inert attribute support for React 19, which is the primary focus of all code changes across the PR.
Linked Issues check ✅ Passed The PR successfully addresses issue #12295 by implementing a React version-aware approach that uses boolean true for React 19+ and an empty-string workaround for React <19, eliminating the warning while maintaining cross-version compatibility.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the inert attribute behavior across multiple components and are scoped to the linked issue requirement; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@patternfly-build
Copy link
Copy Markdown
Collaborator

patternfly-build commented Apr 2, 2026

@logonoff logonoff force-pushed the 12295-drawerpanel branch from a88edd6 to 3c37bfc Compare April 2, 2026 23:29
@logonoff logonoff changed the title fix(DrawerPanelContent): inert react 19 support fix: inert react 19 support Apr 2, 2026
@logonoff
Copy link
Copy Markdown
Member Author

logonoff commented Apr 2, 2026

@coderabbitai review

Check if it's possible and compatible with React 17, 18, and 19 to just use inert: 'true' as a cleaner workaround to the problem of inert not being supported prior to React 19

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 2, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd DrawerPanelContent.tsx --exec cat {}

Length of output: 16858


🏁 Script executed:

#!/bin/bash
fd inert --exec cat {}

Length of output: 278


🌐 Web query:

React 19 boolean attribute inert string "true" warning behavior

💡 Result:

In React 19, inert is treated as a real boolean DOM attribute (like disabled). You should pass a boolean, not a string. inert being present means “true”, and absent means “false”. [1][2]

What happens with inert="true" (string)

If you render:

<div inert="true" />

React 19 will print a dev warning like “Received the string true for the boolean attribute inert… Did you mean inert={true}?”, because "true"/"false" strings are a common bug for boolean attributes (the browser treats inert="false" as enabled, since the attribute is still present). Use inert / inert={true} / inert={someBoolean} instead. [3][4]

Special case: inert="" (empty string)

React 19 also warns on inert="" and (importantly) treats it as false: “Received an empty string for a boolean attribute inert. This will treat the attribute as if it were false…”. So the old React 18-era workaround of using "" to mean “true” for inert is no longer correct in React 19. [2]

Correct React 19 usage

<div inert />                 // true
<div inert={true} />          // true
<div inert={isModalOpen} />   // boolean
<div inert={false} />         // false (attribute removed)

Sources:
[1] MDN: HTMLElement.inert (boolean attribute/property) (developer.mozilla.org)
[2] React DOM attribute test snippet showing inert="" warning + treated as false (fossies.org)
[3] Example of React’s “string 'true' for boolean attribute” warning pattern (bootschool.net)
[4] Explanation that "true"/"false" string values for boolean attributes trigger React warnings (deepscan.io)

Citations:


@logonoff Using inert: 'true' (the string "true") is not a viable universal workaround. Here's why it fails across versions:

React version inert: 'true' behavior Result
17 / 18 inert is unrecognized; React passes the string through to the DOM as inert="true" ✅ Functionally works (browser treats any value as present/truthy for boolean attributes)
19 inert is now a first-class boolean attribute ❌ React 19 emits a dev warning: "Received the string true for the boolean attribute inert… Did you mean inert={true}?"

In React 19, inert is treated as a real boolean DOM attribute. Passing the string "true" triggers a React warning about receiving a string for a boolean attribute, similar to how disabled="true" behaves. This is essentially the same class of warning the PR is trying to resolve, just with a different value.

The current implementation in helpers/inert.ts is the correct approach:

// React < 19: empty string '' → attribute presence in DOM → element is inert ✓  
// React >= 19: boolean true  → correct native boolean value → no warning ✓
export const IS_INERT = Number(version.split('.')[0]) >= 19 ? true : ('' as unknown as boolean);
  • In React < 19, inert="" (empty string) was passed through to the DOM as attribute presence, making the element inert. The '' as unknown as boolean cast satisfies TypeScript while preserving that behavior.
  • In React 19, inert="" is treated as false, breaking the old workaround, so the version check correctly switches to true (boolean).

There's no single value that works warning-free across all three React major versions — the version-guarded IS_INERT helper is the right solution.

[review]


🧠 Learnings used
Learnt from: arpanroy41
Repo: patternfly/patternfly-react PR: 12237
File: packages/react-core/src/components/Toolbar/examples/ToolbarFlexGrowAndWidths.tsx:6-6
Timestamp: 2026-02-06T19:58:56.464Z
Learning: In the patternfly-react repository, default imports from 'react' (e.g., `import React from 'react'`) are restricted. React is available globally through the build configuration, so `React.FunctionComponent` and similar React APIs can be used without importing React.

Learnt from: tlabaj
Repo: patternfly/patternfly-react PR: 12288
File: packages/react-core/src/components/Accordion/__tests__/Accordion.test.tsx:144-150
Timestamp: 2026-03-24T14:47:38.734Z
Learning: In PatternFly React, `isPlain` is a public component prop that defaults to `false` by convention, consistent with other PatternFly components. Omitting `isPlain` is treated as equivalent to `isPlain={false}`. Do not suggest changing the default to `undefined` or altering `!isPlain`-based conditional logic, as this would diverge from established PatternFly API patterns and could be a behavioral breaking change.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react-table/src/components/Table/Tr.tsx`:
- Line 7: The Tr.tsx file imports IS_INERT from a private path; replace that
private import by either (A) importing IS_INERT from react-core's public
entrypoint (e.g., from '@patternfly/react-core' or the public helpers index if
the project exports it) or (B) remove the private dependency and implement the
inert detection inline in the Tr component (define a local IS_INERT constant
using the same feature-detection logic used in react-core), then use that local
symbol wherever IS_INERT is referenced in Tr.tsx.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5dde64ab-c303-412d-a62d-d54fd01ce0ff

📥 Commits

Reviewing files that changed from the base of the PR and between a88edd6 and 3c37bfc.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (9)
  • packages/react-core/src/components/Compass/Compass.tsx
  • packages/react-core/src/components/Drawer/DrawerPanelContent.tsx
  • packages/react-core/src/components/DualListSelector/DualListSelectorTreeItem.tsx
  • packages/react-core/src/components/Form/InternalFormFieldGroup.tsx
  • packages/react-core/src/components/Nav/NavExpandable.tsx
  • packages/react-core/src/components/SearchInput/SearchInput.tsx
  • packages/react-core/src/components/TreeView/TreeViewListItem.tsx
  • packages/react-core/src/helpers/inert.ts
  • packages/react-table/src/components/Table/Tr.tsx

Comment thread packages/react-table/src/components/Table/Tr.tsx Outdated
@logonoff logonoff force-pushed the 12295-drawerpanel branch from 3c37bfc to 71ccccd Compare April 2, 2026 23:41
@logonoff
Copy link
Copy Markdown
Member Author

logonoff commented Apr 27, 2026

@kmcfaul / @nicolethoen please take a look, thanks!

@logonoff logonoff force-pushed the 12295-drawerpanel branch from 71ccccd to adcb841 Compare April 27, 2026 17:05
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/react-core/src/helpers/inert.ts (1)

3-7: Tighten the JSDoc — the empty string isn't treated as truthy by React JSX.

The mechanism is slightly different from what the comment implies. In React 17/18, inert is not a recognized DOM attribute, so React forwards whatever you pass (including "") directly to the DOM, and the DOM treats the presence of the attribute as "inert" regardless of value. React 19 added inert to its known boolean‑attribute list, so an empty string is now coerced to false (attribute removed) — hence the need to pass boolean true. Recommend updating the doc and adding a short note that the boolean type is a deliberate widening so consumers can spread it as a JSX prop without ceremony.

📝 Suggested doc tweak
 /**
- * Before React 19, React JSX treats empty string "" as truthy for inert prop.
+ * Cross-version value for the `inert` DOM attribute.
+ *
+ * - React >= 19 recognizes `inert` as a boolean attribute and requires `true`
+ *   (an empty string would be coerced to `false` and the attribute removed).
+ * - React < 19 does not recognize `inert`, so an empty string is forwarded
+ *   to the DOM as `inert=""`; the DOM then treats the attribute's presence
+ *   as inert regardless of value.
+ *
+ * The runtime value is widened to `boolean` so consumers can spread it as a
+ * JSX prop without dealing with a `boolean | ''` union.
  * `@see` {`@link` https://stackoverflow.com/questions/72720469}
  */
 export const IS_INERT = Number(version.split('.')[0]) >= 19 ? true : ('' as unknown as boolean);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-core/src/helpers/inert.ts` around lines 3 - 7, The JSDoc for
IS_INERT is misleading about how empty string is treated; update the comment
above the exported constant IS_INERT to explain that in React 17/18 `inert` is
unknown so React forwards attributes verbatim and the DOM treats presence of the
attribute as truthy regardless of value, whereas React 19 treats `inert` as a
known boolean attribute so empty string is coerced to false (removed), and also
add a short note that the type widening to boolean (the cast) is deliberate so
consumers can spread IS_INERT as a JSX prop without needing to convert types;
reference the IS_INERT constant and the version variable in the comment so
future readers know why the runtime check exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react-core/src/helpers/inert.ts`:
- Around line 1-7: The current IS_INERT computation reads React's version string
naively and will mis-handle prerelease tags like "0.0.0-experimental-...";
update the export const IS_INERT logic to robustly extract the numeric major
version from the version string (e.g. use a regex to find the first contiguous
digits /(\d+)/, parseInt that result, and fall back to 0 when no numeric major
is found) and then compute Number(major) >= 19; change the symbol export const
IS_INERT accordingly so prerelease formats safely fall back to the conservative
behavior.

---

Nitpick comments:
In `@packages/react-core/src/helpers/inert.ts`:
- Around line 3-7: The JSDoc for IS_INERT is misleading about how empty string
is treated; update the comment above the exported constant IS_INERT to explain
that in React 17/18 `inert` is unknown so React forwards attributes verbatim and
the DOM treats presence of the attribute as truthy regardless of value, whereas
React 19 treats `inert` as a known boolean attribute so empty string is coerced
to false (removed), and also add a short note that the type widening to boolean
(the cast) is deliberate so consumers can spread IS_INERT as a JSX prop without
needing to convert types; reference the IS_INERT constant and the version
variable in the comment so future readers know why the runtime check exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 755418a0-9e4c-4f6c-8a98-09dd2604fa26

📥 Commits

Reviewing files that changed from the base of the PR and between 71ccccd and adcb841.

📒 Files selected for processing (10)
  • packages/react-core/src/components/Compass/Compass.tsx
  • packages/react-core/src/components/Drawer/DrawerPanelContent.tsx
  • packages/react-core/src/components/DualListSelector/DualListSelectorTreeItem.tsx
  • packages/react-core/src/components/Form/InternalFormFieldGroup.tsx
  • packages/react-core/src/components/Nav/NavExpandable.tsx
  • packages/react-core/src/components/SearchInput/SearchInput.tsx
  • packages/react-core/src/components/TreeView/TreeViewListItem.tsx
  • packages/react-core/src/helpers/inert.ts
  • packages/react-table/src/components/Table/Tr.tsx
  • packages/react-table/src/components/Table/utils/inert.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/react-core/src/components/Drawer/DrawerPanelContent.tsx
  • packages/react-core/src/components/TreeView/TreeViewListItem.tsx
  • packages/react-core/src/components/Nav/NavExpandable.tsx
  • packages/react-core/src/components/Form/InternalFormFieldGroup.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-table/src/components/Table/utils/inert.ts
  • packages/react-core/src/components/SearchInput/SearchInput.tsx

Comment on lines +1 to +7
import { version } from 'react';

/**
* Before React 19, React JSX treats empty string "" as truthy for inert prop.
* @see {@link https://stackoverflow.com/questions/72720469}
*/
export const IS_INERT = Number(version.split('.')[0]) >= 19 ? true : ('' as unknown as boolean);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Is versiona stable named export from thereact package across React 17, 18, and 19, and what format does it follow?

💡 Result:

Yes. version is a stable named export from the react package across React 17, 18, and 19. Format: it follows the same “version string” format React uses for stable vs prerelease npm channels. For the Latest (stable) channel it’s SemVer-style major.minor.patch (e.g. 18.0.0), but for prerelease channel builds (Canary/Experimental) React generates a version string that includes extra metadata (hash + commit date), in the pattern shown by React’s versioning policy docs (e.g. 18.3.0-canary-- / 0.0.0-experimental--).

Citations:


React's version export is stable, but prerelease builds require special handling.

The version export is confirmed as a stable named export across React 17, 18, and 19. For stable releases, it follows SemVer format (major.minor.patch), so version.split('.')[0] reliably extracts the major version. However, prerelease builds (Canary/Experimental) use an extended format like 0.0.0-experimental-<hash>-<YYYYMMDD>, which would cause the version parsing logic to extract 0 instead of the actual major version. Consider adding a safeguard if prerelease versions need to be handled explicitly, or document that this code path uses the conservative fallback for prerelease builds.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-core/src/helpers/inert.ts` around lines 1 - 7, The current
IS_INERT computation reads React's version string naively and will mis-handle
prerelease tags like "0.0.0-experimental-..."; update the export const IS_INERT
logic to robustly extract the numeric major version from the version string
(e.g. use a regex to find the first contiguous digits /(\d+)/, parseInt that
result, and fall back to 0 when no numeric major is found) and then compute
Number(major) >= 19; change the symbol export const IS_INERT accordingly so
prerelease formats safely fall back to the conservative behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug - [DrawerPanelContent] - Received an empty string for a boolean attribute inert

2 participants