Use Cases (अपडेट: 2/6/2026)

Claude Code से Web Components बनाना

Claude Code से Web Components बनाएं: Custom Elements, Shadow DOM, React/Vue integration, review prompts और tests.

Claude Code से Web Components बनाना

Claude Code और Web Components क्यों साथ अच्छे हैं

Web Components browser standard APIs हैं जिनसे आप reusable HTML elements बना सकते हैं। Custom Elements अपना tag register करते हैं, Shadow DOM internal markup और CSS को host page से isolate करता है, और templates या slots structure को repeatable बनाते हैं। इसका उद्देश्य React या Vue को हटाना नहीं है। उद्देश्य है छोटा UI contract बनाना जो React, Vue, CMS और plain HTML में एक जैसा चले।

Claude Code इस काम में उपयोगी है क्योंकि Web Components सिर्फ syntax से नहीं टूटते। वे तब टूटते हैं जब public API साफ नहीं होती। कौन से attributes हैं, JavaScript property कैसे sync होगी, event का नाम क्या होगा, event Shadow DOM के बाहर जाएगा या नहीं, CSS theme कैसे बदलेगी, accessibility और tests क्या होंगे, ये सब पहले लिखना पड़ता है।

Masa ने यह समस्या तब देखी जब React dashboard का quantity selector CMS और Vue screen में भी चाहिए था। छोटे widget के लिए पूरा React runtime load करना सही नहीं था, और copy-paste करने पर behavior अलग हो जाता था। Web Component बनाने के बाद वही tag static HTML, CMS, React और Vue में काम करने लगा। असली सावधानी CSS boundary, event design, accessibility और versioning में थी।

इस guide में हम quantity-stepper बनाएंगे। इसमें Custom Element minimal implementation, attribute monitoring, CustomEvent, Shadow DOM styling, React/Vue usage, Claude Code review prompt और Vitest test शामिल हैं। Team-level design system बनाना हो तो Claude Code design system guide भी देखें।

Official reference के लिए MDN Web Components, MDN Using custom elements, और MDN CustomEvent देखें।

सही use cases

Web Components तब अच्छे हैं जब UI छोटा, isolated और multiple stacks में reusable हो। पूरी page routing, global state और complex SSR के लिए framework component बेहतर रहता है। Claude Code को पहले बताएं कि component कहां use होगा और क्या जिम्मेदारी नहीं लेगा।

Use caseक्यों सही हैClaude Code को क्या बताएं
Design system distributionReact, Vue और static pages same package use कर सकते हैंtokens, sizes, states, accessibility
CMS embedCMS में interaction जोड़ सकते हैं बिना app runtime केglobal CSS dependency नहीं, script order safe
Multi-framework UIMigration में old और new screens same tag use करते हैंattributes, event names, version policy
Form componentsquantity, rating, autocomplete अलग package हो सकते हैंlabel, error, keyboard, form behavior
Internal widgetsAdmin portal और BI pages same widget use करते हैंsecrets attributes में नहीं, logs, version

Copy button, quantity selector, rating, search box और help launcher अच्छे examples हैं। Full checkout page या complex editor को Web Component बनाना अक्सर जिम्मेदारी बहुत बढ़ा देता है।

flowchart LR
  A["Design tokens"] --> B["Web Component package"]
  B --> C["React app"]
  B --> D["Vue app"]
  B --> E["CMS page"]
  B --> F["Static HTML"]
  B --> G["Internal widget"]

Minimal Custom Element

इस code को quantity-stepper.ts में रखें। यह HTML attributes पढ़ता है, values को number में normalize करता है, value reflect करता है और user interaction पर quantity-change event भेजता है।

const toNumber = (value: string | null, fallback: number) => {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : fallback;
};

class QuantityStepper extends HTMLElement {
  static observedAttributes = ["value", "step", "label"];

  #root = this.attachShadow({ mode: "open" });
  #value = 0;
  #step = 1;

  connectedCallback() {
    this.#syncFromAttributes();
    this.#render();
  }

  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
    if (oldValue === newValue || !this.isConnected) return;
    this.#syncFromAttributes();
    this.#render();
  }

  get value() {
    return this.#value;
  }

  set value(nextValue: number) {
    const normalized = Number.isFinite(nextValue) ? nextValue : 0;
    if (normalized === this.#value) return;

    this.#value = normalized;
    this.setAttribute("value", String(normalized));
    this.#emitChange();
    this.#render();
  }

  #syncFromAttributes() {
    this.#value = toNumber(this.getAttribute("value"), 0);
    this.#step = toNumber(this.getAttribute("step"), 1);
  }

  #update(direction: -1 | 1) {
    this.value = this.#value + this.#step * direction;
  }

  #emitChange() {
    this.dispatchEvent(
      new CustomEvent("quantity-change", {
        detail: { value: this.#value },
        bubbles: true,
        composed: true,
      }),
    );
  }

  #render() {
    const label = this.getAttribute("label") || "Quantity";

    this.#root.innerHTML = `
      <style>
        :host {
          --quantity-accent: #2563eb;
          --quantity-border: #cbd5e1;
          --quantity-bg: #ffffff;
          --quantity-text: #0f172a;
          display: inline-flex;
          font-family: system-ui, sans-serif;
        }
        .control {
          display: inline-grid;
          grid-template-columns: 2.5rem minmax(3rem, auto) 2.5rem;
          align-items: center;
          border: 1px solid var(--quantity-border);
          border-radius: 8px;
          background: var(--quantity-bg);
          color: var(--quantity-text);
          overflow: hidden;
        }
        button {
          min-width: 2.5rem;
          min-height: 2.5rem;
          border: 0;
          background: transparent;
          color: var(--quantity-accent);
          font: inherit;
          cursor: pointer;
        }
        button:focus-visible {
          outline: 2px solid var(--quantity-accent);
          outline-offset: -2px;
        }
        output {
          min-width: 3rem;
          text-align: center;
          font-weight: 700;
        }
        .sr-only {
          position: absolute;
          width: 1px;
          height: 1px;
          padding: 0;
          margin: -1px;
          overflow: hidden;
          clip: rect(0, 0, 0, 0);
          white-space: nowrap;
          border: 0;
        }
      </style>

      <div class="control" role="group" aria-label="${label}">
        <button part="button decrement" data-action="decrement" type="button">
          <span aria-hidden="true">-</span>
          <span class="sr-only">Decrease ${label}</span>
        </button>
        <output part="value" aria-live="polite">${this.#value}</output>
        <button part="button increment" data-action="increment" type="button">
          <span aria-hidden="true">+</span>
          <span class="sr-only">Increase ${label}</span>
        </button>
      </div>
    `;

    this.#root
      .querySelector('[data-action="decrement"]')
      ?.addEventListener("click", () => this.#update(-1));

    this.#root
      .querySelector('[data-action="increment"]')
      ?.addEventListener("click", () => this.#update(1));
  }
}

if (!customElements.get("quantity-stepper")) {
  customElements.define("quantity-stepper", QuantityStepper);
}
<script type="module" src="/src/quantity-stepper.ts"></script>

<quantity-stepper value="2" step="1" label="Seats"></quantity-stepper>

Attributes, properties और events

HTML attribute हमेशा string होता है। JavaScript property number या object हो सकती है। इसलिए value="2" और element.value = 2 अलग रास्ते हैं। Component को दोनों रास्तों को sync करना चाहिए।

इस implementation में value, step, label observe होते हैं। Attribute बदलने पर internal state और render update होता है। Click होने पर value setter attribute update करता है, event emit करता है और फिर render करता है।

const stepper = document.querySelector("quantity-stepper");

stepper?.addEventListener("quantity-change", (event) => {
  const { value } = (event as CustomEvent<{ value: number }>).detail;
  console.log("Selected quantity:", value);
});

Event नाम specific होना चाहिए। quantity-change generic change से बेहतर है। Shadow DOM के बाहर सुनना हो तो bubbles: true और composed: true public API का हिस्सा हैं।

Shadow DOM styling

Shadow DOM global CSS से component को बचाता है, लेकिन theme बदलने के लिए hooks चाहिए। CSS Custom Properties token देते हैं और part limited internal styling देता है।

quantity-stepper {
  --quantity-accent: #16a34a;
  --quantity-border: #94a3b8;
  --quantity-bg: #f8fafc;
}

quantity-stepper::part(button) {
  font-weight: 700;
}

quantity-stepper::part(value) {
  min-width: 4rem;
}

Claude Code को कहें कि component global CSS पर depend न करे, पर theme hooks expose करे। Focus ring, native button, label और aria-live भी check करें। Accessibility review के लिए Claude Code accessibility guide उपयोगी है।

React और Vue में उपयोग

React में CustomEvent सुनने के लिए ref और addEventListener reliable है।

import { useEffect, useRef, useState } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import "./quantity-stepper";

type QuantityStepperElement = HTMLElement & { value: number };

declare global {
  namespace JSX {
    interface IntrinsicElements {
      "quantity-stepper": DetailedHTMLProps<
        HTMLAttributes<QuantityStepperElement>,
        QuantityStepperElement
      > & {
        value?: string;
        step?: string;
        label?: string;
      };
    }
  }
}

export function QuantityField() {
  const [quantity, setQuantity] = useState(2);
  const ref = useRef<QuantityStepperElement>(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const handleQuantityChange = (event: Event) => {
      setQuantity((event as CustomEvent<{ value: number }>).detail.value);
    };

    element.addEventListener("quantity-change", handleQuantityChange);
    return () => {
      element.removeEventListener("quantity-change", handleQuantityChange);
    };
  }, []);

  return (
    <div>
      <quantity-stepper ref={ref} value="2" step="1" label="Seats" />
      <p>Selected: {quantity}</p>
    </div>
  );
}

Vue में template ref और lifecycle hooks use करें।

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from "vue";
import "./quantity-stepper";

const quantity = ref(2);
const stepper = ref<HTMLElement | null>(null);

const handleQuantityChange = (event: Event) => {
  quantity.value = (event as CustomEvent<{ value: number }>).detail.value;
};

onMounted(() => {
  stepper.value?.addEventListener("quantity-change", handleQuantityChange);
});

onBeforeUnmount(() => {
  stepper.value?.removeEventListener("quantity-change", handleQuantityChange);
});
</script>

<template>
  <quantity-stepper ref="stepper" value="2" step="1" label="Seats" />
  <p>Selected: {{ quantity }}</p>
</template>
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag === "quantity-stepper",
        },
      },
    }),
  ],
});

Claude Code review prompt

You are editing only src/components/quantity-stepper.ts and its tests.

Goal:
Build a framework-agnostic Web Component named quantity-stepper.

Public API:
- attributes: value, step, label
- property: value as number
- event: quantity-change with detail { value: number }
- event must bubble and cross Shadow DOM with composed: true

Implementation rules:
- Use Custom Elements and Shadow DOM.
- Do not depend on React or Vue.
- Expose theme hooks with CSS Custom Properties.
- Expose ::part(button) and ::part(value).
- Keep keyboard and screen reader behavior usable.
- Do not remove visible focus styles.

Review before finishing:
- Attribute/property synchronization
- Event naming and detail shape
- Shadow DOM styling boundaries
- Accessibility labels and aria-live
- Test coverage for click, attribute update, and event emission
Review this Web Component as production code.
Prioritize bugs over style preferences.
Report issues in this order:
1. Broken public API or breaking change risk
2. Accessibility defects
3. Shadow DOM styling leaks
4. React/Vue integration problems
5. Missing tests

For every finding, include the file, line, why it fails, and a minimal fix.

इससे Claude Code visual changes से पहले contract, accessibility और integration risks देखता है। Team workflow के लिए छोटा version CLAUDE.md में रखें।

Tests

import { beforeEach, describe, expect, it } from "vitest";
import "./quantity-stepper";

describe("quantity-stepper", () => {
  beforeEach(() => {
    document.body.innerHTML = "";
  });

  it("increments by step and emits a composed event", () => {
    const element = document.createElement("quantity-stepper") as HTMLElement & {
      value: number;
    };

    element.setAttribute("value", "3");
    element.setAttribute("step", "2");
    element.setAttribute("label", "Seats");
    document.body.append(element);

    const events: Array<CustomEvent<{ value: number }>> = [];
    element.addEventListener("quantity-change", (event) => {
      events.push(event as CustomEvent<{ value: number }>);
    });

    element.shadowRoot
      ?.querySelector<HTMLButtonElement>('[data-action="increment"]')
      ?.click();

    expect(element.getAttribute("value")).toBe("5");
    expect(element.value).toBe(5);
    expect(events).toHaveLength(1);
    expect(events[0].detail.value).toBe(5);
    expect(events[0].bubbles).toBe(true);
    expect(events[0].composed).toBe(true);
  });

  it("updates rendering when the value attribute changes", () => {
    const element = document.createElement("quantity-stepper");
    document.body.append(element);

    element.setAttribute("value", "9");

    expect(element.shadowRoot?.querySelector("output")?.textContent).toBe("9");
  });
});

Test public contract बचाता है: attribute, property, event payload, Shadow DOM propagation और rendering. Production से पहले Playwright keyboard और real browser focus test जोड़ें।

Common pitfalls

पहला, Shadow DOM और CSS। Global CSS पर भरोसा करेंगे तो host page component तोड़ सकता है। Theme hooks नहीं देंगे तो design system लागू नहीं होगा।

दूसरा, attribute/property sync। Attribute update, JS property update और internal click तीनों paths test करें।

तीसरा, event design। Event name, detail, bubbles और composed public API हैं।

चौथा, accessibility। Custom tag अपने आप button नहीं बनता। Native button, labels, focus और keyboard support जरूरी हैं।

पांचवां, SSR/hydration। customElements.define load होने से पहले tag inactive है। Critical forms में fallback रखें।

छठा, versioning। Attributes, CSS variables, part names और event payload README या Storybook में document करें।

Summary और validation note

Web Components छोटे UI contracts के लिए अच्छे हैं जो multiple frameworks में same behavior दें। Claude Code को public API, styling boundary, events, accessibility और tests पहले दें। Lit बड़े components में मदद कर सकता है, लेकिन API discipline की जगह नहीं लेता।

ClaudeCodeLab design system, CMS embed, internal widget library और Claude Code review workflow बनाने में मदद कर सकता है। Real repository पर काम करना हो तो Claude Code training and consultation देखें।

Validation note: इस example में वही quantity-stepper contract attributes, property, Shadow DOM buttons, quantity-change event और React/Vue listeners से check होता है। Production से पहले keyboard और focus के real-browser tests जोड़ें।

#Claude Code #Web Components #Custom Elements #Shadow DOM #Lit
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.