원클릭으로
custom-elements
Define and use custom HTML elements. Use when creating new components, defining custom tags, or using project-specific elements beyond standard HTML5.
메뉴
Define and use custom HTML elements. Use when creating new components, defining custom tags, or using project-specific elements beyond standard HTML5.
Generate standardized static site structures. Use when creating new websites, setting up demos, or need consistent site structure with SEO, PWA, and accessibility foundations.
Write Node.js CLI tools with zero dependencies. Use when creating command-line tools, argument parsing, colored output, or interactive prompts.
Modern CSS organization with native @import, @layer cascade control, CSS nesting, design tokens, and element-focused selectors. AUTO-INVOKED when editing .css files.
Umbrella coordinator for image handling. Coordinates responsive-images, placeholder-images, and automation scripts. Use when adding images to any page, optimizing existing images, or setting up image pipelines.
Generate SVG placeholder images for prototypes. Use when adding placeholder images for layouts, mockups, or development. Supports simple, labeled, and brand-aware types.
Modern responsive image techniques using picture element, srcset, sizes, and modern formats. Use when adding images that need to adapt to different screen sizes, resolutions, or support modern image formats.
| 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 |
This skill provides guidance for defining and using custom HTML elements in this project.
| 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 for Custom Elements Manifest generation.
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) |
<!-- Product card with attributes -->
<product-card sku="ABC123" price="29.99">
<h2>Product Name</h2>
<p>Product description here.</p>
</product-card>
<!-- Void element (self-closing) -->
<icon-element name="star"/>
<user-avatar src="/avatar.jpg" alt="John Doe" size="medium"/>
<!-- Inline element -->
<p>Status: <status-badge type="success">Active</status-badge></p>
<!-- Block elements -->
<nav-menu orientation="horizontal">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav-menu>
<data-table source="/api/users" sortable="">
<!-- Table content -->
</data-table>
For one-off custom elements not worth defining in .claude/schemas/elements.json, use the x-* prefix:
<x-highlight>Important text</x-highlight>
<x-tooltip data-text="Help text">Hover me</x-tooltip>
<x-badge>New</x-badge>
The x-* pattern is excluded from validation by default.
/add-element my-widget
Add to .claude/schemas/elements.json:
{
"my-element": {
"flow": true,
"phrasing": false,
"permittedContent": ["@flow"],
"attributes": {
"required-attr": { "required": true },
"optional-attr": { "required": false }
}
}
}
| 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 |
@flow - Flow content (most elements)@phrasing - Phrasing content (inline elements)@interactive - Interactive elements["p", "div"] - Specific elements only{
"attributes": {
"name": {
"required": true // Must be present
},
"type": {
"required": false,
"enum": ["a", "b", "c"] // Restricted values
},
"enabled": {
"boolean": true // Boolean attribute
}
}
}
{
"card-component": {
"flow": true,
"phrasing": false,
"permittedContent": ["@flow"],
"attributes": {
"variant": {
"required": false,
"enum": ["default", "outlined", "elevated"]
},
"clickable": {
"boolean": true
}
}
}
}
Usage:
<card-component variant="elevated" clickable="">
<h2>Card Title</h2>
<p>Card content</p>
</card-component>
{
"loading-spinner": {
"void": true,
"flow": true,
"phrasing": true,
"attributes": {
"size": {
"enum": ["small", "medium", "large"]
}
}
}
}
Usage:
<loading-spinner size="medium"/>
{
"price-tag": {
"flow": true,
"phrasing": true,
"permittedContent": ["@phrasing"],
"attributes": {
"currency": {
"required": false,
"enum": ["USD", "EUR", "GBP"]
}
}
}
}
Usage:
<p>Price: <price-tag currency="USD">29.99</price-tag></p>
my-elementproduct-card not pcform-input, form-select, form-buttonCustom elements can be used in two distinct ways. Choose based on your needs.
Use the element as a semantic styling hook without JavaScript:
<product-card>
<img src="product.jpg" alt="Widget Pro" />
<h3>Widget Pro</h3>
<p>The best widget money can buy.</p>
</product-card>
product-card {
display: block; /* Required: browsers default to inline */
padding: var(--spacing-lg);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
}
Characteristics:
customElements.define() call:not(:defined) pseudo-classdisplay: block (handled by shared reset)Best for:
.card classes)Register the element with JavaScript for encapsulated behavior:
class ProductCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
:host { display: block; }
/* Encapsulated styles */
</style>
<slot></slot>
`;
}
}
customElements.define('product-card', ProductCard);
Characteristics:
customElements.define():defined pseudo-class after registrationBest for:
| 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 |
CSS-only elements need explicit display:
The shared reset (_reset.css) handles this automatically with:
:not(:defined) {
display: block;
}
If you need a different display value, override it in your component CSS:
product-card {
display: grid; /* Overrides the reset's block */
}
Web Components control their own display:
// Inside the component
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
/* Component defines its own display */
}
</style>
...
`;
For one-off custom elements, use the x-* prefix (e.g., <x-highlight>). These:
<p>This is <x-highlight>important text</x-highlight> in a paragraph.</p>
x-highlight {
background: var(--warning-light);
padding-inline: var(--spacing-xs);
}