Skip to content

feat(S2): show a console warning when content components are used standalone#9604

Closed
reidbarber wants to merge 7 commits intomainfrom
warn-if-content-used-standalone
Closed

feat(S2): show a console warning when content components are used standalone#9604
reidbarber wants to merge 7 commits intomainfrom
warn-if-content-used-standalone

Conversation

@reidbarber
Copy link
Member

@reidbarber reidbarber commented Feb 4, 2026

When content components (Text, Heading, etc.) are used standalone, they don't inherit any styles. This PR's change will show a warning in this case, so the user knows to use native HTML elements with the style macro instead.

This change is also oriented to AI tools that frequently make this mistake, although it seems like they more often look at lint errors instead of browser errors. Not sure this is something we can catch with a lint plugin.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

See unit tests.

🧢 Your Project:

</svg>
</CenterBaseline>
<Text>{children}</Text>
<span>{children}</span>
Copy link
Member Author

Choose a reason for hiding this comment

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

I guess we probably added this for Skeleton loading capabilities. I can see if we go back to Text and update our hook to detect this instead.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah. I guess we're currently relying on the font styles being inherited from the div. You could potentially move those so they are directly applied to the Text instead?

@rspbot
Copy link

rspbot commented Feb 4, 2026

<PickerItem textValue="Emily" id="Emily"><Text>Emily</Text></PickerItem>
<PickerItem textValue="Amelia" id="Amelia"><Text>Amelia</Text></PickerItem>
<PickerItem textValue="Isla" id="Isla"><Text>Isla</Text></PickerItem>
<PickerItem textValue="Eva" id="Eva">Eva</PickerItem>
Copy link
Member

Choose a reason for hiding this comment

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

don't need textValue either then?

export const Heading = forwardRef(// Wrapper around RAC Heading to unmount when hidden.
function Heading(props: HeadingProps, ref: DOMRef<HTMLHeadingElement>) {
[props, ref] = useSpectrumContextProps(props, ref, HeadingContext);
useWarnIfNoStyles('Heading', props);
Copy link
Member

Choose a reason for hiding this comment

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

this is just checking props, would it be better to check that there's a context object? otherwise you wont' get the warning sometimes

you could pass the context to retrieve instead of props
useWarnIfNoStyles('Heading', HeadingContext);

then in the hook, useContext(Context) and just check if that returns an object at all

Copy link
Member

Choose a reason for hiding this comment

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

It's happening after the props are merged from context so should be ok right?

Copy link
Member Author

Choose a reason for hiding this comment

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

I wonder if this would be better?

useWarnIfNoStyles('Heading', ...allContexts);

We could include SkeletonContext and anything else that comes along.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, what I meant was, if we check props, then if someone sets their own props directly on the component, then we don't get the warn.

<Text UNSAFE_className="blah">Foo</Text>

would not warn

If we check just the context, then we know if it's being used inside something providing the styles. And from what we see, a lot of people reach for the UNSAFE, so i think it'd be better to warn for this, then they can just use an html element and won't need the unsafe anymore

console.warn(
`${componentName} is being used outside of a component that provides automatic styling. ` +
'Consider using a standard HTML element instead (e.g., <h1>, <div>, <p>, etc.), ' +
'and use the \'styles\' prop from the style macro to provide custom styles: https://react-spectrum.adobe.com/styling'
Copy link
Member

Choose a reason for hiding this comment

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

It would be className not styles if we are recommending using standard HTML elements. But seems like it could also be an OR instead of an AND? Like, they could use <Heading> or <Text> as long as they also provide styles. Which do we want to recommend? As you noticed, Text does have the benefit of working with skeleton as well so there could be cases where people want to use that.

export const Heading = forwardRef(// Wrapper around RAC Heading to unmount when hidden.
function Heading(props: HeadingProps, ref: DOMRef<HTMLHeadingElement>) {
[props, ref] = useSpectrumContextProps(props, ref, HeadingContext);
useWarnIfNoStyles('Heading', props);
Copy link
Member

Choose a reason for hiding this comment

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

It's happening after the props are merged from context so should be ok right?

</svg>
</CenterBaseline>
<Text>{children}</Text>
<span>{children}</span>
Copy link
Member

Choose a reason for hiding this comment

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

Yeah. I guess we're currently relying on the font styles being inherited from the div. You could potentially move those so they are directly applied to the Text instead?

</CenterBaseline>
}
<Text slot="title">{toast.content.children}</Text>
<TextContext.Provider value={{}}>
Copy link
Member

Choose a reason for hiding this comment

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

what happened here? won't this make the slot="title" unnecessary?

Copy link
Member

Choose a reason for hiding this comment

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

same with all the other components above?

}

function useWarnIfStandalone<T>(componentName: string, context: Context<ContextValue<T, any>>) {
let ctx = useSlottedContext(context);
Copy link
Member

Choose a reason for hiding this comment

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

probably need to pass along the props.slot from the caller as well

@reidbarber
Copy link
Member Author

Going to close this out for now, I think it might be a bit too strict and noisy.

Mainly due to the fact that:

  • These incorrect usages seem more common than I expected, and I think they may produce a ton of unexpected warnings after people upgrade.
  • Using these incorrectly doesn't necessarily result in invalid HTML or inaccessible UIs.

This would probably be better implemented inside a custom ESLint plugin for React Spectrum.

@reidbarber reidbarber closed this Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants