From 7f46844523be95e2e53e09a8cbdecc1514412ecb Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 4 Feb 2026 15:17:36 -0600 Subject: [PATCH 1/4] show a warning when content components are used standalone --- packages/@react-spectrum/s2/src/Content.tsx | 22 +++- .../@react-spectrum/s2/test/Content.test.tsx | 102 ++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 packages/@react-spectrum/s2/test/Content.test.tsx diff --git a/packages/@react-spectrum/s2/src/Content.tsx b/packages/@react-spectrum/s2/src/Content.tsx index 662dff0a2a2..d055893305b 100644 --- a/packages/@react-spectrum/s2/src/Content.tsx +++ b/packages/@react-spectrum/s2/src/Content.tsx @@ -11,7 +11,7 @@ */ import {ContextValue, Keyboard as KeyboardAria, Header as RACHeader, Heading as RACHeading, TextContext as RACTextContext, SlotProps, Text as TextAria} from 'react-aria-components'; -import {createContext, forwardRef, ReactNode, useContext} from 'react'; +import {createContext, forwardRef, ReactNode, useContext, useEffect, useRef} from 'react'; import {DOMRef, DOMRefValue} from '@react-types/shared'; import {inertValue} from '@react-aria/utils'; import {StyleString} from '../style/types'; @@ -37,11 +37,26 @@ interface HeadingProps extends Omit { level?: number } +function useWarnIfNoStyles(componentName: string, props: {styles?: string, UNSAFE_className?: string, isHidden?: boolean}) { + let hasWarned = useRef(false); + useEffect(() => { + if (!hasWarned.current && !props.isHidden && !props.styles && !props.UNSAFE_className) { + console.warn( + `${componentName} is being used outside of a component that provides automatic styling. ` + + 'Consider using a standard HTML element instead (e.g.,

,
,

, etc.), ' + + 'and use the \'styles\' prop from the style macro to provide custom styles: https://react-spectrum.adobe.com/styling' + ); + hasWarned.current = true; + } + }, [componentName, props.styles, props.UNSAFE_className, props.isHidden]); +} + export const HeadingContext = createContext, DOMRefValue>>(null); export const Heading = forwardRef(// Wrapper around RAC Heading to unmount when hidden. function Heading(props: HeadingProps, ref: DOMRef) { [props, ref] = useSpectrumContextProps(props, ref, HeadingContext); + useWarnIfNoStyles('Heading', props); let domRef = useDOMRef(ref); let {UNSAFE_className = '', UNSAFE_style, styles = '', isHidden, slot, ...otherProps} = props; if (isHidden) { @@ -62,6 +77,7 @@ export const HeaderContext = createContext, D export const Header = forwardRef(function Header(props: ContentProps, ref: DOMRef) { [props, ref] = useSpectrumContextProps(props, ref, HeaderContext); + useWarnIfNoStyles('Header', props); let domRef = useDOMRef(ref); let {UNSAFE_className = '', UNSAFE_style, styles = '', isHidden, slot, ...otherProps} = props; if (isHidden) { @@ -82,6 +98,7 @@ export const ContentContext = createContext, export const Content = forwardRef(function Content(props: ContentProps, ref: DOMRef) { [props, ref] = useSpectrumContextProps(props, ref, ContentContext); + useWarnIfNoStyles('Content', props); let domRef = useDOMRef(ref); let {UNSAFE_className = '', UNSAFE_style, styles = '', isHidden, slot, ...otherProps} = props; if (isHidden) { @@ -101,6 +118,7 @@ export const TextContext = createContext, DOM export const Text = forwardRef(function Text(props: ContentProps, ref: DOMRef) { [props, ref] = useSpectrumContextProps(props, ref, TextContext); + useWarnIfNoStyles('Text', props); let domRef = useDOMRef(ref); let {UNSAFE_className = '', UNSAFE_style, styles = '', isHidden, slot, children, ...otherProps} = props; let racContext = useContext(RACTextContext); @@ -135,6 +153,7 @@ export const KeyboardContext = createContext, export const Keyboard = forwardRef(function Keyboard(props: ContentProps, ref: DOMRef) { [props, ref] = useSpectrumContextProps(props, ref, KeyboardContext); + useWarnIfNoStyles('Keyboard', props); let domRef = useDOMRef(ref); let {UNSAFE_className = '', UNSAFE_style, styles = '', isHidden, slot, ...otherProps} = props; if (isHidden) { @@ -154,6 +173,7 @@ export const FooterContext = createContext, D export const Footer = forwardRef(function Footer(props: ContentProps, ref: DOMRef) { [props, ref] = useSpectrumContextProps(props, ref, FooterContext); + useWarnIfNoStyles('Footer', props); let domRef = useDOMRef(ref); let {UNSAFE_className = '', UNSAFE_style, styles = '', isHidden, slot, ...otherProps} = props; if (isHidden) { diff --git a/packages/@react-spectrum/s2/test/Content.test.tsx b/packages/@react-spectrum/s2/test/Content.test.tsx new file mode 100644 index 00000000000..80c5883d527 --- /dev/null +++ b/packages/@react-spectrum/s2/test/Content.test.tsx @@ -0,0 +1,102 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {act, pointerMap, render} from '@react-spectrum/test-utils-internal'; +import {ActionButton, ActionMenu, Button, ButtonGroup, Checkbox, Content, Dialog, DialogTrigger, Footer, Header, Heading, Keyboard, MenuItem, Text} from '../src'; +import React from 'react'; +import SaveFloppy from '../s2wf-icons/S2_Icon_SaveFloppy_20_N.svg'; +import userEvent from '@testing-library/user-event'; + +const getStyleWarning = (componentName: string) => `${componentName} is being used outside of a component that provides automatic styling. ` + + 'Consider using a standard HTML element instead (e.g.,

,
,

, etc.), ' + + 'and use the \'styles\' prop from the style macro to provide custom styles: https://react-spectrum.adobe.com/styling'; + +describe('Content components', () => { + let warn: jest.SpyInstance; + let user; + + beforeAll(() => { + jest.useFakeTimers(); + user = userEvent.setup({delay: null, pointerMap}); + }); + + beforeEach(() => { + warn = jest.spyOn(global.console, 'warn').mockImplementation(); + }); + + afterEach(() => { + warn.mockRestore(); + jest.clearAllMocks(); + act(() => jest.runAllTimers()); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it.each` + Name | Component + ${'Heading'} | ${Heading} + ${'Header'} | ${Header} + ${'Content'} | ${Content} + ${'Text'} | ${Text} + ${'Keyboard'} | ${Keyboard} + ${'Footer'} | ${Footer} + `('should warn when $Name is used standalone', ({Name, Component}) => { + render(Test {Name}); + expect(warn).toHaveBeenCalledWith(getStyleWarning(Name)); + }); + + it('should not warn when content components are used correctly', async () => { + let {getByRole} = render( + + Open dialog +

+ {({close}) => ( + <> + Dialog title +
Header text
+ +

This is the main content of the dialog.

+ + alert('copy')}> + Copy + Copy the selected text + ⌘C + + +
+
Don't show this again
+ + + + + + )} +
+ + ); + + // Open the dialog to render all content components + let trigger = getByRole('button'); + await user.click(trigger); + act(() => {jest.runAllTimers();}); + + // Should not show warnings + expect(warn).not.toHaveBeenCalled(); + }); +}); From 75bc53846b019089126685ba4d0467eef12bda99 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 4 Feb 2026 15:50:32 -0600 Subject: [PATCH 2/4] cleanup other tests --- .../s2/test/EditableTableView.test.tsx | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/@react-spectrum/s2/test/EditableTableView.test.tsx b/packages/@react-spectrum/s2/test/EditableTableView.test.tsx index 560fdd4a338..c6bb6a480bf 100644 --- a/packages/@react-spectrum/s2/test/EditableTableView.test.tsx +++ b/packages/@react-spectrum/s2/test/EditableTableView.test.tsx @@ -27,7 +27,6 @@ import { TableHeader, TableView, TableViewProps, - Text, TextField } from '../src'; import Edit from '../s2wf-icons/S2_Icon_Edit_20_N.svg'; @@ -210,16 +209,16 @@ describe('TableView', () => { autoFocus defaultValue={item[column.id!]} name={column.id! as string}> - Eva - Steven - Michael - Sara - Karina - Otto - Matt - Emily - Amelia - Isla + Eva + Steven + Michael + Sara + Karina + Otto + Matt + Emily + Amelia + Isla )}>
{item[column.id]}
@@ -582,16 +581,16 @@ describe('TableView', () => { autoFocus defaultValue={item[column.id!]} name={column.id! as string}> - Eva - Steven - Michael - Sara - Karina - Otto - Matt - Emily - Amelia - Isla + Eva + Steven + Michael + Sara + Karina + Otto + Matt + Emily + Amelia + Isla )}>
{item[column.id]}
From 19b65eb625b7fe7a40d6ac2fb5d5b44e1114193c Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 4 Feb 2026 15:52:53 -0600 Subject: [PATCH 3/4] add env check --- packages/@react-spectrum/s2/src/Content.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@react-spectrum/s2/src/Content.tsx b/packages/@react-spectrum/s2/src/Content.tsx index d055893305b..4b593aa633b 100644 --- a/packages/@react-spectrum/s2/src/Content.tsx +++ b/packages/@react-spectrum/s2/src/Content.tsx @@ -40,7 +40,7 @@ interface HeadingProps extends Omit { function useWarnIfNoStyles(componentName: string, props: {styles?: string, UNSAFE_className?: string, isHidden?: boolean}) { let hasWarned = useRef(false); useEffect(() => { - if (!hasWarned.current && !props.isHidden && !props.styles && !props.UNSAFE_className) { + if (process.env.NODE_ENV !== 'production' && !hasWarned.current && !props.isHidden && !props.styles && !props.UNSAFE_className) { console.warn( `${componentName} is being used outside of a component that provides automatic styling. ` + 'Consider using a standard HTML element instead (e.g.,

,
,

, etc.), ' + From c650b52dd3cd3ea985b11600076b12346aa19c50 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 4 Feb 2026 16:22:30 -0600 Subject: [PATCH 4/4] remove Text StatusLight --- packages/@react-spectrum/s2/src/StatusLight.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/@react-spectrum/s2/src/StatusLight.tsx b/packages/@react-spectrum/s2/src/StatusLight.tsx index ee0b46aa718..7a2ada21f14 100644 --- a/packages/@react-spectrum/s2/src/StatusLight.tsx +++ b/packages/@react-spectrum/s2/src/StatusLight.tsx @@ -17,7 +17,6 @@ import {controlFont, getAllowedOverrides, StyleProps} from './style-utils' with import {createContext, forwardRef, ReactNode} from 'react'; import {filterDOMProps} from '@react-aria/utils'; import {style} from '../style' with {type: 'macro'}; -import {Text} from './Content'; import {useDOMRef} from '@react-spectrum/utils'; import {useIsSkeleton} from './Skeleton'; import {useSpectrumContextProps} from './useSpectrumContextProps'; @@ -134,7 +133,7 @@ export const StatusLight = /*#__PURE__*/ forwardRef(function StatusLight(props: - {children} + {children}

); });