Contents
see List개요
Web Components는 재사용 가능한 커스텀 HTML 요소를 만들 수 있는 웹 표준 기술의 집합입니다. Custom Elements, Shadow DOM, HTML Templates 세 가지 핵심 스펙으로 구성되며, 프레임워크에 독립적인 컴포넌트를 만들 수 있다는 것이 가장 큰 장점입니다.
2025년 현재 Web Components는 모든 주요 브라우저에서 완벽하게 지원되며, Declarative Shadow DOM과 같은 새로운 기능이 추가되면서 SSR(서버 사이드 렌더링)과의 호환성도 크게 개선되었습니다. React, Vue, Angular 등 어떤 프레임워크와도 함께 사용할 수 있어 디자인 시스템 구축에 특히 적합합니다.
핵심 개념
Web Components의 세 가지 핵심 기술을 이해해야 합니다.
- Custom Elements:
customElements.define()로 새로운 HTML 태그를 등록합니다. 반드시 하이픈(-)을 포함한 이름이어야 합니다. - Shadow DOM: 컴포넌트의 DOM과 스타일을 외부로부터 격리합니다.
attachShadow({ mode: 'open' })으로 생성합니다. - HTML Templates:
<template>과<slot>으로 재사용 가능한 마크업 구조를 정의합니다. - Declarative Shadow DOM:
<template shadowrootmode="open">으로 JavaScript 없이 Shadow DOM을 선언합니다. SSR에 필수입니다. - 라이프사이클 콜백: connectedCallback, disconnectedCallback, attributeChangedCallback 등으로 컴포넌트 생명주기를 관리합니다.
실전 예제
실무에서 바로 사용할 수 있는 알림 카드 Web Component 예제입니다.
<!-- 사용 예시 -->
<alert-card type="warning" dismissible>
<strong>주의:</strong> 이 작업은 되돌릴 수 없습니다.
</alert-card>
<alert-card type="success">
저장이 완료되었습니다.
</alert-card>
<!-- Declarative Shadow DOM (SSR 호환) -->
<alert-card type="info">
<template shadowrootmode="open">
<style>
:host { display: block; }
.alert { padding: 1rem; border-radius: 8px; }
</style>
<div class="alert">
<slot></slot>
</div>
</template>
서버에서 렌더링된 알림입니다.
</alert-card>
<script>
class AlertCard extends HTMLElement {
static observedAttributes = ['type', 'dismissible'];
// 타입별 스타일 맵
static styles = {
info: { bg: '#eff6ff', border: '#3b82f6', icon: 'info' },
success: { bg: '#f0fdf4', border: '#22c55e', icon: 'check_circle' },
warning: { bg: '#fffbeb', border: '#f59e0b', icon: 'warning' },
error: { bg: '#fef2f2', border: '#ef4444', icon: 'error' }
};
constructor() {
super();
// Declarative Shadow DOM이 없으면 imperative로 생성
if (!this.shadowRoot) {
this.attachShadow({ mode: 'open' });
}
}
connectedCallback() {
this.render();
}
attributeChangedCallback(name, oldVal, newVal) {
if (oldVal !== newVal) this.render();
}
get type() {
return this.getAttribute('type') || 'info';
}
get dismissible() {
return this.hasAttribute('dismissible');
}
render() {
const style = AlertCard.styles[this.type] || AlertCard.styles.info;
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
margin: 1rem 0;
}
:host([hidden]) { display: none; }
.alert {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 1rem 1.25rem;
border-radius: 8px;
border-left: 4px solid ${style.border};
background: ${style.bg};
font-family: inherit;
line-height: 1.6;
}
.close-btn {
margin-left: auto;
background: none;
border: none;
cursor: pointer;
font-size: 1.25rem;
opacity: 0.6;
padding: 0;
}
.close-btn:hover { opacity: 1; }
/* 외부 폰트 상속 */
::slotted(*) {
margin: 0;
}
</style>
<div class="alert" role="alert">
<div class="content">
<slot></slot>
</div>
${this.dismissible ? '<button class="close-btn" aria-label="닫기">x</button>' : ''}
</div>
`;
if (this.dismissible) {
this.shadowRoot.querySelector('.close-btn')
?.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('dismiss'));
this.remove();
});
}
}
}
customElements.define('alert-card', AlertCard);
</script>
활용 팁
- 디자인 시스템에 최적: React, Vue, Angular 등 어떤 프레임워크에서든 사용할 수 있으므로, 조직의 디자인 시스템 컴포넌트로 적합합니다.
- CSS Custom Properties로 테마 적용: Shadow DOM은 스타일을 격리하지만, CSS 변수(custom properties)는 경계를 넘어 전달됩니다. 이를 활용해 테마를 적용하세요.
- ::part()과 exportparts로 Shadow DOM 내부 요소에 외부에서 제한적으로 스타일을 적용할 수 있습니다.
- Lit 프레임워크 검토: 순수 Web Components가 번거롭다면 Google의 Lit을 사용하면 간결한 문법으로 동일한 웹 컴포넌트를 만들 수 있습니다.
- 폼 연동:
ElementInternalsAPI를 사용하면 커스텀 요소가 네이티브 폼에 참여할 수 있습니다(Form-Associated Custom Elements).
마무리
Web Components는 프레임워크 전쟁에서 벗어나 웹 표준에 기반한 컴포넌트를 만들 수 있게 해줍니다. Shadow DOM의 스타일 격리, Custom Elements의 재사용성, Declarative Shadow DOM의 SSR 호환성이 결합되어, 이제 프로덕션에서도 충분히 활용할 수 있는 수준에 도달했습니다. 특히 여러 프레임워크가 혼재하는 환경이나 디자인 시스템 구축에 Web Components를 적극 고려해 보시기 바랍니다.