DatePicker

Import

<!-- CSS -->
<link rel="stylesheet" href="/guide/css/base/index.css" />
<link rel="stylesheet" href="/guide/css/components/datepicker.css" />

<!-- jQuery + JS (body 닫기 전) -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="/guide/js/components/datepicker.js"></script>

Live

Single

선택된 값 (hidden input):

Range

범위:

States

Default
Disabled

Specs

요소 토큰
Input 폰트 16px Regular var(--font-size-body-sm)
팝업 너비 264px
팝업 Border Radius 12px var(--radius-lg)
팝업 Shadow var(--shadow-md)
날짜 셀 높이 34px
날짜 폰트 14px Regular var(--font-size-label)
오늘 표시 primary 테두리 1.5px var(--color-primary-main)
선택됨 배경 primary var(--color-primary-main)
전송 값 형식 YYYY-MM-DD (ISO 8601) hidden input

Code

HTML

<div class="datepicker" id="dp-1">
  <div class="datepicker__input-wrap">
    <input class="datepicker__input" type="text"
           placeholder="날짜를 선택하세요" readonly
           aria-label="날짜 선택" aria-haspopup="dialog" />
    <input class="datepicker__hidden" type="hidden" name="date" />
    <button class="datepicker__trigger" type="button"
            aria-label="달력 열기" aria-expanded="false"
            aria-controls="dp-1-popup">
      <!-- 캘린더 SVG 아이콘 -->
      <svg viewBox="0 0 20 20" fill="none">
        <rect x="2.5" y="4" width="15" height="13" rx="2" stroke="currentColor" stroke-width="1.5"/>
        <path d="M2.5 8h15" stroke="currentColor" stroke-width="1.5"/>
        <path d="M6.5 2v3M13.5 2v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
      </svg>
    </button>
  </div>

  <div class="datepicker__popup" id="dp-1-popup"
       role="dialog" aria-label="날짜 선택 달력" aria-modal="true">
    <div class="datepicker__header">
      <button class="datepicker__nav datepicker__nav--prev" type="button" aria-label="이전 달">
        <svg viewBox="0 0 16 16" fill="none">
          <path d="M10 12L6 8l4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
        </svg>
      </button>
      <span class="datepicker__month-year"></span>
      <button class="datepicker__nav datepicker__nav--next" type="button" aria-label="다음 달">
        <svg viewBox="0 0 16 16" fill="none">
          <path d="M6 12l4-4-4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
        </svg>
      </button>
    </div>
    <div class="datepicker__weekdays">
      <span>일</span><span>월</span><span>화</span>
      <span>수</span><span>목</span><span>금</span><span>토</span>
    </div>
    <div class="datepicker__days"><!-- jQuery로 동적 생성 --></div>
    <div class="datepicker__footer">
      <button class="datepicker__today-btn" type="button">오늘</button>
	  <button class="datepicker__today-btn" type="button">초기화</button>
      <button class="datepicker__confirm-btn" type="button">확인</button>
    </div>
    <button class="datepicker__close" type="button" aria-label="닫기"></button>
  </div>
</div>

JavaScript 초기화

/* 기본 초기화 — HTML에 .datepicker 클래스가 있으면 자동 실행됨 */
$('.datepicker').datepicker();

/* 옵션: 오늘 이후만 선택 가능 */
$('#dp-1').datepicker({ minDate: new Date() });

/* 옵션: 특정 범위 */
$('#dp-1').datepicker({
  minDate: new Date('2026-01-01'),
  maxDate: new Date('2026-12-31')
});

/* 옵션: 날짜 선택 시 콜백 */
$('#dp-1').datepicker({
  onChange: function (isoString) {
    console.log('선택된 날짜:', isoString); /* '2026-06-05' */
  }
});

/* data 속성으로 옵션 (HTML에서 설정) */
/* <div class="datepicker" data-min-date="2026-01-01" data-max-date="2026-12-31"> */

백엔드 연동

/* ──────────────────────────────────────────────────────────
   백엔드 전송값: .datepicker__hidden input[type="hidden"]에
   'YYYY-MM-DD' 형식으로 자동 저장됩니다.
   ────────────────────────────────────────────────────────── */

/* ① HTML form submit — hidden input이 자동으로 포함됩니다 */
<form method="POST" action="/api/reservation">
  <div class="datepicker" id="dp-1">
    ...
    <input class="datepicker__hidden" type="hidden" name="reservation_date" />
    ...
  </div>
  <button type="submit">예약하기</button>
</form>
/* → 서버에서 req.body.reservation_date 로 수신 (예: '2026-06-15') */


/* ② jQuery AJAX */
$('form').on('submit', function (e) {
  e.preventDefault();
  $.ajax({
    url: '/api/reservation',
    method: 'POST',
    data: {
      reservation_date: $('[name="reservation_date"]').val()
      /* 값 예: '2026-06-15' */
    },
    success: function (res) {
      console.log('저장 완료:', res);
    },
    error: function (err) {
      console.error('오류:', err);
    }
  });
});


/* ③ fetch API (JSON) */
async function submitDate() {
  const date = document.querySelector('[name="reservation_date"]').value;
  /* 값 예: '2026-06-15' */

  const res = await fetch('/api/reservation', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ reservation_date: date })
  });
  const data = await res.json();
  console.log('저장 완료:', data);
}


/* ④ 서버 수신 예시 */

/* Node.js (Express) */
app.post('/api/reservation', (req, res) => {
  const date = req.body.reservation_date; /* '2026-06-15' */
  /* DB 저장: await db.query('INSERT INTO reservations (date) VALUES (?)', [date]) */
  res.json({ success: true });
});

/* PHP */
$date = $_POST['reservation_date']; /* '2026-06-15' */
/* $stmt = $pdo->prepare("INSERT INTO reservations (date) VALUES (?)");
   $stmt->execute([$date]); */

/* Spring Boot (Java) */
/* @PostMapping("/api/reservation")
   public ResponseEntity save(@RequestParam String reservation_date) {
     // reservation_date = "2026-06-15"
   } */

Range — HTML

<div class="datepicker datepicker--range" id="dp-range-1">
  <div class="datepicker__range-inputs">
    <div class="datepicker__input-wrap">
      <input class="datepicker__input datepicker__input--start" type="text"
             placeholder="시작일" readonly aria-label="시작일 선택" />
    </div>
    <span class="datepicker__range-sep" aria-hidden="true">~</span>
    <div class="datepicker__input-wrap">
      <input class="datepicker__input datepicker__input--end" type="text"
             placeholder="종료일" readonly aria-label="종료일 선택" />
    </div>
    <input class="datepicker__hidden datepicker__hidden--start" type="hidden" name="start_date" />
    <input class="datepicker__hidden datepicker__hidden--end" type="hidden" name="end_date" />
    <button class="datepicker__trigger" type="button" aria-label="날짜 범위 선택" aria-expanded="false">
      <!-- 캘린더 SVG -->
    </button>
  </div>

  <div class="datepicker__popup" role="dialog" aria-label="날짜 범위 선택" aria-modal="true">
    <div class="datepicker__header">...</div>
    <span class="datepicker__range-phase"></span> <!-- '시작일을 선택하세요' / '종료일을 선택하세요' -->
    <div class="datepicker__weekdays">...</div>
    <div class="datepicker__days"></div>
    <div class="datepicker__footer">
      <button class="datepicker__reset-btn" type="button">초기화</button>
      <button class="datepicker__confirm-btn" type="button">확인</button>
    </div>
    <button class="datepicker__close" type="button" aria-label="닫기"></button>
  </div>
</div>

Range — JavaScript

/* 자동 초기화 — .datepicker--range 클래스가 있으면 자동 실행됨 */
$('.datepicker--range').datepickerRange();

/* onChange 콜백 */
$('#dp-range-1').datepickerRange({
  onChange: function (startISO, endISO) {
    console.log('범위:', startISO, '~', endISO);
    /* 예: '2026-06-01' ~ '2026-06-30' */
  }
});

/* 선택된 값 읽기 */
var start = $('[name="start_date"]').val(); /* '2026-06-01' */
var end   = $('[name="end_date"]').val();   /* '2026-06-30' */

Range — 백엔드 연동

/* ① form submit */
<form method="POST" action="/api/search">
  <input type="hidden" name="start_date" />  <!-- '2026-06-01' -->
  <input type="hidden" name="end_date" />    <!-- '2026-06-30' -->
</form>

/* ② fetch */
const payload = {
  start_date: $('[name="start_date"]').val(),
  end_date:   $('[name="end_date"]').val()
};
await fetch('/api/search', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload)
});

/* ③ 서버 수신 (Node.js) */
app.post('/api/search', (req, res) => {
  const { start_date, end_date } = req.body;
  /* '2026-06-01', '2026-06-30' */
  /* SQL: WHERE date BETWEEN ? AND ? → [start_date, end_date] */
});

Classes

클래스 타입 설명
.datepicker 루트 전체 컨테이너 (position: relative, max-width 320px)
.datepicker--disabled 수정자 전체 비활성 — 클릭 불가, 흐릿한 스타일
.datepicker__input-wrap 요소 input + 트리거 버튼 감싸는 박스 (H 44px)
.datepicker__input 요소 텍스트 입력 (readonly) — 표시용 날짜 'YYYY년 M월 D일'
.datepicker__hidden 요소 히든 input — 백엔드 전송값 'YYYY-MM-DD'
.datepicker__trigger 요소 달력 아이콘 버튼 — 팝업 토글
.datepicker__popup 요소 달력 팝업 (absolute, opacity/transform 전환)
.datepicker__popup--open 수정자 팝업 표시 — JS가 자동 추가/제거
.datepicker__header 요소 년/월 레이블 + 이전/다음 네비게이션
.datepicker__month-year 요소 현재 년/월 텍스트 — JS가 갱신
.datepicker__nav 요소 이전/다음 달 버튼 (원형)
.datepicker__nav--prev 수정자 이전 달
.datepicker__nav--next 수정자 다음 달
.datepicker__weekdays 요소 요일 헤더 (일~토, 7열 그리드)
.datepicker__days 요소 날짜 버튼 그리드 — JS가 동적 생성
.datepicker__day 요소 날짜 버튼 셀 (H 34px, 원형)
.datepicker__day--today 수정자 오늘 — primary 테두리
.datepicker__day--selected 수정자 선택된 날짜 — primary 배경, 흰 텍스트
.datepicker__day--disabled 수정자 선택 불가 날짜 (minDate/maxDate 범위 밖)
.datepicker__day--empty 수정자 1일 이전 빈 셀 (포인터 이벤트 없음)
.datepicker__footer 요소 오늘/확인 버튼 영역
.datepicker__today-btn 요소 오늘 날짜로 이동 + 선택
.datepicker__confirm-btn 요소 선택 확인 후 팝업 닫기
Range 전용 (.datepicker--range)
.datepicker--range 수정자 Range 모드 활성 — 두 날짜 입력 + 범위 강조
.datepicker__range-inputs 요소 시작일 input + 구분자 + 종료일 input + 트리거 버튼 행
.datepicker__input--start 수정자 시작일 표시 input (readonly)
.datepicker__input--end 수정자 종료일 표시 input (readonly)
.datepicker__hidden--start 수정자 시작일 hidden input — 백엔드 전송값
.datepicker__hidden--end 수정자 종료일 hidden input — 백엔드 전송값
.datepicker__range-sep 요소 시작~종료 구분자 (~)
.datepicker__range-phase 요소 선택 단계 안내 텍스트 — JS가 자동 갱신
.datepicker__day--range-start 수정자 범위 시작일 — primary 왼쪽 반원
.datepicker__day--range-end 수정자 범위 종료일 — primary 오른쪽 반원
.datepicker__day--in-range 수정자 범위 내 날짜 — 연한 초록 배경, 직사각형
.datepicker__day--range-hover-end 수정자 hover 중인 종료일 예정 — 점선 테두리 미리보기
.datepicker__reset-btn 요소 범위 초기화 버튼 (시작일·종료일 모두 삭제)