A Practical Guide to Developing Web Components with Claude Code
A practical guide to developing web components using Claude Code with real-world code examples.
Web Components の概要
Web Componentsは、再利用可能なカスタムHTML要素を作成するためのWeb標準技術です。フレームワークに依存せず、どのプロジェクトでも使えるコンポーネントを作れます。Claude Codeを使えば、Web Componentsの開発を効率的に進められます。
Custom Elements の基本
class AppCounter extends HTMLElement {
private count = 0;
private shadow: ShadowRoot;
constructor() {
super();
this.shadow = this.attachShadow({ mode: "open" });
}
// 監視する属性
static get observedAttributes() {
return ["initial", "step"];
}
connectedCallback() {
this.count = parseInt(this.getAttribute("initial") || "0");
this.render();
}
attributeChangedCallback(
name: string,
oldValue: string,
newValue: string
) {
if (name === "initial" && oldValue !== newValue) {
this.count = parseInt(newValue);
this.render();
}
}
private get step(): number {
return parseInt(this.getAttribute("step") || "1");
}
private render() {
this.shadow.innerHTML = `
<style>
:host {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: system-ui, sans-serif;
}
button {
padding: 4px 12px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #f0f0f0;
}
.count {
font-size: 24px;
font-weight: bold;
min-width: 40px;
text-align: center;
}
</style>
<button id="dec">-</button>
<span class="count">${this.count}</span>
<button id="inc">+</button>
`;
this.shadow.getElementById("dec")!.addEventListener("click", () => {
this.count -= this.step;
this.render();
this.dispatchEvent(new CustomEvent("change", { detail: this.count }));
});
this.shadow.getElementById("inc")!.addEventListener("click", () => {
this.count += this.step;
this.render();
this.dispatchEvent(new CustomEvent("change", { detail: this.count }));
});
}
}
customElements.define("app-counter", AppCounter);
使い方:
<app-counter initial="10" step="5"></app-counter>
Lit フレームワークで効率化
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@customElement("app-todo-list")
class AppTodoList extends LitElement {
static styles = css`
:host {
display: block;
max-width: 400px;
font-family: system-ui, sans-serif;
}
.input-row {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
input {
flex: 1;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
ul {
list-style: none;
padding: 0;
}
li {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.done {
text-decoration: line-through;
color: #999;
}
`;
@state()
private todos: Array<{ text: string; done: boolean }> = [];
@state()
private newTodo = "";
private addTodo() {
if (!this.newTodo.trim()) return;
this.todos = [...this.todos, { text: this.newTodo, done: false }];
this.newTodo = "";
}
private toggleTodo(index: number) {
this.todos = this.todos.map((todo, i) =>
i === index ? { ...todo, done: !todo.done } : todo
);
}
render() {
return html`
<div class="input-row">
<input
.value=${this.newTodo}
@input=${(e: InputEvent) =>
(this.newTodo = (e.target as HTMLInputElement).value)}
@keyup=${(e: KeyboardEvent) => e.key === "Enter" && this.addTodo()}
placeholder="タスクを入力..."
/>
<button @click=${this.addTodo}>追加</button>
</div>
<ul>
${this.todos.map(
(todo, i) => html`
<li>
<input
type="checkbox"
.checked=${todo.done}
@change=${() => this.toggleTodo(i)}
/>
<span class=${todo.done ? "done" : ""}>${todo.text}</span>
</li>
`
)}
</ul>
`;
}
}
スロットによるコンポジション
@customElement("app-card")
class AppCard extends LitElement {
static styles = css`
:host {
display: block;
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
}
.header {
padding: 16px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
}
.body {
padding: 16px;
}
.footer {
padding: 16px;
background: #f8fafc;
border-top: 1px solid #e2e8f0;
}
`;
render() {
return html`
<div class="header">
<slot name="header"></slot>
</div>
<div class="body">
<slot></slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
`;
}
}
<app-card>
<h2 slot="header">カードタイトル</h2>
<p>カードの本文がここに入ります。</p>
<button slot="footer">アクション</button>
</app-card>
Reactとの統合
import { useRef, useEffect } from "react";
function CounterWrapper({
initial,
onChange,
}: {
initial: number;
onChange: (value: number) => void;
}) {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const el = ref.current;
const handler = (e: Event) => {
onChange((e as CustomEvent).detail);
};
el?.addEventListener("change", handler);
return () => el?.removeEventListener("change", handler);
}, [onChange]);
return <app-counter ref={ref} initial={initial} />;
}
Claude Codeでの活用
Web Components開発をClaude Codeに依頼する例です。スタイリングにはTailwind CSS活用法、コンポーネント設計の参考としてコード分割・遅延読み込みも参照してください。
Litを使ってデザインシステムのコンポーネントを作って。
- app-button, app-input, app-modal, app-toast
- CSS Custom Propertiesでテーマ対応
- TypeScriptで型安全に
- Storybookでドキュメント化して
Web Componentsの仕様はMDN Web Componentsを参照してください。Claude Codeの詳細は公式ドキュメントで確認できます。
Summary
Web Componentsはフレームワーク非依存の再利用可能なUIを構築できる標準技術です。Claude Codeを使えば、Custom ElementsやLitの実装を効率的に進められ、既存プロジェクトとの統合もスムーズに行えます。
Related Posts
How to Supercharge Your Side Projects with Claude Code [With Examples]
How to Supercharge Your Side Projects with Claude Code [With Examples]. A practical guide with code examples.
How to Automate Refactoring with Claude Code
Learn how to automate refactoring using Claude Code. Includes practical code examples and step-by-step guidance.
Complete CORS Configuration Guide with Claude Code
Learn about complete cors configuration guide using Claude Code. Practical tips and code examples included.