Repository: primer/react
Pull request #6007: refactor(Flash): update sx usage to CSS Modules
Author: login: joshblack
Labels: name: integration-tests: recommended
description: This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm
name: staff
description: Author is a staff member
Created: 2025-05-05T20:13:28Z | Merged: 2025-05-06T19:06:50Z

<!-- Provide the GitHub issue that this issue closes. Start typing the number or name of the issue after the # below. -->

Update the `Flash` component from sx to CSS Modules. This change is not behind a feature flag since we're in the process of removing our GA flags.

<!-- Provide an overview of the changes, including before/after screenshots, videos, or graphs when helpful -->

### Changelog

<!-- Under the headings below, list out relevant API changes that this Pull Request introduces -->

#### New

<!-- List of things added in this PR -->

#### Changed

<!-- List of things changed in this PR -->

- Update the `sx` usage of Flash internally to use CSS Modules instead

#### Removed

<!-- List of things removed in this PR -->

### Rollout strategy

<!-- How do you recommend this change to be rolled out? Refer to [contributor docs on Versioning](https://github.com/primer/react/blob/main/contributor-docs/versioning.md) for details. -->


- [x] Minor release



Diff:
diff --git a/.changeset/giant-bushes-matter.md b/.changeset/giant-bushes-matter.md
new file mode 100644
index 00000000000..6d7dd23f819
--- /dev/null
+++ b/.changeset/giant-bushes-matter.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': minor
+---
+
+Update Flash from sx to CSS Modules
diff --git a/packages/react/src/Flash/Flash.module.css b/packages/react/src/Flash/Flash.module.css
new file mode 100644
index 00000000000..c16e839a4e3
--- /dev/null
+++ b/packages/react/src/Flash/Flash.module.css
@@ -0,0 +1,60 @@
+.Flash {
+  position: relative;
+  padding: var(--base-size-16);
+  margin-top: 0;
+  color: var(--fgColor-default);
+  border-style: solid;
+  border-width: var(--borderWidth-thin);
+  border-radius: var(--borderRadius-medium);
+
+  &:where([data-variant='default']) {
+    background-color: var(--bgColor-accent-muted);
+    border-color: var(--borderColor-accent-muted);
+
+    & :where(svg) {
+      color: var(--fgColor-accent);
+    }
+  }
+
+  &:where([data-variant='success']) {
+    background-color: var(--bgColor-success-muted);
+    border-color: var(--borderColor-success-muted);
+
+    & :where(svg) {
+      color: var(--fgColor-success);
+    }
+  }
+
+  &:where([data-variant='danger']) {
+    background-color: var(--bgColor-danger-muted);
+    border-color: var(--borderColor-danger-muted);
+
+    & :where(svg) {
+      color: var(--fgColor-danger);
+    }
+  }
+
+  &:where([data-variant='warning']) {
+    background-color: var(--bgColor-attention-muted);
+    border-color: var(--borderColor-attention-muted);
+
+    & :where(svg) {
+      color: var(--fgColor-attention);
+    }
+  }
+
+  &:where([data-full]) {
+    /* stylelint-disable-next-line primer/spacing */
+    margin-top: -1px;
+    border-width: var(--borderWidth-thin) 0;
+    border-radius: 0;
+  }
+
+  & :where(p:last-child) {
+    margin-bottom: 0;
+  }
+
+  & :where(svg) {
+    margin-right: var(--base-size-8);
+  }
+}
diff --git a/packages/react/src/Flash/Flash.tsx b/packages/react/src/Flash/Flash.tsx
index 8701f8ea8d9..557f0e33cce 100644
--- a/packages/react/src/Flash/Flash.tsx
+++ b/packages/react/src/Flash/Flash.tsx
@@ -1,80 +1,29 @@
+import {clsx} from 'clsx'
 import React from 'react'
-import styled from 'styled-components'
-import {variant} from 'styled-system'
-import {get} from '../constants'
 import type {SxProp} from '../sx'
-import sx from '../sx'
-import type {ComponentProps} from '../utils/types'
 import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
+import {BoxWithFallback} from '../internal/components/BoxWithFallback'
+import classes from './Flash.module.css'
 
-const variants = variant({
-  variants: {
-    default: {
-      color: 'fg.default',
-      backgroundColor: 'accent.subtle',
-      borderColor: 'accent.muted',
-      svg: {
-        color: 'accent.fg',
-      },
-    },
-    success: {
-      color: 'fg.default',
-      backgroundColor: 'success.subtle',
-      borderColor: 'success.muted',
-      svg: {
-        color: 'success.fg',
-      },
-    },
-    danger: {
-      color: 'fg.default',
-      backgroundColor: 'danger.subtle',
-      borderColor: 'danger.muted',
-      svg: {
-        color: 'danger.fg',
-      },
-    },
-    warning: {
-      color: 'fg.default',
-      backgroundColor: 'attention.subtle',
-      borderColor: 'attention.muted',
-      svg: {
-        color: 'attention.fg',
-      },
-    },
-  },
-})
-
-type StyledFlashProps = {
+export type FlashProps = React.ComponentPropsWithoutRef<'div'> & {
+  className?: string
   variant?: 'default' | 'warning' | 'success' | 'danger'
   full?: boolean
 } & SxProp
 
-const StyledFlash = styled.div<StyledFlashProps>`
-  position: relative;
-  color: ${get('colors.fg.default')};
-  padding: ${get('space.3')};
-  border-style: solid;
-  border-width: ${props => (props.full ? '1px 0px' : '1px')};
-  border-radius: ${props => (props.full ? '0' : get('radii.2'))};
-  margin-top: ${props => (props.full ? '-1px' : '0')};
-
-  p:last-child {
-    margin-bottom: 0;
-  }
-
-  svg {
-    margin-right: ${get('space.2')};
-  }
-
-  ${variants};
-  ${sx};
-`
-
-export type FlashProps = ComponentProps<typeof StyledFlash>
-
-const Flash = React.forwardRef(function Flash({as, variant = 'default', ...rest}, ref) {
-  return <StyledFlash ref={ref} as={as} variant={variant} {...rest} />
-}) as PolymorphicForwardRefComponent<'div', StyledFlashProps>
+const Flash = React.forwardRef(function Flash({as, className, variant = 'default', full, sx, ...rest}, ref) {
+  return (
+    <BoxWithFallback
+      {...rest}
+      ref={ref}
+      as={as}
+      className={clsx(classes.Flash, className)}
+      data-full={full ? '' : undefined}
+      data-variant={variant}
+      sx={sx}
+    />
+  )
+}) as PolymorphicForwardRefComponent<'div', FlashProps>
 
 if (__DEV__) {
   Flash.displayName = 'Flash'
diff --git a/packages/react/src/Flash/__tests__/Flash.test.tsx b/packages/react/src/Flash/__tests__/Flash.test.tsx
index d6a118f8823..ec838d5a256 100644
--- a/packages/react/src/Flash/__tests__/Flash.test.tsx
+++ b/packages/react/src/Flash/__tests__/Flash.test.tsx
@@ -1,44 +1,42 @@
 import React from 'react'
 import Flash from '..'
-import {render, behavesAsComponent, checkExports} from '../../utils/testing'
-import {render as HTMLRender} from '@testing-library/react'
-import axe from 'axe-core'
+import {render, screen} from '@testing-library/react'
 
 describe('Flash', () => {
-  behavesAsComponent({Component: Flash})
-
-  checkExports('Flash', {
-    default: Flash,
+  it('should support the `full` prop', () => {
+    render(
+      <>
+        <Flash data-testid="full" full />
+        <Flash data-testid="no-full" />
+      </>,
+    )
+    expect(screen.getByTestId('full')).toHaveAttribute('data-full', '')
+    expect(screen.getByTestId('no-full')).not.toHaveAttribute('data-full')
   })
 
-  it('should have no axe violations', async () => {
-    const {container} = HTMLRender(<Flash variant="warning" />)
-    const results = await axe.run(container)
-    expect(results).toHaveNoViolations()
+  it('should support the `variant` prop', () => {
+    render(
+      <>
+        <Flash data-testid="danger" variant="danger" />
+        <Flash data-testid="success" variant="success" />
+        <Flash data-testid="warning" variant="warning" />
+        <Flash data-testid="default" variant="default" />
+      </>,
+    )
+
+    expect(screen.getByTestId('danger')).toHaveAttribute('data-variant', 'danger')
+    expect(screen.getByTestId('success')).toHaveAttribute('data-variant', 'success')
+    expect(screen.getByTestId('warning')).toHaveAttribute('data-variant', 'warning')
+    expect(screen.getByTestId('default')).toHaveAttribute('data-variant', 'default')
   })
 
-  it('respects the "full" prop', () => {
-    expect(render(<Flash full />)).toHaveStyleRule('margin-top', '-1px')
-    expect(render(<Flash full />)).toHaveStyleRule('border-radius', '0')
-    expect(render(<Flash full />)).toHaveStyleRule('border-width', '1px 0px')
+  it('should support `className` on the outermost element', () => {
+    const {container} = render(<Flash className="test-class" />)
+    expect(container.firstChild).toHaveClass('test-class')
   })
 
-  it('respects the "variant" prop', () => {
-    expect(render(<Flash variant="warning" />)).toHaveStyleRule(
-      'background-color',
-      'var(--bgColor-attention-muted,var(--color-attention-subtle,#fff8c5))',
-    )
-    expect(render(<Flash variant="danger" />)).toHaveStyleRule(
-      'background-color',
-      'var(--bgColor-danger-muted,var(--color-danger-subtle,#ffebe9))',
-    )
-    expect(render(<Flash variant="success" />)).toHaveStyleRule(
-      'background-color',
-      'var(--bgColor-success-muted,var(--color-success-subtle,#dafbe1))',
-    )
-    expect(render(<Flash />)).toHaveStyleRule(
-      'background-color',
-      'var(--bgColor-accent-muted,var(--color-accent-subtle,#ddf4ff))',
-    )
+  it('should spread props to the outermost element', () => {
+    const {container} = render(<Flash data-testid="test" />)
+    expect(container.firstChild).toHaveAttribute('data-testid', 'test')
   })
 })


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR refactors the `Flash` component to use CSS Modules instead of `sx` and `styled-components`, and updates the component implementation to leverage `BoxWithFallback`.

- Removed `styled-components` and `styled-system` variants
- Added `Flash.module.css` with equivalent styles using CSS Modules and data attributes
- Updated `Flash.tsx` to render a `BoxWithFallback` with CSS class and data attributes

### Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

| File                                      | Description                                             |
| ----------------------------------------- | ------------------------------------------------------- |
| packages/react/src/Flash/Flash.tsx        | Refactored component to use `BoxWithFallback` and CSS Modules, removed `sx` usage |
| packages/react/src/Flash/Flash.module.css | Added CSS Module definitions for variant and layout styles |
| .changeset/giant-bushes-matter.md         | Minor release changeset noting the refactor              |




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T20:15:50Z
user: login: jonrohan
body: Seems good, just needs the CSS lint fixes
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-06T02:32:49Z

Review comments:
diff_hunk: @@ -0,0 +1,59 @@
+.Flash {
+  position: relative;
+  color: var(--fgColor-default);
+  padding: var(--base-size-16);
+  border-style: solid;
+  border-width: 1px;
+  border-radius: var(--borderRadius-medium);
+  margin-top: 0;
+
+  &:where([data-variant='default']) {
+    background-color: var(--bgColor-accent-muted);
path: packages/react/src/Flash/Flash.module.css
user: login: Copilot
body: The default variant background token should be `accent.subtle`, so this should use `var(--bgColor-accent-subtle)` instead of `--bgColor-accent-muted`.
```suggestion
    background-color: var(--bgColor-accent-subtle);
```
created_at: 2025-05-05T20:15:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+.Flash {
+  position: relative;
+  color: var(--fgColor-default);
+  padding: var(--base-size-16);
+  border-style: solid;
+  border-width: 1px;
+  border-radius: var(--borderRadius-medium);
+  margin-top: 0;
+
+  &:where([data-variant='default']) {
+    background-color: var(--bgColor-accent-muted);
+    border-color: var(--borderColor-accent-muted);
+
+    & :where(svg) {
+      color: var(--fgColor-accent);
+    }
+  }
+
+  &:where([data-variant='success']) {
+    background-color: var(--bgColor-success-muted);
path: packages/react/src/Flash/Flash.module.css
user: login: Copilot
body: The success variant background token should be `success.subtle`, so this should use `var(--bgColor-success-subtle)` instead of `--bgColor-success-muted`.
```suggestion
    background-color: var(--bgColor-success-subtle);
```
created_at: 2025-05-05T20:15:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+.Flash {
+  position: relative;
+  color: var(--fgColor-default);
+  padding: var(--base-size-16);
+  border-style: solid;
+  border-width: 1px;
+  border-radius: var(--borderRadius-medium);
+  margin-top: 0;
+
+  &:where([data-variant='default']) {
+    background-color: var(--bgColor-accent-muted);
+    border-color: var(--borderColor-accent-muted);
+
+    & :where(svg) {
+      color: var(--fgColor-accent);
+    }
+  }
+
+  &:where([data-variant='success']) {
+    background-color: var(--bgColor-success-muted);
+    border-color: var(--borderColor-success-muted);
+
+    & :where(svg) {
+      color: var(--fgColor-success);
+    }
+  }
+
+  &:where([data-variant='danger']) {
+    background-color: var(--bgColor-danger-muted);
path: packages/react/src/Flash/Flash.module.css
user: login: Copilot
body: The danger variant background token should be `danger.subtle`, so this should use `var(--bgColor-danger-subtle)` instead of `--bgColor-danger-muted`.
```suggestion
    background-color: var(--bgColor-danger-subtle);
```
created_at: 2025-05-05T20:15:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+.Flash {
+  position: relative;
+  color: var(--fgColor-default);
+  padding: var(--base-size-16);
+  border-style: solid;
+  border-width: 1px;
+  border-radius: var(--borderRadius-medium);
+  margin-top: 0;
+
+  &:where([data-variant='default']) {
+    background-color: var(--bgColor-accent-muted);
+    border-color: var(--borderColor-accent-muted);
+
+    & :where(svg) {
+      color: var(--fgColor-accent);
+    }
+  }
+
+  &:where([data-variant='success']) {
+    background-color: var(--bgColor-success-muted);
+    border-color: var(--borderColor-success-muted);
+
+    & :where(svg) {
+      color: var(--fgColor-success);
+    }
+  }
+
+  &:where([data-variant='danger']) {
+    background-color: var(--bgColor-danger-muted);
+    border-color: var(--borderColor-danger-muted);
+
+    & :where(svg) {
+      color: var(--fgColor-danger);
+    }
+  }
+
+  &:where([data-variant='warning']) {
+    background-color: var(--bgColor-attention-muted);
path: packages/react/src/Flash/Flash.module.css
user: login: Copilot
body: The warning variant background token should be `attention.subtle`, so this should use `var(--bgColor-attention-subtle)` instead of `--bgColor-attention-muted`.
```suggestion
    background-color: var(--bgColor-attention-subtle);
```
created_at: 2025-05-05T20:15:50Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-05T20:13:31Z
body: ###  🦋  Changeset detected

Latest commit: 85067e1f3cf02f4c1f20d7cfeca86062dab4d62a

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name          | Type  |
| ------------- | ----- |
| @primer/react | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/primer/react/new/refactor/update-flash-from-sx-to-css-modules?filename=.changeset/wicked-crabs-share.md&value=---%0A%22%40primer%2Freact%22%3A%20patch%0A---%0A%0Arefactor(Flash)%3A%20update%20sx%20usage%20to%20CSS%20Modules%0A)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-05T20:13:59Z
body: <!-- recommend-integration-tests.yml -->

 :wave: Hi, this pull request contains changes to the source code that github/github depends on. If you are GitHub staff, we recommend testing these changes with github/github using the [integration workflow](https://gh.io/testing_primer_at_dotcom). Thanks!
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-05T20:16:43Z
body: ## size-limit report 📦 
| Path                               | Size                 |
| ---------------------------------- | -------------------- |
| packages/react/dist/browser.esm.js | 98.77 KB (-0.28% 🔽) |
| packages/react/dist/browser.umd.js | 99.14 KB (+0.01% 🔺) |
author_association: CONTRIBUTOR