슬라이더
슬라이더 (Slider)
슬라이더는 사용자가 값의 범위(range)에서 선택을 할 수 있게 해 주는 컴포넌트예요. 슬라이더는 막대(bar)를 따라 값의 범위를 반영하며, 사용자는 그중 단일 값을 선택할 수 있어요. 볼륨, 밝기 같은 설정을 조정하거나 이미지 필터를 적용할 때 이상적이랍니다.
출처: 문서
본문
연속 슬라이더 (Continuous sliders)
연속 슬라이더는 사용자가 주관적인 범위에서 값을 선택할 수 있게 해 줘요.
import * as React from 'react';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Slider from '@mui/material/Slider';
import VolumeDown from '@mui/icons-material/VolumeDown';
import VolumeUp from '@mui/icons-material/VolumeUp';
export default function ContinuousSlider() {
const [value, setValue] = React.useState<number>(30);
const handleChange = (event: Event, newValue: number) => {
setValue(newValue);
};
return (
<Box sx={{ width: 200 }}>
<Stack spacing={2} direction="row" sx={{ alignItems: 'center', mb: 1 }}>
<VolumeDown />
<Slider aria-label="Volume" value={value} onChange={handleChange} />
<VolumeUp />
</Stack>
<Slider disabled defaultValue={30} aria-label="Disabled slider" />
</Box>
);
}
크기 (Sizes)
더 작은 슬라이더를 만들려면 size="small" prop을 사용하세요.
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
export default function SliderSizes() {
return (
<Box sx={{ width: 300 }}>
<Slider
size="small"
defaultValue={70}
aria-label="Small"
valueLabelDisplay="auto"
/>
<Slider defaultValue={50} aria-label="Default" valueLabelDisplay="auto" />
</Box>
);
}
이산 슬라이더 (Discrete sliders)
이산 슬라이더는 값 표시기(value indicator)를 참조해 특정 값으로 조정할 수 있어요. marks={true}로 각 단계(step)에 마크를 생성할 수 있습니다.
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
function valuetext(value: number) {
return `${value}°C`;
}
export default function DiscreteSlider() {
return (
<Box sx={{ width: 300 }}>
<Slider
aria-label="Temperature"
defaultValue={30}
getAriaValueText={valuetext}
valueLabelDisplay="auto"
shiftStep={30}
step={10}
marks
min={10}
max={110}
/>
<Slider defaultValue={30} step={10} marks min={10} max={110} disabled />
</Box>
);
}
작은 단계 (Small steps)
기본 단계 증가량(step increment)을 변경할 수 있어요. shiftStep prop (Page Up/Down 또는 Shift + Arrow Up/Down을 사용할 때 슬라이더가 이동할 수 있는 세밀도)을 step으로 나누어 떨어지는 값으로 조정하는 것을 잊지 마세요.
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
function valuetext(value: number) {
return `${value}°C`;
}
export default function DiscreteSliderSteps() {
return (
<Box sx={{ width: 300 }}>
<Slider
aria-label="Small steps"
defaultValue={0.00000005}
getAriaValueText={valuetext}
step={0.00000001}
marks
min={-0.00000005}
max={0.0000001}
valueLabelDisplay="auto"
/>
</Box>
);
}
커스텀 마크 (Custom marks)
marks prop에 풍부한 배열을 제공해 커스텀 마크를 만들 수 있어요.
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
const marks = [
{
value: 0,
label: '0°C',
},
{
value: 20,
label: '20°C',
},
{
value: 37,
label: '37°C',
},
{
value: 100,
label: '100°C',
},
];
function valuetext(value: number) {
return `${value}°C`;
}
export default function DiscreteSliderMarks() {
return (
<Box sx={{ width: 300 }}>
<Slider
aria-label="Custom marks"
defaultValue={20}
getAriaValueText={valuetext}
step={10}
valueLabelDisplay="auto"
marks={marks}
/>
</Box>
);
}
제한된 값 (Restricted values)
step={null}로 marks prop에 제공된 값들로만 선택 가능한 값을 제한할 수 있어요.
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
const marks = [
{
value: 0,
label: '0°C',
},
{
value: 20,
label: '20°C',
},
{
value: 37,
label: '37°C',
},
{
value: 100,
label: '100°C',
},
];
function valuetext(value: number) {
return `${value}°C`;
}
export default function DiscreteSliderValues() {
return (
<Box sx={{ width: 300 }}>
<Slider
aria-label="Restricted values"
defaultValue={20}
getAriaValueText={valuetext}
step={null}
valueLabelDisplay="auto"
marks={marks}
/>
</Box>
);
}
라벨 항상 표시 (Label always visible)
valueLabelDisplay="on"으로 thumb 라벨을 항상 표시하도록 강제할 수 있어요.
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
const marks = [
{
value: 0,
label: '0°C',
},
{
value: 20,
label: '20°C',
},
{
value: 37,
label: '37°C',
},
{
value: 100,
label: '100°C',
},
];
function valuetext(value: number) {
return `${value}°C`;
}
export default function DiscreteSliderLabel() {
return (
<Box sx={{ width: 300 }}>
<Slider
aria-label="Always visible"
defaultValue={80}
getAriaValueText={valuetext}
step={10}
marks={marks}
valueLabelDisplay="on"
/>
</Box>
);
}
범위 슬라이더 (Range slider)
value prop에 값 배열을 공급해 슬라이더가 범위의 시작과 끝을 설정하는 데 사용되게 할 수 있어요.
import * as React from 'react';
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
function valuetext(value: number) {
return `${value}°C`;
}
export default function RangeSlider() {
const [value, setValue] = React.useState<number[]>([20, 37]);
const handleChange = (event: Event, newValue: number[]) => {
setValue(newValue);
};
return (
<Box sx={{ width: 300 }}>
<Slider
getAriaLabel={() => 'Temperature range'}
value={value}
onChange={handleChange}
valueLabelDisplay="auto"
getAriaValueText={valuetext}
/>
</Box>
);
}
최소 거리 (Minimum distance)
onChange 이벤트 핸들러에서 값 사이의 최소 거리를 강제할 수 있어요. 기본적으로 다른 thumb을 드래그하는 동안 포인터를 특정 thumb 위로 움직이면, 활성 thumb이 호버된 thumb으로 교체됩니다. 이 동작은 disableSwap prop으로 비활성화할 수 있어요. 최소 거리에 도달했을 때 범위가 함께 이동하도록 하려면 onChange에서 activeThumb 매개변수를 활용할 수 있습니다.
import * as React from 'react';
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
function valuetext(value: number) {
return `${value}°C`;
}
const minDistance = 10;
export default function MinimumDistanceSlider() {
const [value1, setValue1] = React.useState<number[]>([20, 37]);
const handleChange1 = (event: Event, newValue: number[], activeThumb: number) => {
if (activeThumb === 0) {
setValue1([Math.min(newValue[0], value1[1] - minDistance), value1[1]]);
} else {
setValue1([value1[0], Math.max(newValue[1], value1[0] + minDistance)]);
}
};
const [value2, setValue2] = React.useState<number[]>([20, 37]);
const handleChange2 = (event: Event, newValue: number[], activeThumb: number) => {
if (newValue[1] - newValue[0] < minDistance) {
if (activeThumb === 0) {
const clamped = Math.min(newValue[0], 100 - minDistance);
setValue2([clamped, clamped + minDistance]);
} else {
const clamped = Math.max(newValue[1], minDistance);
setValue2([clamped - minDistance, clamped]);
}
} else {
setValue2(newValue);
}
};
return (
<Box sx={{ width: 300 }}>
<Slider
getAriaLabel={() => 'Minimum distance'}
value={value1}
onChange={handleChange1}
valueLabelDisplay="auto"
getAriaValueText={valuetext}
disableSwap
/>
<Slider
getAriaLabel={() => 'Minimum distance shift'}
value={value2}
onChange={handleChange2}
valueLabelDisplay="auto"
getAriaValueText={valuetext}
disableSwap
/>
</Box>
);
}
입력 필드가 있는 슬라이더 (Slider with input field)
이 예시에서는 입력(input)이 이산 값을 설정할 수 있게 해 줘요.
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import Slider from '@mui/material/Slider';
import MuiInput from '@mui/material/Input';
import VolumeUp from '@mui/icons-material/VolumeUp';
const Input = styled(MuiInput)`
width: 42px;
`;
export default function InputSlider() {
const [value, setValue] = React.useState(30);
const handleSliderChange = (event: Event, newValue: number) => {
setValue(newValue);
};
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value === '' ? 0 : Number(event.target.value));
};
const handleBlur = () => {
if (value < 0) {
setValue(0);
} else if (value > 100) {
setValue(100);
}
};
return (
<Box sx={{ width: 250 }}>
<Typography id="input-slider" gutterBottom>
Volume
</Typography>
<Grid container spacing={2} sx={{ alignItems: 'center' }}>
<Grid>
<VolumeUp />
</Grid>
<Grid size="grow">
<Slider
value={typeof value === 'number' ? value : 0}
onChange={handleSliderChange}
aria-labelledby="input-slider"
/>
</Grid>
<Grid>
<Input
value={value}
size="small"
onChange={handleInputChange}
onBlur={handleBlur}
inputProps={{
step: 10,
min: 0,
max: 100,
type: 'number',
'aria-labelledby': 'input-slider',
}}
/>
</Grid>
</Grid>
</Box>
);
}
색상 (Color)
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
function valuetext(value: number) {
return `${value}°C`;
}
export default function ColorSlider() {
return (
<Box sx={{ width: 300 }}>
<Slider
aria-label="Temperature"
defaultValue={30}
getAriaValueText={valuetext}
color="secondary"
/>
</Box>
);
}
커스터마이징 (Customization)
컴포넌트를 커스터마이징하는 몇 가지 예시예요. 자세한 내용은 오버라이드 문서 페이지에서 확인할 수 있어요.
import * as React from 'react';
import Slider, { SliderThumb, SliderValueLabelProps } from '@mui/material/Slider';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import Tooltip from '@mui/material/Tooltip';
import Box from '@mui/material/Box';
function ValueLabelComponent(props: SliderValueLabelProps) {
const { children, value } = props;
return (
<Tooltip enterTouchDelay={0} placement="top" title={value}>
{children}
</Tooltip>
);
}
const iOSBoxShadow =
'0 3px 1px rgba(0,0,0,0.1),0 4px 8px rgba(0,0,0,0.13),0 0 0 1px rgba(0,0,0,0.02)';
const IOSSlider = styled(Slider)(({ theme }) => ({
color: '#007bff',
height: 5,
padding: '15px 0',
'& .MuiSlider-thumb': {
height: 20,
width: 20,
backgroundColor: '#fff',
boxShadow: '0 0 2px 0px rgba(0, 0, 0, 0.1)',
'&:focus, &:hover, &.Mui-active': {
boxShadow: '0px 0px 3px 1px rgba(0, 0, 0, 0.1)',
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
boxShadow: iOSBoxShadow,
},
},
'&:before': {
boxShadow:
'0px 0px 1px 0px rgba(0,0,0,0.2), 0px 0px 0px 0px rgba(0,0,0,0.14), 0px 0px 1px 0px rgba(0,0,0,0.12)',
},
},
'& .MuiSlider-valueLabel': {
fontSize: 12,
fontWeight: 'normal',
top: -6,
backgroundColor: 'unset',
color: theme.palette.text.primary,
'&::before': {
display: 'none',
},
'& *': {
background: 'transparent',
color: '#000',
...theme.applyStyles('dark', {
color: '#fff',
}),
},
},
'& .MuiSlider-track': {
border: 'none',
height: 5,
},
'& .MuiSlider-rail': {
opacity: 0.5,
boxShadow: 'inset 0px 0px 4px -2px #000',
backgroundColor: '#d0d0d0',
},
...theme.applyStyles('dark', {
color: '#0a84ff',
}),
}));
const PrettoSlider = styled(Slider)({
color: '#52af77',
height: 8,
'& .MuiSlider-track': {
border: 'none',
},
'& .MuiSlider-thumb': {
height: 24,
width: 24,
backgroundColor: '#fff',
border: '2px solid currentColor',
'&:focus, &:hover, &.Mui-active, &.Mui-focusVisible': {
boxShadow: 'inherit',
},
'&::before': {
display: 'none',
},
},
'& .MuiSlider-valueLabel': {
lineHeight: 1.2,
fontSize: 12,
background: 'unset',
padding: 0,
width: 32,
height: 32,
borderRadius: '50% 50% 50% 0',
backgroundColor: '#52af77',
transformOrigin: 'bottom left',
transform: 'translate(50%, -100%) rotate(-45deg) scale(0)',
'&::before': { display: 'none' },
'&.MuiSlider-valueLabelOpen': {
transform: 'translate(50%, -100%) rotate(-45deg) scale(1)',
},
'& > *': {
transform: 'rotate(45deg)',
},
},
});
const AirbnbSlider = styled(Slider)(({ theme }) => ({
color: '#3a8589',
height: 3,
padding: '13px 0',
'& .MuiSlider-thumb': {
height: 27,
width: 27,
backgroundColor: '#fff',
border: '1px solid currentColor',
'&:hover': {
boxShadow: '0 0 0 8px rgba(58, 133, 137, 0.16)',
},
'& .airbnb-bar': {
height: 9,
width: 1,
backgroundColor: 'currentColor',
marginLeft: 1,
marginRight: 1,
},
},
'& .MuiSlider-track': {
height: 3,
},
'& .MuiSlider-rail': {
color: '#d8d8d8',
opacity: 1,
height: 3,
...theme.applyStyles('dark', {
color: '#bfbfbf',
opacity: undefined,
}),
},
}));
interface AirbnbThumbComponentProps extends React.HTMLAttributes<unknown> {}
function AirbnbThumbComponent(props: AirbnbThumbComponentProps) {
const { children, ...other } = props;
return (
<SliderThumb {...other}>
{children}
<span className="airbnb-bar" />
<span className="airbnb-bar" />
<span className="airbnb-bar" />
</SliderThumb>
);
}
export default function CustomizedSlider() {
return (
<Box sx={{ width: 320 }}>
<Typography gutterBottom>iOS</Typography>
<IOSSlider aria-label="ios slider" defaultValue={60} valueLabelDisplay="on" />
<Box sx={{ m: 3 }} />
<Typography gutterBottom>pretto.fr</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={20}
/>
<Box sx={{ m: 3 }} />
<Typography gutterBottom>Tooltip value label</Typography>
<Slider
valueLabelDisplay="auto"
slots={{
valueLabel: ValueLabelComponent,
}}
aria-label="custom thumb label"
defaultValue={20}
/>
<Box sx={{ m: 3 }} />
<Typography gutterBottom>Airbnb</Typography>
<AirbnbSlider
slots={{ thumb: AirbnbThumbComponent }}
getAriaLabel={(index) => (index === 0 ? 'Minimum price' : 'Maximum price')}
defaultValue={[20, 40]}
/>
</Box>
);
}
음악 플레이어 (Music player)
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Slider from '@mui/material/Slider';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import PauseRounded from '@mui/icons-material/PauseRounded';
import PlayArrowRounded from '@mui/icons-material/PlayArrowRounded';
import FastForwardRounded from '@mui/icons-material/FastForwardRounded';
import FastRewindRounded from '@mui/icons-material/FastRewindRounded';
import VolumeUpRounded from '@mui/icons-material/VolumeUpRounded';
import VolumeDownRounded from '@mui/icons-material/VolumeDownRounded';
const WallPaper = styled('div')({
position: 'absolute',
width: '100%',
height: '100%',
top: 0,
left: 0,
overflow: 'hidden',
background: 'linear-gradient(rgb(255, 38, 142) 0%, rgb(255, 105, 79) 100%)',
transition: 'all 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s',
'&::before': {
content: '""',
width: '140%',
height: '140%',
position: 'absolute',
top: '-40%',
right: '-50%',
background:
'radial-gradient(at center center, rgb(62, 79, 249) 0%, rgba(62, 79, 249, 0) 64%)',
},
'&::after': {
content: '""',
width: '140%',
height: '140%',
position: 'absolute',
bottom: '-50%',
left: '-30%',
background:
'radial-gradient(at center center, rgb(247, 237, 225) 0%, rgba(247, 237, 225, 0) 70%)',
transform: 'rotate(30deg)',
},
});
const Widget = styled('div')(({ theme }) => ({
padding: 16,
borderRadius: 16,
width: 343,
maxWidth: '100%',
margin: 'auto',
position: 'relative',
zIndex: 1,
backgroundColor: 'rgba(255,255,255,0.4)',
backdropFilter: 'blur(40px)',
...theme.applyStyles('dark', {
backgroundColor: 'rgba(0,0,0,0.6)',
}),
}));
const CoverImage = styled('div')({
width: 100,
height: 100,
objectFit: 'cover',
overflow: 'hidden',
flexShrink: 0,
borderRadius: 8,
backgroundColor: 'rgba(0,0,0,0.08)',
'& > img': {
width: '100%',
},
});
const TinyText = styled(Typography)({
fontSize: '0.75rem',
opacity: 0.38,
fontWeight: 500,
letterSpacing: 0.2,
});
export default function MusicPlayerSlider() {
const duration = 200; // seconds
const [position, setPosition] = React.useState(32);
const [paused, setPaused] = React.useState(false);
function formatDuration(value: number) {
const minute = Math.floor(value / 60);
const secondLeft = value - minute * 60;
return `${minute}:${secondLeft < 10 ? `0${secondLeft}` : secondLeft}`;
}
return (
<Box sx={{ width: '100%', overflow: 'hidden', position: 'relative', p: 3 }}>
<Widget>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<CoverImage>
<img
alt="can't win - Chilling Sunday"
src="/static/images/sliders/chilling-sunday.jpg"
/>
</CoverImage>
<Box sx={{ ml: 1.5, minWidth: 0 }}>
<Typography
variant="caption"
sx={{ color: 'text.secondary', fontWeight: 500 }}
>
Jun Pulse
</Typography>
<Typography noWrap>
<b>คนเก่าเขาทำไว้ดี (Can't win)</b>
</Typography>
<Typography noWrap sx={{ letterSpacing: -0.25 }}>
Chilling Sunday — คนเก่าเขาทำไว้ดี
</Typography>
</Box>
</Box>
<Slider
aria-label="time-indicator"
size="small"
value={position}
min={0}
step={1}
max={duration}
onChange={(_, value) => setPosition(value)}
sx={(t) => ({
color: 'rgba(0,0,0,0.87)',
height: 4,
'& .MuiSlider-thumb': {
width: 8,
height: 8,
transition: '0.3s cubic-bezier(.47,1.64,.41,.8)',
'&::before': {
boxShadow: '0 2px 12px 0 rgba(0,0,0,0.4)',
},
'&:hover, &.Mui-focusVisible': {
boxShadow: `0px 0px 0px 8px ${'rgb(0 0 0 / 16%)'}`,
...t.applyStyles('dark', {
boxShadow: `0px 0px 0px 8px ${'rgb(255 255 255 / 16%)'}`,
}),
},
'&.Mui-active': {
width: 20,
height: 20,
},
},
'& .MuiSlider-rail': {
opacity: 0.28,
},
...t.applyStyles('dark', {
color: '#fff',
}),
})}
/>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
mt: -2,
}}
>
<TinyText>{formatDuration(position)}</TinyText>
<TinyText>-{formatDuration(duration - position)}</TinyText>
</Box>
<Box
sx={(theme) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mt: -1,
'& svg': {
color: '#000',
...theme.applyStyles('dark', {
color: '#fff',
}),
},
})}
>
<IconButton aria-label="previous song">
<FastRewindRounded fontSize="large" />
</IconButton>
<IconButton
aria-label={paused ? 'play' : 'pause'}
onClick={() => setPaused(!paused)}
>
{paused ? (
<PlayArrowRounded sx={{ fontSize: '3rem' }} />
) : (
<PauseRounded sx={{ fontSize: '3rem' }} />
)}
</IconButton>
<IconButton aria-label="next song">
<FastForwardRounded fontSize="large" />
</IconButton>
</Box>
<Stack
spacing={2}
direction="row"
sx={(theme) => ({
alignItems: 'center',
mb: 1,
px: 1,
'& > svg': {
color: 'rgba(0,0,0,0.4)',
...theme.applyStyles('dark', {
color: 'rgba(255,255,255,0.4)',
}),
},
})}
>
<VolumeDownRounded />
<Slider
aria-label="Volume"
defaultValue={30}
sx={(t) => ({
color: 'rgba(0,0,0,0.87)',
'& .MuiSlider-track': {
border: 'none',
},
'& .MuiSlider-thumb': {
width: 24,
height: 24,
backgroundColor: '#fff',
'&::before': {
boxShadow: '0 4px 8px rgba(0,0,0,0.4)',
},
'&:hover, &.Mui-focusVisible, &.Mui-active': {
boxShadow: 'none',
},
},
...t.applyStyles('dark', {
color: '#fff',
}),
})}
/>
<VolumeUpRounded />
</Stack>
</Widget>
<WallPaper />
</Box>
);
}
세로 슬라이더 (Vertical sliders)
orientation prop을 "vertical"로 설정해 세로 슬라이더를 만들 수 있어요. thumb은 가로 이동 대신 세로 이동을 추적합니다.
import Stack from '@mui/material/Stack';
import Slider from '@mui/material/Slider';
export default function VerticalSlider() {
return (
<Stack sx={{ height: 300 }} spacing={1} direction="row">
<Slider
aria-label="Temperature"
orientation="vertical"
getAriaValueText={getAriaValueText}
valueLabelDisplay="auto"
defaultValue={30}
/>
<Slider
aria-label="Temperature"
orientation="vertical"
defaultValue={30}
valueLabelDisplay="auto"
disabled
/>
<Slider
getAriaLabel={() => 'Temperature'}
orientation="vertical"
getAriaValueText={getAriaValueText}
defaultValue={[20, 37]}
valueLabelDisplay="auto"
marks={marks}
/>
</Stack>
);
}
function getAriaValueText(value: number) {
return `${value}°C`;
}
const marks = [
{
value: 0,
label: '0°C',
},
{
value: 20,
label: '20°C',
},
{
value: 37,
label: '37°C',
},
{
value: 100,
label: '100°C',
},
];
:::warning
124 미만의 Chrome 버전은 세로 슬라이더의 aria-orientation을 잘못 구현해서 접근성 트리에서 'horizontal'로 노출합니다. (Chromium issue #40736841)
이러한 구버전에서는 -webkit-appearance: slider-vertical CSS 속성으로 이를 바로잡을 수 있는데, 단점으로는 새 Chrome 버전에서 콘솔 경고가 발생한다는 점이 있어요:
.MuiSlider-thumb input {
-webkit-appearance: slider-vertical;
}
:::
마크 배치 (Marks placement)
최소값과 최대값에 대한 마크를 추가하고 재배치해서 슬라이더를 커스터마이징할 수 있어요.
import * as React from 'react';
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
import Typography from '@mui/material/Typography';
const MAX = 100;
const MIN = 0;
const marks = [
{
value: MIN,
label: '',
},
{
value: MAX,
label: '',
},
];
export default function CustomMarks() {
const [val, setVal] = React.useState<number>(MIN);
const handleChange = (_: Event, newValue: number) => {
setVal(newValue);
};
return (
<Box sx={{ width: 250 }}>
<Slider
marks={marks}
step={10}
value={val}
valueLabelDisplay="auto"
min={MIN}
max={MAX}
onChange={handleChange}
/>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography
variant="body2"
onClick={() => setVal(MIN)}
sx={{ cursor: 'pointer' }}
>
{MIN} min
</Typography>
<Typography
variant="body2"
onClick={() => setVal(MAX)}
sx={{ cursor: 'pointer' }}
>
{MAX} max
</Typography>
</Box>
</Box>
);
}
트랙 (Track)
트랙은 사용자 선택에 사용 가능한 범위를 보여줍니다.
트랙 제거 (Removed track)
track={false}로 트랙을 끌 수 있어요.
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Slider from '@mui/material/Slider';
const Separator = styled('div')(
({ theme }) => `
height: ${theme.spacing(3)};
`,
);
const marks = [
{
value: 0,
label: '0°C',
},
{
value: 20,
label: '20°C',
},
{
value: 37,
label: '37°C',
},
{
value: 100,
label: '100°C',
},
];
function valuetext(value: number) {
return `${value}°C`;
}
export default function TrackFalseSlider() {
const id = React.useId();
const rangeId = React.useId();
return (
<Box sx={{ width: 250 }}>
<Typography id={`${id}-label`} gutterBottom>
Removed track
</Typography>
<Slider
track={false}
aria-labelledby={`${id}-label`}
getAriaValueText={valuetext}
defaultValue={30}
marks={marks}
/>
<Separator />
<Typography id={`${rangeId}-label`} gutterBottom>
Removed track range slider
</Typography>
<Slider
track={false}
aria-labelledby={`${rangeId}-label`}
getAriaValueText={valuetext}
defaultValue={[20, 37, 50]}
marks={marks}
/>
</Box>
);
}
반전된 트랙 (Inverted track)
track="inverted"로 트랙을 반전시킬 수 있어요.
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Slider from '@mui/material/Slider';
const Separator = styled('div')(
({ theme }) => `
height: ${theme.spacing(3)};
`,
);
const marks = [
{
value: 0,
label: '0°C',
},
{
value: 20,
label: '20°C',
},
{
value: 37,
label: '37°C',
},
{
value: 100,
label: '100°C',
},
];
function valuetext(value: number) {
return `${value}°C`;
}
export default function TrackInvertedSlider() {
return (
<Box sx={{ width: 250 }}>
<Typography id="track-inverted-slider" gutterBottom>
Inverted track
</Typography>
<Slider
track="inverted"
aria-labelledby="track-inverted-slider"
getAriaValueText={valuetext}
defaultValue={30}
marks={marks}
/>
<Separator />
<Typography id="track-inverted-range-slider" gutterBottom>
Inverted track range
</Typography>
<Slider
track="inverted"
aria-labelledby="track-inverted-range-slider"
getAriaValueText={valuetext}
defaultValue={[20, 37]}
marks={marks}
/>
</Box>
);
}
비선형 스케일 (Non-linear scale)
scale prop을 사용해서 value를 다른 스케일로 표현할 수 있어요.
다음 데모에서 값 _x_는 값 _2^x_를 나타냅니다. _x_를 1만큼 늘리면 표현되는 값은 _2_배가 되어요.
import * as React from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Slider from '@mui/material/Slider';
function valueLabelFormat(value: number) {
const units = ['KB', 'MB', 'GB', 'TB'];
let unitIndex = 0;
let scaledValue = value;
while (scaledValue >= 1024 && unitIndex < units.length - 1) {
unitIndex += 1;
scaledValue /= 1024;
}
return `${scaledValue} ${units[unitIndex]}`;
}
function calculateValue(value: number) {
return 2 ** value;
}
export default function NonLinearSlider() {
const [value, setValue] = React.useState<number>(10);
const handleChange = (event: Event, newValue: number) => {
setValue(newValue);
};
return (
<Box sx={{ width: 250 }}>
<Typography id="non-linear-slider" gutterBottom>
Storage: {valueLabelFormat(calculateValue(value))}
</Typography>
<Slider
value={value}
min={5}
step={1}
max={30}
scale={calculateValue}
getAriaValueText={valueLabelFormat}
valueLabelFormat={valueLabelFormat}
onChange={handleChange}
valueLabelDisplay="auto"
aria-labelledby="non-linear-slider"
/>
</Box>
);
}
접근성 (Accessibility)
(WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/)
컴포넌트는 접근성을 확보하는 데 필요한 대부분의 작업을 처리해 줘요. 그러나 다음 사항을 직접 확인해야 합니다:
- 각 thumb은 사용자 친화적인 라벨(
aria-label,aria-labelledby또는getAriaLabelprop)을 가져야 해요. - 각 thumb은 현재 값에 대한 사용자 친화적인 텍스트를 가져야 해요. 값이 라벨의 의미와 일치한다면 필수는 아니에요.
getAriaValueText또는aria-valuetextprop으로 이름을 변경할 수 있어요.
Slider API
데모 (Demos)
이 React 컴포넌트의 사용 예시와 자세한 내용은 컴포넌트 데모 페이지를 방문해 보세요:
Import
import Slider from '@mui/material/Slider';
// or
import { Slider } from '@mui/material';
Props
| 이름 (Name) | 타입 (Type) | 기본값 (Default) | 필수 (Required) | 설명 (Description) |
|---|---|---|---|---|
| aria-label | string |
- | No | |
| aria-labelledby | string |
- | No | |
| aria-valuetext | string |
- | No | |
| classes | object |
- | No | Override or extend the styles applied to the component. |
| color | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' | string |
'primary' |
No | |
| defaultValue | Array<number> | number |
- | No | |
| disabled | bool |
false |
No | |
| disableSwap | bool |
false |
No | |
| getAriaLabel | function(index: number) => string |
- | No | |
| getAriaValueText | function(value: number, index: number) => string |
- | No | |
| marks | Array<{ label?: node, value: number }> | bool |
false |
No | |
| max | number |
100 |
No | |
| min | number |
0 |
No | |
| name | string |
- | No | |
| onChange | function(event: Event, value: Value, activeThumb: number) => void |
- | No | |
| onChangeCommitted | function(event: React.SyntheticEvent | Event, value: Value) => void |
- | No | |
| orientation | 'horizontal' | 'vertical' |
'horizontal' |
No | |
| scale | function(x: any) => any |
`function Identity(x) { | ||
| return x; | ||||
| }` | No | |||
| shiftStep | number |
10 |
No | |
| size | 'small' | 'medium' | string |
'medium' |
No | |
| slotProps | { input?: func | object, mark?: func | object, markLabel?: func | object, rail?: func | object, root?: func | object, thumb?: func | object, track?: func | object, valueLabel?: func | { children?: element, className?: string, open?: bool, style?: object, value?: node, valueLabelDisplay?: 'auto' | 'off' | 'on' } } |
{} |
No | |
| slots | { input?: elementType, mark?: elementType, markLabel?: elementType, rail?: elementType, root?: elementType, thumb?: elementType, track?: elementType, valueLabel?: elementType } |
{} |
No | |
| step | number |
1 |
No | |
| sx | Array<func | object | bool> | func | object |
- | No | The system prop that allows defining system overrides as well as additional CSS styles. |
| tabIndex | number |
- | No | |
| track | 'inverted' | 'normal' | false |
'normal' |
No | |
| value | Array<number> | number |
- | No | |
| valueLabelDisplay | 'auto' | 'off' | 'on' |
'off' |
No | |
| valueLabelFormat | func | string |
`function Identity(x) { | ||
| return x; | ||||
| }` | No |
참고 (Note):
ref는 루트 요소(HTMLSpanElement)로 전달됩니다.
제공된 다른 모든 props는 루트 요소(네이티브 요소)로 전달됩니다.
테마 기본 props (Theme default props)
MuiSlider를 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.
슬롯 (Slots)
| 이름 (Name) | 기본값 (Default) | 클래스 (Class) | 설명 (Description) |
|---|---|---|---|
| root | 'span' |
.MuiSlider-root |
The component that renders the root. |
| track | 'span' |
.MuiSlider-track |
The component that renders the track. |
| rail | 'span' |
.MuiSlider-rail |
The component that renders the rail. |
| thumb | 'span' |
.MuiSlider-thumb |
The component that renders the thumb. |
| mark | 'span' |
.MuiSlider-mark |
The component that renders the mark. |
| markLabel | 'span' |
.MuiSlider-markLabel |
The component that renders the mark label. |
| valueLabel | SliderValueLabel |
.MuiSlider-valueLabel |
The component that renders the value label. |
| input | 'input' |
- | The component that renders the input. |
CSS
규칙 이름 (Rule name)
| 전역 클래스 (Global class) | 규칙 이름 (Rule name) | 설명 (Description) |
|---|---|---|
.Mui-active |
- | State class applied to the thumb element if it's active. |
| - | colorError | Styles applied to the root element if color="error". |
| - | colorInfo | Styles applied to the root element if color="info". |
| - | colorPrimary | Styles applied to the root element if color="primary". |
| - | colorSecondary | Styles applied to the root element if color="secondary". |
| - | colorSuccess | Styles applied to the root element if color="success". |
| - | colorWarning | Styles applied to the root element if color="warning". |
.Mui-disabled |
- | State class applied to the root and thumb element if disabled={true}. |
| - | dragging | State class applied to the root if a thumb is being dragged. |
.Mui-focusVisible |
- | State class applied to the thumb element if keyboard focused. |
| - | markActive | Styles applied to the mark element if active (depending on the value). |
| - | marked | Styles applied to the root element if marks is provided with at least one label. |
| - | markLabelActive | Styles applied to the mark label element if active (depending on the value). |
| - | sizeSmall | Styles applied to the root element if size="small". |
| - | trackFalse | Styles applied to the root element if track={false}. |
| - | trackInverted | Styles applied to the root element if track="inverted". |
| - | valueLabelCircle | Styles applied to the thumb label's circle element. |
| - | valueLabelLabel | Styles applied to the thumb label's label element. |
| - | valueLabelOpen | Styles applied to the thumb label element if it's open. |
| - | vertical | Styles applied to the root element if orientation="vertical". |
소스 코드 (Source code)
이 페이지에서 필요한 정보를 찾지 못했다면, 컴포넌트의 구현을 살펴보면서 더 자세한 내용을 확인해 보세요.