프론트엔드 웹 개발을 하다 보면 HTML 요소에 동적 ID나 상태 값을 임시로 저장해야 하는 상황을 자주 겪게 된다.
다만 클래스(class) 이름에 파싱하기 힘든 데이터 값을 억지로 욱여넣거나 브라우저 명세에 없는 비표준 속성을 무분별하게 정의해서 마크업을 오염시키는 경우가 많다.
이번 글에서는 HTML5 커스텀 데이터 속성(data-*)이 정확히 무엇인지, 왜 필요한지, 그리고 JavaScript dataset API를 활용해 마크업과 스크립트 상태를 깔끔하게 다루는 방법을 완벽하게 정리해서 소개하겠다.
과거에는 특정 HTML 요소에 데이터베이스의 PK값이나 상태 정보를 저장하기 위해 비표준 속성을 마음대로 만들어 쓰거나 `class="item id-1042 active"`처럼 클래스명을 편법으로 활용하곤 했다. 하지만 이는 W3C HTML 유효성 검사를 통과하지 못하거나 CSS 스타일 규칙과 데이터가 엉키는 심각한 유지보수 문제를 일으킨다.
HTML5에서 도입된 Custom Data Attributes는 `data-`라는 접두사만 붙이면 개발자가 원하는 이름의 속성을 자유롭게 정의할 수 있도록 지원한다. 브라우저 파서 역시 이를 표준 요소로 인식하므로 마크업 유효성을 깨뜨리지 않는다. 이렇게 정의된 데이터는 JavaScript의 `dataset` 객체를 통해 매우 간단하게 접근할 수 있다.
| 항목 | 기존 비표준 / 클래스 활용 방식 | HTML5 data-* 및 dataset API 방식 |
|---|---|---|
| 표준 준수 | 웹 표준 유효성 검사 실패 위험 높음 | HTML5 공식 웹 표준 완벽 준수 |
| 데이터 읽기/쓰기 | getAttribute() 또는 정규식 문자열 추출 | element.dataset.속성명 형식으로 간편 접근 |
| CSS 연동성 | 클래스 오염으로 인한 스타일 충돌 위험 | [data-status="..."] 속성 선택자로 스타일 분리 |
| 가독성 및 유지보수 | 데이터와 디자인 역할이 뒤섞여 파악 어려움 | 데이터 역할이 명확히 구분되어 가독성 향상 |
HTML 마크업에 속성을 정의할 때는 `data-` 뒤에 원하는 이름을 케밥 케이스(kebab-case, 하이픈으로 단어 연결)로 작성한다. 이를 JavaScript에서 조작할 때는 dataset 객체를 통해 접근하는데, 이때 브라우저가 케밥 케이스를 카멜 케이스(camelCase)로 자동 변환해 준다.
예를 들어 HTML에 `data-user-role="admin"`으로 작성되어 있다면, JavaScript에서는 `element.dataset.userRole`로 값을 읽거나 수정할 수 있다. `getAttribute()`나 `setAttribute()`를 매번 호출하지 않아도 객체의 프로퍼티처럼 다룰 수 있다는 점이 핵심이다.
data-* 속성은 단순히 스크립트에서 읽는 용도에 그치지 않고 CSS와 직접 결합하여 UI 상태를 변경하는 데도 탁월하다. `[data-active="true"]` 스타일 규칙을 선언해 두면, JavaScript에서 dataset 값만 바꾸는 것으로 레이아웃과 애니메이션을 제어할 수 있다.
사용자 리스트에서 특정 항목의 상태를 토글하고 데이터 PK를 가져오는 실무 코드 예시를 살펴보자.
✗ 잘못된 작성 예시 (비표준 속성 및 클래스 오남용)
<!-- 잘못된 예: 비표준 속성(item-id)과 class를 통한 데이터 저장 -->
<li class="user-item item-42 is-pending" item-id="42" status="pending">
<span>홍길동</span>
<button type="button" onclick="toggleUserStatus(this)">상태 변경</button>
</li>
<script>
function toggleUserStatus(btn) {
const li = btn.closest('li');
// 비표준 getAttribute 사용 및 class 문자열을 쪼개서 데이터 추출하는 지저분한 방식
const userId = li.getAttribute('item-id');
const currentStatus = li.getAttribute('status');
if (currentStatus === 'pending') {
li.setAttribute('status', 'active');
li.classList.remove('is-pending');
li.classList.add('is-active');
}
}
</script>
✓ 올바른 작성 예시 (data-* 속성과 dataset API 활용)
<!-- 올바른 예: HTML5 data-* 표준 속성 정의 -->
<li class="user-item" data-user-id="42" data-status="pending">
<span>홍길동</span>
<button type="button" class="btn-toggle">상태 변경</button>
</li>
<style>
/* CSS 속성 선택자로 상태에 따른 스타일을 깔끔하게 분리 */
.user-item[data-status="pending"] { background-color: #fff3cd; }
.user-item[data-status="active"] { background-color: #d1e7dd; }
.user-item::after {
content: " (" attr(data-status) ")"; /* CSS attr() 함수로 값 표시 가능 */
}
</style>
<script>
document.querySelector('.btn-toggle').addEventListener('click', (e) => {
const li = e.target.closest('.user-item');
// dataset 객체를 통해 카멜케이스로 직관적 접근
const userId = li.dataset.userId;
const currentStatus = li.dataset.status;
// 상태 값 업데이트 (DOM 데이터 변경 시 CSS 선택자가 즉시 반응함)
li.dataset.status = (currentStatus === 'pending') ? 'active' : 'pending';
console.log(`User ID: ${userId}, Changed Status: ${li.dataset.status}`);
});
</script>
출력 결과(콘솔 메시지 및 UI 변화)
User ID: 42, Changed Status: active
# 버튼 클릭 시 배경색이 노란색(#fff3cd)에서 초록색(#d1e7dd)으로 자동 전환됨
dataset API를 사용할 때는 케밥 케이스와 카멜 케이스 간의 변환 규칙을 정확히 이해해야 한다. 마크업에 `data-user-first-name="Gildong"`이라고 적어두고 스크립트에서 `dataset['user-first-name']`으로 접근하려 하면 값을 읽을 수 없거나 예기치 않은 오류가 발생한다.
✗ 잘못된 케이스 접근 방식
// ✗ 하이픈 문자를 그대로 사용하려고 함
const name = element.dataset['user-first-name']; // undefined 반환
// ✗ 대문자를 HTML 속성명에 직접 작성함
// HTML: <div data-userId="100"></div>
// HTML은 대소문자를 구분하지 않고 소문자로 변환되므로 data-userid가 됨
const userId = element.dataset.userId; // undefined 반환
✓ 올바른 케이스 접근 및 대처 방법
// ✓ HTML: data-user-first-name="Gildong"
const name = element.dataset.userFirstName; // "Gildong" 정상 반환
// ✓ HTML: data-user-id="100"
const userId = element.dataset.userId; // "100" 정상 반환
또한 data-* 속성에 저장된 모든 값은 문자열(String) 타입으로 다뤄진다. 숫자 `100`을 dataset에 할당하더라도 자동으로 문자열 `"100"`으로 변환되므로, 연산이 필요한 경우 `parseInt()`나 `Number()`를 사용해 형변환을 명시적으로 거쳐야 한다. 마지막으로, HTML 데이터 속성은 브라우저 개발자 도구(F12)에서 누구나 열람할 수 있으므로 비밀번호, API 시크릿 키, 개인정보 등 민감한 데이터는 절대로 넣지 않아야 한다.
HTML5 Custom Data Attributes와 dataset API는 프론트엔드 마크업을 클린하게 유지하면서 상태 관리를 명확하게 처리하는 필수 기본기다. 클래스 이름을 데이터 저장소로 오남용하던 기존 습관을 버리고, 상태 데이터와 CSS 스타일 선택자를 구조화하는 습관이 모여서 단단하고 유지보수가 쉬운 웹 애플리케이션을 만든다. 오늘 다룬 예제 코드와 케밥-카멜케이스 변환 규칙을 참고해 현재 프로젝트의 DOM 데이터 바인딩 로직을 리팩토링해보면 훨씬 깔끔해진 코드베이스를 경험할 수 있을 것이다.