--- name: custom-elements description: Define and use custom HTML elements. Use when creating new components, defining custom tags, or using project-specific elements beyond standard HTML5. allowed-tools: Read, Write, Edit, Bash --- # Custom Elements Skill This skill provides guidance for defining and using custom HTML elements in this project. ## Two Definition Systems | System | File | Purpose | |--------|------|---------| | **HTML Validation** | `.claude/schemas/elements.json` | Validates custom elements in HTML (html-validate) | | **Custom Elements Manifest** | `custom-elements.json` | Documents components for IDEs, Storybook, docs | **Both are recommended** - elements.json for build-time HTML validation, CEM for runtime tooling. See [MANIFEST.md](./MANIFEST.md) for Custom Elements Manifest generation. ## Using Existing Elements Check `.claude/schemas/elements.json` for defined custom elements: | Element | Type | Purpose | |---------|------|---------| | `product-card` | Block | Product display with sku, price attributes | | `icon-element` | Void | Self-closing icon with required name attribute | | `user-avatar` | Void | Avatar with required src, alt and optional size | | `status-badge` | Inline | Status indicator with type (success/warning/error/info) | | `data-table` | Block | Data table with source, sortable attributes | | `nav-menu` | Block | Navigation with orientation (horizontal/vertical) | ## Usage Examples ```html

Product Name

Product description here.

Status: Active

``` ## Ad-hoc Custom Elements For one-off custom elements not worth defining in `.claude/schemas/elements.json`, use the `x-*` prefix: ```html Important text Hover me New ``` The `x-*` pattern is excluded from validation by default. ## Defining New Elements ### Using Slash Command ``` /add-element my-widget ``` ### Manual Definition Add to `.claude/schemas/elements.json`: ```json { "my-element": { "flow": true, "phrasing": false, "permittedContent": ["@flow"], "attributes": { "required-attr": { "required": true }, "optional-attr": { "required": false } } } } ``` ## Element Schema Reference ### Content Model | Property | Values | Description | |----------|--------|-------------| | `flow` | boolean | Can appear where flow content is expected | | `phrasing` | boolean | Can appear where phrasing content is expected | | `void` | boolean | Self-closing element (no content) | | `permittedContent` | array | What content is allowed inside | ### Permitted Content Values - `@flow` - Flow content (most elements) - `@phrasing` - Phrasing content (inline elements) - `@interactive` - Interactive elements - `["p", "div"]` - Specific elements only ### Attribute Definitions ```json { "attributes": { "name": { "required": true // Must be present }, "type": { "required": false, "enum": ["a", "b", "c"] // Restricted values }, "enabled": { "boolean": true // Boolean attribute } } } ``` ## Complete Examples ### Block Element ```json { "card-component": { "flow": true, "phrasing": false, "permittedContent": ["@flow"], "attributes": { "variant": { "required": false, "enum": ["default", "outlined", "elevated"] }, "clickable": { "boolean": true } } } } ``` Usage: ```html

Card Title

Card content

``` ### Void Element ```json { "loading-spinner": { "void": true, "flow": true, "phrasing": true, "attributes": { "size": { "enum": ["small", "medium", "large"] } } } } ``` Usage: ```html ``` ### Inline Element ```json { "price-tag": { "flow": true, "phrasing": true, "permittedContent": ["@phrasing"], "attributes": { "currency": { "required": false, "enum": ["USD", "EUR", "GBP"] } } } } ``` Usage: ```html

Price: 29.99

``` ## Naming Conventions - Use lowercase with hyphens: `my-element` - Names must contain a hyphen (web component spec) - Be descriptive: `product-card` not `pc` - Use consistent prefixes for related elements: `form-input`, `form-select`, `form-button` --- ## CSS-Only vs Full Web Components Custom elements can be used in two distinct ways. Choose based on your needs. ### CSS-Only Custom Elements Use the element as a semantic styling hook without JavaScript: ```html Widget Pro

Widget Pro

The best widget money can buy.

``` ```css product-card { display: block; /* Required: browsers default to inline */ padding: var(--spacing-lg); border: 1px solid var(--border); border-radius: var(--radius-lg); } ``` **Characteristics:** - No JavaScript required - No `customElements.define()` call - Matches `:not(:defined)` pseudo-class - Requires explicit `display: block` (handled by shared reset) - Content is in light DOM (no Shadow DOM) - Styles work immediately, no FOUC **Best for:** - Semantic styling hooks (replacing `.card` classes) - Progressive enhancement base - Static content components - Simple layout containers ### Full Web Components Register the element with JavaScript for encapsulated behavior: ```javascript class ProductCard extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.shadowRoot.innerHTML = ` `; } } customElements.define('product-card', ProductCard); ``` **Characteristics:** - Requires JavaScript - Registered via `customElements.define()` - Matches `:defined` pseudo-class after registration - Can use Shadow DOM for style encapsulation - Can have custom properties, methods, lifecycle hooks **Best for:** - Interactive components requiring JS - Reusable across different projects - Components needing encapsulated styles - Complex state management ### When to Choose Each | Factor | CSS-Only | Full Web Component | |--------|----------|-------------------| | JavaScript required | No | Yes | | Works without JS | Yes | Needs fallback | | Style encapsulation | No (global CSS) | Yes (Shadow DOM) | | Complexity | Low | Higher | | Interactivity | CSS-only (`:has`, `:checked`) | Full JS capability | | Browser default display | `inline` (needs reset) | Controlled in `:host` | | Progressive enhancement | Natural | Requires planning | ### Styling Considerations **CSS-only elements need explicit display:** The shared reset (`_reset.css`) handles this automatically with: ```css :not(:defined) { display: block; } ``` If you need a different display value, override it in your component CSS: ```css product-card { display: grid; /* Overrides the reset's block */ } ``` **Web Components control their own display:** ```javascript // Inside the component this.shadowRoot.innerHTML = ` ... `; ``` ### The x-* Pattern for Ad-Hoc Elements For one-off custom elements, use the `x-*` prefix (e.g., ``). These: - Are excluded from HTML validation by default - Work well as CSS-only elements - Signal "ad-hoc, not reusable" to other developers ```html

This is important text in a paragraph.

``` ```css x-highlight { background: var(--warning-light); padding-inline: var(--spacing-xs); } ``` ## Related Skills - **javascript-author** - Write vanilla JavaScript for Web Components with function... - **data-attributes** - Using data-* attributes as the HTML/CSS/JS bridge for sta... - **state-management** - Client-side state patterns for Web Components - **accessibility-checker** - Ensure WCAG2AA accessibility compliance