텍스트 필드

텍스트 필드 (Text Field)

Text Field는 사용자가 텍스트를 입력하고 편집할 수 있게 해 줘요.

출처: 문서

본문

Text field는 사용자가 UI에 텍스트를 입력할 수 있게 해 줘요. 보통 폼과 다이얼로그에 나타나요.

기본 TextField (Basic TextField)

TextField 래퍼 컴포넌트는 라벨, 입력, 도움말 텍스트를 포함한 완전한 폼 컨트롤이에요. outlined(기본값), filled, standard의 세 가지 변형이 있어요.

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

export default function BasicTextFields() {
  return (
    <Box
      component="form"
      sx={{ '& > :not(style)': { m: 1, width: '25ch' } }}
      noValidate
      autoComplete="off"
    >
      <TextField id="outlined-basic" label="Outlined" variant="outlined" />
      <TextField id="filled-basic" label="Filled" variant="filled" />
      <TextField id="standard-basic" label="Standard" variant="standard" />
    </Box>
  );
}

:::info Text Field의 standard 변형은 더 이상 Material Design 가이드라인(왜 그런지 설명하는 이 글 참고)에 문서화되지 않지만, Material UI는 계속 지원할 거예요. :::

폼 props (Form props)

required, disabled, type 등 표준 폼 속성이 지원되며, 필드 입력에 대한 맥락(예: 입력이 어떻게 사용될지)을 제공하는 helperText도 지원해요.

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

export default function FormPropsTextFields() {
  return (
    <Box
      component="form"
      sx={{ '& .MuiTextField-root': { m: 1, width: '25ch' } }}
      noValidate
      autoComplete="off"
    >
      <div>
        <TextField
          required
          id="outlined-required"
          label="Required"
          defaultValue="Hello World"
        />
        <TextField
          disabled
          id="outlined-disabled"
          label="Disabled"
          defaultValue="Hello World"
        />
        <TextField
          id="outlined-password-input"
          label="Password"
          type="password"
          autoComplete="current-password"
        />
        <TextField
          id="outlined-read-only-input"
          label="Read Only"
          defaultValue="Hello World"
          slotProps={{
            input: {
              readOnly: true,
            },
          }}
        />
        <TextField id="outlined-search" label="Search field" type="search" />
        <TextField
          id="outlined-helperText"
          label="Helper text"
          defaultValue="Default Value"
          helperText="Some important text"
        />
      </div>
      <div>
        <TextField
          required
          id="filled-required"
          label="Required"
          defaultValue="Hello World"
          variant="filled"
        />
        <TextField
          disabled
          id="filled-disabled"
          label="Disabled"
          defaultValue="Hello World"
          variant="filled"
        />
        <TextField
          id="filled-password-input"
          label="Password"
          type="password"
          autoComplete="current-password"
          variant="filled"
        />
        <TextField
          id="filled-read-only-input"
          label="Read Only"
          defaultValue="Hello World"
          variant="filled"
          slotProps={{
            input: {
              readOnly: true,
            },
          }}
        />
        <TextField
          id="filled-search"
          label="Search field"
          type="search"
          variant="filled"
        />
        <TextField
          id="filled-helperText"
          label="Helper text"
          defaultValue="Default Value"
          helperText="Some important text"
          variant="filled"
        />
      </div>
      <div>
        <TextField
          required
          id="standard-required"
          label="Required"
          defaultValue="Hello World"
          variant="standard"
        />
        <TextField
          disabled
          id="standard-disabled"
          label="Disabled"
          defaultValue="Hello World"
          variant="standard"
        />
        <TextField
          id="standard-password-input"
          label="Password"
          type="password"
          autoComplete="current-password"
          variant="standard"
        />
        <TextField
          id="standard-read-only-input"
          label="Read Only"
          defaultValue="Hello World"
          variant="standard"
          slotProps={{
            input: {
              readOnly: true,
            },
          }}
        />
        <TextField
          id="standard-search"
          label="Search field"
          type="search"
          variant="standard"
        />
        <TextField
          id="standard-helperText"
          label="Helper text"
          defaultValue="Default Value"
          helperText="Some important text"
          variant="standard"
        />
      </div>
    </Box>
  );
}

HTML input 제어 (Controlling the HTML input)

slotProps.htmlInput을 사용해 내부의 <input> 요소에 속성을 전달해요.

<TextField slotProps={{ htmlInput: { 'data-testid': '…' } }} />

렌더링된 HTML input은 다음과 같이 보여요:

<input
  aria-invalid="false"
  class="MuiInputBase-input MuiOutlinedInput-input"
  type="text"
  data-testid="…"
/>

:::warning slotProps.htmlInput은 slotProps.input과 같지 않아요. slotProps.input은 지정된 variant prop에 따라 렌더링되는 React <Input /> 컴포넌트를 가리켜요. slotProps.htmlInput은 variant와 무관하게 그 Input 컴포넌트 안에서 렌더링되는 HTML <input> 요소를 가리켜요. :::

유효성 검사 (Validation)

error prop이 오류 상태를 토글해요. 그런 다음 helperText prop을 사용해 오류에 대한 피드백을 사용자에게 제공할 수 있어요.

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

export default function ValidationTextFields() {
  return (
    <Box
      component="form"
      sx={{ '& .MuiTextField-root': { m: 1, width: '25ch' } }}
      noValidate
      autoComplete="off"
    >
      <div>
        <TextField
          error
          id="outlined-error"
          label="Error"
          defaultValue="Hello World"
        />
        <TextField
          error
          id="outlined-error-helper-text"
          label="Error"
          defaultValue="Hello World"
          helperText="Incorrect entry."
        />
      </div>
      <div>
        <TextField
          error
          id="filled-error"
          label="Error"
          defaultValue="Hello World"
          variant="filled"
        />
        <TextField
          error
          id="filled-error-helper-text"
          label="Error"
          defaultValue="Hello World"
          helperText="Incorrect entry."
          variant="filled"
        />
      </div>
      <div>
        <TextField
          error
          id="standard-error"
          label="Error"
          defaultValue="Hello World"
          variant="standard"
        />
        <TextField
          error
          id="standard-error-helper-text"
          label="Error"
          defaultValue="Hello World"
          helperText="Incorrect entry."
          variant="standard"
        />
      </div>
    </Box>
  );
}

여러 줄 (Multiline)

multiline prop은 Text Field를 Textarea Autosize 요소로 변환해요. rows prop이 설정되지 않는 한, 텍스트 필드의 높이는 콘텐츠에 맞춰 동적으로 변화해요. minRows와 maxRows props로 상한/하한을 둘 수 있어요.

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

export default function MultilineTextFields() {
  return (
    <Box
      component="form"
      sx={{ '& .MuiTextField-root': { m: 1, width: '25ch' } }}
      noValidate
      autoComplete="off"
    >
      <div>
        <TextField
          id="outlined-multiline-flexible"
          label="Multiline"
          multiline
          maxRows={4}
        />
        <TextField
          id="outlined-textarea"
          label="Multiline Placeholder"
          placeholder="Placeholder"
          multiline
        />
        <TextField
          id="outlined-multiline-static"
          label="Multiline"
          multiline
          rows={4}
          defaultValue="Default Value"
        />
      </div>
      <div>
        <TextField
          id="filled-multiline-flexible"
          label="Multiline"
          multiline
          maxRows={4}
          variant="filled"
        />
        <TextField
          id="filled-textarea"
          label="Multiline Placeholder"
          placeholder="Placeholder"
          multiline
          variant="filled"
        />
        <TextField
          id="filled-multiline-static"
          label="Multiline"
          multiline
          rows={4}
          defaultValue="Default Value"
          variant="filled"
        />
      </div>
      <div>
        <TextField
          id="standard-multiline-flexible"
          label="Multiline"
          multiline
          maxRows={4}
          variant="standard"
        />
        <TextField
          id="standard-textarea"
          label="Multiline Placeholder"
          placeholder="Placeholder"
          multiline
          variant="standard"
        />
        <TextField
          id="standard-multiline-static"
          label="Multiline"
          multiline
          rows={4}
          defaultValue="Default Value"
          variant="standard"
        />
      </div>
    </Box>
  );
}

Select

select prop은 텍스트 필드가 내부적으로 Select 컴포넌트를 사용하게 해요.

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import MenuItem from '@mui/material/MenuItem';

const currencies = [
  {
    value: 'USD',
    label: '$',
  },
  {
    value: 'EUR',
    label: '€',
  },
  {
    value: 'BTC',
    label: '฿',
  },
  {
    value: 'JPY',
    label: '¥',
  },
];

export default function SelectTextFields() {
  return (
    <Box
      component="form"
      sx={{ '& .MuiTextField-root': { m: 1, width: '25ch' } }}
      noValidate
      autoComplete="off"
    >
      <div>
        <TextField
          id="outlined-select-currency"
          select
          label="Select"
          defaultValue="EUR"
          helperText="Please select your currency"
        >
          {currencies.map((option) => (
            <MenuItem key={option.value} value={option.value}>
              {option.label}
            </MenuItem>
          ))}
        </TextField>
        <TextField
          id="outlined-select-currency-native"
          select
          label="Native select"
          defaultValue="EUR"
          slotProps={{
            select: {
              native: true,
            },
          }}
          helperText="Please select your currency"
        >
          {currencies.map((option) => (
            <option key={option.value} value={option.value}>
              {option.label}
            </option>
          ))}
        </TextField>
      </div>
      <div>
        <TextField
          id="filled-select-currency"
          select
          label="Select"
          defaultValue="EUR"
          helperText="Please select your currency"
          variant="filled"
        >
          {currencies.map((option) => (
            <MenuItem key={option.value} value={option.value}>
              {option.label}
            </MenuItem>
          ))}
        </TextField>
        <TextField
          id="filled-select-currency-native"
          select
          label="Native select"
          defaultValue="EUR"
          slotProps={{
            select: {
              native: true,
            },
          }}
          helperText="Please select your currency"
          variant="filled"
        >
          {currencies.map((option) => (
            <option key={option.value} value={option.value}>
              {option.label}
            </option>
          ))}
        </TextField>
      </div>
      <div>
        <TextField
          id="standard-select-currency"
          select
          label="Select"
          defaultValue="EUR"
          helperText="Please select your currency"
          variant="standard"
        >
          {currencies.map((option) => (
            <MenuItem key={option.value} value={option.value}>
              {option.label}
            </MenuItem>
          ))}
        </TextField>
        <TextField
          id="standard-select-currency-native"
          select
          label="Native select"
          defaultValue="EUR"
          slotProps={{
            select: {
              native: true,
            },
          }}
          helperText="Please select your currency"
          variant="standard"
        >
          {currencies.map((option) => (
            <option key={option.value} value={option.value}>
              {option.label}
            </option>
          ))}
        </TextField>
      </div>
    </Box>
  );
}

아이콘 (Icons)

텍스트 필드에 아이콘을 표시하는 방법은 여러 가지가 있어요.

import * as React from 'react';
import Box from '@mui/material/Box';
import Input from '@mui/material/Input';
import InputLabel from '@mui/material/InputLabel';
import InputAdornment from '@mui/material/InputAdornment';
import FormControl from '@mui/material/FormControl';
import TextField from '@mui/material/TextField';
import AccountCircle from '@mui/icons-material/AccountCircle';

export default function InputWithIcon() {
  const adornmentId = React.useId();
  const textFieldId = React.useId();
  const sxId = React.useId();
  return (
    <Box sx={{ '& > :not(style)': { m: 1 } }}>
      <FormControl variant="standard">
        <InputLabel htmlFor={`${adornmentId}-input`}>
          With a start adornment
        </InputLabel>
        <Input
          id={`${adornmentId}-input`}
          startAdornment={
            <InputAdornment position="start">
              <AccountCircle />
            </InputAdornment>
          }
        />
      </FormControl>
      <TextField
        id={`${textFieldId}-input`}
        label="TextField"
        slotProps={{
          input: {
            startAdornment: (
              <InputAdornment position="start">
                <AccountCircle />
              </InputAdornment>
            ),
          },
        }}
        variant="standard"
      />
      <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
        <AccountCircle sx={{ color: 'action.active', mr: 1, my: 0.5 }} />
        <TextField id={`${sxId}-input`} label="With sx" variant="standard" />
      </Box>
    </Box>
  );
}

Input Adornments

주된 방법은 InputAdornment을 사용하는 것이에요. 이것을 사용해 입력에 접두사, 접미사, 또는 동작을 추가할 수 있어요. 예를 들어 아이콘 버튼을 사용해 비밀번호를 숨기거나 보여줄 수 있어요.

import * as React from 'react';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import Input from '@mui/material/Input';
import FilledInput from '@mui/material/FilledInput';
import OutlinedInput from '@mui/material/OutlinedInput';
import InputLabel from '@mui/material/InputLabel';
import InputAdornment from '@mui/material/InputAdornment';
import FormHelperText from '@mui/material/FormHelperText';
import FormControl from '@mui/material/FormControl';
import TextField from '@mui/material/TextField';
import MenuItem from '@mui/material/MenuItem';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
import InfoOutlined from '@mui/icons-material/InfoOutlined';

export default function InputAdornments() {
  const outlinedStartId = React.useId();
  const outlinedWeightId = React.useId();
  const outlinedPasswordId = React.useId();
  const outlinedAmountId = React.useId();
  const filledStartId = React.useId();
  const filledWeightId = React.useId();
  const filledPasswordId = React.useId();
  const filledAmountId = React.useId();
  const standardStartId = React.useId();
  const standardWeightId = React.useId();
  const standardPasswordId = React.useId();
  const standardAmountId = React.useId();
  const [showPassword, setShowPassword] = React.useState(false);

  const handleClickShowPassword = () => setShowPassword((show) => !show);

  const handleMouseDownPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
    event.preventDefault();
  };

  const handleMouseUpPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
    event.preventDefault();
  };

  // An endAdornment coexists with the Select's chevron without overlapping it.
  const infoEndAdornment = (
    <InputAdornment position="end">
      <InfoOutlined />
    </InputAdornment>
  );
  const infoStartAdornment = (
    <InputAdornment position="start">
      <InfoOutlined />
    </InputAdornment>
  );

  return (
    <Box sx={{ display: 'flex', flexWrap: 'wrap' }}>
      <div>
        <TextField
          label="With normal TextField"
          id={`${outlinedStartId}-input`}
          sx={{ m: 1, width: '25ch' }}
          slotProps={{
            input: {
              startAdornment: <InputAdornment position="start">kg</InputAdornment>,
            },
          }}
        />
        <FormControl sx={{ m: 1, width: '25ch' }} variant="outlined">
          <OutlinedInput
            id={`${outlinedWeightId}-input`}
            endAdornment={<InputAdornment position="end">kg</InputAdornment>}
            aria-describedby={`${outlinedWeightId}-helper-text`}
            inputProps={{
              'aria-label': 'weight',
            }}
          />
          <FormHelperText id={`${outlinedWeightId}-helper-text`}>
            Weight
          </FormHelperText>
        </FormControl>
        <FormControl sx={{ m: 1, width: '25ch' }} variant="outlined">
          <InputLabel htmlFor={`${outlinedPasswordId}-input`}>Password</InputLabel>
          <OutlinedInput
            id={`${outlinedPasswordId}-input`}
            type={showPassword ? 'text' : 'password'}
            endAdornment={
              <InputAdornment position="end">
                <IconButton
                  aria-label={
                    showPassword ? 'hide the password' : 'display the password'
                  }
                  onClick={handleClickShowPassword}
                  onMouseDown={handleMouseDownPassword}
                  onMouseUp={handleMouseUpPassword}
                  edge="end"
                >
                  {showPassword ? <VisibilityOff /> : <Visibility />}
                </IconButton>
              </InputAdornment>
            }
            label="Password"
          />
        </FormControl>
        <div>
          <FormControl sx={{ m: 1, width: '25ch' }}>
            <InputLabel htmlFor={`${outlinedAmountId}-input`}>Amount</InputLabel>
            <OutlinedInput
              id={`${outlinedAmountId}-input`}
              startAdornment={<InputAdornment position="start">$</InputAdornment>}
              label="Amount"
            />
          </FormControl>
          <TextField
            select
            label="Select"
            defaultValue={20}
            sx={{ m: 1, width: '25ch' }}
            slotProps={{ select: { endAdornment: infoEndAdornment } }}
          >
            <MenuItem value={10}>Ten</MenuItem>
            <MenuItem value={20}>Twenty</MenuItem>
            <MenuItem value={30}>Thirty</MenuItem>
          </TextField>
          <TextField
            select
            label="Native"
            defaultValue={20}
            sx={{ m: 1, width: '25ch' }}
            slotProps={{
              select: { native: true, startAdornment: infoStartAdornment },
            }}
          >
            <option value={10}>Ten</option>
            <option value={20}>Twenty</option>
            <option value={30}>Thirty</option>
          </TextField>
        </div>
      </div>
      <div>
        <TextField
          label="With normal TextField"
          id={`${filledStartId}-input`}
          sx={{ m: 1, width: '25ch' }}
          slotProps={{
            input: {
              startAdornment: <InputAdornment position="start">kg</InputAdornment>,
            },
          }}
          variant="filled"
        />
        <FormControl sx={{ m: 1, width: '25ch' }} variant="filled">
          <FilledInput
            id={`${filledWeightId}-input`}
            endAdornment={<InputAdornment position="end">kg</InputAdornment>}
            aria-describedby={`${filledWeightId}-helper-text`}
            inputProps={{
              'aria-label': 'weight',
            }}
          />
          <FormHelperText id={`${filledWeightId}-helper-text`}>
            Weight
          </FormHelperText>
        </FormControl>
        <FormControl sx={{ m: 1, width: '25ch' }} variant="filled">
          <InputLabel htmlFor={`${filledPasswordId}-input`}>Password</InputLabel>
          <FilledInput
            id={`${filledPasswordId}-input`}
            type={showPassword ? 'text' : 'password'}
            endAdornment={
              <InputAdornment position="end">
                <IconButton
                  aria-label={
                    showPassword ? 'hide the password' : 'display the password'
                  }
                  onClick={handleClickShowPassword}
                  onMouseDown={handleMouseDownPassword}
                  onMouseUp={handleMouseUpPassword}
                  edge="end"
                >
                  {showPassword ? <VisibilityOff /> : <Visibility />}
                </IconButton>
              </InputAdornment>
            }
          />
        </FormControl>
        <div>
          <FormControl sx={{ m: 1, width: '25ch' }} variant="filled">
            <InputLabel htmlFor={`${filledAmountId}-input`}>Amount</InputLabel>
            <FilledInput
              id={`${filledAmountId}-input`}
              startAdornment={<InputAdornment position="start">$</InputAdornment>}
            />
          </FormControl>
          <TextField
            select
            label="Select"
            defaultValue={20}
            variant="filled"
            sx={{ m: 1, width: '25ch' }}
            slotProps={{ select: { endAdornment: infoEndAdornment } }}
          >
            <MenuItem value={10}>Ten</MenuItem>
            <MenuItem value={20}>Twenty</MenuItem>
            <MenuItem value={30}>Thirty</MenuItem>
          </TextField>
          <TextField
            select
            label="Native"
            defaultValue={20}
            variant="filled"
            sx={{ m: 1, width: '25ch' }}
            slotProps={{
              select: { native: true, startAdornment: infoStartAdornment },
            }}
          >
            <option value={10}>Ten</option>
            <option value={20}>Twenty</option>
            <option value={30}>Thirty</option>
          </TextField>
        </div>
      </div>
      <div>
        <TextField
          label="With normal TextField"
          id={`${standardStartId}-input`}
          sx={{ m: 1, width: '25ch' }}
          slotProps={{
            input: {
              startAdornment: <InputAdornment position="start">kg</InputAdornment>,
            },
          }}
          variant="standard"
        />
        <FormControl variant="standard" sx={{ m: 1, mt: 3, width: '25ch' }}>
          <Input
            id={`${standardWeightId}-input`}
            endAdornment={<InputAdornment position="end">kg</InputAdornment>}
            aria-describedby={`${standardWeightId}-helper-text`}
            inputProps={{
              'aria-label': 'weight',
            }}
          />
          <FormHelperText id={`${standardWeightId}-helper-text`}>
            Weight
          </FormHelperText>
        </FormControl>
        <FormControl sx={{ m: 1, width: '25ch' }} variant="standard">
          <InputLabel htmlFor={`${standardPasswordId}-input`}>Password</InputLabel>
          <Input
            id={`${standardPasswordId}-input`}
            type={showPassword ? 'text' : 'password'}
            endAdornment={
              <InputAdornment position="end">
                <IconButton
                  aria-label={
                    showPassword ? 'hide the password' : 'display the password'
                  }
                  onClick={handleClickShowPassword}
                  onMouseDown={handleMouseDownPassword}
                  onMouseUp={handleMouseUpPassword}
                >
                  {showPassword ? <VisibilityOff /> : <Visibility />}
                </IconButton>
              </InputAdornment>
            }
          />
        </FormControl>
        <div>
          <FormControl sx={{ m: 1, width: '25ch' }} variant="standard">
            <InputLabel htmlFor={`${standardAmountId}-input`}>Amount</InputLabel>
            <Input
              id={`${standardAmountId}-input`}
              startAdornment={<InputAdornment position="start">$</InputAdornment>}
            />
          </FormControl>
          <TextField
            select
            label="Select"
            defaultValue={20}
            variant="standard"
            sx={{ m: 1, width: '25ch' }}
            slotProps={{ select: { endAdornment: infoEndAdornment } }}
          >
            <MenuItem value={10}>Ten</MenuItem>
            <MenuItem value={20}>Twenty</MenuItem>
            <MenuItem value={30}>Thirty</MenuItem>
          </TextField>
          <TextField
            select
            label="Native"
            defaultValue={20}
            variant="standard"
            sx={{ m: 1, width: '25ch' }}
            slotProps={{
              select: { native: true, startAdornment: infoStartAdornment },
            }}
          >
            <option value={10}>Ten</option>
            <option value={20}>Twenty</option>
            <option value={30}>Thirty</option>
          </TextField>
        </div>
      </div>
    </Box>
  );
}

Adornments 커스터마이즈 (Customizing adornments)

Adornments에 커스텀 스타일을 적용하고, 다른 adornment의 속성에 기반해 하나를 변경할 수 있어요. 예를 들어 아래 데모는 라벨의 [data-shrink=true] 속성을 사용해 라벨이 축소(shrink)된 상태일 때 접미사를 (opacity를 통해) 보이게 해요.

import Box from '@mui/material/Box';
import { filledInputClasses } from '@mui/material/FilledInput';
import { inputBaseClasses } from '@mui/material/InputBase';
import TextField from '@mui/material/TextField';
import InputAdornment from '@mui/material/InputAdornment';

export default function InputSuffixShrink() {
  return (
    <Box
      component="form"
      sx={{ '& > :not(style)': { m: 1, width: '25ch' } }}
      noValidate
      autoComplete="off"
    >
      <TextField
        id="outlined-suffix-shrink"
        label="Outlined"
        variant="outlined"
        slotProps={{
          input: {
            endAdornment: (
              <InputAdornment
                position="end"
                sx={{
                  opacity: 0,
                  pointerEvents: 'none',
                  [`[data-shrink=true] ~ .${inputBaseClasses.root} > &`]: {
                    opacity: 1,
                  },
                }}
              >
                lbs
              </InputAdornment>
            ),
          },
        }}
      />
      <TextField
        id="filled-suffix-shrink"
        label="Filled"
        variant="filled"
        slotProps={{
          input: {
            endAdornment: (
              <InputAdornment
                position="end"
                sx={{
                  alignSelf: 'flex-end',
                  opacity: 0,
                  pointerEvents: 'none',
                  [`.${filledInputClasses.root} &`]: {
                    marginBottom: '7.5px',
                  },
                  [`[data-shrink=true] ~ .${inputBaseClasses.root} > &`]: {
                    opacity: 1,
                  },
                }}
              >
                days
              </InputAdornment>
            ),
          },
        }}
      />
      <TextField
        id="standard-suffix-shrink"
        label="Standard"
        variant="standard"
        slotProps={{
          htmlInput: {
            sx: { textAlign: 'right' },
          },
          input: {
            endAdornment: (
              <InputAdornment
                position="end"
                sx={{
                  alignSelf: 'flex-end',
                  margin: 0,
                  marginBottom: '5px',
                  opacity: 0,
                  pointerEvents: 'none',
                  [`[data-shrink=true] ~ .${inputBaseClasses.root} > &`]: {
                    opacity: 1,
                  },
                }}
              >
                @gmail.com
              </InputAdornment>
            ),
          },
        }}
      />
    </Box>
  );
}

크기 (Sizes)

더 작은 입력이 좋나요? size prop을 사용해 보세요.

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

export default function TextFieldSizes() {
  return (
    <Box
      component="form"
      sx={{ '& .MuiTextField-root': { m: 1, width: '25ch' } }}
      noValidate
      autoComplete="off"
    >
      <div>
        <TextField
          label="Size"
          id="outlined-size-small"
          defaultValue="Small"
          size="small"
        />
        <TextField label="Size" id="outlined-size-normal" defaultValue="Normal" />
      </div>
      <div>
        <TextField
          label="Size"
          id="filled-size-small"
          defaultValue="Small"
          variant="filled"
          size="small"
        />
        <TextField
          label="Size"
          id="filled-size-normal"
          defaultValue="Normal"
          variant="filled"
        />
      </div>
      <div>
        <TextField
          label="Size"
          id="standard-size-small"
          defaultValue="Small"
          size="small"
          variant="standard"
        />
        <TextField
          label="Size"
          id="standard-size-normal"
          defaultValue="Normal"
          variant="standard"
        />
      </div>
    </Box>
  );
}

filled 변형 입력의 높이는 라벨을 그 바깥에 렌더링해 더 줄일 수 있어요.

import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';

export default function TextFieldHiddenLabel() {
  return (
    <Stack
      component="form"
      sx={{ width: '25ch' }}
      spacing={2}
      noValidate
      autoComplete="off"
    >
      <TextField
        hiddenLabel
        id="filled-hidden-label-small"
        defaultValue="Small"
        variant="filled"
        size="small"
      />
      <TextField
        hiddenLabel
        id="filled-hidden-label-normal"
        defaultValue="Normal"
        variant="filled"
      />
    </Stack>
  );
}

여백 (Margin)

margin prop을 사용해 텍스트 필드의 세로 간격을 조정할 수 있어요. none(기본값)은 FormControl에 여백을 적용하지 않는 반면, dense와 normal은 적용해요.

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

function RedBar() {
  return (
    <Box
      sx={(theme) => ({
        height: 20,
        backgroundColor: 'rgba(255, 0, 0, 0.1)',
        ...theme.applyStyles('dark', {
          backgroundColor: 'rgb(255 132 132 / 25%)',
        }),
      })}
    />
  );
}

export default function LayoutTextFields() {
  return (
    <Box
      sx={{
        display: 'flex',
        flexDirection: 'column',
        '& .MuiTextField-root': { width: '25ch' },
      }}
    >
      <RedBar />
      <TextField label={'margin="none"'} id="margin-none" />
      <RedBar />
      <TextField label={'margin="dense"'} id="margin-dense" margin="dense" />
      <RedBar />
      <TextField label={'margin="normal"'} id="margin-normal" margin="normal" />
      <RedBar />
    </Box>
  );
}

전체 너비 (Full width)

fullWidth를 사용해 입력이 컨테이너 전체 너비를 차지하게 할 수 있어요.

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

export default function FullWidthTextField() {
  return (
    <Box sx={{ width: 500, maxWidth: '100%' }}>
      <TextField fullWidth label="fullWidth" id="fullWidth" />
    </Box>
  );
}

비제어 대 제어 (Uncontrolled vs. Controlled)

컴포넌트는 제어(controlled)되거나 비제어(uncontrolled)일 수 있어요.

:::info

  • 컴포넌트가 부모에 의해 props로 관리되면 제어(controlled)예요.
  • 컴포넌트가 자체 로컬 상태로 관리되면 비제어(uncontrolled)예요.

제어/비제어 컴포넌트에 대해 더 알아보려면 React 문서를 참조하세요. :::

import * as React from 'react';
import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

export default function StateTextFields() {
  const [name, setName] = React.useState('Cat in the Hat');

  return (
    <Box
      component="form"
      sx={{ '& > :not(style)': { m: 1, width: '25ch' } }}
      noValidate
      autoComplete="off"
    >
      <TextField
        id="outlined-controlled"
        label="Controlled"
        value={name}
        onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
          setName(event.target.value);
        }}
      />
      <TextField
        id="outlined-uncontrolled"
        label="Uncontrolled"
        defaultValue="foo"
      />
    </Box>
  );
}

컴포넌트 (Components)

TextField은 더 작은 컴포넌트들 (FormControl, Input, FilledInput, InputLabel, OutlinedInput, 그리고 FormHelperText)로 구성되어 있으며, 이를 직접 활용해 폼 입력을 크게 커스터마이즈할 수 있어요.

또한 일부 네이티브 HTML input 속성이 TextField 컴포넌트에서 빠져 있다는 걸 눈치챘을 수도 있어요. 이것은 의도적이에요. 컴포넌트는 가장 많이 쓰이는 속성들을 처리해요. 그 다음에는 아래 데모에 보이는 기본 컴포넌트를 사용하는 것은 사용자 몫이에요. 그래도 보일러플레이트를 피하고 싶다면 slotProps.htmlInput(그리고 slotProps.input, slotProps.inputLabel 속성)을 사용할 수 있어요.

import * as React from 'react';
import Box from '@mui/material/Box';
import FilledInput from '@mui/material/FilledInput';
import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
import Input from '@mui/material/Input';
import InputLabel from '@mui/material/InputLabel';
import OutlinedInput from '@mui/material/OutlinedInput';

export default function ComposedTextField() {
  const simpleId = React.useId();
  const helperId = React.useId();
  const disabledId = React.useId();
  const errorId = React.useId();
  const outlinedId = React.useId();
  const filledId = React.useId();
  return (
    <Box
      component="form"
      sx={{ '& > :not(style)': { m: 1 } }}
      noValidate
      autoComplete="off"
    >
      <FormControl variant="standard">
        <InputLabel htmlFor={`${simpleId}-input`}>Name</InputLabel>
        <Input id={`${simpleId}-input`} defaultValue="Composed TextField" />
      </FormControl>
      <FormControl variant="standard">
        <InputLabel htmlFor={`${helperId}-input`}>Name</InputLabel>
        <Input
          id={`${helperId}-input`}
          defaultValue="Composed TextField"
          aria-describedby={`${helperId}-helper-text`}
        />
        <FormHelperText id={`${helperId}-helper-text`}>
          Some important helper text
        </FormHelperText>
      </FormControl>
      <FormControl disabled variant="standard">
        <InputLabel htmlFor={`${disabledId}-input`}>Name</InputLabel>
        <Input id={`${disabledId}-input`} defaultValue="Composed TextField" />
        <FormHelperText>Disabled</FormHelperText>
      </FormControl>
      <FormControl error variant="standard">
        <InputLabel htmlFor={`${errorId}-input`}>Name</InputLabel>
        <Input
          id={`${errorId}-input`}
          defaultValue="Composed TextField"
          aria-describedby={`${errorId}-error-text`}
        />
        <FormHelperText id={`${errorId}-error-text`}>Error</FormHelperText>
      </FormControl>
      <FormControl>
        <InputLabel htmlFor={`${outlinedId}-input`}>Name</InputLabel>
        <OutlinedInput
          id={`${outlinedId}-input`}
          defaultValue="Composed TextField"
          label="Name"
        />
      </FormControl>
      <FormControl variant="filled">
        <InputLabel htmlFor={`${filledId}-input`}>Name</InputLabel>
        <FilledInput id={`${filledId}-input`} defaultValue="Composed TextField" />
      </FormControl>
    </Box>
  );
}

Inputs

import Box from '@mui/material/Box';
import Input from '@mui/material/Input';

const ariaLabel = { 'aria-label': 'description' };

export default function Inputs() {
  return (
    <Box
      component="form"
      sx={{ '& > :not(style)': { m: 1 } }}
      noValidate
      autoComplete="off"
    >
      <Input defaultValue="Hello world" inputProps={ariaLabel} />
      <Input placeholder="Placeholder" inputProps={ariaLabel} />
      <Input disabled defaultValue="Disabled" inputProps={ariaLabel} />
      <Input defaultValue="Error" error inputProps={ariaLabel} />
    </Box>
  );
}

색상 (Color)

color prop은 포커스될 때 텍스트 필드의 하이라이트 색상을 변경해요.

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

export default function ColorTextFields() {
  return (
    <Box
      component="form"
      sx={{ '& > :not(style)': { m: 1, width: '25ch' } }}
      noValidate
      autoComplete="off"
    >
      <TextField label="Outlined secondary" color="secondary" focused />
      <TextField label="Filled success" variant="filled" color="success" focused />
      <TextField
        label="Standard warning"
        variant="standard"
        color="warning"
        focused
      />
    </Box>
  );
}

커스터마이즈 (Customization)

컴포넌트를 커스터마이즈하는 몇 가지 예제예요. 자세한 내용은 overrides 문서 페이지에서 배울 수 있어요.

styled API 사용 (Using the styled API)

import * as React from 'react';
import { alpha, styled } from '@mui/material/styles';
import InputBase from '@mui/material/InputBase';
import Box from '@mui/material/Box';
import InputLabel from '@mui/material/InputLabel';
import TextField, { TextFieldProps } from '@mui/material/TextField';
import FormControl from '@mui/material/FormControl';
import { OutlinedInputProps } from '@mui/material/OutlinedInput';

const CssTextField = styled(TextField)({
  '& label.Mui-focused': {
    color: '#A0AAB4',
  },
  '& .MuiInput-underline:after': {
    borderBottomColor: '#B2BAC2',
  },
  '& .MuiOutlinedInput-root': {
    '& fieldset': {
      borderColor: '#E0E3E7',
    },
    '&:hover fieldset': {
      borderColor: '#B2BAC2',
    },
    '&.Mui-focused fieldset': {
      borderColor: '#6F7E8C',
    },
  },
});

const BootstrapInput = styled(InputBase)(({ theme }) => ({
  'label + &': {
    marginTop: theme.spacing(3),
  },
  '& .MuiInputBase-input': {
    borderRadius: 4,
    position: 'relative',
    backgroundColor: '#F3F6F9',
    border: '1px solid',
    borderColor: '#E0E3E7',
    fontSize: 16,
    width: 'auto',
    padding: '10px 12px',
    transition: theme.transitions.create([
      'border-color',
      'background-color',
      'box-shadow',
    ]),
    // Use the system font instead of the default Roboto font.
    fontFamily: [
      '-apple-system',
      'BlinkMacSystemFont',
      '"Segoe UI"',
      'Roboto',
      '"Helvetica Neue"',
      'Arial',
      'sans-serif',
      '"Apple Color Emoji"',
      '"Segoe UI Emoji"',
      '"Segoe UI Symbol"',
    ].join(','),
    '&:focus': {
      boxShadow: `${alpha(theme.palette.primary.main, 0.25)} 0 0 0 0.2rem`,
      borderColor: theme.palette.primary.main,
    },
    ...theme.applyStyles('dark', {
      backgroundColor: '#1A2027',
      borderColor: '#2D3843',
    }),
  },
}));

const RedditTextField = styled((props: TextFieldProps) => (
  <TextField
    slotProps={{
      input: { disableUnderline: true } as Partial<OutlinedInputProps>,
    }}
    {...props}
  />
))(({ theme }) => ({
  '& .MuiFilledInput-root': {
    overflow: 'hidden',
    borderRadius: 4,
    border: '1px solid',
    backgroundColor: '#F3F6F9',
    borderColor: '#E0E3E7',
    transition: theme.transitions.create([
      'border-color',
      'background-color',
      'box-shadow',
    ]),
    '&:hover': {
      backgroundColor: 'transparent',
    },
    '&.Mui-focused': {
      backgroundColor: 'transparent',
      boxShadow: `${alpha(theme.palette.primary.main, 0.25)} 0 0 0 2px`,
      borderColor: theme.palette.primary.main,
    },
    ...theme.applyStyles('dark', {
      backgroundColor: '#1A2027',
      borderColor: '#2D3843',
    }),
  },
}));

const ValidationTextField = styled(TextField)({
  '& input:valid + fieldset': {
    borderColor: '#E0E3E7',
    borderWidth: 1,
  },
  '& input:invalid + fieldset': {
    borderColor: 'red',
    borderWidth: 1,
  },
  '& input:valid:focus + fieldset': {
    borderLeftWidth: 4,
    padding: '4px !important', // override inline-style
  },
});

export default function CustomizedInputsStyled() {
  const bootstrapId = React.useId();
  const redditId = React.useId();
  const cssId = React.useId();
  const validationId = React.useId();
  return (
    <Box
      component="form"
      noValidate
      sx={{ display: 'grid', gridTemplateColumns: { sm: '1fr 1fr' }, gap: 2 }}
    >
      <FormControl variant="standard">
        <InputLabel shrink htmlFor={`${bootstrapId}-input`}>
          Bootstrap
        </InputLabel>
        <BootstrapInput defaultValue="react-bootstrap" id={`${bootstrapId}-input`} />
      </FormControl>
      <RedditTextField
        label="Reddit"
        defaultValue="react-reddit"
        id={`${redditId}-input`}
        variant="filled"
        style={{ marginTop: 11 }}
      />
      <CssTextField label="Custom CSS" id={`${cssId}-input`} />
      <ValidationTextField
        label="CSS validation style"
        required
        variant="outlined"
        defaultValue="Success"
        id={`${validationId}-input`}
      />
    </Box>
  );
}

테마 스타일 오버라이드 API 사용 (Using the theme style overrides API)

styleOverrides 키를 사용해 Material UI가 DOM에 주입하는 어떤 스타일이든 변경할 수 있어요. 자세한 내용은 theme style overrides 문서를 참조하세요.

import TextField from '@mui/material/TextField';
import { outlinedInputClasses } from '@mui/material/OutlinedInput';
import Box from '@mui/material/Box';
import { createTheme, ThemeProvider, Theme, useTheme } from '@mui/material/styles';

const customTheme = (outerTheme: Theme) =>
  createTheme({
    palette: {
      mode: outerTheme.palette.mode,
    },
    components: {
      MuiTextField: {
        styleOverrides: {
          root: {
            '--TextField-brandBorderColor': '#E0E3E7',
            '--TextField-brandBorderHoverColor': '#B2BAC2',
            '--TextField-brandBorderFocusedColor': '#6F7E8C',
            '& label.Mui-focused': {
              color: 'var(--TextField-brandBorderFocusedColor)',
            },
          },
        },
      },
      MuiOutlinedInput: {
        styleOverrides: {
          notchedOutline: {
            borderColor: 'var(--TextField-brandBorderColor)',
          },
          root: {
            [`&:hover .${outlinedInputClasses.notchedOutline}`]: {
              borderColor: 'var(--TextField-brandBorderHoverColor)',
            },
            [`&.Mui-focused .${outlinedInputClasses.notchedOutline}`]: {
              borderColor: 'var(--TextField-brandBorderFocusedColor)',
            },
          },
        },
      },
      MuiFilledInput: {
        styleOverrides: {
          root: {
            '&::before, &::after': {
              borderBottom: '2px solid var(--TextField-brandBorderColor)',
            },
            '&:hover:not(.Mui-disabled, .Mui-error):before': {
              borderBottom: '2px solid var(--TextField-brandBorderHoverColor)',
            },
            '&.Mui-focused:after': {
              borderBottom: '2px solid var(--TextField-brandBorderFocusedColor)',
            },
          },
        },
      },
      MuiInput: {
        styleOverrides: {
          root: {
            '&::before': {
              borderBottom: '2px solid var(--TextField-brandBorderColor)',
            },
            '&:hover:not(.Mui-disabled, .Mui-error):before': {
              borderBottom: '2px solid var(--TextField-brandBorderHoverColor)',
            },
            '&.Mui-focused:after': {
              borderBottom: '2px solid var(--TextField-brandBorderFocusedColor)',
            },
          },
        },
      },
    },
  });

export default function CustomizedInputsStyleOverrides() {
  const outerTheme = useTheme();

  return (
    <Box
      sx={{ display: 'grid', gridTemplateColumns: { sm: '1fr 1fr 1fr' }, gap: 2 }}
    >
      <ThemeProvider theme={customTheme(outerTheme)}>
        <TextField label="Outlined" />
        <TextField label="Filled" variant="filled" />
        <TextField label="Standard" variant="standard" />
      </ThemeProvider>
    </Box>
  );
}

커스터마이즈는 CSS에서 끝나지 않아요. 컴포지션을 사용해 커스텀 컴포넌트를 만들고 앱에 독특한 느낌을 줄 수 있어요. 아래는 Google Maps에서 영감을 받은 InputBase 컴포넌트를 사용한 예제예요.

import Paper from '@mui/material/Paper';
import InputBase from '@mui/material/InputBase';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import SearchIcon from '@mui/icons-material/Search';
import DirectionsIcon from '@mui/icons-material/Directions';

export default function CustomizedInputBase() {
  return (
    <Paper
      component="form"
      sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: 400 }}
    >
      <IconButton sx={{ p: '10px' }} aria-label="menu">
        <MenuIcon />
      </IconButton>
      <InputBase
        sx={{ ml: 1, flex: 1 }}
        placeholder="Search Google Maps"
        inputProps={{ 'aria-label': 'search google maps' }}
      />
      <IconButton type="button" sx={{ p: '10px' }} aria-label="search">
        <SearchIcon />
      </IconButton>
      <Divider sx={{ height: 28, m: 0.5 }} orientation="vertical" />
      <IconButton color="primary" sx={{ p: '10px' }} aria-label="directions">
        <DirectionsIcon />
      </IconButton>
    </Paper>
  );
}

🎨 영감이 필요하다면 MUI Treasury의 커스터마이즈 예제를 확인해 보세요.

useFormControl

고급 커스터마이즈 사용 사례를 위해 useFormControl() 훅이 제공돼요. 이 훅은 부모 FormControl 컴포넌트의 컨텍스트 값을 반환해요.

API

import { useFormControl } from '@mui/material/FormControl';

반환값 (Returns)

value (object):

  • value.adornedStart (bool): 자식 Input 또는 Select 컴포넌트가 시작 adornment를 갖는지 여부를 나타내요.
  • value.setAdornedStart (func): adornedStart 상태 값의 setter 함수예요.
  • value.color (string): FormControl의 color prop에서 상속받아 사용되는 테마 색상이에요.
  • value.disabled (bool): FormControl의 disabled prop에서 상속받아 비활성 상태로 표시되는지 여부를 나타내요.
  • value.error (bool): FormControl의 error prop에서 상속받아 오류 상태로 표시되는지 여부를 나타내요.
  • value.filled (bool): 입력이 채워졌는지 여부를 나타내요.
  • value.focused (bool): 컴포넌트와 그 자식들이 포커스된 상태로 표시되는지 여부를 나타내요.
  • value.fullWidth (bool): FormControl의 fullWidth prop에서 상속받아 컴포넌트가 컨테이너 전체 너비를 차지하는지 여부를 나타내요.
  • value.hiddenLabel (bool): FormControl의 hiddenLabel prop에서 상속받아 라벨이 숨겨지는지 여부를 나타내요.
  • value.required (bool): FormControl의 required prop에서 상속받아 라벨이 입력이 필수임을 나타내는지 여부를 나타내요.
  • value.size (string): FormControl의 size prop에서 상속받은 컴포넌트 크기예요.
  • value.variant (string): FormControl의 variant prop에서 상속받아 FormControl 컴포넌트와 그 자식들이 사용하는 변형이에요.
  • value.onBlur (func): 입력이 블러(blur)될 때 호출해야 해요.
  • value.onFocus (func): 입력이 포커스될 때 호출해야 해요.
  • value.onEmpty (func): 입력이 비워질 때 호출해야 해요.
  • value.onFilled (func): 입력이 채워질 때 호출해야 해요.

예제 (Example)

import * as React from 'react';
import FormControl, { useFormControl } from '@mui/material/FormControl';
import OutlinedInput from '@mui/material/OutlinedInput';
import FormHelperText from '@mui/material/FormHelperText';

function MyFormHelperText() {
  const { focused } = useFormControl() || {};

  const helperText = React.useMemo(() => {
    if (focused) {
      return 'This field is being focused';
    }

    return 'Helper text';
  }, [focused]);

  return <FormHelperText>{helperText}</FormHelperText>;
}

export default function UseFormControl() {
  return (
    <form noValidate autoComplete="off">
      <FormControl sx={{ width: '25ch' }}>
        <OutlinedInput placeholder="Please enter text" />
        <MyFormHelperText />
      </FormControl>
    </form>
  );
}

성능 (Performance)

자동 채움(auto-fill) 키프레임을 위한 전역 스타일은 각 마운트와 언마운트 시 각각 주입되고 제거돼요. 한 번에 많은 수의 Text Field 컴포넌트를 로딩한다면, MuiInputBase에서 disableInjectingGlobalStyles을 활성화해 이 기본 동작을 변경하는 것이 좋을 수 있어요. 앱 최상단에 자동 채움 키프레임용 GlobalStyles를 주입하세요.

import { GlobalStyles, createTheme, ThemeProvider } from '@mui/material';

const theme = createTheme({
  components: {
    MuiInputBase: {
      defaultProps: {
        disableInjectingGlobalStyles: true,
      },
    },
  },
});

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      <GlobalStyles
        styles={{
          '@keyframes mui-auto-fill': { from: { animationName: 'mui-auto-fill' } },
          '@keyframes mui-auto-fill-cancel': {
            from: { animationName: 'mui-auto-fill-cancel' },
          },
        }}
      />
      ...
    </ThemeProvider>
  );
}

제한 사항 (Limitations)

Shrink

입력 라벨의 "shrink" 상태가 항상 정확하지 않아요. 입력 라벨은 입력이 무언가 표시하는 즉시 축소되어야 해요. 어떤 상황에서는 "shrink" 상태를 결정할 수 없어요 (datetime 입력, Stripe 입력). 겹침이 발생할 수 있어요.

shrink

이 문제를 우회하려면 라벨의 "shrink" 상태를 강제할 수 있어요.

<TextField slotProps={{ inputLabel: { shrink: true } }} />

또는

<InputLabel shrink>Count</InputLabel>

플로팅 라벨 (Floating label)

플로팅 라벨은 절대 위치(absolute positioning)로 배치돼요. 페이지 레이아웃에 영향을 주지 않아요. 올바르게 표시되도록 입력이 라벨보다 큰지 확인하세요.

type="number"

:::warning type="number"를 Text Field와 함께 사용하는 것은 잠재적인 사용성 문제 때문에 권장하지 않아요:

  • 특정 비숫자 문자('e', '+', '-', '.')를 허용하고 다른 것들은 조용히 버려요

  • 숫자를 스크롤해 증가/감소시키는 기능은 우발적이고 알아차리기 어려운 변경을 일으킬 수 있어요

  • 그리고 더 — <input type="number">의 한계에 대한 더 자세한 설명은 Why the GOV.UK Design System team changed the input type for numbers를 참조하세요

    :::

숫자 유효성 검사가 있는 텍스트 필드가 필요하다면, 대신 Number Field를 사용할 수 있어요.

도움말 텍스트 (Helper text)

helper text prop은 텍스트 필드의 높이에 영향을 줘요. 두 개의 텍스트 필드를 나란히 배치할 때, 하나는 helper text가 있고 다른 하나는 없으면 높이가 달라져요. 예를 들어:

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

export default function HelperTextMisaligned() {
  return (
    <Box sx={{ display: 'flex', alignItems: 'center', '& > :not(style)': { m: 1 } }}>
      <TextField
        helperText="Please enter your name"
        id="demo-helper-text-misaligned"
        label="Name"
      />
      <TextField id="demo-helper-text-misaligned-no-helper" label="Name" />
    </Box>
  );
}

이것은 helperText prop에 공백 문자를 전달해 고칠 수 있어요:

import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';

export default function HelperTextAligned() {
  return (
    <Box sx={{ display: 'flex', alignItems: 'center', '& > :not(style)': { m: 1 } }}>
      <TextField
        helperText="Please enter your name"
        id="demo-helper-text-aligned"
        label="Name"
      />
      <TextField
        helperText=" "
        id="demo-helper-text-aligned-no-helper"
        label="Name"
      />
    </Box>
  );
}

타사 입력 라이브러리와의 통합 (Integration with 3rd party input libraries)

타사 라이브러리를 사용해 입력의 형식을 지정할 수 있어요. inputComponent 속성으로 <input> 요소의 커스텀 구현을 제공해야 해요.

아래 데모는 react-imask와 react-number-format 라이브러리를 사용해요. 같은 개념을 예를 들어 react-stripe-element에도 적용할 수 있어요.

import * as React from 'react';
import { IMaskInput } from 'react-imask';
import { NumericFormat } from 'react-number-format';
import Stack from '@mui/material/Stack';
import Input from '@mui/material/Input';
import InputLabel from '@mui/material/InputLabel';
import TextField from '@mui/material/TextField';
import FormControl from '@mui/material/FormControl';

interface CustomProps {
  onChange: (event: { target: { name: string; value: string } }) => void;
  name: string;
}

const TextMaskCustom = React.forwardRef<HTMLInputElement, CustomProps>(
  function TextMaskCustom(props, ref) {
    const { onChange, ...other } = props;
    return (
      <IMaskInput
        {...other}
        mask="(#00) 000-0000"
        definitions={{
          '#': /[1-9]/,
        }}
        inputRef={ref}
        onAccept={(value: any) => onChange({ target: { name: props.name, value } })}
        overwrite
      />
    );
  },
);

export default function FormattedInputs() {
  const id = React.useId();
  const [values, setValues] = React.useState({
    textmask: '(100) 000-0000',
    numberformat: '1320',
  });

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValues({
      ...values,
      [event.target.name]: event.target.value,
    });
  };

  return (
    <Stack direction="row" spacing={2}>
      <FormControl variant="standard">
        <InputLabel htmlFor={`${id}-input`}>react-imask</InputLabel>
        <Input
          value={values.textmask}
          onChange={handleChange}
          name="textmask"
          id={`${id}-input`}
          inputComponent={TextMaskCustom as any}
        />
      </FormControl>
      <NumericFormat
        value={values.numberformat}
        onChange={handleChange}
        customInput={TextField}
        thousandSeparator
        valueIsNumericString
        prefix="$"
        variant="standard"
        label="react-number-format"
      />
    </Stack>
  );
}

제공되는 입력 컴포넌트는 다음 인터페이스를 구현하는 value를 가진 ref를 노출해야 해요:

interface InputElement {
  focus(): void;
  value?: string;
}
const MyInputComponent = React.forwardRef((props, ref) => {
  const { component: Component, ...other } = props;

  // implement `InputElement` interface
  React.useImperativeHandle(ref, () => ({
    focus: () => {
      // logic to focus the rendered component from 3rd party belongs here
    },
    // hiding the value e.g. react-stripe-elements
  }));

  // `Component` will be your `SomeThirdPartyComponent` from below
  return <Component {...other} />;
});

// usage
<TextField
  slotProps={{
    input: {
      inputComponent: MyInputComponent,
      inputProps: {
        component: SomeThirdPartyComponent,
      },
    },
  }}
/>;

접근성 (Accessibility)

텍스트 필드가 접근 가능하려면, 입력이 라벨과 helper text에 연결되어야 해요. 기본 DOM 노드는 다음과 같은 구조를 가져야 해요:

<div class="form-control">
  <label for="my-input">Email address</label>
  <input id="my-input" aria-describedby="my-helper-text" />
  <span id="my-helper-text">We'll never share your email.</span>
</div>
  • TextField 컴포넌트를 사용한다면, TextField를 클라이언트 쪽에서만 사용하지 않는 한 고유한 id만 제공하면 돼요. UI가 hydrate되기 전까지 명시적 id가 없는 TextField는 연결된 라벨이 없을 거예요.
  • 컴포넌트를 직접 구성한다면:
<FormControl>
  <InputLabel htmlFor="my-input">Email address</InputLabel>
  <Input id="my-input" aria-describedby="my-helper-text" />
  <FormHelperText id="my-helper-text">We'll never share your email.</FormHelperText>
</FormControl>

보조 프로젝트 (Supplementary projects)

더 고급 사용 사례에서는 다음을 활용할 수 있어요:

FilledInput API

데모 (Demos)

이 React 컴포넌트 사용에 대한 예제와 세부 사항은 컴포넌트 데모 페이지를 확인하세요:

Import

import FilledInput from '@mui/material/FilledInput';
// or
import { FilledInput } from '@mui/material';

Props

Name Type Default Required Description
autoComplete string - No
autoFocus bool - No
classes object - No Override or extend the styles applied to the component.
color 'primary' | 'secondary' | string - No
defaultValue any - No
disabled bool - No
disableUnderline bool false No
endAdornment node - No
error bool - No
fullWidth bool false No
hiddenLabel bool false No
id string - No
inputComponent elementType 'input' No
inputProps object {} No
inputRef ref - No
margin 'dense' | 'none' - No
maxRows number | string - No
minRows number | string - No
multiline bool false No
name string - No
onChange function(event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void - No
placeholder string - No
readOnly bool - No
required bool - No
rows number | string - No
slotProps { input?: object, root?: object } {} No
slots { input?: elementType, root?: elementType } {} No
startAdornment node - No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
type string 'text' No
value any - No

Note: The ref is forwarded to the root element (HTMLDivElement).

Any other props supplied will be provided to the root element (InputBase).

Inheritance

위에서 명시적으로 문서화되지 않았지만, InputBase 컴포넌트의 props는 FilledInput에서도 사용할 수 있어요.

Theme default props

MuiFilledInput을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.

CSS

Rule name

Global class Rule name Description
- adornedEnd Styles applied to the root element if endAdornment is provided.
- adornedStart Styles applied to the root element if startAdornment is provided.
- colorSecondary Styles applied to the root element if color secondary.
.Mui-disabled - State class applied to the root element if disabled={true}.
.Mui-error - State class applied to the root element if error={true}.
.Mui-focused - State class applied to the root element if the component is focused.
- hiddenLabel Styles applied to the root element if hiddenLabel={true}.
- input Styles applied to the input element.
- multiline Styles applied to the root element if multiline={true}.
- root Styles applied to the root element.
- sizeSmall Styles applied to the root element if size="small".
- underline Styles applied to the root element unless disableUnderline={true}.

Source code

이 페이지에서 정보를 찾지 못했다면, 컴포넌트 구현을 살펴보고 더 자세한 내용을 확인해 보세요.

FormControl API

데모 (Demos)

이 React 컴포넌트 사용에 대한 예제와 세부 사항은 컴포넌트 데모 페이지를 확인하세요:

Import

import FormControl from '@mui/material/FormControl';
// or
import { FormControl } from '@mui/material';

Props

Name Type Default Required Description
children node - No
classes object - No Override or extend the styles applied to the component.
color 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' | string 'primary' No
component elementType - No
disabled bool false No
error bool false No
focused bool - No
fullWidth bool false No
hiddenLabel bool false No
margin 'dense' | 'none' | 'normal' 'none' No
required bool false No
size 'medium' | 'small' | string 'medium' No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
variant 'filled' | 'outlined' | 'standard' 'outlined' No

Note: The ref is forwarded to the root element (HTMLDivElement).

Any other props supplied will be provided to the root element (native element).

Theme default props

MuiFormControl을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.

CSS

Rule name

Global class Rule name Description
- fullWidth Styles applied to the root element if fullWidth={true}.
- marginDense Styles applied to the root element if margin="dense".
- marginNormal Styles applied to the root element if margin="normal".
- root Styles applied to the root element.

Source code

이 페이지에서 정보를 찾지 못했다면, 컴포넌트 구현을 살펴보고 더 자세한 내용을 확인해 보세요.

FormHelperText API

데모 (Demos)

이 React 컴포넌트 사용에 대한 예제와 세부 사항은 컴포넌트 데모 페이지를 확인하세요:

Import

import FormHelperText from '@mui/material/FormHelperText';
// or
import { FormHelperText } from '@mui/material';

Props

Name Type Default Required Description
children node - No
classes object - No Override or extend the styles applied to the component.
component elementType - No
disabled bool - No
error bool - No
filled bool - No
focused bool - No
margin 'dense' - No
required bool - No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
variant 'filled' | 'outlined' | 'standard' | string - No

Note: The ref is forwarded to the root element (HTMLParagraphElement).

Any other props supplied will be provided to the root element (native element).

Theme default props

MuiFormHelperText을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.

CSS

Rule name

Global class Rule name Description
- contained Styles applied to the root element if variant="filled" or variant="outlined".
.Mui-disabled - State class applied to the root element if disabled={true}.
.Mui-error - State class applied to the root element if error={true}.
- filled State class applied to the root element if filled={true}.
.Mui-focused - State class applied to the root element if focused={true}.
.Mui-required - State class applied to the root element if required={true}.
- root Styles applied to the root element.
- sizeSmall Styles applied to the root element if size="small".

Source code

이 페이지에서 정보를 찾지 못했다면, 컴포넌트 구현을 살펴보고 더 자세한 내용을 확인해 보세요.

Input API

데모 (Demos)

이 React 컴포넌트 사용에 대한 예제와 세부 사항은 컴포넌트 데모 페이지를 확인하세요:

Import

import Input from '@mui/material/Input';
// or
import { Input } from '@mui/material';

Props

Name Type Default Required Description
autoComplete string - No
autoFocus bool - No
classes object - No Override or extend the styles applied to the component.
color 'primary' | 'secondary' | string - No
defaultValue any - No
disabled bool - No
disableUnderline bool false No
endAdornment node - No
error bool - No
fullWidth bool false No
id string - No
inputComponent elementType 'input' No
inputProps object {} No
inputRef ref - No
margin 'dense' | 'none' - No
maxRows number | string - No
minRows number | string - No
multiline bool false No
name string - No
onChange function(event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void - No
placeholder string - No
readOnly bool - No
required bool - No
rows number | string - No
slotProps { input?: object, root?: object } {} No
slots { input?: elementType, root?: elementType } {} No
startAdornment node - No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
type string 'text' No
value any - No

Note: The ref is forwarded to the root element (HTMLDivElement).

Any other props supplied will be provided to the root element (InputBase).

Inheritance

위에서 명시적으로 문서화되지 않았지만, InputBase 컴포넌트의 props는 Input에서도 사용할 수 있어요.

Theme default props

MuiInput을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.

CSS

Rule name

Global class Rule name Description
- colorSecondary Styles applied to the root element if color secondary.
.Mui-disabled - Styles applied to the root element if disabled={true}.
.Mui-error - State class applied to the root element if error={true}.
.Mui-focused - Styles applied to the root element if the component is focused.
- formControl Styles applied to the root element if the component is a descendant of FormControl.
- fullWidth Styles applied to the root element if fullWidth={true}.
- input Styles applied to the input element.
- inputTypeSearch Styles applied to the input element if type="search".
- multiline Styles applied to the root element if multiline={true}.
- root Styles applied to the root element.
- sizeSmall Styles applied to the input element if size="small".
- underline Styles applied to the root element unless disableUnderline={true}.

Source code

이 페이지에서 정보를 찾지 못했다면, 컴포넌트 구현을 살펴보고 더 자세한 내용을 확인해 보세요.

InputAdornment API

데모 (Demos)

이 React 컴포넌트 사용에 대한 예제와 세부 사항은 컴포넌트 데모 페이지를 확인하세요:

Import

import InputAdornment from '@mui/material/InputAdornment';
// or
import { InputAdornment } from '@mui/material';

Props

Name Type Default Required Description
position 'end' | 'start' - Yes
children node - No
classes object - No Override or extend the styles applied to the component.
component elementType - No
disablePointerEvents bool false No
disableTypography bool false No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
variant 'filled' | 'outlined' | 'standard' - No

Note: The ref is forwarded to the root element (HTMLDivElement).

Any other props supplied will be provided to the root element (native element).

Theme default props

MuiInputAdornment을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.

CSS

Rule name

Global class Rule name Description
- disablePointerEvents Styles applied to the root element if disablePointerEvents={true}.
- filled Styles applied to the root element if variant="filled".
- hiddenLabel Styles applied if the adornment is used inside ``.
- outlined Styles applied to the root element if variant="outlined".
- positionEnd Styles applied to the root element if position="end".
- positionStart Styles applied to the root element if position="start".
- root Styles applied to the root element.
- sizeSmall Styles applied if the adornment is used inside ``.
- standard Styles applied to the root element if variant="standard".

Source code

이 페이지에서 정보를 찾지 못했다면, 컴포넌트 구현을 살펴보고 더 자세한 내용을 확인해 보세요.

InputBase API

데모 (Demos)

이 React 컴포넌트 사용에 대한 예제와 세부 사항은 컴포넌트 데모 페이지를 확인하세요:

Import

import InputBase from '@mui/material/InputBase';
// or
import { InputBase } from '@mui/material';

Props

Name Type Default Required Description
autoComplete string - No
autoFocus bool - No
classes object - No Override or extend the styles applied to the component.
color 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' | string - No
defaultValue any - No
disabled bool - No
disableInjectingGlobalStyles bool false No
endAdornment node - No
error bool - No
fullWidth bool false No
id string - No
inputComponent element type 'input' No
inputProps object {} No
inputRef ref - No
margin 'dense' | 'none' - No
maxRows number | string - No
minRows number | string - No
multiline bool false No
name string - No
onBlur func - No
onChange function(event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void - No
onInvalid func - No
placeholder string - No
readOnly bool - No
required bool - No
rows number | string - No
size 'medium' | 'small' | string - No
slotProps { input?: object, root?: object } {} No
slots { input?: elementType, root?: elementType } {} No
startAdornment node - No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
type string 'text' No
value any - No

Note: The ref is forwarded to the root element (HTMLDivElement).

Any other props supplied will be provided to the root element (native element).

Theme default props

MuiInputBase을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.

CSS

Rule name

Global class Rule name Description
- adornedEnd Styles applied to the root element if endAdornment is provided.
- adornedStart Styles applied to the root element if startAdornment is provided.
- colorSecondary Styles applied to the root element if the color is secondary.
.Mui-disabled - Styles applied to the root element if disabled={true}.
.Mui-error - State class applied to the root element if error={true}.
.Mui-focused - Styles applied to the root element if the component is focused.
- formControl Styles applied to the root element if the component is a descendant of FormControl.
- fullWidth Styles applied to the root element if fullWidth={true}.
- hiddenLabel Styles applied to the root element if hiddenLabel={true}.
- input Styles applied to the input element.
- inputTypeSearch
- multiline Styles applied to the root element if multiline={true}.
.Mui-readOnly - State class applied to the root element if readOnly={true}.
- root Styles applied to the root element.
- sizeSmall Styles applied to the input element if size="small".

Source code

이 페이지에서 정보를 찾지 못했다면, 컴포넌트 구현을 살펴보고 더 자세한 내용을 확인해 보세요.

InputLabel API

데모 (Demos)

이 React 컴포넌트 사용에 대한 예제와 세부 사항은 컴포넌트 데모 페이지를 확인하세요:

Import

import InputLabel from '@mui/material/InputLabel';
// or
import { InputLabel } from '@mui/material';

Props

Name Type Default Required Description
children node - No
classes object - No Override or extend the styles applied to the component.
color 'error' | 'info' | 'primary' | 'secondary' | 'success' | 'warning' | string - No
disableAnimation bool false No
disabled bool - No
error bool - No
focused bool - No
margin 'dense' - No
required bool - No
shrink bool - No
size 'medium' | 'small' | string 'medium' No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
variant 'filled' | 'outlined' | 'standard' - No

Note: The ref is forwarded to the root element (HTMLLabelElement).

Any other props supplied will be provided to the root element (FormLabel).

Inheritance

위에서 명시적으로 문서화되지 않았지만, FormLabel 컴포넌트의 props는 InputLabel에서도 사용할 수 있어요.

Theme default props

MuiInputLabel을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.

CSS

Rule name

Global class Rule name Description
- animated Styles applied to the input element unless disableAnimation={true}.
- asterisk State class applied to the asterisk element.
.Mui-disabled - State class applied to the root element if disabled={true}.
.Mui-error - State class applied to the root element if error={true}.
- filled Styles applied to the root element if variant="filled".
.Mui-focused - State class applied to the root element if focused={true}.
- formControl Styles applied to the root element if the component is a descendant of FormControl.
- outlined Styles applied to the root element if variant="outlined".
.Mui-required - State class applied to the root element if required={true}.
- root Styles applied to the root element.
- shrink Styles applied to the input element if shrink={true}.
- sizeSmall Styles applied to the root element if size="small".
- standard Styles applied to the root element if variant="standard".

Source code

이 페이지에서 정보를 찾지 못했다면, 컴포넌트 구현을 살펴보고 더 자세한 내용을 확인해 보세요.

OutlinedInput API

데모 (Demos)

이 React 컴포넌트 사용에 대한 예제와 세부 사항은 컴포넌트 데모 페이지를 확인하세요:

Import

import OutlinedInput from '@mui/material/OutlinedInput';
// or
import { OutlinedInput } from '@mui/material';

Props

Name Type Default Required Description
autoComplete string - No
autoFocus bool - No
classes object - No Override or extend the styles applied to the component.
color 'primary' | 'secondary' | string - No
defaultValue any - No
disabled bool - No
endAdornment node - No
error bool - No
fullWidth bool false No
id string - No
inputComponent elementType 'input' No
inputProps object {} No
inputRef ref - No
label node - No
margin 'dense' | 'none' - No
maxRows number | string - No
minRows number | string - No
multiline bool false No
name string - No
notched bool - No
onChange function(event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void - No
placeholder string - No
readOnly bool - No
required bool - No
rows number | string - No
slotProps { input?: object, notchedOutline?: func | object, root?: object } {} No
slots { input?: elementType, notchedOutline?: elementType, root?: elementType } {} No
startAdornment node - No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
type string 'text' No
value any - No

Note: The ref is forwarded to the root element (HTMLDivElement).

Any other props supplied will be provided to the root element (InputBase).

Inheritance

위에서 명시적으로 문서화되지 않았지만, InputBase 컴포넌트의 props는 OutlinedInput에서도 사용할 수 있어요.

Theme default props

MuiOutlinedInput을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.

CSS

Rule name

Global class Rule name Description
- adornedEnd Styles applied to the root element if endAdornment is provided.
- adornedStart Styles applied to the root element if startAdornment is provided.
- colorSecondary Styles applied to the root element if the color is secondary.
.Mui-disabled - Styles applied to the root element if disabled={true}.
.Mui-error - State class applied to the root element if error={true}.
.Mui-focused - Styles applied to the root element if the component is focused.
- input Styles applied to the input element.
- inputTypeSearch Styles applied to the input element if type="search".
- multiline Styles applied to the root element if multiline={true}.
- notchedOutline Styles applied to the NotchedOutline element.
- root Styles applied to the root element.
- sizeSmall Styles applied to the input element if size="small".

Source code

이 페이지에서 정보를 찾지 못했다면, 컴포넌트 구현을 살펴보고 더 자세한 내용을 확인해 보세요.

TextField API

데모 (Demos)

이 React 컴포넌트 사용에 대한 예제와 세부 사항은 컴포넌트 데모 페이지를 확인하세요:

Import

import TextField from '@mui/material/TextField';
// or
import { TextField } from '@mui/material';

Props

Name Type Default Required Description
autoComplete string - No
autoFocus bool false No
classes object - No Override or extend the styles applied to the component.
color 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' | string 'primary' No
defaultValue any - No
disabled bool false No
error bool false No
fullWidth bool false No
helperText node - No
id string - No
inputRef ref - No
label node - No
margin 'dense' | 'none' | 'normal' 'none' No
maxRows number | string - No
minRows number | string - No
multiline bool false No
name string - No
onChange function(event: object) => void - No
placeholder string - No
required bool false No
rows number | string - No
select bool false No
size 'medium' | 'small' | string 'medium' No
slotProps { formHelperText?: func | object, htmlInput?: func | object, input?: func | object, inputLabel?: func | object, select?: func | object } {} No
slots { formHelperText?: elementType, htmlInput?: elementType, input?: elementType, inputLabel?: elementType, root?: elementType, select?: elementType } {} No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
type string - No
value any - No
variant 'filled' | 'outlined' | 'standard' 'outlined' No

Note: The ref is forwarded to the root element.

Any other props supplied will be provided to the root element (FormControl).

Inheritance

위에서 명시적으로 문서화되지 않았지만, FormControl 컴포넌트의 props는 TextField에서도 사용할 수 있어요.

Theme default props

MuiTextField을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.

Slots

Name Default Class Description
root FormControl .MuiTextField-root The component that renders the root.
input OutlinedInput - The component that renders the input.
inputLabel InputLabel - The component that renders the input's label.
htmlInput 'input' - The html input element.
formHelperText FormHelperText - The component that renders the helper text.
select Select - The component that renders the select.

Source code

이 페이지에서 정보를 찾지 못했다면, 컴포넌트 구현을 살펴보고 더 자세한 내용을 확인해 보세요.

더 알아보기 (Learn more)