원클릭으로
create-new-component
// Create a new Lit web component following project conventions, including component class, styles, tests, Storybook story, and proper exports
// Create a new Lit web component following project conventions, including component class, styles, tests, Storybook story, and proper exports
Identify and select the right Ignite UI Web Components for your app UI, then navigate to official docs, usage examples, and API references
Implement application views from design images using Ignite UI Web Components. Uses MCP servers (igniteui-cli, igniteui-theming) to discover components, generate themes, and follow best practices. Triggers when the user provides a design image (screenshot, mockup, wireframe) and wants it built as a working view with Ignite UI Web Components. Also triggers when the user asks to "implement this design", "build this UI", "convert this mockup", or "create a page from this image" in an Ignite UI Web Components project.
Integrate Ignite UI Web Components packages into React, Angular, Vue, or vanilla JS applications with framework-specific configurations
Customize Ignite UI Web Components styling using CSS custom properties, optional Sass, and the igniteui-theming MCP server for AI-assisted theming
Optimize application bundle size by importing only necessary components and using tree-shaking effectively
Add a reactive property to an existing Lit web component with proper decorators, types, tests, and documentation
| name | create-new-component |
| description | Create a new Lit web component following project conventions, including component class, styles, tests, Storybook story, and proper exports |
This skill guides you through creating a new Lit web component that follows all project conventions and best practices.
Before starting, ensure:
Gather or confirm with the user:
IgcProgressBarComponentCreate the component folder with theme directories:
mkdir -p src/components/[component-name]/themes/{light,dark,shared}
Example: For "progress-bar":
mkdir -p src/components/progress-bar/themes/{light,dark,shared}
Create src/components/[component-name]/[component-name].ts:
import { html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { addThemingController } from '../../theming/theming-controller.js';
import { registerComponent } from '../common/definitions/register.js';
import { styles } from './themes/[component-name].base.css.js';
import { styles as shared } from './themes/shared/[component-name].common.css.js';
import { all } from './themes/themes.js';
/**
* [Component description - one line]
*
* @element igc-[component-name]
*
* @slot - [Default slot description]
* @slot [slot-name] - [Named slot description if any]
*
* @csspart [part-name] - [Description of the CSS part]
*
* @cssproperty --[property-name] - [Description if using CSS custom properties]
*/
export default class Igc[ComponentName]Component extends LitElement {
public static readonly tagName = 'igc-[component-name]';
public static override styles = [styles, shared];
/* blazorSuppress */
public static register(): void {
registerComponent(Igc[ComponentName]Component);
}
//#region Public properties
/**
* [Property description]
* @attr [attribute-name]
*/
@property({ reflect: true })
public someProp = 'default-value';
//#endregion
constructor() {
super();
addThemingController(this, all);
}
//#region Lit lifecycle
protected override render() {
return html`
<div part="base">
<slot></slot>
</div>
`;
}
//#endregion
}
declare global {
interface HTMLElementTagNameMap {
'igc-[component-name]': Igc[ComponentName]Component;
}
}
Critical Points:
.js extension (TypeScript ESM convention)@property decorator for reactive propertiestagName and register() static members with readonly modifier_internalMethod()Create src/components/[component-name]/themes/[component-name].base.scss:
@use '../../../styles/utilities' as *;
:host {
display: block;
}
[part='base'] {
// Component styles here
}
Note: This will be transpiled to .ts by the build system. Do NOT create .ts files manually.
Create theme-specific SCSS files:
themes/shared/[component-name].bootstrap.scssthemes/shared/[component-name].material.scssthemes/shared/[component-name].fluent.scssthemes/shared/[component-name].indigo.scssMinimal theme (themes/shared/[component-name].bootstrap.scss):
@use '../../../../theming/functions' as *;
:host {
// Theme-specific styles using igniteui-theming package
}
Repeat for all four themes (bootstrap, material, fluent, indigo).
Create src/components/[component-name]/themes/themes.ts:
import { css } from 'lit';
import type { Themes } from '../../../theming/types.js';
// Shared Styles
import { styles as bootstrap } from './shared/[component-name].bootstrap.css.js';
import { styles as material } from './shared/[component-name].material.css.js';
import { styles as fluent } from './shared/[component-name].fluent.css.js';
import { styles as indigo } from './shared/[component-name].indigo.css.js';
const light = {
bootstrap: css`
${bootstrap}
`,
material: css`
${material}
`,
fluent: css`
${fluent}
`,
indigo: css`
${indigo}
`,
};
const dark = {
bootstrap: css`
${bootstrap}
`,
material: css`
${material}
`,
fluent: css`
${fluent}
`,
indigo: css`
${indigo}
`,
};
export const all: Themes = { light, dark };
Run the build script to convert SCSS to Lit CSS:
npm run build:styles
Verify: .css.js files are created next to .scss files.
Create src/components/[component-name]/[component-name].spec.ts:
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
import { defineComponents } from '../common/definitions/defineComponents.js';
import Igc[ComponentName]Component from './[component-name].js';
describe('[ComponentName]', () => {
before(() => {
defineComponents(Igc[ComponentName]Component);
});
it('passes the a11y audit', async () => {
const el = await fixture<Igc[ComponentName]Component>(
html`<igc-[component-name]></igc-[component-name]>`
);
await expect(el).shadowDom.to.be.accessible();
await expect(el).to.be.accessible();
});
it('should initialize with default values', async () => {
const el = await fixture<Igc[ComponentName]Component>(
html`<igc-[component-name]></igc-[component-name]>`
);
expect(el.someProp).to.equal('default-value');
});
it('should render content inside', async () => {
const content = 'Test Content';
const el = await fixture<Igc[ComponentName]Component>(
html`<igc-[component-name]>${content}</igc-[component-name]>`
);
expect(el).dom.to.have.text(content);
});
it('can change properties', async () => {
const el = await fixture<Igc[ComponentName]Component>(
html`<igc-[component-name]></igc-[component-name]>`
);
el.someProp = 'new-value';
await elementUpdated(el);
expect(el.someProp).to.equal('new-value');
});
});
Testing Requirements:
elementUpdated() after programmatic changesCreate stories/[component-name].stories.ts:
import type { Meta, StoryObj } from '@storybook/web-components-vite';
import { html } from 'lit';
import {
Igc[ComponentName]Component,
defineComponents,
} from 'igniteui-webcomponents';
defineComponents(Igc[ComponentName]Component);
const metadata: Meta<Igc[ComponentName]Component> = {
title: '[ComponentName]',
component: 'igc-[component-name]',
parameters: {
docs: {
description: {
component: '[Component description for Storybook docs]',
},
},
},
argTypes: {
someProp: {
type: 'string',
description: '[Property description]',
control: 'text',
table: { defaultValue: { summary: 'default-value' } },
},
},
args: {
someProp: 'default-value',
},
};
export default metadata;
interface Igc[ComponentName]Args {
/** [Property description] */
someProp: string;
}
type Story = StoryObj<Igc[ComponentName]Args>;
export const Basic: Story = {
render: (args) => html`
<igc-[component-name] .someProp=${args.someProp}>
Content
</igc-[component-name]>
`,
};
Story Guidelines:
.propName) for non-primitivesAdd to src/index.ts in alphabetical order:
export { default as Igc[ComponentName]Component } from './components/[component-name]/[component-name].js';
Example:
export { default as IgcProgressBarComponent } from './components/progress-bar/progress-bar.js';
Run type-checking and test suite:
# Check that the project will transpile
npm run check-types
# Run tests
npm run test
# Start Storybook to verify
npm run storybook
Verify all of the following:
src/components/[name]/[name].tsLitElement (or appropriate mixin)tagName and register() static members defined@element, @slot, @csspartthemes.ts aggregator imports all themessrc/index.ts alphabeticallynpm run check-types)npm run test)Problem: Importing .css.js files that don't exist
Solution: Always run npm run build:styles after creating SCSS files
Problem: Using .ts instead of .js in imports
Solution: This project uses .js extensions for TypeScript imports (ESM standard)
Problem: Trying to expose objects/arrays as HTML attributes Solution: Per project guidelines, only primitives (string, number, boolean) should be attributes
Problem: TypeScript doesn't recognize custom element tag
Solution: Always include declare global { interface HTMLElementTagNameMap {...} }
Problem: Component doesn't respond to theme changes
Solution: Add addThemingController(this, all) in constructor
export defaultProblem: Import errors in other files
Solution: Always use export default class for component classes
Problem: Exports not alphabetized in src/index.ts
Solution: Find correct alphabetical position before adding
See: src/components/badge/badge.ts
Characteristics:
LitElement directly@property decoratorspartMapSee: src/components/textarea/textarea.ts
Characteristics:
FormAssociatedRequiredMixinEventEmitterMixin for events@shadowOptions({ delegatesFocus: true })See: src/components/card/card.ts
Characteristics:
any types_prefix or TypeScript private, not #css template literalsnpm run styles:watch@open-wc/testing