- name
- create-new-component
- description
- Create a new Lit web component following project conventions, including specification, 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. Write the specification
`src/components/[name]/spec.md` is the behavioral contract of the component, and it comes
before the implementation — writing it is how the public API, the keyboard model and the ARIA
semantics get decided. Copy the structure from `src/components/splitter/spec.md`, the reference
for every spec in the repository; the rules below summarize
[Specifications](../../CODING_GUIDELINES.md#specifications):
```markdown
# [Name] specification
- [[Name] specification](#name-specification)
- [Revision history](#revision-history)
...
## Revision history
| Version | Date | Notes |
| ------: | ---------- | --------------------- |
| 1 | YYYY-MM-DD | Initial specification |
## Overview
### Key features
### Acceptance criteria
## User stories
### End-user stories
### Developer stories
## Functionality
### End-user experience
### Developer experience
### Localization
### Keyboard interactions
## API
### Properties and attributes
### Methods
### Events
### Slots
### CSS Shadow parts
## Test scenarios
## Assumptions and limitations
## Accessibility
### ARIA roles and properties
### Keyboard support
### Right to Left support
```
Rules:
- **One `spec.md` per directory.** A directory holding several components documents all of them
in one file, with a table or a subsection per component — the button and the icon button
share `button/spec.md`, the two progress indicators share `progress/spec.md`.
- **The table of contents is hand-maintained.** Every `##` and `###` heading gets an entry, and
the anchors follow the GitHub slug rules (lowercase, punctuation dropped, spaces to hyphens).
- **No ownership, approval or sign-off sections**, and no author column in the revision
history. A new component starts at version 1.
- **Preserve design hand-off links.** A Figma link belongs under `### End-user experience`. Say
so plainly when there is none rather than leaving a placeholder.
- **Tag names are fine here.** The prose rule against `igc-` names applies to JSDoc, which ships
into the framework wrappers; a spec is repository documentation and names the elements
directly.
- **Link sibling specs relatively** — `[the popover](../popover/spec.md)`, and to a heading with
`../popover/spec.md#keyboard-interactions`.
- **Internal components get a spec too** when they carry behavior others depend on. The
validation container, the popover and the focus trap each have one.
Fill in `## Test scenarios` once the tests exist; see step 7.
### 3. 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).
### 4. 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.
### 5. 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 };
```
### 6. 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.
### 7. 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.
Now fill in the `## Test scenarios` section of the spec. It mirrors the suite that actually
exists: one subsection per `describe` block, keeping its name, with the scenarios numbered
contiguously across the whole section. Name the shared runners the suite uses
(`runValidationContainerTests`, `runAriaProjectionTests`, `runInvokerCommandsTests`, …) rather
than restating what they assert, and list the suite files in a table when there is more than
one:
```markdown
| Suite | File |
Auf GitHub ansehen