Build Lit Web Components using the composition API from lit-composition. Supports setup functions, hooks, props, and light/shadow DOM rendering.
Build Lit Web Components using the composition API from the `lit-composition` library. This skill helps you create components with `defineElement()`, use lifecycle hooks, manage props, and handle both shadow and light DOM rendering.
Helps you work with the `lit-composition` library to:
The primary API is `defineElement(options)` which returns a LitElement subclass and optionally registers it as a custom element.
**Options:**
**Declarative (render only):**
```ts
defineElement({
name: 'hello-world',
props: { msg: { type: String } },
render() {
return html`<div>${this.msg ?? 'Hello'}</div>`
}
})
```
**Imperative (setup-driven):**
```ts
defineElement({
name: 'my-counter',
props: { count: { type: Number } },
setup() {
onConnected(() => console.log('connected'))
return () => html`<button @click=${() => this.count++}>${this.count ?? 0}</button>`
}
})
```
Call these inside `setup()`:
**For declarative components:**
**For setup-driven components:**
Define props using Lit's property declaration shape:
```ts
props: {
count: { type: Number, reflect: true },
mode: { type: String as PropType<'a'|'b'>, attribute: true },
items: { type: Array }
}
```
**Shadow DOM (default):**
```ts
defineElement({
name: 'my-shadow',
render() { return html`<slot></slot>` }
})
```
**Light DOM:**
```ts
defineElement({
name: 'my-light',
shadowRoot: false,
render() { return html`<div>Light DOM content</div>` }
})
```
When using light DOM, manually handle style scoping (no automatic encapsulation).
Always call hooks inside `setup()`:
```ts
setup() {
onConnected(() => {
// Subscribe to stores, add event listeners
})
onUpdated((changed) => {
if (changed.has('count')) {
// Respond to prop changes
}
})
onDisconnected(() => {
// Cleanup subscriptions
})
return () => html`<div>Content</div>`
}
```
**Auto-register:**
```ts
defineElement({ name: 'my-element', render() {} })
```
**Manual registration:**
```ts
const MyElement = defineElement({
name: 'my-element',
register: false,
render() {}
})
customElements.define('my-element', MyElement)
```
Import from `src/utils/mixin.ts`:
```ts
const WithLogging = (Base) => class extends Base {
connectedCallback() {
super.connectedCallback()
console.log('logged')
}
}
defineElement({
name: 'mixed-element',
parent: WithLogging(LitElement),
render() {}
})
```
Keep mixins side-effect free. Don't register elements inside mixins.
Use `provide` and `inject` from `src/context/`:
```ts
// Provider
setup() {
provide(this, MyContext, myValue)
}
// Consumer
setup() {
const value = inject(this, MyContext)
}
```
Only call inside `setup()`. Requires `@lit/context` peer dependency.
**Unit tests (Vitest):**
```ts
test('increments count', async () => {
const el = await fixture(html`<my-counter></my-counter>`)
const btn = el.shadowRoot.querySelector('button')
btn.click()
await el.updateComplete
expect(el.count).toBe(1)
})
```
**Component tests (Cypress):**
**Key commands:**
**Do:**
**Don't:**
**Counter with hooks:**
```ts
import { html } from 'lit'
import { defineElement, onConnected, onUpdated } from 'lit-composition'
export const MyCounter = defineElement({
name: 'my-counter',
props: { count: { type: Number, reflect: true } },
setup() {
onConnected(() => console.log('Counter mounted'))
onUpdated((changed) => {
if (changed.has('count')) console.log('Count changed:', this.count)
})
return () => html`<button @click=${() => this.count++}>
Count: ${this.count ?? 0}
</button>`
}
})
```
**Light DOM component:**
```ts
import { html, css } from 'lit'
import { defineElement } from 'lit-composition'
export const MyLight = defineElement({
name: 'my-light',
shadowRoot: false,
styles: css`.container { padding: 1rem; }`,
props: { title: { type: String } },
render() {
return html`<div class="container">${this.title}</div>`
}
})
```
**Component with context:**
```ts
import { defineElement, provide, inject } from 'lit-composition'
import { MyContext } from './context'
// Provider
defineElement({
name: 'my-provider',
setup() {
provide(this, MyContext, { data: 'value' })
return () => html`<slot></slot>`
}
})
// Consumer
defineElement({
name: 'my-consumer',
setup() {
const ctx = inject(this, MyContext)
return () => html`<div>${ctx.data}</div>`
}
})
```
Before submitting changes, verify:
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/lit-composition-component-builder-crii58/raw