Skip to content

chore(CheckboxOrRadioGroup): Remove the CSS modules feature flag from the CheckboxOrRadioGroup component #6013

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/afraid-pens-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Remove the CSS modules feature flag from the CheckboxOrRadioGroup component
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import React from 'react'
import styled from 'styled-components'
import Box from '../../../Box'
import ValidationAnimationContainer from '../ValidationAnimationContainer'
import {get} from '../../../constants'
import {useId} from '../../../hooks/useId'
import CheckboxOrRadioGroupCaption from './CheckboxOrRadioGroupCaption'
import CheckboxOrRadioGroupLabel from './CheckboxOrRadioGroupLabel'
Expand All @@ -12,10 +9,8 @@ import VisuallyHidden from '../../../_VisuallyHidden'
import {useSlots} from '../../../hooks/useSlots'
import type {SxProp} from '../../../sx'
import classes from './CheckboxOrRadioGroup.module.css'
import {toggleStyledComponent} from '../../utils/toggleStyledComponent'
import {useFeatureFlag} from '../../../FeatureFlags'
import {clsx} from 'clsx'
import {CSS_MODULES_FLAG} from './FeatureFlag'
import {BoxWithFallback} from '../BoxWithFallback'

export type CheckboxOrRadioGroupProps = {
/** Class name for custom styling */
Expand All @@ -39,22 +34,6 @@ export type CheckboxOrRadioGroupProps = {
required?: boolean
} & SxProp

const Body = toggleStyledComponent(
CSS_MODULES_FLAG,
'div',
styled.div`
display: flex;
flex-direction: column;
list-style: none;
margin: 0;
padding: 0;

> * + * {
margin-top: ${get('space.2')};
}
`,
)

const CheckboxOrRadioGroup: React.FC<React.PropsWithChildren<CheckboxOrRadioGroupProps>> = ({
'aria-labelledby': ariaLabelledby,
children,
Expand Down Expand Up @@ -91,150 +70,6 @@ const CheckboxOrRadioGroup: React.FC<React.PropsWithChildren<CheckboxOrRadioGrou

const isLegendVisible = React.isValidElement(labelChild) && !labelChild.props.visuallyHidden

const enabled = useFeatureFlag(CSS_MODULES_FLAG)

if (enabled) {
if (sx) {
return (
<CheckboxOrRadioGroupContext.Provider
value={{
disabled,
required,
captionId,
validationMessageId,
}}
>
<div>
<Box
className={clsx(className, classes.GroupFieldset)}
data-validation={validationChild ? '' : undefined}
{...(labelChild
? {
as: 'fieldset',
disabled,
}
: {})}
sx={sx}
>
{labelChild ? (
/*
Placing the caption text and validation text in the <legend> provides a better user
experience for more screenreaders.

Reference: https://blog.tenon.io/accessible-validation-of-checkbox-and-radiobutton-groups/
*/
<legend className={classes.GroupLegend} data-legend-visible={isLegendVisible ? '' : undefined}>
{slots.label}
{slots.caption}
{React.isValidElement(slots.validation) && slots.validation.props.children && (
<VisuallyHidden>{slots.validation.props.children}</VisuallyHidden>
)}
</legend>
) : (
/*
If CheckboxOrRadioGroup.Label wasn't passed as a child, we don't render a <legend>
but we still want to render a caption
*/
slots.caption
)}

<Body
className={classes.Body}
{...(!labelChild
? {
['aria-labelledby']: ariaLabelledby,
['aria-describedby']: [validationMessageId, captionId].filter(Boolean).join(' '),
as: 'div',
role: 'group',
}
: {})}
>
{React.Children.toArray(rest).filter(child => React.isValidElement(child))}
</Body>
</Box>
{validationChild && (
<ValidationAnimationContainer
// If we have CheckboxOrRadioGroup.Label as a child, we render a screenreader-accessible validation message in the <legend>
aria-hidden={Boolean(labelChild)}
show
>
{slots.validation}
</ValidationAnimationContainer>
)}
</div>
</CheckboxOrRadioGroupContext.Provider>
)
}
return (
<CheckboxOrRadioGroupContext.Provider
value={{
disabled,
required,
captionId,
validationMessageId,
}}
>
<div>
<fieldset
className={clsx(className, classes.GroupFieldset)}
data-validation={validationChild ? '' : undefined}
{...(labelChild
? {
as: 'fieldset',
disabled,
}
: {})}
>
{labelChild ? (
/*
Placing the caption text and validation text in the <legend> provides a better user
experience for more screenreaders.

Reference: https://blog.tenon.io/accessible-validation-of-checkbox-and-radiobutton-groups/
*/
<legend className={classes.GroupLegend} data-legend-visible={isLegendVisible ? '' : undefined}>
{slots.label}
{slots.caption}
{React.isValidElement(slots.validation) && slots.validation.props.children && (
<VisuallyHidden>{slots.validation.props.children}</VisuallyHidden>
)}
</legend>
) : (
/*
If CheckboxOrRadioGroup.Label wasn't passed as a child, we don't render a <legend>
but we still want to render a caption
*/
slots.caption
)}

<Body
className={classes.Body}
{...(!labelChild
? {
['aria-labelledby']: ariaLabelledby,
['aria-describedby']: [validationMessageId, captionId].filter(Boolean).join(' '),
as: 'div',
role: 'group',
}
: {})}
>
{React.Children.toArray(rest).filter(child => React.isValidElement(child))}
</Body>
</fieldset>
{validationChild && (
<ValidationAnimationContainer
// If we have CheckboxOrRadioGroup.Label as a child, we render a screenreader-accessible validation message in the <legend>
aria-hidden={Boolean(labelChild)}
show
>
{slots.validation}
</ValidationAnimationContainer>
)}
</div>
</CheckboxOrRadioGroupContext.Provider>
)
}

return (
<CheckboxOrRadioGroupContext.Provider
value={{
Expand All @@ -245,43 +80,41 @@ const CheckboxOrRadioGroup: React.FC<React.PropsWithChildren<CheckboxOrRadioGrou
}}
>
<div>
<Box
border="none"
margin={0}
mb={validationChild ? 2 : undefined}
padding={0}
<BoxWithFallback
className={clsx(className, classes.GroupFieldset)}
data-validation={validationChild ? '' : undefined}
{...(labelChild
? {
as: 'fieldset',
disabled,
}
: {})}
className={className}
sx={sx}
>
{labelChild ? (
/*
Placing the caption text and validation text in the <legend> provides a better user
experience for more screenreaders.
Placing the caption text and validation text in the <legend> provides a better user
experience for more screenreaders.

Reference: https://blog.tenon.io/accessible-validation-of-checkbox-and-radiobutton-groups/
*/
<Box as="legend" mb={isLegendVisible ? 2 : undefined} padding={0}>
Reference: https://blog.tenon.io/accessible-validation-of-checkbox-and-radiobutton-groups/
*/
<legend className={classes.GroupLegend} data-legend-visible={isLegendVisible ? '' : undefined}>
{slots.label}
{slots.caption}
{React.isValidElement(slots.validation) && slots.validation.props.children && (
<VisuallyHidden>{slots.validation.props.children}</VisuallyHidden>
)}
</Box>
</legend>
) : (
/*
If CheckboxOrRadioGroup.Label wasn't passed as a child, we don't render a <legend>
but we still want to render a caption
*/
If CheckboxOrRadioGroup.Label wasn't passed as a child, we don't render a <legend>
but we still want to render a caption
*/
slots.caption
)}

<Body
<div
className={classes.Body}
{...(!labelChild
? {
['aria-labelledby']: ariaLabelledby,
Expand All @@ -292,8 +125,8 @@ const CheckboxOrRadioGroup: React.FC<React.PropsWithChildren<CheckboxOrRadioGrou
: {})}
>
{React.Children.toArray(rest).filter(child => React.isValidElement(child))}
</Body>
</Box>
</div>
</BoxWithFallback>
{validationChild && (
<ValidationAnimationContainer
// If we have CheckboxOrRadioGroup.Label as a child, we render a screenreader-accessible validation message in the <legend>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,14 @@ import Text from '../../../Text'
import type {SxProp} from '../../../sx'
import CheckboxOrRadioGroupContext from './CheckboxOrRadioGroupContext'
import classes from './CheckboxOrRadioGroup.module.css'
import {CSS_MODULES_FLAG} from './FeatureFlag'
import {useFeatureFlag} from '../../../FeatureFlags'
import {clsx} from 'clsx'

type CheckboxOrRadioGroupCaptionProps = React.PropsWithChildren<SxProp> & {className?: string}

const CheckboxOrRadioGroupCaption: React.FC<CheckboxOrRadioGroupCaptionProps> = ({className, children, sx}) => {
const {disabled, captionId} = React.useContext(CheckboxOrRadioGroupContext)
const enabled = useFeatureFlag(CSS_MODULES_FLAG)
if (enabled) {
if (sx) {
return (
<Text className={clsx(className, classes.CheckboxOrRadioGroupCaption)} id={captionId} sx={sx}>
{children}
</Text>
)
}
return (
<span className={clsx(className, classes.CheckboxOrRadioGroupCaption)} id={captionId}>
{children}
</span>
)
}

const {captionId} = React.useContext(CheckboxOrRadioGroupContext)
return (
<Text color={disabled ? 'fg.muted' : 'fg.subtle'} fontSize={1} id={captionId} sx={sx} className={className}>
<Text className={clsx(className, classes.CheckboxOrRadioGroupCaption)} id={captionId} sx={sx}>
{children}
</Text>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React from 'react'
import Box from '../../../Box'
import VisuallyHidden from '../../../_VisuallyHidden'
import type {SxProp} from '../../../sx'
import CheckboxOrRadioGroupContext from './CheckboxOrRadioGroupContext'
import {CSS_MODULES_FLAG} from './FeatureFlag'
import {useFeatureFlag} from '../../../FeatureFlags'
import classes from './CheckboxOrRadioGroup.module.css'
import {Stack} from '../../../Stack'
import {clsx} from 'clsx'
Expand All @@ -25,67 +22,20 @@ const CheckboxOrRadioGroupLabel: React.FC<React.PropsWithChildren<CheckboxOrRadi
sx,
}) => {
const {required, disabled} = React.useContext(CheckboxOrRadioGroupContext)
const enabled = useFeatureFlag(CSS_MODULES_FLAG)

if (enabled) {
if (sx) {
return (
<VisuallyHidden
className={clsx(className, classes.RadioGroupLabel)}
isVisible={!visuallyHidden}
title={required ? 'required field' : undefined}
data-label-disabled={disabled ? '' : undefined}
sx={sx}
>
{required ? (
<Stack direction="horizontal" gap="none">
<div className={classes.GroupLabelChildren}>{children}</div>
<span>*</span>
</Stack>
) : (
children
)}
</VisuallyHidden>
)
}

return (
<VisuallyHidden
className={clsx(className, classes.RadioGroupLabel)}
isVisible={!visuallyHidden}
title={required ? 'required field' : undefined}
data-label-disabled={disabled ? '' : undefined}
>
{required ? (
<Stack direction="horizontal" gap="none">
<div className={classes.GroupLabelChildren}>{children}</div>
<span>*</span>
</Stack>
) : (
children
)}
</VisuallyHidden>
)
}

return (
<VisuallyHidden
className={className}
className={clsx(className, classes.RadioGroupLabel)}
isVisible={!visuallyHidden}
title={required ? 'required field' : undefined}
sx={{
display: 'block',
color: disabled ? 'fg.muted' : undefined,
fontSize: 1,
fontWeight: 'bold',
...sx,
}}
data-label-disabled={disabled ? '' : undefined}
sx={sx}
>
{required ? (
<Box display="flex" as="span">
<Box mr={1}>{children}</Box>
<Stack direction="horizontal" gap="none">
<div className={classes.GroupLabelChildren}>{children}</div>
<span>*</span>
</Box>
</Stack>
) : (
children
)}
Expand Down

This file was deleted.

Loading