Radio Group
Radio Group (라디오 버튼 그룹)
Radio Group은 사용자가 여러 옵션 중 하나를 선택할 수 있게 해주는 컴포넌트야. 사용자가 모든 옵션을 한 번에 봐야 한다면 라디오 버튼을 쓰고, 옵션을 접어서 숨길 수 있다면 공간을 덜 차지하는 Select 컴포넌트를 고려해 봐. 기본적으로 가장 자주 쓰는 옵션을 선택된 상태로 두는 게 좋아.
출처: 문서
본문
Radio group
RadioGroup은 여러 Radio 컴포넌트를 묶어주는 유용한 래퍼로, 더 쉬운 API와 그룹에 대한 적절한 키보드 접근성을 제공해요.
import * as React from 'react';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
export default function RadioButtonsGroup() {
const id = React.useId();
return (
<FormControl>
<FormLabel id={`${id}-label`}>Gender</FormLabel>
<RadioGroup
aria-labelledby={`${id}-label`}
defaultValue="female"
name="radio-buttons-group"
>
<FormControlLabel value="female" control={<Radio />} label="Female" />
<FormControlLabel value="male" control={<Radio />} label="Male" />
<FormControlLabel value="other" control={<Radio />} label="Other" />
</RadioGroup>
</FormControl>
);
}
방향 (Direction)
버튼을 가로로 배치하려면 row prop을 설정하면 돼요:
import * as React from 'react';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
export default function RowRadioButtonsGroup() {
const id = React.useId();
return (
<FormControl>
<FormLabel id={`${id}-label`}>Gender</FormLabel>
<RadioGroup row aria-labelledby={`${id}-label`} name="row-radio-buttons-group">
<FormControlLabel value="female" control={<Radio />} label="Female" />
<FormControlLabel value="male" control={<Radio />} label="Male" />
<FormControlLabel value="other" control={<Radio />} label="Other" />
<FormControlLabel
value="disabled"
disabled
control={<Radio />}
label="other"
/>
</RadioGroup>
</FormControl>
);
}
제어 (Controlled)
value와 onChange props를 이용해 라디오를 제어할 수 있어요:
import * as React from 'react';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
export default function ControlledRadioButtonsGroup() {
const id = React.useId();
const [value, setValue] = React.useState('female');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue((event.target as HTMLInputElement).value);
};
return (
<FormControl>
<FormLabel id={`${id}-label`}>Gender</FormLabel>
<RadioGroup
aria-labelledby={`${id}-label`}
name="controlled-radio-buttons-group"
value={value}
onChange={handleChange}
>
<FormControlLabel value="female" control={<Radio />} label="Female" />
<FormControlLabel value="male" control={<Radio />} label="Male" />
</RadioGroup>
</FormControl>
);
}
단독 라디오 버튼 (Standalone radio buttons)
Radio는 RadioGroup 래퍼 없이도 단독으로 사용할 수 있어요.
import * as React from 'react';
import Radio from '@mui/material/Radio';
export default function RadioButtons() {
const [selectedValue, setSelectedValue] = React.useState('a');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedValue(event.target.value);
};
return (
<div>
<Radio
checked={selectedValue === 'a'}
onChange={handleChange}
value="a"
name="radio-buttons"
slotProps={{ input: { 'aria-label': 'A' } }}
/>
<Radio
checked={selectedValue === 'b'}
onChange={handleChange}
value="b"
name="radio-buttons"
slotProps={{ input: { 'aria-label': 'B' } }}
/>
</div>
);
}
크기 (Size)
size prop을 쓰거나 svg 아이콘들의 font size를 커스터마이징해서 라디오 크기를 바꿀 수 있어요.
import * as React from 'react';
import Radio from '@mui/material/Radio';
export default function SizeRadioButtons() {
const [selectedValue, setSelectedValue] = React.useState('a');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedValue(event.target.value);
};
const controlProps = (item: string) => ({
checked: selectedValue === item,
onChange: handleChange,
value: item,
name: 'size-radio-button-demo',
inputProps: { 'aria-label': item },
});
return (
<div>
<Radio {...controlProps('a')} size="small" />
<Radio {...controlProps('b')} />
<Radio
{...controlProps('c')}
sx={{
'& .MuiSvgIcon-root': {
fontSize: 28,
},
}}
/>
</div>
);
}
색상 (Color)
import * as React from 'react';
import { pink } from '@mui/material/colors';
import Radio from '@mui/material/Radio';
export default function ColorRadioButtons() {
const [selectedValue, setSelectedValue] = React.useState('a');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedValue(event.target.value);
};
const controlProps = (item: string) => ({
checked: selectedValue === item,
onChange: handleChange,
value: item,
name: 'color-radio-button-demo',
inputProps: { 'aria-label': item },
});
return (
<div>
<Radio {...controlProps('a')} />
<Radio {...controlProps('b')} color="secondary" />
<Radio {...controlProps('c')} color="success" />
<Radio {...controlProps('d')} color="default" />
<Radio
{...controlProps('e')}
sx={{
color: pink[800],
'&.Mui-checked': {
color: pink[600],
},
}}
/>
</div>
);
}
라벨 배치 (Label placement)
FormControlLabel 컴포넌트의 labelPlacement prop으로 라벨의 위치를 바꿀 수 있어요:
import * as React from 'react';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
export default function FormControlLabelPlacement() {
const id = React.useId();
return (
<FormControl>
<FormLabel id={`${id}-label`}>Label placement</FormLabel>
<RadioGroup
row
aria-labelledby={`${id}-label`}
name="position"
defaultValue="top"
>
<FormControlLabel
value="bottom"
control={<Radio />}
label="Bottom"
labelPlacement="bottom"
/>
<FormControlLabel value="end" control={<Radio />} label="End" />
</RadioGroup>
</FormControl>
);
}
오류 표시 (Show error)
일반적으로 라디오 버튼은 기본적으로 값이 선택되어 있어야 해요. 그렇지 않은 경우, 폼 제출 시 값이 선택되지 않았으면 오류를 표시할 수 있어요:
import * as React from 'react';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
import FormLabel from '@mui/material/FormLabel';
import Button from '@mui/material/Button';
export default function ErrorRadios() {
const id = React.useId();
const [value, setValue] = React.useState('');
const [error, setError] = React.useState(false);
const [helperText, setHelperText] = React.useState('Choose wisely');
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
setError(false);
setHelperText('Choose wisely');
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (value === 'best') {
setHelperText('You got it!');
setError(false);
} else if (value === 'worst') {
setHelperText('Sorry, wrong answer!');
setError(true);
} else {
setHelperText('Please select an option.');
setError(true);
}
};
return (
<form onSubmit={handleSubmit}>
<FormControl sx={{ m: 3 }} error={error} variant="standard">
<FormLabel id={`${id}-label`}>Pop quiz: MUI is…</FormLabel>
<RadioGroup
aria-labelledby={`${id}-label`}
name="quiz"
value={value}
onChange={handleRadioChange}
>
<FormControlLabel value="best" control={<Radio />} label="The best!" />
<FormControlLabel value="worst" control={<Radio />} label="The worst." />
</RadioGroup>
<FormHelperText>{helperText}</FormHelperText>
<Button sx={{ mt: 1, mr: 1 }} type="submit" variant="outlined">
Check Answer
</Button>
</FormControl>
</form>
);
}
커스터마이징 (Customization)
여기 컴포넌트를 커스터마이징하는 예시가 있어요. 자세한 내용은 overrides 문서 페이지에서 확인할 수 있어요.
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Radio, { RadioProps } from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
const BpIcon = styled('span')(({ theme }) => ({
borderRadius: '50%',
width: 16,
height: 16,
boxShadow: 'inset 0 0 0 1px rgba(16,22,26,.2), inset 0 -1px 0 rgba(16,22,26,.1)',
backgroundColor: '#f5f8fa',
backgroundImage: 'linear-gradient(180deg,hsla(0,0%,100%,.8),hsla(0,0%,100%,0))',
'.Mui-focusVisible &': {
outline: '2px auto rgba(19,124,189,.6)',
outlineOffset: 2,
},
'input:hover ~ &': {
backgroundColor: '#ebf1f5',
...theme.applyStyles('dark', {
backgroundColor: '#30404d',
}),
},
'input:disabled ~ &': {
boxShadow: 'none',
background: 'rgba(206,217,224,.5)',
...theme.applyStyles('dark', {
background: 'rgba(57,75,89,.5)',
}),
'@media (forced-colors: active)': {
outline: '1px solid GrayText',
},
},
'@media (forced-colors: active)': {
outline: '1px solid ButtonText',
},
...theme.applyStyles('dark', {
boxShadow: '0 0 0 1px rgb(16 22 26 / 40%)',
backgroundColor: '#394b59',
backgroundImage: 'linear-gradient(180deg,hsla(0,0%,100%,.05),hsla(0,0%,100%,0))',
}),
}));
const BpCheckedIcon = styled(BpIcon)({
backgroundColor: '#137cbd',
backgroundImage: 'linear-gradient(180deg,hsla(0,0%,100%,.1),hsla(0,0%,100%,0))',
'&::before': {
display: 'block',
width: 16,
height: 16,
backgroundImage: 'radial-gradient(#fff,#fff 28%,transparent 32%)',
content: '""',
'@media (forced-colors: active)': {
backgroundImage: 'none',
backgroundColor: 'ButtonText',
borderRadius: '50%',
width: 8,
height: 8,
margin: 4,
},
},
'input:hover ~ &': {
backgroundColor: '#106ba3',
},
'@media (forced-colors: active)': {
outline: '2px solid ButtonText',
},
});
// Inspired by blueprintjs
function BpRadio(props: RadioProps) {
return (
<Radio
disableRipple
color="default"
checkedIcon={<BpCheckedIcon />}
icon={<BpIcon />}
{...props}
/>
);
}
export default function CustomizedRadios() {
const id = React.useId();
return (
<FormControl>
<FormLabel id={`${id}-label`}>Gender</FormLabel>
<RadioGroup
defaultValue="female"
aria-labelledby={`${id}-label`}
name="customized-radios"
>
<FormControlLabel value="female" control={<BpRadio />} label="Female" />
<FormControlLabel value="male" control={<BpRadio />} label="Male" />
<FormControlLabel value="other" control={<BpRadio />} label="Other" />
<FormControlLabel
value="disabled"
disabled
control={<BpRadio />}
label="(Disabled option)"
/>
</RadioGroup>
</FormControl>
);
}
useRadioGroup
고급 커스터마이징 케이스를 위해 useRadioGroup() 훅이 노출돼 있어요. 이 훅은 부모 라디오 그룹의 컨텍스트 값을 반환해요. Radio 컴포넌트도 내부적으로 이 훅을 사용해요.
API
import { useRadioGroup } from '@mui/material/RadioGroup';
반환 값 (Returns)
value (object):
value.name(string [optional]): 컨트롤의 값 참조에 사용하는 name.value.onChange(func [optional]): 라디오 버튼이 선택될 때 실행되는 콜백.value.value(any [optional]): 선택된 라디오 버튼의 값.
예시 (Example)
import { styled } from '@mui/material/styles';
import RadioGroup, { useRadioGroup } from '@mui/material/RadioGroup';
import FormControlLabel, {
FormControlLabelProps,
} from '@mui/material/FormControlLabel';
import Radio from '@mui/material/Radio';
interface StyledFormControlLabelProps extends FormControlLabelProps {
checked: boolean;
}
const StyledFormControlLabel = styled((props: StyledFormControlLabelProps) => (
<FormControlLabel {...props} />
))(({ theme }) => ({
variants: [
{
props: { checked: true },
style: {
'.MuiFormControlLabel-label': {
color: theme.palette.primary.main,
},
},
},
],
}));
function MyFormControlLabel(props: FormControlLabelProps) {
const radioGroup = useRadioGroup();
let checked = false;
if (radioGroup) {
checked = radioGroup.value === props.value;
}
return <StyledFormControlLabel checked={checked} {...props} />;
}
export default function UseRadioGroup() {
return (
<RadioGroup name="use-radio-group" defaultValue="first">
<MyFormControlLabel value="first" label="First" control={<Radio />} />
<MyFormControlLabel value="second" label="Second" control={<Radio />} />
</RadioGroup>
);
}
언제 쓸까 (When to use)
접근성 (Accessibility)
(WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/radio/)
- 모든 폼 컨트롤에는 라벨이 있어야 해요. 여기에는 라디오 버튼, 체크박스, 스위치가 포함돼요. 대부분의 경우
<label>요소(FormControlLabel)를 사용해서 처리해요. - 라벨을 사용할 수 없을 때는 input 컴포넌트에 직접 속성을 추가해야 해요. 이 경우
slotProps.input속성을 통해 추가 속성(예:aria-label,aria-labelledby,title)을 적용할 수 있어요.
<Radio
value="radioA"
slotProps={{
input: { 'aria-label': 'Radio A' },
}}
/>
FormControl / FormControlLabel / FormLabel / Radio / RadioGroup API
임포트 (Import):
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormLabel from '@mui/material/FormLabel';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
// or
import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup } from '@mui/material';
주요 props 요약:
FormControl:error,disabled,required,fullWidth,margin,size,variant,color등을 제공해요.ref는 루트 HTMLDivElement로 전달돼요.FormControlLabel:control(필수),label,labelPlacement('bottom' | 'end' | 'start' | 'top', 기본'end'),disableTypography,required,value등을 제공해요.ref는 HTMLLabelElement로 전달돼요.FormLabel:error,disabled,filled,focused,required,color등을 제공해요.ref는 HTMLLabelElement로 전달돼요.Radio:checked,checkedIcon,color,disabled,disableRipple,icon,size,value,slotProps,slots등을 제공해요.ref는 루트 HTMLSpanElement로 전달되고, ButtonBase의 props도 상속받아요.RadioGroup:defaultValue,name,onChange,value등을 제공해요.ref는 HTMLDivElement로 전달되고, FormGroup의 props도 상속받아요.
테마에서 MuiRadio, MuiRadioGroup 등을 사용하면 각 컴포넌트의 기본 props를 바꿀 수 있어요.