복잡한 HTML 구조를 관리하거나 재사용 가능한 컴포넌트를 만들 때, Shadow DOM과 template 태그를 활용하면 코드의 모듈화와 스타일 캡슐화가 가능합니다.
Shadow DOM은 브라우저가 기본으로 렌더링하지 않는 숨겨진 DOM 트리입니다.
주로 <input type="range"> 같은 내장 요소 내부에서 여러 <div>가 숨어있는 구조를 볼 수 있는데, 이처럼 일반 사용자에게는 보이지 않는 HTML 구조를 뜻합니다.

개발자가 직접 만들어서 외부에 노출되지 않는 별도의 DOM 영역에 코드를 작성할 수 있습니다.
<div class="mordor"></div>
<script>
// 'mordor' 클래스를 가진 요소에 shadow DOM 생성
document.querySelector('.mordor').attachShadow({ mode: 'open' });
// shadow DOM 내부에 HTML 삽입
document.querySelector('.mordor').shadowRoot.innerHTML = '<p>심연에서왔도다</p>';
</script>
attachShadow({ mode: 'open' })를 사용하여 요소에 shadowRoot라는 별도의 DOM 영역을 생성합니다.shadowRoot에 원하는 HTML 코드를 넣으면, 이 코드는 외부에서 직접 접근할 수 없게 됩니다.<style> 태그를 사용하면 다른 요소에도 스타일이 적용될 위험이 있습니다.<script>
class CustomInput extends HTMLElement {
connectedCallback() {
// shadow DOM 생성
this.attachShadow({ mode: 'open' });
// shadow DOM 내부에 HTML과 스타일 포함
this.shadowRoot.innerHTML = `
<label>이름을 입력하쇼</label>
<input>
<style>
label { color: red; }
</style>
`;
}
}
customElements.define("custom-input", CustomInput);
</script>
<custom-input></custom-input>
<label>오 이제 바깥건 안빨개짐</label>
<style>은 shadow DOM 내에서만 적용되므로, 외부 스타일 오염을 막을 수 있습니다.template 태그에 작성된 HTML은 렌더링되지 않고, 필요할 때 동적으로 클론하여 사용할 수 있습니다.<!-- 사용자 정의 컴포넌트 사용 -->
<custom-input></custom-input>
<!-- HTML 임시보관함: 렌더링되지 않음 -->
<template id="template1">
<label>이메일을 입력하쇼</label>
<input>
<style> label { color: red; } </style>
</template>
<script>
class CustomInput extends HTMLElement {
connectedCallback() {
// shadow DOM 생성
this.attachShadow({ mode: 'open' });
// template 내용을 클론하여 shadow DOM에 추가
const template = document.getElementById('template1');
this.shadowRoot.append(template.content.cloneNode(true));
}
}
customElements.define("custom-input", CustomInput);
</script>
<template id="template1"> 안의 HTML은 페이지에 바로 렌더링되지 않습니다.connectedCallback()에서 template의 내용을 클론(cloneNode)하여 shadow DOM에 추가하면, 해당 컴포넌트에 HTML이 삽입됩니다.<label>에 클릭 이벤트를 부착합니다.<custom-input></custom-input>
<template id="template1">
<label>이메일을 입력하쇼</label>
<input>
<style> label { color: red; } </style>
</template>
<script>
class CustomInput extends HTMLElement {
connectedCallback() {
// shadow DOM 생성 및 template 클론
this.attachShadow({ mode: 'open' });
const template = document.getElementById('template1');
this.shadowRoot.append(template.content.cloneNode(true));
// shadow DOM 내 label 요소 선택 및 이벤트 리스너 추가
const el = this.shadowRoot.querySelector('label');
el.addEventListener('click', () => {
console.log('클릭함');
});
}
}
customElements.define("custom-input", CustomInput);
</script>