diff --git a/.changeset/tasty-suns-behave.md b/.changeset/tasty-suns-behave.md
new file mode 100644
index 00000000000..b0b7961fc54
--- /dev/null
+++ b/.changeset/tasty-suns-behave.md
@@ -0,0 +1,6 @@
+
+---
+"@primer/react": major
+"@primer/styled-react": patch
+---
+Remove sx property from Button
diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-colorblind-linux.png
deleted file mode 100644
index 57aa4d25ef0..00000000000
Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-colorblind-linux.png and /dev/null differ
diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-dimmed-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-dimmed-linux.png
deleted file mode 100644
index 1fd26a1e4f1..00000000000
Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-dimmed-linux.png and /dev/null differ
diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-high-contrast-linux.png
deleted file mode 100644
index 96bb3a24e79..00000000000
Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-high-contrast-linux.png and /dev/null differ
diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-linux.png
deleted file mode 100644
index 57aa4d25ef0..00000000000
Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-linux.png and /dev/null differ
diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-tritanopia-linux.png
deleted file mode 100644
index 57aa4d25ef0..00000000000
Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-tritanopia-linux.png and /dev/null differ
diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-colorblind-linux.png
deleted file mode 100644
index 1136e949782..00000000000
Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-colorblind-linux.png and /dev/null differ
diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-high-contrast-linux.png
deleted file mode 100644
index 99f7fa8376a..00000000000
Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-high-contrast-linux.png and /dev/null differ
diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-linux.png
deleted file mode 100644
index 1136e949782..00000000000
Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-linux.png and /dev/null differ
diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-tritanopia-linux.png
deleted file mode 100644
index 1136e949782..00000000000
Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-tritanopia-linux.png and /dev/null differ
diff --git a/e2e/components/Button.test.ts b/e2e/components/Button.test.ts
index b48c0475612..22eab45b1ca 100644
--- a/e2e/components/Button.test.ts
+++ b/e2e/components/Button.test.ts
@@ -75,10 +75,6 @@ const stories = [
title: 'Dev Invisible Variants',
id: 'components-button-dev--invisible-variants',
},
- {
- title: 'Dev Sx Prop',
- id: 'components-button-dev--test-sx-prop',
- },
{
title: 'Aria Expanded Buttons',
id: 'components-button-features--expanded-button',
diff --git a/examples/nextjs/src/app/page.tsx b/examples/nextjs/src/app/page.tsx
index 4910ebc2e78..88a1fb74ce4 100644
--- a/examples/nextjs/src/app/page.tsx
+++ b/examples/nextjs/src/app/page.tsx
@@ -1,6 +1,6 @@
'use client'
-import {Button, Stack, Box} from '@primer/react'
+import {Button, Stack} from '@primer/react'
import {useTheme} from '@primer/styled-react'
import styled from 'styled-components'
@@ -28,10 +28,12 @@ const ThemeUser = () => {
export default function IndexPage() {
return (
-
diff --git a/packages/react/src/Button/Button.dev.stories.tsx b/packages/react/src/Button/Button.dev.stories.tsx
index 1ec0d70bb6d..4df2b81c135 100644
--- a/packages/react/src/Button/Button.dev.stories.tsx
+++ b/packages/react/src/Button/Button.dev.stories.tsx
@@ -1,8 +1,6 @@
-import {SearchIcon, TriangleDownIcon, EyeIcon, IssueClosedIcon, HeartFillIcon} from '@primer/octicons-react'
+import {SearchIcon, TriangleDownIcon, EyeIcon, HeartFillIcon} from '@primer/octicons-react'
import {Button, IconButton} from '.'
-import {default as Text} from '../Text'
import {Stack} from '../Stack'
-import classes from './Button.dev.stories.module.css'
export default {
title: 'Components/Button/Dev',
@@ -33,69 +31,6 @@ export const InvisibleVariants = () => {
)
}
-export const TestSxProp = () => {
- const count = 4
- return (
-
-
- Medium Red
-
-
- Red
-
-
- Invariant color overridden
-
-
- Close issue
-
- span': {
- boxShadow: `inset 0 0 0 2px deeppink`,
- },
- },
- }}
- >
- Custom size
-
-
- Overridden Block
-
-
- Watch
-
-
- )
-}
-
export const DisabledButtonVariants = () => {
return (
diff --git a/packages/react/src/Button/Button.docs.json b/packages/react/src/Button/Button.docs.json
index c1b59a42c56..91707c4c7bd 100644
--- a/packages/react/src/Button/Button.docs.json
+++ b/packages/react/src/Button/Button.docs.json
@@ -151,11 +151,6 @@
"type": "'small'\n| 'medium'\n| 'large'",
"defaultValue": "'medium'"
},
- {
- "name": "sx",
- "type": "SystemStyleObject",
- "deprecated": true
- },
{
"name": "trailingIcon",
"type": "React.ComponentType",
diff --git a/packages/react/src/Button/Button.tsx b/packages/react/src/Button/Button.tsx
index 9019ba071a1..ad4c4968a61 100644
--- a/packages/react/src/Button/Button.tsx
+++ b/packages/react/src/Button/Button.tsx
@@ -2,94 +2,17 @@ import {forwardRef} from 'react'
import type {ButtonProps} from './types'
import {ButtonBase} from './ButtonBase'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
-import {defaultSxProp} from '../utils/defaultSxProp'
-import type {BetterSystemStyleObject, CSSCustomProperties} from '../sx'
-
-const ButtonComponent = forwardRef(({children, sx: sxProp = defaultSxProp, ...props}, forwardedRef): JSX.Element => {
- const {block, size = 'medium', leadingVisual, trailingVisual, trailingAction} = props
- let sxStyles = sxProp
- const style: CSSCustomProperties = {}
-
- if (sxProp !== null && Object.keys(sxProp).length > 0) {
- sxStyles = generateCustomSxProp({block, size, leadingVisual, trailingVisual, trailingAction}, sxProp)
-
- // @ts-ignore sxProp can have color attribute
- const {color} = sxProp
- if (color) style['--button-color'] = color
- }
+const ButtonComponent = forwardRef(({children, ...props}, forwardedRef): JSX.Element => {
return (
-
+
{children}
)
}) as PolymorphicForwardRefComponent<'button', ButtonProps>
-// This function is used to generate a custom cssSelector for the sxProp
-
-// The usual sx prop can like this:
-// sx={{
-// [`@media (max-width: 768px)`]: {
-// '& > ul': {
-// backgroundColor: 'deeppink',
-// },
-// '&:hover': {
-// backgroundColor: 'yellow',
-// },
-// },
-// '&:hover': {
-// backgroundColor: 'yellow',
-// },
-// '&': {
-// width : 320px
-// }
-// }}
-//*
-/* What we want for Button styles is this:
-sx={{
-// [`@media (max-width: 768px)`]: {
-// '&[data-attribute="something"] > ul': {
-// backgroundColor: 'deeppink',
-// },
-// '&[data-attribute="something"]:hover': {
-// backgroundColor: 'yellow',
-// },
-// },
-// '&[data-attribute="something"]:hover': {
-// backgroundColor: 'yellow',
-// },
-// '&[data-attribute="something"]': {
-// width : 320px
-// }
-// }}
-
-// We need to make sure we append the customCSSSelector to the original class selector. i.e & - > &[data-attribute="Icon"][data-size="small"]
-*/
-export function generateCustomSxProp(
- props: Partial>,
- providedSx: BetterSystemStyleObject,
-) {
- // Possible data attributes: data-size, data-block, data-no-visuals
- const size = `[data-size="${props.size}"]`
- const block = props.block ? `[data-block="block"]` : ''
- const noVisuals = props.leadingVisual || props.trailingVisual || props.trailingAction ? '' : '[data-no-visuals]'
-
- // this is a custom selector. We need to make sure we add the data attributes to the base css class (& -> &[data-attributename="value"]])
- const cssSelector = `&${size}${block}${noVisuals}` // &[data-size="small"][data-block="block"][data-no-visuals]
-
- const customSxProp: {
- [key: string]: BetterSystemStyleObject
- } = {}
-
- if (!providedSx) return customSxProp
- else {
- customSxProp[cssSelector] = providedSx
- return customSxProp
- }
-}
-
ButtonComponent.displayName = 'Button'
-ButtonComponent.__SLOT__ = Symbol('Button')
+export {ButtonComponent, type ButtonProps}
-export {ButtonComponent}
+ButtonComponent.__SLOT__ = Symbol('Button')
diff --git a/packages/react/src/Button/ButtonBase.tsx b/packages/react/src/Button/ButtonBase.tsx
index d5e3ea3b689..97f3564e35c 100644
--- a/packages/react/src/Button/ButtonBase.tsx
+++ b/packages/react/src/Button/ButtonBase.tsx
@@ -1,12 +1,7 @@
import React, {forwardRef} from 'react'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
-import styled from 'styled-components'
-import sx from '../sx'
-import type {SxProp} from '../sx'
import type {ButtonProps} from './types'
-import {getAlignContentSize} from './styles'
import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef'
-import {defaultSxProp} from '../utils/defaultSxProp'
import {VisuallyHidden} from '../VisuallyHidden'
import Spinner from '../Spinner'
import CounterLabel from '../CounterLabel'
@@ -17,12 +12,6 @@ import {clsx} from 'clsx'
import classes from './ButtonBase.module.css'
import {isElement} from 'react-is'
-// TODO: remove this when we remove the `sx` prop from buttons
-// Styled span component for button content that can handle sx prop
-const BoxTemporaryWorkaround = styled.span`
- ${sx};
-`
-
const renderModuleVisual = (
Visual: React.ElementType | React.ReactElement,
loading: boolean,
@@ -37,294 +26,168 @@ const renderModuleVisual = (
)
-const ButtonBase = forwardRef(
- ({children, as: Component = 'button', sx: sxProp = defaultSxProp, ...props}, forwardedRef): JSX.Element => {
- const {
- leadingVisual: LeadingVisual,
- trailingVisual: TrailingVisual,
- trailingAction: TrailingAction,
- ['aria-describedby']: ariaDescribedBy,
- ['aria-labelledby']: ariaLabelledBy,
- count,
- icon: Icon,
- id,
- variant = 'default',
- size = 'medium',
- alignContent = 'center',
- block = false,
- loading,
- loadingAnnouncement = 'Loading',
- inactive,
- onClick,
- labelWrap,
- className,
- ...rest
- } = props
+const ButtonBase = forwardRef(({children, as: Component = 'button', ...props}, forwardedRef): JSX.Element => {
+ const {
+ leadingVisual: LeadingVisual,
+ trailingVisual: TrailingVisual,
+ trailingAction: TrailingAction,
+ ['aria-describedby']: ariaDescribedBy,
+ ['aria-labelledby']: ariaLabelledBy,
+ count,
+ icon: Icon,
+ id,
+ variant = 'default',
+ size = 'medium',
+ alignContent = 'center',
+ block = false,
+ loading,
+ loadingAnnouncement = 'Loading',
+ inactive,
+ onClick,
+ labelWrap,
+ className,
+ ...rest
+ } = props
- const innerRef = React.useRef(null)
- useRefObjectAsForwardedRef(forwardedRef, innerRef)
+ const innerRef = React.useRef(null)
+ useRefObjectAsForwardedRef(forwardedRef, innerRef)
- const uuid = useId(id)
- const loadingAnnouncementID = `${uuid}-loading-announcement`
+ const uuid = useId(id)
+ const loadingAnnouncementID = `${uuid}-loading-announcement`
- if (__DEV__) {
- /**
- * The Linter yells because it thinks this conditionally calls an effect,
- * but since this is a compile-time flag and not a runtime conditional
- * this is safe, and ensures the entire effect is kept out of prod builds
- * shaving precious bytes from the output, and avoiding mounting a noop effect
- */
- // eslint-disable-next-line react-compiler/react-compiler
- // eslint-disable-next-line react-hooks/rules-of-hooks
- React.useEffect(() => {
- if (
- innerRef.current &&
- !(innerRef.current instanceof HTMLButtonElement) &&
- !((innerRef.current as unknown) instanceof HTMLAnchorElement) &&
- !((innerRef.current as HTMLElement).tagName === 'SUMMARY')
- ) {
- // eslint-disable-next-line no-console
- console.warn('This component should be an instanceof a semantic button or anchor')
+ if (__DEV__) {
+ /**
+ * The Linter yells because it thinks this conditionally calls an effect,
+ * but since this is a compile-time flag and not a runtime conditional
+ * this is safe, and ensures the entire effect is kept out of prod builds
+ * shaving precious bytes from the output, and avoiding mounting a noop effect
+ */
+ // eslint-disable-next-line react-compiler/react-compiler
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ React.useEffect(() => {
+ if (
+ innerRef.current &&
+ !(innerRef.current instanceof HTMLButtonElement) &&
+ !((innerRef.current as unknown) instanceof HTMLAnchorElement) &&
+ !((innerRef.current as HTMLElement).tagName === 'SUMMARY')
+ ) {
+ // eslint-disable-next-line no-console
+ console.warn('This component should be an instanceof a semantic button or anchor')
+ }
+ }, [innerRef])
+ }
+ return (
+
+ Boolean(descriptionID))
+ .join(' ')}
+ // aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state.
+ // We only set it when the button is in a loading state because it will supersede the aria-label when the screen
+ // reader announces the button name.
+ aria-labelledby={
+ loading ? [`${uuid}-label`, ariaLabelledBy].filter(labelID => Boolean(labelID)).join(' ') : ariaLabelledBy
}
- }, [innerRef])
- }
-
- if (sxProp !== defaultSxProp) {
- return (
-
- Boolean(descriptionID))
- .join(' ')}
- // aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state.
- // We only set it when the button is in a loading state because it will supersede the aria-label when the screen
- // reader announces the button name.
- aria-labelledby={
- loading ? [`${uuid}-label`, ariaLabelledBy].filter(labelID => Boolean(labelID)).join(' ') : ariaLabelledBy
- }
- id={id}
- onClick={loading ? undefined : onClick}
- >
- {Icon ? (
- loading ? (
-
- ) : isElement(Icon) ? (
- Icon
- ) : (
-
- )
- ) : (
- <>
-
- {
- /* If there are no leading/trailing visuals/actions to replace with a loading spinner,
- render a loading spiner in place of the button content. */
- loading &&
- !LeadingVisual &&
- !TrailingVisual &&
- !TrailingAction &&
- count === undefined &&
- renderModuleVisual(Spinner, loading, 'loadingSpinner', false)
- }
- {
- /* Render a leading visual unless the button is in a loading state.
- Then replace the leading visual with a loading spinner. */
- LeadingVisual && renderModuleVisual(LeadingVisual, Boolean(loading), 'leadingVisual', false)
- }
- {children && (
-
- {children}
-
- )}
- {
- /* If there is a count, render a counter label unless there is a trailing visual.
- Then render the counter label as a trailing visual.
- Replace the counter label or the trailing visual with a loading spinner if:
- - the button is in a loading state
- - there is no leading visual to replace with a loading spinner
- */
- count !== undefined && !TrailingVisual
- ? renderModuleVisual(
- () => (
-
- {count}
-
- ),
- Boolean(loading) && !LeadingVisual,
- 'trailingVisual',
- true,
- )
- : TrailingVisual
- ? renderModuleVisual(
- TrailingVisual,
- Boolean(loading) && !LeadingVisual,
- 'trailingVisual',
- false,
- )
- : null
- }
-
- {
- /* If there is a trailing action, render it unless the button is in a loading state
- and there is no leading or trailing visual to replace with a loading spinner. */
- TrailingAction &&
- renderModuleVisual(
- TrailingAction,
- Boolean(loading) && !LeadingVisual && !TrailingVisual,
- 'trailingAction',
- false,
- )
- }
- >
- )}
-
- {loading && (
-
- {loadingAnnouncement}
-
- )}
-
- )
- }
- return (
-
- Boolean(descriptionID))
- .join(' ')}
- // aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state.
- // We only set it when the button is in a loading state because it will supersede the aria-label when the screen
- // reader announces the button name.
- aria-labelledby={
- loading ? [`${uuid}-label`, ariaLabelledBy].filter(labelID => Boolean(labelID)).join(' ') : ariaLabelledBy
- }
- id={id}
- // @ts-ignore temporary disable as we migrate to css modules, until we remove PolymorphicForwardRefComponent
- onClick={loading ? undefined : onClick}
- >
- {Icon ? (
- loading ? (
-
- ) : isElement(Icon) ? (
- Icon
- ) : (
-
- )
+ {Icon ? (
+ loading ? (
+
+ ) : isElement(Icon) ? (
+ Icon
) : (
- <>
-
- {
- /* If there are no leading/trailing visuals/actions to replace with a loading spinner,
+
+ )
+ ) : (
+ <>
+
+ {
+ /* If there are no leading/trailing visuals/actions to replace with a loading spinner,
render a loading spiner in place of the button content. */
- loading &&
- !LeadingVisual &&
- !TrailingVisual &&
- !TrailingAction &&
- count === undefined &&
- renderModuleVisual(Spinner, loading, 'loadingSpinner', false)
- }
- {
- /* Render a leading visual unless the button is in a loading state.
+ loading &&
+ !LeadingVisual &&
+ !TrailingVisual &&
+ !TrailingAction &&
+ count === undefined &&
+ renderModuleVisual(Spinner, loading, 'loadingSpinner', false)
+ }
+ {
+ /* Render a leading visual unless the button is in a loading state.
Then replace the leading visual with a loading spinner. */
- LeadingVisual && renderModuleVisual(LeadingVisual, Boolean(loading), 'leadingVisual', false)
- }
- {children && (
-
- {children}
-
- )}
- {
- /* If there is a count, render a counter label unless there is a trailing visual.
+ LeadingVisual && renderModuleVisual(LeadingVisual, Boolean(loading), 'leadingVisual', false)
+ }
+ {children && (
+
+ {children}
+
+ )}
+ {
+ /* If there is a count, render a counter label unless there is a trailing visual.
Then render the counter label as a trailing visual.
Replace the counter label or the trailing visual with a loading spinner if:
- the button is in a loading state
- there is no leading visual to replace with a loading spinner
*/
- count !== undefined && !TrailingVisual
- ? renderModuleVisual(
- () => (
-
- {count}
-
- ),
- Boolean(loading) && !LeadingVisual,
- 'trailingVisual',
- true,
- )
- : TrailingVisual
- ? renderModuleVisual(TrailingVisual, Boolean(loading) && !LeadingVisual, 'trailingVisual', false)
- : null
- }
-
- {
- /* If there is a trailing action, render it unless the button is in a loading state
- and there is no leading or trailing visual to replace with a loading spinner. */
- TrailingAction &&
- renderModuleVisual(
- TrailingAction,
- Boolean(loading) && !LeadingVisual && !TrailingVisual,
- 'trailingAction',
- false,
- )
+ count !== undefined && !TrailingVisual
+ ? renderModuleVisual(
+ () => (
+
+ {count}
+
+ ),
+ Boolean(loading) && !LeadingVisual,
+ 'trailingVisual',
+ true,
+ )
+ : TrailingVisual
+ ? renderModuleVisual(TrailingVisual, Boolean(loading) && !LeadingVisual, 'trailingVisual', false)
+ : null
}
- >
- )}
-
- {loading && (
-
- {loadingAnnouncement}
-
+
+ {
+ /* If there is a trailing action, render it unless the button is in a loading state
+ and there is no leading or trailing visual to replace with a loading spinner. */
+ TrailingAction &&
+ renderModuleVisual(
+ TrailingAction,
+ Boolean(loading) && !LeadingVisual && !TrailingVisual,
+ 'trailingAction',
+ false,
+ )
+ }
+ >
)}
-
- )
- },
-) as PolymorphicForwardRefComponent<'button' | 'a', ButtonProps>
+
+ {loading && (
+
+ {loadingAnnouncement}
+
+ )}
+
+ )
+}) as PolymorphicForwardRefComponent<'button' | 'a', ButtonProps>
export {ButtonBase}
diff --git a/packages/react/src/Button/IconButton.docs.json b/packages/react/src/Button/IconButton.docs.json
index aea91b2869e..8e5929fa90f 100644
--- a/packages/react/src/Button/IconButton.docs.json
+++ b/packages/react/src/Button/IconButton.docs.json
@@ -126,11 +126,6 @@
"required": false,
"description": "If `description` is provided, we will use a Tooltip to describe the button. Then `aria-label` is used to label the button.",
"defaultValue": ""
- },
- {
- "name": "sx",
- "type": "SystemStyleObject",
- "deprecated": true
}
],
"subcomponents": []
diff --git a/packages/react/src/Button/IconButton.tsx b/packages/react/src/Button/IconButton.tsx
index 4b86ea1bd48..7c439047429 100644
--- a/packages/react/src/Button/IconButton.tsx
+++ b/packages/react/src/Button/IconButton.tsx
@@ -2,8 +2,6 @@ import React, {forwardRef} from 'react'
import type {IconButtonProps} from './types'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {ButtonBase} from './ButtonBase'
-import {defaultSxProp} from '../utils/defaultSxProp'
-import {generateCustomSxProp} from './Button'
import {TooltipContext, Tooltip} from '../TooltipV2/Tooltip'
import {TooltipContext as TooltipContextV1} from '../Tooltip/Tooltip'
import classes from './ButtonBase.module.css'
@@ -12,7 +10,6 @@ import {clsx} from 'clsx'
const IconButton = forwardRef(
(
{
- sx: sxProp = defaultSxProp,
icon: Icon,
'aria-label': ariaLabel,
description,
@@ -27,14 +24,6 @@ const IconButton = forwardRef(
},
forwardedRef,
): JSX.Element => {
- let sxStyles = sxProp
- // grap the button props that have associated data attributes in the styles
- const {size = 'medium'} = props
-
- if (sxProp !== null && Object.keys(sxProp).length > 0) {
- sxStyles = generateCustomSxProp({size}, sxProp)
- }
-
// If the icon button is already wrapped in a tooltip, do not add one.
const {tooltipId} = React.useContext(TooltipContext) // Tooltip v2
const {tooltipId: tooltipIdV1} = React.useContext(TooltipContextV1) // Tooltip v1
@@ -49,7 +38,6 @@ const IconButton = forwardRef(
icon={Icon}
className={clsx(className, classes.IconButton)}
data-component="IconButton"
- sx={sxStyles}
type="button"
aria-label={ariaLabel}
disabled={disabled}
@@ -72,7 +60,6 @@ const IconButton = forwardRef(
icon={Icon}
className={clsx(className, classes.IconButton)}
data-component="IconButton"
- sx={sxStyles}
type="button"
aria-keyshortcuts={keyshortcuts ?? undefined}
// If description is provided, we will use the tooltip to describe the button, so we need to keep the aria-label to label the button.
diff --git a/packages/react/src/Button/LinkButton.tsx b/packages/react/src/Button/LinkButton.tsx
index 73cc704ffc1..bac52d497b3 100644
--- a/packages/react/src/Button/LinkButton.tsx
+++ b/packages/react/src/Button/LinkButton.tsx
@@ -3,7 +3,7 @@ import type {LinkButtonProps as BaseLinkButtonProps, ButtonProps} from './types'
import {ButtonBase} from './ButtonBase'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
-export type LinkButtonProps = Omit
+export type LinkButtonProps = BaseLinkButtonProps & ButtonProps
const LinkButton = forwardRef(({children, as: Component = 'a', ...props}, forwardedRef): JSX.Element => {
return (
diff --git a/packages/react/src/Button/__tests__/Button.types.test.tsx b/packages/react/src/Button/__tests__/Button.types.test.tsx
index 50cebbc11a7..866d9b3046d 100644
--- a/packages/react/src/Button/__tests__/Button.types.test.tsx
+++ b/packages/react/src/Button/__tests__/Button.types.test.tsx
@@ -21,9 +21,6 @@ export function ShouldAcceptKnownButtonPropsAndDomProps() {
// current target is assignable to HTMLButtonElement
buttonEl.current = e.currentTarget
}}
- sx={{
- m: 1,
- }}
>
Child
@@ -59,7 +56,6 @@ export function iconButtonOptionalProps() {
<>
-
>
)
}
diff --git a/packages/react/src/Button/styles.ts b/packages/react/src/Button/styles.ts
deleted file mode 100644
index dafe8a8050a..00000000000
--- a/packages/react/src/Button/styles.ts
+++ /dev/null
@@ -1,423 +0,0 @@
-import type {VariantType, AlignContent} from './types'
-import type {Theme} from '../ThemeProvider'
-
-export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme) => {
- const style = {
- default: {
- color: 'btn.text',
- backgroundColor: 'btn.bg',
- boxShadow: `${theme?.shadows.btn.shadow}, ${theme?.shadows.btn.insetShadow}`,
- '&:hover:not([disabled]):not([data-inactive])': {
- backgroundColor: 'btn.hoverBg',
- borderColor: `var(--button-default-borderColor-hover, ${theme?.colors.btn.hoverBorder})`,
- },
- '&:active:not([disabled]):not([data-inactive])': {
- backgroundColor: 'btn.activeBg',
- borderColor: `var(--button-default-borderColor-active, ${theme?.colors.btn.activeBorder})`,
- },
- '&:disabled': {
- color: 'primer.fg.disabled',
- borderColor: `var(--button-default-borderColor-disabled, ${theme?.colors.btn.border})`,
- backgroundColor: `var(--button-default-bgColor-disabled, ${theme?.colors.input.disabledBg})`,
- '[data-component=ButtonCounter]': {
- color: 'inherit',
- },
- },
- '&[aria-expanded=true]': {
- backgroundColor: 'btn.activeBg',
- borderColor: `var(--button-default-borderColor-active, ${theme?.colors.btn.activeBorder})`,
- },
- '[data-component="leadingVisual"], [data-component="trailingVisual"], [data-component="trailingAction"]': {
- color: `var(--button-color, ${theme?.colors.fg.muted})`,
- },
- '[data-component=ButtonCounter]': {
- backgroundColor: 'btn.counterBg',
- },
- '&[data-component="IconButton"][data-no-visuals]:not(:disabled)': {
- color: 'fg.muted',
- },
- },
- primary: {
- color: 'btn.primary.text',
- backgroundColor: 'btn.primary.bg',
- borderColor: 'btn.primary.border',
- boxShadow: `${theme?.shadows.btn.primary.shadow}`,
- '&:hover:not([disabled]):not([data-inactive])': {
- color: 'btn.primary.hoverText',
- backgroundColor: 'btn.primary.hoverBg',
- },
- '&:focus:not([disabled])': {
- boxShadow: 'inset 0 0 0 3px',
- },
- '&:focus-visible:not([disabled])': {
- boxShadow: 'inset 0 0 0 3px',
- },
- '&:active:not([disabled]):not([data-inactive])': {
- backgroundColor: 'btn.primary.selectedBg',
- boxShadow: `${theme?.shadows.btn.primary.selectedShadow}`,
- },
- '&:disabled': {
- color: 'btn.primary.disabledText',
- backgroundColor: 'btn.primary.disabledBg',
- borderColor: 'btn.primary.disabledBorder',
- '[data-component=ButtonCounter]': {
- color: 'inherit',
- },
- },
- '[data-component=ButtonCounter]': {
- backgroundColor: 'btn.primary.counterBg',
- color: 'btn.primary.text',
- },
- '&[aria-expanded=true]': {
- backgroundColor: 'btn.primary.selectedBg',
- boxShadow: `${theme?.shadows.btn.primary.selectedShadow}`,
- },
- },
- danger: {
- color: 'btn.danger.text',
- backgroundColor: 'btn.bg',
- boxShadow: `${theme?.shadows.btn.shadow}`,
- '&:hover:not([disabled]):not([data-inactive])': {
- color: 'btn.danger.hoverText',
- backgroundColor: 'btn.danger.hoverBg',
- borderColor: 'btn.danger.hoverBorder',
- boxShadow: `${theme?.shadows.btn.danger.hoverShadow}`,
- '[data-component=ButtonCounter]': {
- backgroundColor: 'btn.danger.hoverCounterBg',
- color: 'btn.danger.hoverCounterFg',
- },
- },
- '&:active:not([disabled]):not([data-inactive])': {
- color: 'btn.danger.selectedText',
- backgroundColor: 'btn.danger.selectedBg',
- boxShadow: `${theme?.shadows.btn.danger.selectedShadow}`,
- borderColor: 'btn.danger.selectedBorder',
- },
- '&:disabled': {
- color: 'btn.danger.disabledText',
- backgroundColor: 'btn.danger.disabledBg',
- borderColor: `var(--button-default-borderColor-disabled, ${theme?.colors.btn.border})`,
- '[data-component=ButtonCounter]': {
- color: 'btn.danger.disabledCounterFg',
- backgroundColor: 'btn.danger.disabledCounterBg',
- },
- },
- '[data-component=ButtonCounter]': {
- color: 'btn.danger.counterFg',
- backgroundColor: 'btn.danger.counterBg',
- },
- '&[aria-expanded=true]': {
- color: 'btn.danger.selectedText',
- backgroundColor: 'btn.danger.selectedBg',
- boxShadow: `${theme?.shadows.btn.danger.selectedShadow}`,
- borderColor: 'btn.danger.selectedBorder',
- },
- },
- invisible: {
- color: `var(--button-invisible-fgColor-rest, ${theme?.colors.btn.text})`,
- backgroundColor: 'transparent',
- borderColor: 'transparent',
- boxShadow: 'none',
- '&:hover:not([disabled])': {
- backgroundColor: 'actionListItem.default.hoverBg',
- },
- '&:active:not([disabled])': {
- backgroundColor: 'actionListItem.default.activeBg',
- },
- '&:disabled': {
- color: 'primer.fg.disabled',
- backgroundColor: `var(--button-invisible-bgColor-disabled, transparent)`,
- '[data-component=ButtonCounter], [data-component="leadingVisual"], [data-component="trailingAction"]': {
- color: 'inherit',
- },
- },
- '&[aria-expanded=true]': {
- backgroundColor: 'actionListItem.default.selectedBg',
- },
- '&[data-component="IconButton"][data-no-visuals]': {
- color: `var(--button-invisible-iconColor-rest, ${theme?.colors.fg.muted})`,
- },
- '[data-component="trailingAction"]': {
- color: `var(--button-invisible-iconColor-rest, ${theme?.colors.fg.muted})`,
- },
- '[data-component="leadingVisual"]': {
- color: `var(--button-invisible-iconColor-rest, ${theme?.colors.fg.muted})`,
- },
- '[data-component="trailingVisual"]': {
- color: `var(--button-invisible-iconColor-rest, ${theme?.colors.fg.muted})`,
- },
- '&[data-no-visuals]': {
- color: `var(--button-invisible-fgColor-rest, ${theme?.colors.btn.text})`,
- },
- '&:has([data-component="ButtonCounter"])': {
- color: `var(--button-invisible-fgColor-rest, ${theme?.colors.btn.text})`,
- },
- '&:disabled[data-no-visuals]': {
- color: 'primer.fg.disabled',
- '[data-component=ButtonCounter]': {
- color: 'inherit',
- },
- },
- },
- outline: {
- color: 'btn.outline.text',
- boxShadow: `${theme?.shadows.btn.shadow}`,
- borderColor: `var(--button-default-borderColor-rest, ${theme?.colors.btn.border})`,
- backgroundColor: 'btn.bg',
-
- '&:hover:not([disabled]):not([data-inactive])': {
- color: 'btn.outline.hoverText',
- backgroundColor: 'btn.outline.hoverBg',
- borderColor: `${theme?.colors.btn.outline.hoverBorder}`,
- boxShadow: `${theme?.shadows.btn.outline.hoverShadow}`,
- '[data-component=ButtonCounter]': {
- backgroundColor: 'btn.outline.hoverCounterBg',
- color: 'btn.outline.hoverCounterFg',
- },
- },
- '&:active:not([disabled]):not([data-inactive])': {
- color: 'btn.outline.selectedText',
- backgroundColor: 'btn.outline.selectedBg',
- boxShadow: `${theme?.shadows.btn.outline.selectedShadow}`,
- borderColor: `${theme?.colors.btn.outline.selectedBorder}`,
- },
-
- '&:disabled': {
- color: 'btn.outline.disabledText',
- backgroundColor: 'btn.outline.disabledBg',
- borderColor: 'btn.border',
- '[data-component=ButtonCounter]': {
- backgroundColor: 'btn.outline.disabledCounterBg',
- color: 'btn.outline.disabledCounterFg',
- },
- },
- '[data-component=ButtonCounter]': {
- backgroundColor: 'btn.outline.counterBg',
- color: 'btn.outline.counterFg',
- },
- '&[aria-expanded=true]': {
- color: 'btn.outline.selectedText',
- backgroundColor: 'btn.outline.selectedBg',
- boxShadow: `${theme?.shadows.btn.outline.selectedShadow}`,
- borderColor: `var(--button-default-borderColor-active, ${theme?.colors.btn.outline.selectedBorder})`,
- },
- },
- link: {
- color: 'var(--fgColor-link)',
- display: 'inline-block',
- fontSize: 'inherit',
- border: 'none',
- height: 'unset',
- padding: '0',
- minWidth: 'fit-content',
- backgroundColor: 'transparent',
-
- '&:hover:not([disabled]):not([data-inactive])': {
- textDecoration: 'underline',
- },
-
- '&:focus-visible:not([disabled])': {
- outlineOffset: '2px',
- },
-
- '&:disabled': {
- color: 'primer.fg.disabled',
- '[data-component=ButtonCounter], [data-component="leadingVisual"], [data-component="trailingAction"]': {
- color: 'inherit',
- },
- },
-
- '[data-component="text"]': {
- whiteSpace: 'unset',
- },
- },
- }
-
- return style[variant]
-}
-
-export const getBaseStyles = (theme?: Theme) => ({
- borderRadius: '2',
- border: '1px solid',
- borderColor: `var(--button-default-borderColor-rest, ${theme?.colors.btn.border})`,
- fontFamily: 'inherit',
- fontWeight: 'semibold',
- fontSize: '1',
- cursor: 'pointer',
- appearance: 'none',
- userSelect: 'none',
- textDecoration: 'none',
- textAlign: 'center',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'space-between',
- height: '32px',
- padding: '0 12px',
- gap: '8px',
- minWidth: 'max-content',
- transition: '80ms cubic-bezier(0.65, 0, 0.35, 1)',
- transitionProperty: 'color, fill, background-color, border-color',
- '&[href]': {
- display: 'inline-flex',
- '&:hover': {
- textDecoration: 'none',
- },
- },
- '&:hover': {
- transitionDuration: '80ms',
- },
- '&:active': {
- transition: 'none',
- },
- '&[data-inactive]': {
- cursor: 'auto',
- },
- '&:disabled': {
- cursor: 'not-allowed',
- boxShadow: 'none',
- },
- '@media (forced-colors: active)': {
- '&:focus': {
- // Support for Windows high contrast https://sarahmhigley.com/writing/whcm-quick-tips
- outline: 'solid 1px transparent',
- },
- },
- '[data-component=ButtonCounter]': {
- fontSize: '0',
- },
- '&[data-component=IconButton]': {
- display: 'inline-grid',
- padding: 'unset',
- placeContent: 'center',
- width: '32px',
- minWidth: 'unset',
- },
- '&[data-size="small"]': {
- padding: '0 8px',
- height: '28px',
- gap: '4px',
- fontSize: '0',
-
- '[data-component="text"]': {
- lineHeight: '1.6666667',
- },
-
- '[data-component=ButtonCounter]': {
- fontSize: '0',
- },
-
- '[data-component="buttonContent"] > :not(:last-child)': {
- mr: '4px',
- },
-
- '&[data-component=IconButton]': {
- width: '28px',
- padding: 'unset',
- },
- },
- '&[data-size="large"]': {
- padding: '0 16px',
- height: '40px',
- gap: '8px',
-
- '[data-component="buttonContent"] > :not(:last-child)': {
- mr: '8px',
- },
-
- '&[data-component=IconButton]': {
- width: '40px',
- padding: 'unset',
- },
- },
-})
-
-export const getButtonStyles = (theme?: Theme) => {
- const styles = {
- ...getBaseStyles(theme),
- '&[data-block="block"]': {
- width: '100%',
- },
- '&[data-label-wrap="true"]': {
- minWidth: 'fit-content',
- height: 'unset',
- minHeight: 'var(--control-medium-size, 2rem)',
-
- '[data-component="buttonContent"]': {
- flex: '1 1 auto',
- alignSelf: 'stretch',
- paddingBlock: 'calc(var(--control-medium-paddingBlock, 0.375rem) - 2px)',
- },
-
- '[data-component="text"]': {
- whiteSpace: 'unset',
- wordBreak: 'break-word',
- },
-
- '&[data-size="small"]': {
- height: 'unset',
- minHeight: 'var(--control-small-size, 1.75rem)',
-
- '[data-component="buttonContent"]': {
- paddingBlock: 'calc(var(--control-small-paddingBlock, 0.25rem) - 2px)',
- },
- },
-
- '&[data-size="large"]': {
- height: 'unset',
- minHeight: 'var(--control-large-size, 2.5rem)',
- paddingInline: 'var(--control-large-paddingInline-spacious, 1rem)',
-
- '[data-component="buttonContent"]': {
- paddingBlock: 'calc(var(--control-large-paddingBlock, 0.625rem) - 2px)',
- },
- },
- },
- '&[data-inactive]:not([disabled])': {
- backgroundColor: `var(--button-inactive-bgColor, ${theme?.colors.btn.inactive.bg})`,
- borderColor: `var(--button-inactive-bgColor, ${theme?.colors.btn.inactive.bg})`,
- color: `var(--button-inactive-fgColor, ${theme?.colors.btn.inactive.text})`,
- },
- '&[data-inactive]:not([disabled]):focus-visible': {
- boxShadow: 'none',
- },
- '[data-component="leadingVisual"]': {
- gridArea: 'leadingVisual',
- },
- '[data-component="text"]': {
- gridArea: 'text',
- lineHeight: '1.4285714',
- whiteSpace: 'nowrap',
- },
- '[data-component="trailingVisual"]': {
- gridArea: 'trailingVisual',
- },
- '[data-component="trailingAction"]': {
- marginRight: '-4px',
- },
- '[data-component="buttonContent"]': {
- flex: '1 0 auto',
- display: 'grid',
- gridTemplateAreas: '"leadingVisual text trailingVisual"',
- gridTemplateColumns: 'min-content minmax(0, auto) min-content',
- alignItems: 'center',
- alignContent: 'center',
- },
- '[data-component="buttonContent"] > :not(:last-child)': {
- mr: '8px',
- },
- '[data-component="loadingSpinner"]': {
- gridArea: 'text',
- marginRight: '0px !important',
- placeSelf: 'center',
- color: 'fg.muted',
- },
- '[data-component="loadingSpinner"] + [data-component="text"]': {
- visibility: 'hidden',
- },
- }
- return styles
-}
-
-export const getAlignContentSize = (alignContent: AlignContent = 'center') => ({
- justifyContent: alignContent === 'center' ? 'center' : 'flex-start',
-})
diff --git a/packages/react/src/Button/types.ts b/packages/react/src/Button/types.ts
index cc9c183bacb..1394f10962a 100644
--- a/packages/react/src/Button/types.ts
+++ b/packages/react/src/Button/types.ts
@@ -1,16 +1,7 @@
import type React from 'react'
-import styled from 'styled-components'
-import type {SxProp} from '../sx'
-import sx from '../sx'
-import getGlobalFocusStyles from '../internal/utils/getGlobalFocusStyles'
import type {TooltipDirection} from '../TooltipV2'
import type {IconProps} from '@primer/octicons-react'
-export const StyledButton = styled.button`
- ${getGlobalFocusStyles('-2px')};
- ${sx};
-`
-
export type VariantType = 'default' | 'primary' | 'invisible' | 'danger' | 'link'
export type Size = 'small' | 'medium' | 'large'
@@ -56,8 +47,7 @@ export type ButtonBaseProps = {
* Whether the button label should wrap to multiple lines if it is longer than the button width
*/
labelWrap?: boolean
-} & SxProp &
- React.ButtonHTMLAttributes
+} & React.ButtonHTMLAttributes
export type ButtonProps = {
/**
diff --git a/packages/react/src/Overlay/Overlay.features.stories.tsx b/packages/react/src/Overlay/Overlay.features.stories.tsx
index 2966536e1b4..dba4f1d0717 100644
--- a/packages/react/src/Overlay/Overlay.features.stories.tsx
+++ b/packages/react/src/Overlay/Overlay.features.stories.tsx
@@ -62,7 +62,7 @@ export const DropdownOverlay = ({anchorSide, open}: Args) => {
return (
<>
- setIsOpen(!isOpen)}>
+ setIsOpen(!isOpen)}>
open overlay
{isOpen || open ? (
@@ -261,7 +261,7 @@ export const MemexNestedOverlays = ({role, open}: Args) => {
Duration:
-
+
{duration}
@@ -359,7 +359,13 @@ export const NestedOverlays = ({role, open}: Args) => {
setCreateListOverlayOpen(!createListOverlayOpen)}
>
@@ -480,13 +486,13 @@ export const MemexIssueOverlay = ({role, open}: Args) => {
ref={buttonRef}
onClick={() => setEditing(true)}
aria-label="Change issue title"
- sx={{
+ style={{
width: '100%',
- fontSize: 3,
- color: 'fg.default',
- p: 2,
+ fontSize: 'var(--text-title-size-medium)',
+ color: 'var(--fgColor-default)',
+ padding: '8px',
textAlign: 'left',
- borderRadius: '2',
+ borderRadius: 'var(--borderRadius-medium)',
}}
>
{title}
@@ -531,8 +537,8 @@ export const PositionedOverlays = ({right, role, open}: Args) => {
setIsOpen(!isOpen)
setDirection('right')
}}
- sx={{
- mt: 2,
+ style={{
+ marginTop: '8px',
}}
>
Open right overlay
diff --git a/packages/react/src/Spinner/Spinner.examples.stories.tsx b/packages/react/src/Spinner/Spinner.examples.stories.tsx
index f478b988126..eabcd4cdc1c 100644
--- a/packages/react/src/Spinner/Spinner.examples.stories.tsx
+++ b/packages/react/src/Spinner/Spinner.examples.stories.tsx
@@ -42,7 +42,7 @@ export const FullLifecycle = () => {
return (
<>
-
+
Load content
{state === 'loading' && }
diff --git a/packages/react/src/UnderlineNav/UnderlineNav.module.css b/packages/react/src/UnderlineNav/UnderlineNav.module.css
index b016174571a..d905aff3fcf 100644
--- a/packages/react/src/UnderlineNav/UnderlineNav.module.css
+++ b/packages/react/src/UnderlineNav/UnderlineNav.module.css
@@ -3,3 +3,20 @@
align-items: center;
justify-content: space-between;
}
+
+/* More button styles migrated from styles.ts (was moreBtnStyles) */
+.MoreButton {
+ margin: 0; /* reset Safari extra margin */
+ border: 0;
+ background: transparent;
+ font-weight: var(--base-text-weight-normal);
+ box-shadow: none;
+ padding-top: var(--base-size-4);
+ padding-bottom: var(--base-size-4);
+ padding-left: var(--base-size-8);
+ padding-right: var(--base-size-8);
+
+ & > [data-component='trailingVisual'] {
+ margin-left: 0;
+ }
+}
diff --git a/packages/react/src/UnderlineNav/UnderlineNav.tsx b/packages/react/src/UnderlineNav/UnderlineNav.tsx
index 796d7f053da..c72f9c57577 100644
--- a/packages/react/src/UnderlineNav/UnderlineNav.tsx
+++ b/packages/react/src/UnderlineNav/UnderlineNav.tsx
@@ -5,7 +5,7 @@ import type {ResizeObserverEntry} from '../hooks/useResizeObserver'
import {useResizeObserver} from '../hooks/useResizeObserver'
import type {ChildWidthArray, ResponsiveProps, ChildSize} from './types'
import VisuallyHidden from '../_VisuallyHidden'
-import {moreBtnStyles, dividerStyles, menuItemStyles, baseMenuMinWidth} from './styles'
+import {dividerStyles, menuItemStyles, baseMenuMinWidth} from './styles'
import {UnderlineItemList, UnderlineWrapper, LoadingCounter, GAP} from '../internal/components/UnderlineTabbedInterface'
import {Button} from '../Button'
import {TriangleDownIcon} from '@primer/octicons-react'
@@ -334,7 +334,7 @@ export const UnderlineNav = forwardRef(
{!onlyMenuVisible && }
span[data-component="trailingVisual"]': {
- marginLeft: 0,
- },
-}
-
export const menuItemStyles = {
// This is needed to hide the selected check icon on the menu item. https://github.com/primer/react/tree/main/packages/react/src/ActionList/Selection.tsx#L32
'& > span': {
diff --git a/packages/react/src/experimental/SelectPanel2/SelectPanel.examples.stories.module.css b/packages/react/src/experimental/SelectPanel2/SelectPanel.examples.stories.module.css
index af5ee03ad03..5fb9501eb35 100644
--- a/packages/react/src/experimental/SelectPanel2/SelectPanel.examples.stories.module.css
+++ b/packages/react/src/experimental/SelectPanel2/SelectPanel.examples.stories.module.css
@@ -23,6 +23,22 @@
color: var(--fgColor-muted);
}
+.WideButton {
+ width: 200px;
+}
+
+.WideButton:where([data-component='buttonContent']) {
+ justify-content: start;
+}
+
+.HiddenSelection:where([data-component='ActionList.Selection']) {
+ display: none;
+}
+
+.NormalFontWeight:where([data-component='text']) {
+ font-weight: var(--base-text-weight-normal);
+}
+
.TextMuted {
color: var(--fgColor-muted);
}
diff --git a/packages/react/src/internal/components/BoxWithFallback.tsx b/packages/react/src/internal/components/BoxWithFallback.tsx
deleted file mode 100644
index 17e9e716c04..00000000000
--- a/packages/react/src/internal/components/BoxWithFallback.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react'
-import Box from '../../Box'
-import {defaultSxProp} from '../../utils/defaultSxProp'
-import type {StyledComponent} from 'styled-components'
-import {includesSystemProps} from '../../utils/includeSystemProps'
-
-const BoxWithFallback = React.forwardRef(function BoxWithFallback(
- {as: BaseComponent = 'div', sx = defaultSxProp, ...rest},
- ref,
-) {
- if (sx !== defaultSxProp || includesSystemProps(rest)) {
- return
- }
- return
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
-}) as StyledComponent<'div', any, React.ComponentPropsWithoutRef, never>
-
-export {BoxWithFallback}
diff --git a/packages/react/src/internal/utils/getGlobalFocusStyles.ts b/packages/react/src/internal/utils/getGlobalFocusStyles.ts
deleted file mode 100644
index 1c22a9d448e..00000000000
--- a/packages/react/src/internal/utils/getGlobalFocusStyles.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import type {CSSProperties} from 'react'
-import {css} from 'styled-components'
-import {get} from '../../constants'
-
-const globalFocusStyle = css`
- box-shadow: none;
- outline: 2px solid ${get('colors.accent.fg')};
-`
-
-const getGlobalFocusStyles = (outlineOffset?: CSSProperties['outlineOffset']) => css`
- /* fallback :focus state */
- &:focus:not(:disabled) {
- ${globalFocusStyle};
- outline-offset: ${typeof outlineOffset === 'undefined' ? '2px' : outlineOffset};
-
- // remove fallback :focus if :focus-visible is supported
- &:not(:focus-visible) {
- outline: solid 1px transparent;
- }
- }
-
- /* default focus state */
- &:focus-visible:not(:disabled) {
- ${globalFocusStyle};
- outline-offset: ${typeof outlineOffset === 'undefined' ? '2px' : outlineOffset};
- }
-`
-
-export default getGlobalFocusStyles
diff --git a/packages/react/src/stories/deprecated/ActionMenu.stories.tsx b/packages/react/src/stories/deprecated/ActionMenu.stories.tsx
index df60fc88a22..a0928a4543d 100644
--- a/packages/react/src/stories/deprecated/ActionMenu.stories.tsx
+++ b/packages/react/src/stories/deprecated/ActionMenu.stories.tsx
@@ -230,7 +230,7 @@ export function ComplexListStory(): JSX.Element {
ComplexListStory.storyName = 'Complex List'
export function CustomTrigger(): JSX.Element {
- const customAnchor = (props: ButtonProps) =>
+ const customAnchor = (props: ButtonProps) =>
const [option, setOption] = useState('Select an option')
const onAction = useCallback((itemProps: ItemProps) => {
setOption(itemProps.text || '')
diff --git a/packages/react/src/utils/defaultSxProp.tsx b/packages/react/src/utils/defaultSxProp.tsx
deleted file mode 100644
index 32b4e0684c4..00000000000
--- a/packages/react/src/utils/defaultSxProp.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import type {BetterSystemStyleObject} from '../sx'
-
-export const defaultSxProp: Readonly = __DEV__
- ? Object.freeze({})
- : {}
diff --git a/packages/react/src/utils/includeSystemProps.ts b/packages/react/src/utils/includeSystemProps.ts
deleted file mode 100644
index c958e79144e..00000000000
--- a/packages/react/src/utils/includeSystemProps.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import {COMMON, TYPOGRAPHY, type SystemCommonProps, type SystemTypographyProps} from '../constants'
-import type {SxProp} from '../sx'
-
-const COMMON_PROP_NAMES = new Set(Object.keys(COMMON))
-const TYPOGRAPHY_PROP_NAMES = new Set(Object.keys(TYPOGRAPHY))
-
-const includesSystemProps = (props: SxProp) => {
- if (props.sx) {
- return true
- }
-
- return Object.keys(props).some(prop => {
- return TYPOGRAPHY_PROP_NAMES.has(prop) || COMMON_PROP_NAMES.has(prop)
- })
-}
-
-type TypographyAndCommonProp = SystemTypographyProps & SystemCommonProps
-
-const getTypographyAndCommonProps = (props: TypographyAndCommonProp): TypographyAndCommonProp => {
- let typographyAndCommonProps = {} as TypographyAndCommonProp
- for (const prop of Object.keys(props)) {
- if (TYPOGRAPHY_PROP_NAMES.has(prop) || COMMON_PROP_NAMES.has(prop)) {
- const p = prop as keyof TypographyAndCommonProp
- typographyAndCommonProps = {...typographyAndCommonProps, [p]: props[p]}
- }
- }
-
- return typographyAndCommonProps
-}
-
-export {includesSystemProps, getTypographyAndCommonProps}
diff --git a/packages/styled-react/src/__tests__/primer-react.browser.test.tsx b/packages/styled-react/src/__tests__/primer-react.browser.test.tsx
index 7366520536d..492035072d2 100644
--- a/packages/styled-react/src/__tests__/primer-react.browser.test.tsx
+++ b/packages/styled-react/src/__tests__/primer-react.browser.test.tsx
@@ -52,11 +52,6 @@ describe('@primer/react', () => {
expect(screen.getByTestId('component')).toHaveAttribute('data-variant', 'inset')
})
- test('ActionMenu.Button supports `sx` prop', () => {
- const {container} = render(test)
- expect(window.getComputedStyle(container.firstElementChild!).backgroundColor).toBe('rgb(255, 0, 0)')
- })
-
test('ActionMenu.Overlay supports `sx` prop', async () => {
const user = userEvent.setup()
render(
diff --git a/packages/styled-react/src/components/Button.tsx b/packages/styled-react/src/components/Button.tsx
new file mode 100644
index 00000000000..6988e71f0bd
--- /dev/null
+++ b/packages/styled-react/src/components/Button.tsx
@@ -0,0 +1,101 @@
+import {Button as PrimerButton, type ButtonProps as PrimerButtonProps, type SlotMarker} from '@primer/react'
+import type {SxProp, CSSCustomProperties} from '../sx'
+import type {BetterSystemStyleObject} from '../styled-props'
+import {forwardRef} from 'react'
+import type {ForwardRefComponent} from '../polymorphic'
+import styled from 'styled-components'
+import {sx} from '../sx'
+
+type ButtonComponentProps = PrimerButtonProps & SxProp & {as?: React.ElementType}
+
+const StyledButtonComponent: ForwardRefComponent<'button', ButtonComponentProps> = styled(PrimerButton).withConfig({
+ shouldForwardProp: prop => (prop as keyof ButtonComponentProps) !== 'sx',
+})`
+ ${sx}
+`
+
+const ButtonComponent = forwardRef(({as, sx, style: propStyle, ...props}: ButtonComponentProps, ref) => {
+ const {block, size = 'medium', leadingVisual, trailingVisual, trailingAction} = props
+ let sxStyles: {[key: string]: BetterSystemStyleObject} = {}
+ const style: CSSCustomProperties = {...(propStyle || {})}
+
+ if (sx !== null && Object.keys(sx || {}).length > 0) {
+ sxStyles = generateCustomSxProp(
+ {block, size, leadingVisual, trailingVisual, trailingAction},
+ sx as BetterSystemStyleObject,
+ )
+
+ const {color} = sx as {color?: string}
+ if (color) style['--button-color'] = color
+ }
+
+ return
+}) as ForwardRefComponent<'button', ButtonComponentProps> & SlotMarker
+
+// This function is used to generate a custom cssSelector for the sxProp
+
+// The usual sx prop can like this:
+// sx={{
+// [`@media (max-width: 768px)`]: {
+// '& > ul': {
+// backgroundColor: 'deeppink',
+// },
+// '&:hover': {
+// backgroundColor: 'yellow',
+// },
+// },
+// '&:hover': {
+// backgroundColor: 'yellow',
+// },
+// '&': {
+// width : 320px
+// }
+// }}
+//*
+/* What we want for Button styles is this:
+sx={{
+// [`@media (max-width: 768px)`]: {
+// '&[data-attribute="something"] > ul': {
+// backgroundColor: 'deeppink',
+// },
+// '&[data-attribute="something"]:hover': {
+// backgroundColor: 'yellow',
+// },
+// },
+// '&[data-attribute="something"]:hover': {
+// backgroundColor: 'yellow',
+// },
+// '&[data-attribute="something"]': {
+// width : 320px
+// }
+// }}
+
+// We need to make sure we append the customCSSSelector to the original class selector. i.e & - > &[data-attribute="Icon"][data-size="small"]
+*/
+export function generateCustomSxProp(
+ props: Partial>,
+ providedSx: BetterSystemStyleObject,
+) {
+ // Possible data attributes: data-size, data-block, data-no-visuals
+ const size = `[data-size="${props.size}"]`
+ const block = props.block ? `[data-block="block"]` : ''
+ const noVisuals = props.leadingVisual || props.trailingVisual || props.trailingAction ? '' : '[data-no-visuals]'
+
+ // this is a custom selector. We need to make sure we add the data attributes to the base css class (& -> &[data-attributename="value"]])
+ const cssSelector = `&${size}${block}${noVisuals}` // &[data-size="small"][data-block="block"][data-no-visuals]
+
+ const customSxProp: {
+ [key: string]: BetterSystemStyleObject
+ } = {}
+
+ if (!providedSx) return customSxProp
+ else {
+ customSxProp[cssSelector] = providedSx
+ return customSxProp
+ }
+}
+
+ButtonComponent.displayName = 'Button'
+ButtonComponent.__SLOT__ = PrimerButton.__SLOT__
+
+export {ButtonComponent, type ButtonComponentProps}
diff --git a/packages/styled-react/src/components/FormControl.tsx b/packages/styled-react/src/components/FormControl.tsx
index f70575b7bef..77fb1e599a4 100644
--- a/packages/styled-react/src/components/FormControl.tsx
+++ b/packages/styled-react/src/components/FormControl.tsx
@@ -16,6 +16,7 @@ const FormControlImpl: React.ComponentType = styled(PrimerForm
`
const FormControl = Object.assign(FormControlImpl, {
+ __SLOT__: PrimerFormControl.__SLOT__,
Caption: PrimerFormControl.Caption,
LeadingVisual: PrimerFormControl.LeadingVisual,
Validation: PrimerFormControl.Validation,
diff --git a/packages/styled-react/src/components/IconButton.tsx b/packages/styled-react/src/components/IconButton.tsx
new file mode 100644
index 00000000000..267358447fc
--- /dev/null
+++ b/packages/styled-react/src/components/IconButton.tsx
@@ -0,0 +1,36 @@
+import {
+ IconButton as PrimerIconButton,
+ type IconButtonProps as PrimerIconButtonProps,
+ type SlotMarker,
+ type SxProp,
+} from '@primer/react'
+import {type ForwardRefComponent} from '../polymorphic'
+import {generateCustomSxProp} from './Button'
+import {forwardRef} from 'react'
+import styled from 'styled-components'
+import {sx} from '../sx'
+
+type IconButtonProps = PrimerIconButtonProps & SxProp & {as?: React.ElementType}
+
+const StyledIconButton = styled(PrimerIconButton).withConfig({
+ shouldForwardProp: prop => (prop as keyof IconButtonProps) !== 'sx',
+})`
+ ${sx}
+`
+
+const IconButton = forwardRef(({as, sx, ...props}: IconButtonProps, ref) => {
+ let sxStyles = sx
+ // grap the button props that have associated data attributes in the styles
+ const {size = 'medium'} = props
+
+ if (sx !== null && sx !== undefined && Object.keys(sx).length > 0) {
+ sxStyles = generateCustomSxProp({size}, sx)
+ }
+
+ return
+}) as ForwardRefComponent<'a' | 'button', IconButtonProps> & SlotMarker
+
+IconButton.__SLOT__ = PrimerIconButton.__SLOT__
+
+export {IconButton}
+export type {IconButtonProps}
diff --git a/packages/styled-react/src/components/PageHeader.tsx b/packages/styled-react/src/components/PageHeader.tsx
index 4b099c64d1c..4b703882da0 100644
--- a/packages/styled-react/src/components/PageHeader.tsx
+++ b/packages/styled-react/src/components/PageHeader.tsx
@@ -6,7 +6,7 @@ import {
type PageHeaderTitleAreaProps as PrimerPageHeaderTitleAreaProps,
} from '@primer/react'
import styled from 'styled-components'
-import {sx, type SxProp} from '../sx'
+import {sx, type SxProp, type CSSCustomProperties} from '../sx'
import type {ForwardRefComponent} from '../polymorphic'
import {Box} from './Box'
import type {PropsWithChildren} from 'react'
@@ -44,10 +44,6 @@ function PageHeaderActions({sx, ...rest}: PageHeaderActionsProps) {
type PageHeaderTitleProps = PropsWithChildren & SxProp
-type CSSCustomProperties = {
- [key: `--${string}`]: string | number
-}
-
function StyledPageHeaderTitle({sx, ...rest}: PageHeaderTitleProps) {
const style: CSSCustomProperties = {}
if (sx) {
diff --git a/packages/styled-react/src/components/TextInput.tsx b/packages/styled-react/src/components/TextInput.tsx
index bbd7744ca98..1777d704137 100644
--- a/packages/styled-react/src/components/TextInput.tsx
+++ b/packages/styled-react/src/components/TextInput.tsx
@@ -3,9 +3,8 @@ import {
type TextInputProps as PrimerTextInputProps,
type TextInputActionProps as PrimerTextInputActionProps,
} from '@primer/react'
-import {forwardRef} from 'react'
+import {forwardRef, type ForwardRefExoticComponent, type RefAttributes} from 'react'
import {sx, type SxProp} from '../sx'
-import type {ForwardRefExoticComponent, RefAttributes} from 'react'
import {type ForwardRefComponent} from '../polymorphic'
import styled from 'styled-components'
@@ -33,6 +32,7 @@ type TextInputComposite = ForwardRefExoticComponent