diff --git a/.changeset/tasty-laws-bathe.md b/.changeset/tasty-laws-bathe.md
new file mode 100644
index 00000000000..441d6684c25
--- /dev/null
+++ b/.changeset/tasty-laws-bathe.md
@@ -0,0 +1,26 @@
+---
+'@primer/components': minor
+---
+
+System props are deprecated in all components except `Box`. Move all system props into the [`sx` prop](https://primer.style/components/overriding-styles) instead. Example:
+
+```diff
+-
++
+```
+
+There is a codemod available to migrate from system props to the `sx` prop:
+
+- TypeScript example:
+
+ ```shell
+ npx jscodeshift -t node_modules/@primer/components/codemods/removeSystemProps.js
+ --parser=tsx path/to/workspace/src/*.tsx
+ ```
+
+- Babel example:
+
+ ```shell
+ npx jscodeshift -t node_modules/@primer/components/codemods/removeSystemProps.js
+ --parser=babel path/to/workspace/src/*.tsx
+ ```
diff --git a/codemods/__tests__/removeSystemProps.js b/codemods/__tests__/removeSystemProps.js
new file mode 100644
index 00000000000..40307079fa7
--- /dev/null
+++ b/codemods/__tests__/removeSystemProps.js
@@ -0,0 +1,225 @@
+import {defineInlineTest} from 'jscodeshift/dist/testUtils'
+import removeSystemProps from '../removeSystemProps'
+
+defineInlineTest(
+ removeSystemProps,
+ {},
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+export default () => (
+
+)
+`.trim(),
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+export default () => (
+
+)
+`.trim(),
+ 'removeSystemProps'
+)
+
+defineInlineTest(
+ removeSystemProps,
+ {},
+ `
+import {Button, StyledOcticon} from '@primer/components'
+import {CheckIcon, ClippyIcon} from '@primer/octicons-react'
+
+const ClipboardCopy = ({value}) =>
+
+`.trim(),
+ `
+import {Button, StyledOcticon} from '@primer/components'
+import {CheckIcon, ClippyIcon} from '@primer/octicons-react'
+
+const ClipboardCopy = ({value}) => (
+
+)`.trim(),
+ 'removeSystemProps'
+)
+
+defineInlineTest(
+ removeSystemProps,
+ {},
+ `
+import {Link} from '@primer/components'
+const siteMetadata = {shortName: 'inline-block'}
+
+const link =
+ Primer
+
+`.trim(),
+ `
+import {Link} from '@primer/components'
+const siteMetadata = {shortName: 'inline-block'}
+
+const link = (
+
+ Primer
+
+)`.trim(),
+ 'removeSystemProps'
+)
+
+defineInlineTest(
+ removeSystemProps,
+ {},
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+export default () => (
+
+)
+`.trim(),
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+export default () => (
+
+)
+`.trim(),
+ 'removeSystemProps'
+)
+
+defineInlineTest(
+ removeSystemProps,
+ {},
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+export default () => (
+
+)
+`.trim(),
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+export default () => (
+
+)
+`.trim(),
+ 'removeSystemProps'
+)
+
+defineInlineTest(
+ removeSystemProps,
+ {},
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+const colorProps = {backgroundColor: 'red'}
+const borderProps = {borderColor: 'red'}
+export default () => (
+
+)
+`.trim(),
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+const colorProps = {backgroundColor: 'red'}
+const borderProps = {borderColor: 'red'}
+export default () => (
+
+)
+`.trim(),
+ 'removeSystemProps'
+)
+
+defineInlineTest(
+ removeSystemProps,
+ {},
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+export default () => (
+
+)
+`.trim(),
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+export default () => (
+
+)
+`.trim(),
+ 'removeSystemProps'
+)
+
+defineInlineTest(
+ removeSystemProps,
+ {},
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+const colorProps = {dark: {backgroundColor: 'red'}}
+export default () => (
+
+)
+`.trim(),
+ `
+import {Label} from '@primer/components'
+const leftMargin = 2
+const colorProps = {dark: {backgroundColor: 'red'}}
+export default () => (
+
+)
+`.trim(),
+ 'removeSystemProps'
+)
diff --git a/codemods/removeSystemProps.js b/codemods/removeSystemProps.js
new file mode 100644
index 00000000000..919c2c9447f
--- /dev/null
+++ b/codemods/removeSystemProps.js
@@ -0,0 +1,312 @@
+const prettify = require('./lib/prettify')
+
+const COMMON = [
+ 'margin',
+ 'marginTop',
+ 'marginRight',
+ 'marginBottom',
+ 'marginLeft',
+ 'marginX',
+ 'marginY',
+ 'm',
+ 'mt',
+ 'mr',
+ 'mb',
+ 'ml',
+ 'mx',
+ 'my',
+ 'padding',
+ 'paddingTop',
+ 'paddingRight',
+ 'paddingBottom',
+ 'paddingLeft',
+ 'paddingX',
+ 'paddingY',
+ 'p',
+ 'pt',
+ 'pr',
+ 'pb',
+ 'pl',
+ 'px',
+ 'py',
+ 'color',
+ 'backgroundColor',
+ 'opacity',
+ 'bg',
+ 'display'
+]
+
+const TYPOGRAPHY = [
+ 'fontFamily',
+ 'fontSize',
+ 'fontWeight',
+ 'lineHeight',
+ 'letterSpacing',
+ 'textAlign',
+ 'fontStyle',
+ 'whiteSpace'
+]
+
+const BORDER = [
+ 'border',
+ 'borderWidth',
+ 'borderStyle',
+ 'borderColor',
+ 'borderRadius',
+ 'borderTop',
+ 'borderTopLeftRadius',
+ 'borderTopRightRadius',
+ 'borderRight',
+ 'borderBottom',
+ 'borderBottomLeftRadius',
+ 'borderBottomRightRadius',
+ 'borderLeft',
+ 'borderX',
+ 'borderY',
+ 'borderTopWidth',
+ 'borderTopColor',
+ 'borderTopStyle',
+ 'borderBottomWidth',
+ 'borderBottomColor',
+ 'borderBottomStyle',
+ 'borderLeftWidth',
+ 'borderLeftColor',
+ 'borderLeftStyle',
+ 'borderRightWidth',
+ 'borderRightColor',
+ 'borderRightStyle',
+ 'boxShadow',
+ 'textShadow'
+]
+
+const LAYOUT = [
+ 'width',
+ 'height',
+ 'minWidth',
+ 'minHeight',
+ 'maxWidth',
+ 'maxHeight',
+ 'size',
+ 'overflow',
+ 'overflowX',
+ 'overflowY',
+ 'display',
+ 'verticalAlign'
+]
+
+const POSITION = ['position', 'zIndex', 'top', 'right', 'bottom', 'left']
+
+const FLEX = [
+ 'alignItems',
+ 'alignContent',
+ 'justifyItems',
+ 'justifyContent',
+ 'flexWrap',
+ 'flexDirection',
+ 'flex',
+ 'flexGrow',
+ 'flexShrink',
+ 'flexBasis',
+ 'justifySelf',
+ 'alignSelf',
+ 'order'
+]
+
+// const GRID = [
+// 'gridGap',
+// 'gridColumnGap',
+// 'gridRowGap',
+// 'gridColumn',
+// 'gridRow',
+// 'gridAutoFlow',
+// 'gridAutoColumns',
+// 'gridAutoRows',
+// 'gridTemplateColumns',
+// 'gridTemplateRows',
+// 'gridTemplateAreas',
+// 'gridArea'
+// ]
+
+const stylePropsMap = {
+ Avatar: [...COMMON],
+ AvatarStack: [...COMMON],
+ BranchName: [...COMMON],
+ Breadcrumb: [...COMMON, ...FLEX],
+ Button: [...COMMON, ...LAYOUT, ...TYPOGRAPHY],
+ ButtonBase: [...COMMON, ...LAYOUT],
+ ButtonClose: [...COMMON, ...LAYOUT],
+ ButtonTableList: [...COMMON, ...TYPOGRAPHY, ...LAYOUT],
+ CircleBadge: [...COMMON],
+ CounterLabel: [...COMMON],
+ Details: [...COMMON],
+ Dialog: [...LAYOUT, ...COMMON, ...POSITION],
+ Dropdown: [...COMMON],
+ FilteredSearch: [...COMMON],
+ FilterList: [...COMMON],
+ Flash: [...COMMON],
+ FormGroup: [...COMMON],
+ FormGroupLabel: [...COMMON, ...TYPOGRAPHY],
+ Header: [...COMMON, ...BORDER],
+ HeaderItem: [...COMMON, ...BORDER],
+ Label: [...COMMON, ...BORDER],
+ LabelGroup: [...COMMON],
+ Link: [...COMMON, ...TYPOGRAPHY],
+ Overlay: [...COMMON],
+ Pagehead: [...COMMON],
+ Pagination: [...COMMON],
+ Popover: [...COMMON, ...LAYOUT, ...POSITION],
+ PopoverContent: [...COMMON, ...LAYOUT, ...POSITION, ...FLEX],
+ SelectMenu: [...COMMON],
+ SelectMenuDivider: [...COMMON],
+ SelectMenuFilter: [...COMMON],
+ SelectMenuFooter: [...COMMON],
+ SelectMenuHeader: [...COMMON, ...TYPOGRAPHY],
+ SelectMenuItem: [...COMMON],
+ SelectMenuList: [...COMMON],
+ SelectMenuLoadingAnimation: [...COMMON],
+ SelectMenuModal: [...COMMON],
+ SelectMenuTab: [...COMMON],
+ SelectMenuTabPanel: [...COMMON],
+ SelectMenuTabs: [...COMMON],
+ SideNav: [...COMMON],
+ Spinner: [...COMMON],
+ StateLabel: [...COMMON],
+ StyledOcticon: [...COMMON],
+ SubNav: [...COMMON, ...FLEX],
+ TabNav: [...COMMON],
+ TabNavLink: [...COMMON, ...TYPOGRAPHY],
+ TextInput: [...COMMON],
+ Timeline: [...COMMON],
+ Tooltip: [...COMMON],
+ Truncate: [...TYPOGRAPHY, ...COMMON],
+ UnderlineNav: [...COMMON]
+}
+
+const expressionToString = expression => {
+ if (expression.type === 'Literal') {
+ const expressionValue = expression.value
+ return typeof expressionValue === 'string' ? `"${expressionValue}"` : expressionValue
+ } else if (expression.type === 'Identifier') {
+ return expression.name
+ } else if (expression.type === 'Identifier') {
+ return expression.name
+ } else if (['null', 'undefined'].includes(expression.raw)) {
+ return expression.raw
+ } else {
+ const start = expression.start
+ const end = expression.end
+ const toks = expression.loc.tokens.filter(token => {
+ return token.type !== 'CommentLine' && token.start >= start && token.end <= end
+ })
+ const vals = toks.map(tok => {
+ return tok.type.label === 'string' ? `"${tok.value}"` : tok.value
+ })
+ return vals.join('')
+ }
+}
+
+const objectToString = (object, spreads = []) => {
+ const values = Object.values(object)
+ const keys = Object.keys(object)
+ const duples = keys.map(function (key, i) {
+ return [key, values[i]]
+ })
+ const accumulator = (string, duple) => {
+ const expression = duple[1]
+ const expressionString = expressionToString(expression)
+ return `${string} ${duple[0]}: ${expressionString},`
+ }
+ const objString = duples.reduce(accumulator, '')
+ const spreadsString = spreads.map(s => `...${s},`).join('')
+ return `{${spreadsString}${objString}}`
+}
+
+module.exports = (file, api) => {
+ const j = api.jscodeshift
+ const ast = j(file.source)
+
+ const importsByName = {}
+
+ ast
+ .find(j.ImportDeclaration, decl => decl.source.value.includes('@primer/components'))
+ .forEach(decl => {
+ j(decl)
+ .find(j.ImportSpecifier)
+ .forEach(spec => {
+ importsByName[spec.node.imported.name] = spec.node.local.name
+ })
+ })
+
+ ast
+ .find(j.JSXElement, {
+ openingElement: {
+ name: {
+ name: name => {
+ return name in stylePropsMap
+ }
+ }
+ }
+ })
+ .forEach(el => {
+ const sx = {}
+ const elementName = el.value?.openingElement?.name?.name
+ const elementNameScrubbed = elementName.replace('.', '')
+ const systemProps = stylePropsMap[elementNameScrubbed]
+ const attrNodes = j(el).find(j.JSXAttribute, {
+ name: name => {
+ const isInElement = name.start >= el.node.start && name.end <= el.value.openingElement.end
+ return systemProps && systemProps.includes(name.name) && isInElement
+ }
+ })
+
+ const sxNodes = j(el).find(j.JSXAttribute, {
+ name: name => {
+ const isInElement = name.start >= el.node.start && name.end <= el.value.openingElement.end
+ return name.name === 'sx' && isInElement
+ }
+ })
+
+ const existingSx = {}
+ const sxNodesArray = sxNodes.nodes() || []
+ const existingSxProps = sxNodesArray[0]?.value?.expression?.properties
+ existingSxProps &&
+ existingSxProps.forEach(p => {
+ const keyName = p?.key?.name
+ const keyValue = p?.key?.raw
+ if (!keyName && !keyValue) {
+ return
+ }
+ existingSx[keyName || keyValue] = p.value
+ })
+ const spreads =
+ existingSxProps &&
+ existingSxProps
+ .filter(p => p.type === 'SpreadElement')
+ .map(s => {
+ const argName = s?.argument?.name
+ const propName = s?.argument?.property?.name
+ const objectName = s?.argument?.object?.name
+ return argName || `${objectName}.${propName}`
+ })
+
+ attrNodes.forEach((attr, index) => {
+ const key = attr?.value?.name?.name
+ const literal = attr?.value?.value
+ const val = literal.type === 'JSXExpressionContainer' ? literal.expression : literal
+ if (key && val) {
+ sx[key] = val
+ }
+ if (index + 1 !== attrNodes.length) {
+ attr.prune()
+ } else {
+ const keys = Object.keys(sx)
+ if (keys.length > 0) {
+ sxNodes.forEach(node => node.prune())
+ j(attr).replaceWith(`sx={${objectToString({...existingSx, ...sx}, spreads)}}`)
+ }
+ }
+ })
+ })
+
+ return prettify(ast, file)
+}
diff --git a/docs/content/AvatarStack.mdx b/docs/content/AvatarStack.mdx
index 61bc65aedd5..1b1ee21728d 100644
--- a/docs/content/AvatarStack.mdx
+++ b/docs/content/AvatarStack.mdx
@@ -5,32 +5,39 @@ title: AvatarStack
AvatarStack is used to display more than one Avatar in an inline stack.
### Default example
+
```jsx live
-
-
-
-
-
-
- ```
-
- ### Right aligned
+
+
+
+
+
+
+```
+
+### Right aligned
+
```jsx live
-
-
-
-
-
-
- ```
+
+
+
+
+
+
+```
## System props
-AvatarStack components get `COMMON` system props, AvatarStack.Item components do not get any system props. Read our [System Props](/system-props) doc page for a full list of available props.
+
+System props are deprecated in all components except [Box](/Box). Please use the [`sx` prop](/overriding-styles) instead.
+
+
+
+AvatarStack components get `COMMON` system props, AvatarStack.Item components do not get any system props. Read our [System Props](/system-props) doc page for a full list of available props.
## AvatarStack Component props
-| Name | Type | Default | Description |
-| :- | :- | :-: | :- |
-| alignRight | Boolean | | Creates right aligned AvatarStack |
\ No newline at end of file
+| Name | Type | Default | Description |
+| :--------- | :------ | :-----: | :-------------------------------- |
+| alignRight | Boolean | | Creates right aligned AvatarStack |
diff --git a/docs/content/BranchName.md b/docs/content/BranchName.md
index 1badc1882a2..be80991dd1b 100644
--- a/docs/content/BranchName.md
+++ b/docs/content/BranchName.md
@@ -10,10 +10,6 @@ BranchName is a label-type component rendered as an `` tag by default with mo
a_new_feature_branch
```
-## System props
-
-BranchName components get `COMMON` system props. Read our [System Props](/system-props) doc page for a full list of available props.
-
## Component props
| Name | Type | Default | Description |
diff --git a/docs/content/Breadcrumbs.md b/docs/content/Breadcrumbs.md
index db6ed9df52e..d9d4a89728a 100644
--- a/docs/content/Breadcrumbs.md
+++ b/docs/content/Breadcrumbs.md
@@ -27,6 +27,12 @@ This ensures that the NavLink gets `activeClassName='selected'`
## System props
+
+
+System props are deprecated in all components except [Box](/Box). Please use the [`sx` prop](/overriding-styles) instead.
+
+
+
Breadcrumb and Breadcrumb.Item components get `COMMON` system props. Read our [System Props](/system-props) doc page for a full list of available props.
## Component props
@@ -37,8 +43,8 @@ The `Breadcrumb` component does not receive any additional props besides `COMMON
### Breadcrumb.Item
-| Prop name | Type | Default | Description |
-| :- | :- | :-: | :- |
-| as | String | `a` | Sets the HTML tag for the component |
-| href | String | | URL to be used for the Link |
-| selected | Boolean | false | Used to style the link as selected or unselected |
+| Prop name | Type | Default | Description |
+| :-------- | :------ | :-----: | :----------------------------------------------- |
+| as | String | `a` | Sets the HTML tag for the component |
+| href | String | | URL to be used for the Link |
+| selected | Boolean | false | Used to style the link as selected or unselected |
diff --git a/docs/content/Buttons.md b/docs/content/Buttons.md
index d576ed76801..4d6d225c72a 100644
--- a/docs/content/Buttons.md
+++ b/docs/content/Buttons.md
@@ -2,7 +2,6 @@
title: Buttons
---
-
`Button` is used for actions, like in forms, while `Link` is used for destinations, or moving from one page to another.
In special cases where you'd like to use a `` styled like a Button, use `