Skip to main content

create-new-component

Create a new Lit web component following project conventions, including component class, styles, tests, Storybook story, and proper exports

Jump to install

Source facts

Repository
IgniteUI/igniteui-webcomponents
Last source activity
August 18, 2026 at 07:53
Detected SKILL.md language
English
Stars
170
Forks
11

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
create-new-component
description
Create a new Lit web component following project conventions, including component class, styles, tests, Storybook story, and proper exports
# Create New Component Creates a new Lit web component that follows the project conventions. Read the [Coding Guidelines](../../CODING_GUIDELINES.md) for the rules behind the steps below. ## When to Use - "Create a new progress-bar component" - "Add a new stepper component to the library" ## Related Skills - [add-component-property](../add-component-property/) - Add properties after creating the component - [update-component-styles](../update-component-styles/) - Modify component styles ## Required Context Confirm with the user before starting: - **Name**: `progress-bar` → tag `igc-progress-bar`, class `IgcProgressBarComponent` - **Purpose**: one-line description used verbatim in the public API docs - **Public API**: initial properties, events, slots, CSS parts - **Kind**: plain display component, container, or form-associated control ## Steps ### 1. Create the directory structure ```bash mkdir -p src/components/[name]/themes/{light,dark,shared} ``` ### 2. Create the component class `src/components/[name]/[name].ts`: ```ts import { html, LitElement } from 'lit'; import { property } from 'lit/decorators.js'; import { registerComponent } from '#internals/definitions/register.js'; import { addThemingController } from '#theming/theming-controller.js'; import { styles } from './themes/[name].base.css.js'; import { styles as shared } from './themes/shared/[name].common.css.js'; import { all } from './themes/themes.js'; /** * [One-line description of what the component is for.] * * @element igc-[name] * * @slot - [Default slot description] * * @csspart base - [Description of the CSS part] */ export default class Igc[Name]Component extends LitElement { public static readonly tagName = 'igc-[name]'; public static override styles = [styles, shared]; /* blazorSuppress */ public static register(): void { registerComponent(Igc[Name]Component); } //#region Public attributes and properties /** * [Property description] * @attr some-prop * @default 'default-value' */ @property({ reflect: true }) public someProp = 'default-value'; //#endregion constructor() { super(); addThemingController(this, all); } protected override render() { return html` <div part="base"> <slot></slot> </div> `; } } declare global { interface HTMLElementTagNameMap { 'igc-[name]': Igc[Name]Component; } } ``` Key points: - Cross-cutting imports go through `#internals/*`, `#theming/*` and `#animations/*`; component imports stay relative. All specifiers end in `.js`. - `registerComponent(Self, ...dependencies)` — pass every component rendered in the template. - Region fences and the member order follow the [component structure](../../CODING_GUIDELINES.md#components). - Internal API is `_`-prefixed; no native private fields (`#`). - Only primitives may be attributes; complex types get `attribute: false`. - For ARIA use `addInternalsController`; for keyboard use `addKeybindings`; for slot state use `addSlotController`. See the [controllers table](../../CODING_GUIDELINES.md#controllers). ### 3. Create the SCSS files SCSS resolves against the `src` and `node_modules` load paths — use package-style specifiers, never relative ones. Indentation in SCSS is 4 spaces. `themes/[name].base.scss` — structure and layout, theme-agnostic: ```scss @use 'styles/common/component'; @use 'styles/utilities' as *; :host { display: block; } [part~='base'] { // Structural styles } ``` `themes/light/_themes.scss` — digest the schemas from `igniteui-theming`: ```scss @use 'styles/utilities' as *; @use 'igniteui-theming/sass/themes/schemas/components/light/[name]' as *; $base: digest-schema($light-[name]); $material: digest-schema($material-[name]); $bootstrap: digest-schema($bootstrap-[name]); $fluent: digest-schema($fluent-[name]); $indigo: digest-schema($indigo-[name]); ``` `themes/dark/_themes.scss` mirrors it with the dark schemas (no `$base`). Then, per theme: - `themes/light/[name].shared.scss` — emits the full variable set from `$base` - `themes/light/[name].{bootstrap,material,fluent,indigo}.scss` — `diff($base, $theme)` - `themes/dark/[name].{bootstrap,material,fluent,indigo}.scss` — `diff(light.$base, $theme)` - `themes/shared/[name].common.scss` — cross-theme styling that reads the variables - `themes/shared/[name].{bootstrap,material,fluent,indigo}.scss` — per-theme structural tweaks (optional) ```scss // themes/light/[name].bootstrap.scss @use 'styles/utilities' as *; @use 'themes' as *; $theme: $bootstrap; :host { @include css-vars-from-theme(diff($base, $theme)); } ``` > [!NOTE] > A brand-new component only has schemas once they are added to `igniteui-theming`. Until > then, declare the CSS variables directly in `themes/shared/[name].common.scss` and keep the > light/dark files empty rather than inventing values per theme. ### 4. Create the theme aggregator `themes/themes.ts` is the only hand-written TypeScript file in the directory: ```ts import { css } from 'lit'; import type { Themes } from '#theming/types.js'; // Dark Overrides import { styles as bootstrapDark } from './dark/[name].bootstrap.css.js'; import { styles as fluentDark } from './dark/[name].fluent.css.js'; import { styles as indigoDark } from './dark/[name].indigo.css.js'; import { styles as materialDark } from './dark/[name].material.css.js'; // Light Overrides import { styles as bootstrapLight } from './light/[name].bootstrap.css.js'; import { styles as fluentLight } from './light/[name].fluent.css.js'; import { styles as indigoLight } from './light/[name].indigo.css.js'; import { styles as materialLight } from './light/[name].material.css.js'; import { styles as shared } from './light/[name].shared.css.js'; const light = { shared: css` ${shared} `, bootstrap: css` ${bootstrapLight} `, material: css` ${materialLight} `, fluent: css` ${fluentLight} `, indigo: css` ${indigoLight} `, }; const dark = { shared: css` ${shared} `, bootstrap: css` ${bootstrapDark} `, material: css` ${materialDark} `, fluent: css` ${fluentDark} `, indigo: css` ${indigoDark} `, }; export const all: Themes = { light, dark }; ``` ### 5. Transpile the styles ```bash npm run build:styles ``` This generates a `.css.ts` next to each `.scss` (imported as `.css.js`). The generated files are **gitignored** — never edit or commit them. Only files matching `*.{base,common,shared,material,bootstrap,indigo,fluent}.scss` are picked up; anything else is silently skipped. ### 6. Write the tests `src/components/[name]/[name].spec.ts`: ```ts import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; import { defineComponents } from '#internals/definitions/defineComponents.js'; import Igc[Name]Component from './[name].js'; describe('[Name]', () => { before(() => { defineComponents(Igc[Name]Component); }); it('passes the a11y audit', async () => { const el = await fixture<Igc[Name]Component>(html`<igc-[name]></igc-[name]>`); await expect(el).shadowDom.to.be.accessible(); await expect(el).to.be.accessible(); }); it('is initialized with the proper default values', async () => { const el = await fixture<Igc[Name]Component>(html`<igc-[name]></igc-[name]>`); expect(el.someProp).to.equal('default-value'); }); it('updates on property change', async () => { const el = await fixture<Igc[Name]Component>(html`<igc-[name]></igc-[name]>`); el.someProp = 'new-value'; await elementUpdated(el); expect(el.someProp).to.equal('new-value'); }); }); ``` Drive user interaction through the shared simulators (`simulateClick`, `simulateKeyboard`, …) from `#internals/testing/simulate.spec.js`, and use `createFormAssociatedTestBed` from `#internals/testing/form-testbed.spec.js` for form-associated controls. ### 7. Create the Storybook story `stories/[name].stories.ts` — the filename must match the tag name, and the generated block must be fenced by `// region default` / `// endregion`: ```ts import type { Meta, StoryObj } from '@storybook/web-components-vite'; import { html } from 'lit'; import { defineComponents, Igc[Name]Component } from 'igniteui-webcomponents'; defineComponents(Igc[Name]Component); // region default const metadata: Meta<Igc[Name]Component> = { title: '[Name]', component: 'igc-[name]', }; export default metadata; type Story = StoryObj<Igc[Name]Component>; // endregion export const Basic: Story = { render: (args) => html` <igc-[name] .someProp=${args.someProp}>Content</igc-[name]> `, }; ``` Everything inside the region is regenerated in the next step — write only the stories. ### 8. Export and generate metadata Add the export to `src/index.ts` in alphabetical order: ```ts export { default as Igc[Name]Component } from './components/[name]/[name].js'; ``` Then regenerate the derived artifacts: ```bash npm run cem # custom-elements.json from the JSDoc npm run build:meta # the `// region default` block of the story ``` ### 9. Verify ```bash npm run check # aliases, dependency rules, types npm run lint # oxlint, lit-analyzer, oxfmt, stylelint npm run test ``` Finally, add a CHANGELOG entry. ## Documentation Conventions Every JSDoc description on a public class, property, method, event, slot, CSS part or CSS custom property is consumed **verbatim** by `custom-elements.json`, the generated story metadata, the published API docs and the Angular / React / Blazor wrappers. Write product documentation, not internal notes. **Never put `igc-` tag names in prose.** Refer to components by their plain-English name — "the carousel", "the tile manager", "toggle buttons". ```ts // ❌ Wrong — tag names leak into the docs of every framework wrapper /** * The `igc-carousel` presents a set of `igc-carousel-slide`s. * * @slot - Renders `igc-toggle-button` component. * @csspart svg - The igc-circular-progress SVG element. */ // ✅ Right /** * The carousel presents a set of slides. * * @slot - Renders the toggle buttons of the group. * @csspart svg - The circular progress SVG element. */ ``` Tag names are allowed **only** in the `@element` tag, fenced `@example` blocks, literal event/attribute names that contain `igc-` (e.g. the `"igc-change-theme"` window event), and `@internal`/`@hidden` members or non-exported internals. **Describe the thing, not the attribute.** `@attr` already says it is an attribute. | ❌ Avoid | ✅ Prefer |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub