The Warning
You open DevTools and see this in the console:
Warning: Received `true` for a non-boolean attribute `loading`.
If you intentionally want it to appear in the DOM as a custom attribute,
spell it as lowercase `loading` instead. If you accidentally passed it
from a parent component, remove it from the DOM element.
A custom boolean prop โ loading, isActive, hasError, fetching โ has leaked through to an actual HTML element. React is telling you the DOM received something it doesn't understand.
Why This Happens
HTML attributes and React props are different things. HTML only recognizes a fixed set of boolean attributes: disabled, checked, readonly, required, and a handful of others. Pass loading={true} to a real DOM node, and React has no idea what to do with it โ so it warns you and writes loading="true" into the actual HTML, which is invalid.
The usual culprit is prop spreading. At first glance this looks fine:
// Button.tsx
function Button({ loading, children, ...rest }) {
return (
// loading is destructured out โ so ...rest is safe here
<button {...rest}>
{loading ? 'Loading...' : children}
</button>
);
}
Right, that's actually correct โ loading gets pulled out before rest. The problem hits when you skip the destructuring entirely:
// BAD โ spreads everything, including `loading`, onto <div>
function Card(props) {
return <div {...props} />;
}
Or a parent passes loading down a multi-level chain, and somewhere near the bottom it hits a DOM element without being stripped. This is especially common in component libraries where wrappers forward all props blindly.
Step-by-Step Fix
Fix 1: Destructure before spreading
Pull the custom prop out explicitly. It gets consumed inside the component and never reaches ...rest:
// GOOD
function Button({ loading, children, ...rest }) {
return (
<button {...rest} disabled={loading}>
{loading ? 'Saving...' : children}
</button>
);
}
Simple and zero overhead. This covers the majority of cases.
Fix 2: Forwarding refs
Same rule when you're using forwardRef โ destructure your custom props before the spread:
const Input = React.forwardRef(function Input(
{ loading, isValid, ...rest },
ref
) {
return <input ref={ref} {...rest} />;
});
Both loading and isValid are gone. Only real HTML input attributes flow through ...rest.
Fix 3: Transient props in styled-components
Prefix your prop with $ โ transient props, available since styled-components v5.1. They're consumed during styling and never forwarded to the DOM:
// styled-components v5.1+
const StyledButton = styled.button<{ $loading?: boolean }>`
opacity: ${(p) => (p.$loading ? 0.6 : 1)};
`;
// Usage
<StyledButton $loading={isLoading}>Submit</StyledButton>
Rename the prop at the call site, and styled-components handles the rest.
Fix 4: shouldForwardProp
When renaming isn't an option โ a third-party prop, a design system API you can't change โ block it with shouldForwardProp:
import styled from 'styled-components';
const StyledDiv = styled('div').withConfig({
shouldForwardProp: (prop) => prop !== 'loading' && prop !== 'fetching',
})`
/* styles */
`;
Emotion works the same way:
import styled from '@emotion/styled';
const StyledDiv = styled('div', {
shouldForwardProp: (prop) => prop !== 'loading',
})`
/* styles */
`;
Fix 5: Explicit filtering for generic wrappers
Building a generic wrapper that forwards unknown props? Maintain a blocklist and strip your custom keys before the spread:
const CUSTOM_PROPS = new Set(['loading', 'isActive', 'hasError']);
function GenericWrapper({ children, ...props }: React.HTMLAttributes<HTMLDivElement> & CustomProps) {
const domProps = Object.fromEntries(
Object.entries(props).filter(([key]) => !CUSTOM_PROPS.has(key))
);
return <div {...domProps}>{children}</div>;
}
Verification
Open DevTools and confirm the warning is gone. Then right-click the element โ Inspect โ you should not see loading="true" or loading="" sitting on the HTML node. The fix worked if the attribute has vanished from the DOM and the component still behaves correctly.
Running React 18 with Strict Mode? Components render twice in development. Make sure the warning is clean on both renders, not just the first.
Quick Reference: What React Forwards Without Complaints
These are the HTML boolean attributes React recognizes and passes through cleanly: disabled, checked, defaultChecked, readOnly, multiple, autoFocus, required, selected. Anything custom โ loading, isOpen, fetching โ must be stripped before it touches a DOM element.
When You Actually Want It in the DOM
Sometimes you want a custom attribute visible in the HTML โ for CSS selectors like [data-loading] or as testing hooks. Use the data-* prefix:
<button data-loading={isLoading ? 'true' : undefined}>
Submit
</button>
Valid HTML, no React warnings. One catch: HTML attributes are always strings, so pass 'true' or undefined โ not a boolean.

