diff --git a/.changeset/tricky-bees-promise.md b/.changeset/tricky-bees-promise.md
new file mode 100644
index 0000000..ece7600
--- /dev/null
+++ b/.changeset/tricky-bees-promise.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-primer-react": minor
+---
+
+feat: Add rule to enforce importing components with sx prop from @primer/styled-react
diff --git a/docs/rules/use-styled-react-import.md b/docs/rules/use-styled-react-import.md
new file mode 100644
index 0000000..03d7a18
--- /dev/null
+++ b/docs/rules/use-styled-react-import.md
@@ -0,0 +1,95 @@
+# use-styled-react-import
+
+💼 This rule is _disabled_ in the ✅ `recommended` config.
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+Enforce importing components that use `sx` prop from `@primer/styled-react` instead of `@primer/react`.
+
+## Rule Details
+
+This rule detects when certain Primer React components are used with the `sx` prop and ensures they are imported from the temporary `@primer/styled-react` package instead of `@primer/react`. It also moves certain types and utilities to the styled-react package.
+
+### Components that should be imported from `@primer/styled-react` when used with `sx`:
+
+- ActionList
+- ActionMenu
+- Box
+- Breadcrumbs
+- Button
+- Flash
+- FormControl
+- Heading
+- IconButton
+- Label
+- Link
+- LinkButton
+- PageLayout
+- Text
+- TextInput
+- Truncate
+- Octicon
+- Dialog
+
+### Types and utilities that should always be imported from `@primer/styled-react`:
+
+- `BoxProps` (type)
+- `SxProp` (type)
+- `BetterSystemStyleObject` (type)
+- `sx` (utility)
+
+## Examples
+
+### ❌ Incorrect
+
+```jsx
+import {Button, Link} from '@primer/react'
+
+const Component = () =>
+```
+
+```jsx
+import {Box} from '@primer/react'
+
+const Component = () => Content
+```
+
+```jsx
+import {sx} from '@primer/react'
+```
+
+### ✅ Correct
+
+```jsx
+import {Link} from '@primer/react'
+import {Button} from '@primer/styled-react'
+
+const Component = () =>
+```
+
+```jsx
+import {Box} from '@primer/styled-react'
+
+const Component = () => Content
+```
+
+```jsx
+import {sx} from '@primer/styled-react'
+```
+
+```jsx
+// Components without sx prop can stay in @primer/react
+import {Button} from '@primer/react'
+
+const Component = () =>
+```
+
+## Options
+
+This rule has no options.
+
+## When Not To Use It
+
+This rule is specifically for migrating components that use the `sx` prop to the temporary `@primer/styled-react` package. If you're not using the `sx` prop or not participating in this migration, you can disable this rule.
diff --git a/src/index.js b/src/index.js
index 68de6f2..1dc3315 100644
--- a/src/index.js
+++ b/src/index.js
@@ -19,6 +19,7 @@ module.exports = {
'prefer-action-list-item-onselect': require('./rules/prefer-action-list-item-onselect'),
'enforce-css-module-identifier-casing': require('./rules/enforce-css-module-identifier-casing'),
'enforce-css-module-default-import': require('./rules/enforce-css-module-default-import'),
+ 'use-styled-react-import': require('./rules/use-styled-react-import'),
},
configs: {
recommended: require('./configs/recommended'),
diff --git a/src/rules/__tests__/use-styled-react-import.test.js b/src/rules/__tests__/use-styled-react-import.test.js
new file mode 100644
index 0000000..e39deb2
--- /dev/null
+++ b/src/rules/__tests__/use-styled-react-import.test.js
@@ -0,0 +1,118 @@
+const rule = require('../use-styled-react-import')
+const {RuleTester} = require('eslint')
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+})
+
+ruleTester.run('use-styled-react-import', rule, {
+ valid: [
+ // Valid: Component used without sx prop
+ `import { Button } from '@primer/react'
+ const Component = () => `,
+
+ // Valid: Component with sx prop imported from styled-react
+ `import { Button } from '@primer/styled-react'
+ const Component = () => `,
+
+ // Valid: Utilities imported from styled-react
+ `import { sx } from '@primer/styled-react'`,
+
+ // Valid: Component not in the styled list
+ `import { Avatar } from '@primer/react'
+ const Component = () => `,
+
+ // Valid: Mixed imports - component without sx prop
+ `import { Button, Text } from '@primer/react'
+ const Component = () => `,
+ ],
+ invalid: [
+ // Invalid: Box with sx prop imported from @primer/react
+ {
+ code: `import { Box } from '@primer/react'
+ const Component = () => Content`,
+ output: `import { Box } from '@primer/styled-react'
+ const Component = () => Content`,
+ errors: [
+ {
+ messageId: 'useStyledReactImport',
+ data: {componentName: 'Box'},
+ },
+ ],
+ },
+
+ // Invalid: Button with sx prop imported from @primer/react
+ {
+ code: `import { Button } from '@primer/react'
+ const Component = () => `,
+ output: `import { Button } from '@primer/styled-react'
+ const Component = () => `,
+ errors: [
+ {
+ messageId: 'useStyledReactImport',
+ data: {componentName: 'Button'},
+ },
+ ],
+ },
+
+ // Invalid: Multiple components, one with sx prop
+ {
+ code: `import { Button, Box, Avatar } from '@primer/react'
+ const Component = () => (
+
+
+
Styled box
+
+
+ )`,
+ output: `import { Button, Avatar } from '@primer/react'
+import { Box } from '@primer/styled-react'
+ const Component = () => (
+
+
+
Styled box
+
+
+ )`,
+ errors: [
+ {
+ messageId: 'useStyledReactImport',
+ data: {componentName: 'Box'},
+ },
+ ],
+ },
+
+ // Invalid: Utility import from @primer/react that should be from styled-react
+ {
+ code: `import { sx } from '@primer/react'`,
+ output: `import { sx } from '@primer/styled-react'`,
+ errors: [
+ {
+ messageId: 'moveToStyledReact',
+ data: {importName: 'sx'},
+ },
+ ],
+ },
+
+ // Invalid: Button and Link, only Button uses sx
+ {
+ code: `import { Button, Link } from '@primer/react'
+ const Component = () => `,
+ output: `import { Link } from '@primer/react'
+import { Button } from '@primer/styled-react'
+ const Component = () => `,
+ errors: [
+ {
+ messageId: 'useStyledReactImport',
+ data: {componentName: 'Button'},
+ },
+ ],
+ },
+ ],
+})
diff --git a/src/rules/use-styled-react-import.js b/src/rules/use-styled-react-import.js
new file mode 100644
index 0000000..91c6749
--- /dev/null
+++ b/src/rules/use-styled-react-import.js
@@ -0,0 +1,202 @@
+'use strict'
+
+const url = require('../url')
+const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')
+
+// Components that should be imported from @primer/styled-react when used with sx prop
+const styledComponents = new Set([
+ 'ActionList',
+ 'ActionMenu',
+ 'Box',
+ 'Breadcrumbs',
+ 'Button',
+ 'Flash',
+ 'FormControl',
+ 'Heading',
+ 'IconButton',
+ 'Label',
+ 'Link',
+ 'LinkButton',
+ 'PageLayout',
+ 'Text',
+ 'TextInput',
+ 'Truncate',
+ 'Octicon',
+ 'Dialog',
+])
+
+// Types that should be imported from @primer/styled-react
+const styledTypes = new Set(['BoxProps', 'SxProp', 'BetterSystemStyleObject'])
+
+// Utilities that should be imported from @primer/styled-react
+const styledUtilities = new Set(['sx'])
+
+/**
+ * @type {import('eslint').Rule.RuleModule}
+ */
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'Enforce importing components that use sx prop from @primer/styled-react',
+ recommended: false,
+ url: url(module),
+ },
+ fixable: 'code',
+ schema: [],
+ messages: {
+ useStyledReactImport: 'Import {{ componentName }} from "@primer/styled-react" when using with sx prop',
+ moveToStyledReact: 'Move {{ importName }} import to "@primer/styled-react"',
+ },
+ },
+ create(context) {
+ const componentsWithSx = new Set()
+ const primerReactImports = new Map() // Map of component name to import node
+ const styledReactImports = new Set() // Set of components already imported from styled-react
+
+ return {
+ ImportDeclaration(node) {
+ const importSource = node.source.value
+
+ if (importSource === '@primer/react') {
+ // Track imports from @primer/react
+ for (const specifier of node.specifiers) {
+ if (specifier.type === 'ImportSpecifier') {
+ const importedName = specifier.imported.name
+ if (
+ styledComponents.has(importedName) ||
+ styledTypes.has(importedName) ||
+ styledUtilities.has(importedName)
+ ) {
+ primerReactImports.set(importedName, {node, specifier})
+ }
+ }
+ }
+ } else if (importSource === '@primer/styled-react') {
+ // Track what's already imported from styled-react
+ for (const specifier of node.specifiers) {
+ if (specifier.type === 'ImportSpecifier') {
+ styledReactImports.add(specifier.imported.name)
+ }
+ }
+ }
+ },
+
+ JSXOpeningElement(node) {
+ const componentName = getJSXOpeningElementName(node)
+
+ // Check if this component has an sx prop
+ const hasSxProp = node.attributes.some(
+ attr => attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'sx',
+ )
+
+ if (hasSxProp && styledComponents.has(componentName)) {
+ componentsWithSx.add(componentName)
+ }
+ },
+
+ 'Program:exit': function () {
+ // Report errors for components used with sx prop that are imported from @primer/react
+ for (const componentName of componentsWithSx) {
+ const importInfo = primerReactImports.get(componentName)
+ if (importInfo && !styledReactImports.has(componentName)) {
+ context.report({
+ node: importInfo.specifier,
+ messageId: 'useStyledReactImport',
+ data: {componentName},
+ fix(fixer) {
+ const {node: importNode, specifier} = importInfo
+ const otherSpecifiers = importNode.specifiers.filter(s => s !== specifier)
+
+ // If this is the only import, replace the whole import
+ if (otherSpecifiers.length === 0) {
+ return fixer.replaceText(importNode, `import { ${componentName} } from '@primer/styled-react'`)
+ }
+
+ // Otherwise, remove from current import and add new import
+ const fixes = []
+
+ // Remove the specifier from current import
+ if (importNode.specifiers.length === 1) {
+ fixes.push(fixer.remove(importNode))
+ } else {
+ const isFirst = importNode.specifiers[0] === specifier
+ const isLast = importNode.specifiers[importNode.specifiers.length - 1] === specifier
+
+ if (isFirst) {
+ const nextSpecifier = importNode.specifiers[1]
+ fixes.push(fixer.removeRange([specifier.range[0], nextSpecifier.range[0]]))
+ } else if (isLast) {
+ const prevSpecifier = importNode.specifiers[importNode.specifiers.length - 2]
+ fixes.push(fixer.removeRange([prevSpecifier.range[1], specifier.range[1]]))
+ } else {
+ const nextSpecifier = importNode.specifiers[importNode.specifiers.indexOf(specifier) + 1]
+ fixes.push(fixer.removeRange([specifier.range[0], nextSpecifier.range[0]]))
+ }
+ }
+
+ // Add new import
+ fixes.push(
+ fixer.insertTextAfter(importNode, `\nimport { ${componentName} } from '@primer/styled-react'`),
+ )
+
+ return fixes
+ },
+ })
+ }
+ }
+
+ // Also report for types and utilities that should always be from styled-react
+ for (const [importName, importInfo] of primerReactImports) {
+ if ((styledTypes.has(importName) || styledUtilities.has(importName)) && !styledReactImports.has(importName)) {
+ context.report({
+ node: importInfo.specifier,
+ messageId: 'moveToStyledReact',
+ data: {importName},
+ fix(fixer) {
+ const {node: importNode, specifier} = importInfo
+ const otherSpecifiers = importNode.specifiers.filter(s => s !== specifier)
+
+ // If this is the only import, replace the whole import
+ if (otherSpecifiers.length === 0) {
+ const prefix = styledTypes.has(importName) ? 'type ' : ''
+ return fixer.replaceText(importNode, `import { ${prefix}${importName} } from '@primer/styled-react'`)
+ }
+
+ // Otherwise, remove from current import and add new import
+ const fixes = []
+
+ // Remove the specifier from current import
+ if (importNode.specifiers.length === 1) {
+ fixes.push(fixer.remove(importNode))
+ } else {
+ const isFirst = importNode.specifiers[0] === specifier
+ const isLast = importNode.specifiers[importNode.specifiers.length - 1] === specifier
+
+ if (isFirst) {
+ const nextSpecifier = importNode.specifiers[1]
+ fixes.push(fixer.removeRange([specifier.range[0], nextSpecifier.range[0]]))
+ } else if (isLast) {
+ const prevSpecifier = importNode.specifiers[importNode.specifiers.length - 2]
+ fixes.push(fixer.removeRange([prevSpecifier.range[1], specifier.range[1]]))
+ } else {
+ const nextSpecifier = importNode.specifiers[importNode.specifiers.indexOf(specifier) + 1]
+ fixes.push(fixer.removeRange([specifier.range[0], nextSpecifier.range[0]]))
+ }
+ }
+
+ // Add new import
+ const prefix = styledTypes.has(importName) ? 'type ' : ''
+ fixes.push(
+ fixer.insertTextAfter(importNode, `\nimport { ${prefix}${importName} } from '@primer/styled-react'`),
+ )
+
+ return fixes
+ },
+ })
+ }
+ }
+ },
+ }
+ },
+}