diff --git a/.changeset/fifty-suns-smoke.md b/.changeset/fifty-suns-smoke.md
new file mode 100644
index 00000000000..749aa264863
--- /dev/null
+++ b/.changeset/fifty-suns-smoke.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": minor
+---
+
+Convert Radio to css modules behind feature flag
diff --git a/packages/react/src/Radio/Radio.dev.stories.tsx b/packages/react/src/Radio/Radio.dev.stories.tsx
new file mode 100644
index 00000000000..56dcf214241
--- /dev/null
+++ b/packages/react/src/Radio/Radio.dev.stories.tsx
@@ -0,0 +1,18 @@
+import React from 'react'
+import {Box, FormControl, Radio} from '..'
+
+export default {
+ title: 'Components/Radio/Dev',
+ component: Radio,
+}
+
+export const SxProp = () => {
+ return (
+
+
+
+ Label
+
+
+ )
+}
diff --git a/packages/react/src/Radio/Radio.module.css b/packages/react/src/Radio/Radio.module.css
new file mode 100644
index 00000000000..2032cb8a697
--- /dev/null
+++ b/packages/react/src/Radio/Radio.module.css
@@ -0,0 +1,34 @@
+.Radio {
+ border-radius: var(--borderRadius-full, 100vh);
+ transition:
+ background-color,
+ border-color 80ms cubic-bezier(0.33, 1, 0.68, 1); /* checked -> unchecked - add 120ms delay to fully see animation-out */
+
+ &:where(:checked) {
+ /* stylelint-disable-next-line primer/colors */
+ background-color: var(--control-checked-fgColor-rest);
+
+ /* using bgColor here to avoid a border change in dark high contrast */
+ /* stylelint-disable-next-line primer/colors */
+ border-color: var(--control-checked-bgColor-rest);
+ border-width: var(--borderWidth-thicker);
+
+ &:disabled {
+ cursor: not-allowed;
+ /* stylelint-disable-next-line primer/colors */
+ background-color: var(--fgColor-muted);
+ /* stylelint-disable-next-line primer/colors */
+ border-color: var(--fgColor-muted);
+ }
+ }
+
+ &:focus,
+ &:focus-within {
+ @mixin focusOutline 2px;
+ }
+
+ @media (forced-colors: active) {
+ background-color: canvastext;
+ border-color: canvastext;
+ }
+}
diff --git a/packages/react/src/Radio/Radio.tsx b/packages/react/src/Radio/Radio.tsx
index 4ddcb346d5d..6040ad4fd20 100644
--- a/packages/react/src/Radio/Radio.tsx
+++ b/packages/react/src/Radio/Radio.tsx
@@ -8,6 +8,11 @@ import {RadioGroupContext} from '../RadioGroup/RadioGroup'
import getGlobalFocusStyles from '../internal/utils/getGlobalFocusStyles'
import {get} from '../constants'
import {sharedCheckboxAndRadioStyles} from '../internal/utils/sharedCheckboxAndRadioStyles'
+import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
+import {useFeatureFlag} from '../FeatureFlags'
+import {clsx} from 'clsx'
+import classes from './Radio.module.css'
+import sharedClasses from '../Checkbox/shared.module.css'
export type RadioProps = {
/**
@@ -42,47 +47,63 @@ export type RadioProps = {
} & InputHTMLAttributes &
SxProp
-const StyledRadio = styled.input`
- ${sharedCheckboxAndRadioStyles};
- border-radius: var(--borderRadius-full, 100vh);
- transition:
- background-color,
- border-color 80ms cubic-bezier(0.33, 1, 0.68, 1); /* checked -> unchecked - add 120ms delay to fully see animation-out */
+const StyledRadio = toggleStyledComponent(
+ 'primer_react_css_modules_team',
+ 'input',
+ styled.input`
+ ${sharedCheckboxAndRadioStyles};
+ border-radius: var(--borderRadius-full, 100vh);
+ transition:
+ background-color,
+ border-color 80ms cubic-bezier(0.33, 1, 0.68, 1); /* checked -> unchecked - add 120ms delay to fully see animation-out */
- &:checked {
- border-width: var(--base-size-4, 4px);
- border-color: var(
- --control-checked-bgColor-rest,
- ${get('colors.accent.fg')}
- ); /* using bgColor here to avoid a border change in dark high contrast */
- background-color: var(--control-checked-fgColor-rest, ${get('colors.fg.onEmphasis')});
+ &:checked {
+ border-width: var(--base-size-4, 4px);
+ border-color: var(
+ --control-checked-bgColor-rest,
+ ${get('colors.accent.fg')}
+ ); /* using bgColor here to avoid a border change in dark high contrast */
+ background-color: var(--control-checked-fgColor-rest, ${get('colors.fg.onEmphasis')});
- &:disabled {
- cursor: not-allowed;
- border-color: ${get('colors.fg.muted')};
- background-color: ${get('colors.fg.muted')};
+ &:disabled {
+ cursor: not-allowed;
+ border-color: ${get('colors.fg.muted')};
+ background-color: ${get('colors.fg.muted')};
+ }
}
- }
- ${getGlobalFocusStyles()};
+ ${getGlobalFocusStyles()};
- @media (forced-colors: active) {
- background-color: canvastext;
- border-color: canvastext;
- }
+ @media (forced-colors: active) {
+ background-color: canvastext;
+ border-color: canvastext;
+ }
- ${sx}
-`
+ ${sx}
+ `,
+)
/**
* An accessible, native radio component for selecting one option from a list.
*/
const Radio = React.forwardRef(
(
- {checked, disabled, name: nameProp, onChange, sx: sxProp, required, validationStatus, value, ...rest}: RadioProps,
+ {
+ checked,
+ disabled,
+ name: nameProp,
+ onChange,
+ sx: sxProp,
+ required,
+ validationStatus,
+ value,
+ className,
+ ...rest
+ }: RadioProps,
ref,
): ReactElement => {
const radioGroupContext = useContext(RadioGroupContext)
+ const enabled = useFeatureFlag('primer_react_css_modules_team')
const handleOnChange: ChangeEventHandler = e => {
radioGroupContext?.onChange && radioGroupContext.onChange(e)
onChange && onChange(e)
@@ -110,6 +131,10 @@ const Radio = React.forwardRef(
aria-invalid={validationStatus === 'error' ? 'true' : 'false'}
sx={sxProp}
onChange={handleOnChange}
+ className={clsx(className, {
+ [sharedClasses.Input]: enabled,
+ [classes.Radio]: enabled,
+ })}
{...rest}
/>
)