Time picker

사용자가 특정 시간 및 시간의 범위를 지정 및 선택할 수 있도록 도움을 주는 요소입니다.

Basic usage

사용자가 00:00:00 (시간, 분, 초)순으로 시간을 입력할 수 있는 폼을 제공합니다.

  • locale, timezone을 지정할 수 있습니다.
  • 기본적으로 TextField 컴포넌트를 사용합니다.
  • 항상 유효한 Date 값을 가지고 있지는 않습니다. Invalid date 상태를 가질 수 있습니다.
import { TimePicker } from '@montage-ui/core';

const Demo = () => {
  return (
    <TimePicker width="40%" />
  )
}

export default Demo;

Format

input에 시간이 표시되는 format 을 지정할 수 있습니다. 아래 format을 모두 사용할 수 있습니다.

  • H, HH, h, hh
  • m, mm
  • s, ss
  • A, a
  • YYYY, YY
  • M, MM, MMM, MMMM
  • D, DD

12시간 형식일 경우 a 또는 A와 h 또는 hh를 함께 사용해야하며 24시간 형식일 경우 H 또는 HH만 사용합니다.

import { TimePicker, FlexBox } from '@montage-ui/core';

const Demo = () => {
  return (
    <FlexBox flexDirection="column" alignItems="center" gap="20px" sx={{ width: '100%' }}>
      <TimePicker width="40%" format="a hh:mm:ss" />
      <TimePicker width="40%" format="a h:m:s" />
      <TimePicker width="40%" format="HH:mm:ss" />
      <TimePicker width="40%" format="H:m:s" />
      <TimePicker width="40%" format="mm:ss" />
      <TimePicker width="40%" format="m:s" />
    </FlexBox>
  )
}

export default Demo;

Localization

  • Locale
    • 기본적으로 locale은 ko-KR 으로 지정되어 있습니다.
    • Intl API 에서 지원하는 언어를 모두 import 없이 사용할 수 있습니다.
  • Timezone
    • 기본적으로 timezone은 사용자의 환경에 따라 결정됩니다.
    • dayjs 에서 지원하는 timezone과 UTC 를 모두 import 없이 사용할 수 있습니다.
import { TimePicker, FlexBox } from '@montage-ui/core';

const Demo = () => {
  return (
    <FlexBox flexDirection="column" alignItems="center" gap="20px" sx={{ width: '100%' }}>
      <TimePicker width="40%" defaultValue={new Date()} locale="ko-KR" timezone="Asia/Seoul" />
      <TimePicker width="40%" defaultValue={new Date()} locale="en-US" timezone="UTC" />
    </FlexBox>
  )
}

export default Demo;

Views

views, defaultView, view, onViewChange 로 선택 UI를 조정할 수 있습니다.

'meridiem' | 'hour' | 'minute' | 'second' 를 지원합니다.

import { TimePicker, FlexBox } from '@montage-ui/core';

const Demo = () => {

  return (
    <FlexBox flexDirection="column" alignItems="center" gap="20px" sx={{ width: '100%' }}>
      <TimePicker
        width="40%"
        format="mm:ss"
        views={['minute', 'second']}
      />
      <TimePicker
        width="40%"
        format="hh a"
        views={['hour', 'meridiem']}
      />
    </FlexBox>
  )
}

export default Demo;

ActionArea

PickerActionArea 를 사용하여 하단 영역의 버튼을 커스텀 할 수 있습니다.

총 4가지 variant를 사용할 수 있습니다.

  • now
  • accept
  • cancel
  • reset

Checkbox 컴포넌트와 함께 사용할 때에는 disableLastUnitClickClose prop 을 사용하여 더욱 자연스러운 동작을 구현할 수 있

import { TimePicker, FlexBox, PickerActionArea, PickerActionAreaButton, FormControl, FormControlField, FormControlLabel, Checkbox } from '@montage-ui/core';

const Demo = () => {
  return (
    <FlexBox flexDirection="column" alignItems="center" gap="20px" sx={{ width: '100%' }}>
      <TimePicker
        width="40%"
        placeholder="현재, 확인"
        actionArea={
          <PickerActionArea>
            <PickerActionAreaButton variant="now">현재</PickerActionAreaButton>
            <PickerActionAreaButton variant="accept">확인</PickerActionAreaButton>
          </PickerActionArea>
        }
      />

      <TimePicker
        width="40%"
        placeholder="취소, 초기화"
        actionArea={
          <PickerActionArea>
            <PickerActionAreaButton variant="cancel">취소</PickerActionAreaButton>
            <PickerActionAreaButton variant="reset">초기화</PickerActionAreaButton>
          </PickerActionArea>
        }
      />


      <TimePicker
        width="40%"
        placeholder="checkbox, 확인"
        disableLastUnitClickClose
        actionArea={
          <PickerActionArea>
            <FormControl
              gap="8px"
              flexDirection="row"
            >
              <FormControlField>
                <Checkbox size="medium" />
              </FormControlField>
              <FormControlLabel>텍스트</FormControlLabel>
            </FormControl>
            <PickerActionAreaButton variant="accept">확인</PickerActionAreaButton>
          </PickerActionArea>
        }
      />
    </FlexBox>
  )
}

export default Demo;

Validation

  • min, max prop 을 사용하여 최소값과 최대값을 설정할 수 있습니다.
  • 키보드 조작을 통해서는 범위를 벗어나는 값을 입력할 수 있습니다.
  • Invalid date 상태를 가질 수 있습니다.
import { TimePicker, FlexBox, FormControlNegativeMessage, FormControl, FormControlLabel, FormControlField } from '@montage-ui/core';
import { Controller, useForm } from 'react-hook-form';

const minTime = new Date(new Date().setHours(10, 0, 0));
const maxTime = new Date(new Date().setHours(17, 30, 0));

const Demo = () => {
  const form = useForm({
    defaultValues: {
      time: new Date(),
    },
    mode: 'onChange'
  });

  return (
    <FlexBox as="form" justifyContent="center" gap="20px" sx={{ width: '100%' }}>
      <Controller
        control={form.control}
        name="time"
        rules={{
          validate: (value) => {
            if (isNaN(new Date(value).getTime())) {
              return '유효한 날짜를 입력해주세요.';
            }

            if (new Date(value).getTime() < minTime.getTime()) {
              return '최소 시간은 10시 이상이어야 합니다.';
            }

            if (new Date(value).getTime() > maxTime.getTime()) {
              return '최대 시간은 17시 30분 이하이어야 합니다.';
            }
            
          return true;
          },
        }}
        render={({ field, formState }) => (
          <FormControl sx={{ width: '40%' }}>
            <FormControlLabel>시간</FormControlLabel>

            <FormControlField>
              <TimePicker
                width="100%"
                format="HH시 mm분"
                minTime={minTime}
                maxTime={maxTime}
                inputRef={field.ref}
                value={field.value}
                onChange={field.onChange}
                status={!!formState.errors.time ? 'negative' : 'normal'}
              />
            </FormControlField>

            <FormControlNegativeMessage>{formState.errors.time?.message}</FormControlNegativeMessage>
          </FormControl>
        )}
      />
    </FlexBox>
  )
}

export default Demo;

Form control

Form과 관련된 컴포넌트와 함께 사용할 수 있습니다.

  • FormControlGroup
  • FormControl
  • FormControlField
  • FormControlLabel
  • FormControlMessage
  • FormControlNegativeMessage
  • FormControlPositiveMessage
  • FormControlMessageAccessory

Helper Message

import { FlexBox, FormControl, FormControlField, FormControlLabel, FormControlMessage, FormControlNegativeMessage, TimePicker } from '@montage-ui/core';

const Demo = () => {
  return (
    <FlexBox justifyContent="center" sx={{ width: '100%' }}>
      <FormControl sx={{ width: '40%' }}>
        <FormControlLabel required>Label</FormControlLabel>
        <FormControlField>
          <TimePicker width="100%" />
        </FormControlField>
        <FormControlMessage>Helper Message</FormControlMessage>
      </FormControl>
    </FlexBox>
  )
}

export default Demo;

Sizes

TextField 기반의 size를 사용할 수 있습니다.

FormControl 과 함께 사용한다면 FormControl에 size를 주어 전파할 수 있습니다.

Helper Message

Helper Message

import { FlexBox, FormControl, FormControlField, FormControlLabel, FormControlMessage, FormControlNegativeMessage, TimePicker, TimePickerField } from '@montage-ui/core';

const Demo = () => {
  return (
    <FlexBox alignItems="center" flexDirection="column" gap="12px" sx={{ width: '100%' }}>
      <FormControl sx={{ width: '40%' }} size="large">
        <FormControlLabel required>Large</FormControlLabel>
        <FormControlField>
          <TimePicker
            placeholder="Large time picker field"
          />
        </FormControlField>
        <FormControlMessage>Helper Message</FormControlMessage>
      </FormControl>

      <FormControl sx={{ width: '40%' }} size="medium">
        <FormControlLabel required>Medium</FormControlLabel>
        <FormControlField>
          <TimePicker
            placeholder="Medium time picker field"
          />
        </FormControlField>
        <FormControlMessage>Helper Message</FormControlMessage>
      </FormControl>
    </FlexBox>
  )
}

export default Demo;

Customize

TextField 컴포넌트 대신 커스텀 컴포넌트를 사용할 수 있습니다.

react 18.3 미만의 버전을 사용 중이라면, forwardRef 를 필수로 작성해주세요.

import { TimePicker, TimePickerFieldProps, Chip, FlexBox } from '@montage-ui/core';
import { forwardRef, useState } from 'react';

const CustomChipField = forwardRef<HTMLDivElement, TimePickerFieldProps>(
  ({ value, onClick, inputRef, ...props }, ref) => {
    return (
      <Chip ref={ref} onClick={onClick}>
        {value || 'YYYY.MM.DD'}
      </Chip>
    )
  }
)

const CustomFieldField = forwardRef<HTMLDivElement, TimePickerFieldProps>(
  ({ trailingContent, inputRef, ...props }, ref) => {
    return (
      <FlexBox ref={ref} gap="10px" alignItems="center">
        <input ref={inputRef} {...props} />
        {trailingContent}
      </FlexBox>
    )
  }
)

const Demo = () => {
  const [open1, setOpen1] = useState(false);
  const [open2, setOpen2] = useState(false);

  return (
    <FlexBox flexDirection="column" gap="12px" alignItems="center" sx={{ width: '100%' }}>
      <TimePicker
        width="40%"
        input={CustomChipField}
        open={open1}
        onOpenChange={setOpen1}
        onClick={() => setOpen1(true)}
      />
      <TimePicker
        width="40%"
        input={CustomFieldField}
        open={open2}
        onOpenChange={setOpen2}
      />
    </FlexBox>
  )
}

export default Demo;

Controlled

기본적으로 비제어 컴포넌트로 동작합니다.

value, onChange prop 을 사용하면 제어 컴포넌트로 동작합니다.

제어 컴포넌트와 비제어 컴포넌트는 React 공식 문서 를 참조해주세요.

import { TimePicker, FlexBox } from '@montage-ui/core';
import { useState } from 'react';

const Demo = () => {
  const [value, setValue] = useState(new Date());

  return (
    <FlexBox alignItems="center" flexDirection="column" gap="12px" alignItems="center" sx={{ width: '100%' }}>
      <TimePicker defaultValue={new Date()} width="40%" />
      <TimePicker value={value} onChange={setValue} width="40%" />
    </FlexBox>
  )
}

export default Demo;

Accessibility

WAI-ARIA Datepicker dialog 패턴 대부분을 지원합니다.

  • Form Control 와 함께 사용할 때 더욱 명확한 접근성 속성을 주입할 수 있습니다.

API

TimePicker

NameTypesdefaultValue
open
boolean
-
defaultOpen
boolean
-
onOpenChange
(state: boolean) => void
-
format
string
"a hh:mm"
input
ElementType<TimePickerFieldProps>
-
inputRef
Ref<HTMLInputElement>
-
contentProps
WithSxProps<Merge<ComponentProps<typeof PopperContent>, ComponentPropsWithoutRef<typeof FocusScope>>>
-
onChange
(date: null | string | Date) => void
-
actionArea
ReactNode
-
disableLastUnitClickClose
boolean
-
defaultValue
null | string | Date
-
children
ReactNode
-
onReset
(event: React.SyntheticEvent<HTMLDivElement, Event>) => void & (prevValue: string) => void
-
disabled
boolean
-
sx
SxProp
-
xl
Merge<Pick<TextFieldDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
lg
Merge<Pick<TextFieldDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
md
Merge<Pick<TextFieldDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
sm
Merge<Pick<TextFieldDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
xs
Merge<Pick<TextFieldDefaultProps, "size" | "height" | "width">, { sx?: CSSInterpolation; }> | undefined
-
leadingContent
ReactNode
-
trailingContent
ReactNode
-
value
null | string | Date
-
size
"medium" | "large"
-
height
Property.Height<string | number> | undefined
-
width
Property.Width<string | number> | undefined
-
views
Array<"meridiem" | "hour" | "minute" | "second">
-
locale
string
"ko-KR"
timezone
string
-
onChangeComplete
(value: null | string | Date) => void
-
readOnly
boolean
-
status
"normal" | "positive" | "negative"
-
trailingButton
ReactNode
-
minTime
null | string | Date
-
maxTime
null | string | Date
-

PickerActionArea

NameTypesdefaultValue
children
ReactNode
-
variant
"strong" | "neutral" | "compact" | "cancel"
-
extra
boolean
-
caption
ReactNode
-
captionIcon
ReactNode
-
extraContent
ReactNode
-
compactContent
ReactNode
-
background
boolean
-
backgroundColor
ThemeColorsToken
-
divider
boolean
-
sx
SxProp
-

PickerActionAreaButton

NameTypesdefaultValue
as
ElementType
-
variant
"cancel" | "accept" | "reset" | "now"
-
color
"primary" | "assistive"
-
children
ReactNode
-
disabled
boolean
-
sx
SxProp
-
xl
Merge<Pick<TextButtonDefaultProps, "size">, { sx?: CSSInterpolation; }> | undefined
-
lg
Merge<Pick<TextButtonDefaultProps, "size">, { sx?: CSSInterpolation; }> | undefined
-
md
Merge<Pick<TextButtonDefaultProps, "size">, { sx?: CSSInterpolation; }> | undefined
-
sm
Merge<Pick<TextButtonDefaultProps, "size">, { sx?: CSSInterpolation; }> | undefined
-
xs
Merge<Pick<TextButtonDefaultProps, "size">, { sx?: CSSInterpolation; }> | undefined
-
disableInteraction
boolean
-
leadingContent
ReactNode
-
trailingContent
ReactNode
-
loading
boolean
-
disableLoadingPreventEvents
boolean
-
size
"small" | "medium"
-

© 2026 Wanted Lab, Inc.