Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ced7ad2
export all utilities
reidbarber Jan 28, 2026
418056f
export remaining utils
reidbarber Jan 28, 2026
2ccfd61
Merge remote-tracking branch 'origin/main' into style-macr-utility-audit
reidbarber Feb 11, 2026
fe2bc92
removed unsafe exports for now
reidbarber Feb 11, 2026
4e620c3
add JSDoc descriptions
reidbarber Feb 11, 2026
0cf5ad7
rename colorScheme() to setColorScheme()
reidbarber Feb 11, 2026
acf0f38
remove raw/keyframes from s2 export
reidbarber Feb 11, 2026
9f6eb4e
export WidthProperties and HeightProperties as types
reidbarber Feb 11, 2026
2034cc0
cleanup JSDocs
reidbarber Feb 11, 2026
68a080f
add docs
reidbarber Feb 11, 2026
ef9ab31
lint
reidbarber Feb 11, 2026
afcd187
Merge remote-tracking branch 'origin/main' into style-macr-utility-audit
reidbarber Feb 23, 2026
c8be472
address review comments
reidbarber Feb 24, 2026
efcd314
extract docs from JSDoc
reidbarber Feb 24, 2026
3003c94
add imports to all examples
reidbarber Feb 24, 2026
c6e6cde
update styles->className in example
reidbarber Mar 9, 2026
15afa36
fix size in md output
reidbarber Mar 9, 2026
302c704
add linearGradient
reidbarber Mar 9, 2026
8ef8ff0
Merge remote-tracking branch 'origin/main' into style-macr-utility-audit
reidbarber Mar 9, 2026
8cc7ebf
remove getAllowedOverrides from exports/docs (could have breaking cha…
reidbarber Mar 10, 2026
4113557
Merge remote-tracking branch 'origin/main' into style-macr-utility-audit
reidbarber Mar 10, 2026
e5c2fd3
remove WidthProperties/HeightProperties since we removed getAllowedOv…
reidbarber Mar 13, 2026
1d716d5
Merge remote-tracking branch 'origin/main' into style-macr-utility-audit
reidbarber Mar 13, 2026
74b6f70
address review comments
reidbarber Mar 17, 2026
12a29ff
Merge remote-tracking branch 'origin/main' into style-macr-utility-audit
reidbarber Mar 17, 2026
d6b5850
rename raw -> css
reidbarber Mar 17, 2026
e2fac29
Merge remote-tracking branch 'origin/main' into style-macr-utility-audit
reidbarber Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/@react-spectrum/s2/src/CoachMark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
import {ButtonContext} from './Button';
import {Card} from './Card';
import {CheckboxContext} from './Checkbox';
import {colorScheme, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {ColorSchemeContext} from './Provider';
import {ContentContext, FooterContext, KeyboardContext, TextContext} from './Content';
import {
Expand All @@ -40,6 +39,7 @@ import {
} from 'react';
import {DividerContext} from './Divider';
import {forwardRefType} from './types';
import {getAllowedOverrides, setColorScheme, StyleProps} from './style-utils' with {type: 'macro'};
import {GlobalDOMAttributes} from '@react-types/shared';
import {ImageContext} from './Image';
import {ImageCoordinator} from './ImageCoordinator';
Expand Down Expand Up @@ -106,7 +106,7 @@ const slideLeftKeyframes = keyframes(`
`);

let popover = style({
...colorScheme(),
...setColorScheme(),
'--s2-container-bg': {
type: 'backgroundColor',
value: 'layer-2'
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-spectrum/s2/src/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
* governing permissions and limitations under the License.
*/

import {colorScheme} from './style-utils' with {type: 'macro'};
import {ColorSchemeContext} from './Provider';
import {DOMRef, GlobalDOMAttributes} from '@react-types/shared';
import {forwardRef, MutableRefObject, useCallback, useContext} from 'react';
import {ModalOverlay, ModalOverlayProps, Modal as RACModal, useLocale} from 'react-aria-components';
import {setColorScheme} from './style-utils' with {type: 'macro'};
import {style} from '../style' with {type: 'macro'};
import {useDOMRef} from '@react-spectrum/utils';

Expand All @@ -28,7 +28,7 @@ interface ModalProps extends Omit<ModalOverlayProps, 'className' | 'style' | 're
}

const modalOverlayStyles = style({
...colorScheme(),
...setColorScheme(),
position: 'absolute',
top: 0,
left: 0,
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-spectrum/s2/src/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import {
OverlayTriggerStateContext,
useLocale
} from 'react-aria-components';
import {colorScheme, getAllowedOverrides, heightProperties, UnsafeStyles, widthProperties} from './style-utils' with {type: 'macro'};
import {ColorSchemeContext} from './Provider';
import {createContext, ForwardedRef, forwardRef, ReactNode, useCallback, useContext, useMemo} from 'react';
import {DOMRef, DOMRefValue, GlobalDOMAttributes} from '@react-types/shared';
import {getAllowedOverrides, heightProperties, setColorScheme, UnsafeStyles, widthProperties} from './style-utils' with {type: 'macro'};
import {lightDark, style} from '../style' with {type: 'macro'};
import {mergeRefs} from '@react-aria/utils';
import {mergeStyles} from '../style/runtime';
Expand Down Expand Up @@ -62,7 +62,7 @@ export interface PopoverProps extends UnsafeStyles, Omit<AriaPopoverProps,
}

let popover = style({
...colorScheme(),
...setColorScheme(),
'--s2-container-bg': {
type: 'backgroundColor',
value: {
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-spectrum/s2/src/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
*/

import type {ColorScheme, Router} from '@react-types/provider';
import {colorScheme, UnsafeStyles} from './style-utils' with {type: 'macro'};
import {createContext, JSX, ReactNode, useContext} from 'react';
import {DOMProps} from '@react-types/shared';
import {filterDOMProps} from '@react-aria/utils';
import {Fonts} from './Fonts';
import {generateDefaultColorSchemeStyles} from './page.macro' with {type: 'macro'};
import {I18nProvider, RouterProvider, useLocale} from 'react-aria-components';
import {mergeStyles} from '../style/runtime';
import {setColorScheme, UnsafeStyles} from './style-utils' with {type: 'macro'};
import {style} from '../style' with {type: 'macro'};
import {StyleString} from '../style/types';

Expand Down Expand Up @@ -77,7 +77,7 @@ export function Provider(props: ProviderProps): JSX.Element {
generateDefaultColorSchemeStyles();

let providerStyles = style({
...colorScheme(),
...setColorScheme(),
'--s2-container-bg': {
type: 'backgroundColor',
value: {
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-spectrum/s2/src/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import {
Virtualizer
} from 'react-aria-components';
import {ButtonGroup} from './ButtonGroup';
import {centerPadding, colorScheme, controlFont, getAllowedOverrides, StylesPropWithHeight, UnsafeStyles} from './style-utils' with {type: 'macro'};
import {centerPadding, controlFont, getAllowedOverrides, setColorScheme, StylesPropWithHeight, UnsafeStyles} from './style-utils' with {type: 'macro'};
import {Checkbox} from './Checkbox';
import Checkmark from '../s2wf-icons/S2_Icon_Checkmark_20_N.svg';
import Chevron from '../ui-icons/Chevron';
Expand Down Expand Up @@ -1189,7 +1189,7 @@ const editableCell = style<CellRenderProps & S2TableProps & {isDivider: boolean,
});

let editPopover = style({
...colorScheme(),
...setColorScheme(),
'--s2-container-bg': {
type: 'backgroundColor',
value: 'layer-2'
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-spectrum/s2/src/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
TooltipRenderProps,
useLocale
} from 'react-aria-components';
import {centerPadding, colorScheme, UnsafeStyles} from './style-utils' with {type: 'macro'};
import {centerPadding, setColorScheme, UnsafeStyles} from './style-utils' with {type: 'macro'};
import {ColorScheme} from '@react-types/provider';
import {ColorSchemeContext} from './Provider';
import {createContext, forwardRef, MutableRefObject, ReactNode, useCallback, useContext, useState} from 'react';
Expand All @@ -44,7 +44,7 @@ export interface TooltipProps extends Omit<AriaTooltipProps, 'children' | 'class
}

const tooltip = style<TooltipRenderProps & {colorScheme: ColorScheme | 'light dark' | null}>({
...colorScheme(),
...setColorScheme(),
justifyContent: 'center',
alignItems: 'center',
maxWidth: 160,
Expand Down
16 changes: 16 additions & 0 deletions packages/@react-spectrum/s2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ export {TreeView, TreeViewItem, TreeViewItemContent, TreeViewLoadMoreItem} from

export {pressScale} from './pressScale';

export {
centerPadding,
setColorScheme
} from './style-utils';

export {mergeStyles} from '../style/runtime';

export {Autocomplete, Collection, FileTrigger, parseColor, useLocale} from 'react-aria-components';
export {useListData, useTreeData, useAsyncList} from 'react-stately';

Expand Down Expand Up @@ -171,3 +178,12 @@ export type {TooltipProps} from './Tooltip';
export type {TreeViewProps, TreeViewItemProps, TreeViewItemContentProps, TreeViewLoadMoreItemProps} from './TreeView';
export type {AutocompleteProps, FileTriggerProps, TooltipTriggerComponentProps as TooltipTriggerProps, SortDescriptor, Color, Key, Selection, RouterConfig} from 'react-aria-components';
export type {ListData, TreeData, AsyncListData} from 'react-stately';

export type {
StylesProp,
StylesPropWithHeight,
StylesPropWithoutWidth,
UnsafeClassName,
UnsafeStyles,
StyleProps
} from './style-utils';
38 changes: 37 additions & 1 deletion packages/@react-spectrum/s2/src/style-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@ import {CSSProperties} from 'react';
import {fontRelative} from '../style';
import {StyleString} from '../style/types';

/**
* Calculates vertical padding to center a single line of text within a container.
* Uses the CSS `self()` function and `1lh` unit to compute the padding based on
* the container's minimum height and border widths.
* This is useful for precise vertical centering without introducing a flex/grid layout to the container.
*
* @param minHeight - A CSS expression for the minimum height to center within. Defaults to `'self(minHeight)'`.
* @returns A CSS `calc()` expression wrapped as an arbitrary style value.
*
* @example
* ```tsx
* import {centerPadding} from '@react-spectrum/s2';
* import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
*
* const styles = style({
* paddingY: centerPadding()
* });
* ```
*/
export function centerPadding(minHeight: string = 'self(minHeight)'): `[${string}]` {
return `[calc((${minHeight} - self(borderTopWidth, 0px) - self(borderBottomWidth, 0px) - 1lh) / 2)]`;
}
Expand Down Expand Up @@ -113,7 +132,24 @@ export const fieldInput = () => ({
containIntrinsicWidth: 'calc(var(--defaultWidth) - self(paddingStart, 0px) - self(paddingEnd, 0px) - self(borderStartWidth, 0px) - self(borderEndWidth, 0px))'
} as const);

export const colorScheme = () => ({
/**
* Returns style properties that set the CSS `color-scheme` for a component.
* Defaults to the page's color scheme and supports `'light'`, `'dark'`, and `'light dark'` values
* via the `colorScheme` render prop condition.
* Intended for root containers (e.g. providers, modals, and popovers), and not needed for individual components.
*
* @example
* ```tsx
* import {setColorScheme} from '@react-spectrum/s2';
* import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
*
* const styles = style({
* ...setColorScheme(),
* backgroundColor: 'layer-1'
* });
* ```
*/
export const setColorScheme = () => ({
colorScheme: {
// Default to page color scheme if none is defined.
default: '[var(--lightningcss-light, light) var(--lightningcss-dark, dark)]',
Expand Down
66 changes: 63 additions & 3 deletions packages/@react-spectrum/s2/style/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,63 @@ import {Inset, fontRelative as internalFontRelative, space as internalSpace, Spa
import type {MacroContext} from '@parcel/macros';
import {StyleString} from './types';

export {baseColor, color, lightDark, colorMix, size, style} from './spectrum-theme';
export {baseColor, color, lightDark, colorMix, linearGradient, size, style} from './spectrum-theme';
export {raw, keyframes} from './style-macro';
export type {StyleString} from './types';

// Wrap these functions in arbitrary value syntax when called from the outside.
/**
* Converts a pixel value to a Spectrum spacing token in `rem` units.
*
* @param px - The spacing in pixels.
* @returns A `rem` value wrapped as an arbitrary style value.
*
* @example
* ```tsx
* import {space} from '@react-spectrum/s2/style' with {type: 'macro'};
*
* const styles = style({
* gap: space(12) // 12/16 = 0.75rem
* });
* ```
*/
export function space(px: number): `[${string}]` {
return `[${internalSpace(px)}]`;
}

export function fontRelative(base: number, baseFontSize?: number): `[${string}]` {
/**
* Converts a pixel value to a font-relative `em` length. Useful for sizing elements
* relative to the current font size. Defaults to a 14px base.
*
* @param base - The pixel value to convert.
* @param baseFontSize - The base font size in pixels to divide by. Defaults to `14`.
* @returns A CSS `em` value wrapped as an arbitrary style value.
*
* @example
* ```tsx
* import {fontRelative} from '@react-spectrum/s2/style' with {type: 'macro'};
*
* const styles = style({
* gap: fontRelative(2) // 2/14 = ~0.143em
* });
* ```
*/
export function fontRelative(base: number, baseFontSize = 14): `[${string}]` {
return `[${internalFontRelative(base, baseFontSize)}]`;
}

/**
* Returns consistent Spectrum focus ring outline styles for interactive components.
Copy link
Member

Choose a reason for hiding this comment

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

we override the results of this one a *lot
I don't think we should export it until we consolidate those into an easier to use macro. Otherwise we semi-lock ourselves into the current implementation and we'd need to build a wrapper macro function

a handful of examples:



Copy link
Member

Choose a reason for hiding this comment

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

just realised, we already export this don't we... womp womp, we should probably build that wrapper around it for ourselves

*
* @example
* ```tsx
* import {focusRing, style} from '@react-spectrum/s2/style' with {type: 'macro'};
*
* const styles = style({
* ...focusRing(),
* borderRadius: 'lg'
* });
* ```
*/
export const focusRing = () => ({
outlineStyle: {
default: 'none',
Expand Down Expand Up @@ -78,6 +123,21 @@ const iconSizes = {
XL: 26
} as const;

/**
* Generates styles for an icon element with the given size, color, and layout options.
* Must be imported with `{type: 'macro'}`.
*
* @param options - Icon styling options including `size` (XS–XL), `color`, and layout properties.
* @returns A `StyleString` that can be applied to an icon element.
*
* @example
* ```tsx
* import {iconStyle} from '@react-spectrum/s2/style' with {type: 'macro'};
* import Edit from '@react-spectrum/s2/icons/Edit';
*
* <Edit styles={iconStyle({size: 'XL', color: 'positive'})} />
* ```
*/
export function iconStyle(this: MacroContext | void, options: IconStyle): StyleString<Exclude<keyof IconStyle, 'color' | 'size'>> {
let {size = 'M', color, ...styles} = options;

Expand Down
16 changes: 16 additions & 0 deletions packages/@react-spectrum/s2/style/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ import {StyleString} from './types';
// };
// }

/**
* Merges multiple style strings together, combining the CSS properties from each.
* Later styles take precedence over earlier ones for the same property.
* Useful for composing styles from multiple `style()` macro calls.
*
* @example
* ```tsx
* import {mergeStyles} from '@react-spectrum/s2';
* import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
*
* const baseStyles = style({padding: 8});
* const overrideStyles = style({padding: 16, color: 'heading'});
* const merged = mergeStyles(baseStyles, overrideStyles);
* // merged has `padding: 16` and `color: heading`.
* ```
*/
export function mergeStyles(...styles: (StyleString | null | undefined)[]): StyleString {
let definedStyles = styles.filter(Boolean) as StyleString[];
if (definedStyles.length === 1) {
Expand Down
Loading
Loading