Claude-skill-registry lit-component
Guide for developing Lit web components in the Common UI v2 system (@commontools/ui/v2). Use when creating or modifying ct- prefixed components, implementing theme integration, working with Cell abstractions, or building reactive UI components that integrate with the Common Tools runtime.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/lit-component" ~/.claude/skills/majiayu000-claude-skill-registry-lit-component && rm -rf "$T"
skills/data/lit-component/SKILL.mdLit Component Development for Common UI
This skill provides guidance for developing Lit web components within the Common UI v2 component library (
packages/ui/src/v2).
When to Use This Skill
Use this skill when:
- Creating new
prefixed components in the UI packagect- - Modifying existing Common UI v2 components
- Implementing theme-aware components
- Integrating components with Cell abstractions from the runtime
- Building reactive components for pattern/recipe UIs
- Debugging component lifecycle or reactivity issues
Core Philosophy
Common UI is inspired by SwiftUI and emphasizes:
- Default Configuration Works: Components should work together with minimal configuration
- Composition Over Control: Emphasize composing components rather than granular styling
- Adaptive to User Preferences: Respect system preferences and theme settings (theme is ambient context, not explicit props)
- Reactive Binding Model: Integration with FRP-style Cell abstractions from the runtime
- Progressive Enhancement: Components work with plain values but enhance with Cells for reactivity
- Separation of Concerns: Presentation components, theme-aware inputs, Cell-aware state, runtime-integrated operations
Quick Start Pattern
1. Choose Component Category
Identify which category the component falls into:
- Layout: Arranges other components (vstack, hstack, screen)
- Visual: Displays styled content (separator, skeleton, label)
- Input: Captures user interaction (button, input, checkbox)
- Complex/Integrated: Deep runtime integration with Cells (render, list, outliner)
Complexity spectrum: Components range from pure presentation (no runtime) to deeply integrated (Cell operations, pattern execution, backlink resolution). Choose the simplest pattern that meets requirements.
See
references/component-patterns.md for detailed patterns for each category and references/advanced-patterns.md for complex integration patterns.
2. Create Component Files
Create the component directory structure:
packages/ui/src/v2/components/ct-component-name/ ├── ct-component-name.ts # Component implementation ├── index.ts # Export and registration └── styles.ts # Optional: for complex components
3. Implement Component
Basic template:
import { css, html } from "lit"; import { BaseElement } from "../../core/base-element.ts"; export class CTComponentName extends BaseElement { static override styles = [ BaseElement.baseStyles, css` :host { display: block; box-sizing: border-box; } *, *::before, *::after { box-sizing: inherit; } `, ]; static override properties = { // Define reactive properties }; constructor() { super(); // Set defaults } override render() { return html`<!-- component template -->`; } } globalThis.customElements.define("ct-component-name", CTComponentName);
4. Create Index File
import { CTComponentName } from "./ct-component-name.ts"; if (!customElements.get("ct-component-name")) { customElements.define("ct-component-name", CTComponentName); } export { CTComponentName }; export type { /* exported types */ };
Theme Integration
For components that need to consume theme (input and complex components):
import { consume } from "@lit/context"; import { property } from "lit/decorators.js"; import { applyThemeToElement, type CTTheme, defaultTheme, themeContext, } from "../theme-context.ts"; export class MyComponent extends BaseElement { @consume({ context: themeContext, subscribe: true }) @property({ attribute: false }) declare theme?: CTTheme; override firstUpdated(changed: Map<string | number | symbol, unknown>) { super.firstUpdated(changed); this._updateThemeProperties(); } override updated(changed: Map<string | number | symbol, unknown>) { super.updated(changed); if (changed.has("theme")) { this._updateThemeProperties(); } } private _updateThemeProperties() { const currentTheme = this.theme || defaultTheme; applyThemeToElement(this, currentTheme); } }
Then use theme CSS variables with fallbacks:
.button { background-color: var( --ct-theme-color-primary, var(--ct-color-primary, #3b82f6) ); border-radius: var( --ct-theme-border-radius, var(--ct-border-radius-md, 0.375rem) ); font-family: var(--ct-theme-font-family, inherit); }
Complete theme reference: See
references/theme-system.md for all available CSS variables and helper functions.
Cell Integration
For components that work with reactive runtime data:
import { property } from "lit/decorators.js"; import type { Cell } from "@commontools/runner"; import { isCell } from "@commontools/runner"; export class MyComponent extends BaseElement { @property({ attribute: false }) declare cell: Cell<MyDataType>; private _unsubscribe: (() => void) | null = null; override updated(changedProperties: Map<string, any>) { super.updated(changedProperties); if (changedProperties.has("cell")) { // Clean up previous subscription if (this._unsubscribe) { this._unsubscribe(); this._unsubscribe = null; } // Subscribe to new Cell if (this.cell && isCell(this.cell)) { this._unsubscribe = this.cell.sink(() => { this.requestUpdate(); }); } } } override disconnectedCallback() { super.disconnectedCallback(); if (this._unsubscribe) { this._unsubscribe(); this._unsubscribe = null; } } override render() { if (!this.cell) return html``; const value = this.cell.get(); return html`<div>${value}</div>`; } }
Complete Cell patterns: See
references/cell-integration.md for:
- Subscription management
- Nested property access with
.key() - Array cell manipulation
- Transaction-based mutations
- Finding cells by equality
Reactive Controllers
For reusable component behaviors, use reactive controllers. Example:
InputTimingController for debouncing/throttling:
import { InputTimingController } from "../../core/input-timing-controller.ts"; export class CTInput extends BaseElement { @property() timingStrategy: "immediate" | "debounce" | "throttle" | "blur" = "debounce"; @property() timingDelay: number = 500; private inputTiming = new InputTimingController(this, { strategy: this.timingStrategy, delay: this.timingDelay, }); private handleInput(event: Event) { const value = (event.target as HTMLInputElement).value; this.inputTiming.schedule(() => { this.emit("ct-change", { value }); }); } }
Common Patterns
Event Emission
Use the
emit() helper from BaseElement:
private handleChange(newValue: string) { this.emit("ct-change", { value: newValue }); }
Events are automatically
bubbles: true and composed: true.
Dynamic Classes
Use
classMap for conditional classes:
import { classMap } from "lit/directives/class-map.js"; const classes = { button: true, [this.variant]: true, disabled: this.disabled, }; return html`<button class="${classMap(classes)}">...</button>`;
List Rendering
Use
repeat directive with stable keys:
import { repeat } from "lit/directives/repeat.js"; return html` ${repeat( items, (item) => item.id, // stable key (item) => html`<div>${item.title}</div>` )} `;
Testing
Colocate tests with components:
// ct-button.test.ts import { describe, it } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CTButton } from "./ct-button.ts"; describe("CTButton", () => { it("should be defined", () => { expect(CTButton).toBeDefined(); }); it("should have default properties", () => { const element = new CTButton(); expect(element.variant).toBe("primary"); }); });
Run with:
deno task test (includes required flags)
Package Structure
Components are exported from
@commontools/ui/v2:
// packages/ui/src/v2/index.ts export { CTButton } from "./components/ct-button/index.ts"; export type { ButtonVariant } from "./components/ct-button/index.ts";
Reference Documentation
Load these references as needed for detailed guidance:
- Detailed patterns for each component category, file structure, type safety, styling conventions, event handling, and lifecycle methodsreferences/component-patterns.md
- Theme philosophy,references/theme-system.md
provider, CTTheme interface, CSS variables, and theming patternsct-theme
- Comprehensive Cell integration patterns including subscriptions, mutations, array handling, and common pitfallsreferences/cell-integration.md
- Advanced architectural patterns revealed by complex components: context provision, third-party integration, reactive controllers, path-based operations, diff-based rendering, and progressive enhancementreferences/advanced-patterns.md
Key Conventions
- Always extend
- ProvidesBaseElement
helper and base CSS variablesemit() - Include box-sizing reset - Ensures consistent layout behavior
- Use
for objects/arrays/Cells - Prevents serialization errorsattribute: false - Prefix custom events with
- Namespace conventionct- - Export types separately - Use
export type { ... } - Clean up subscriptions - Always unsubscribe in
disconnectedCallback() - Use transactions for Cell mutations - Never mutate cells directly
- Provide CSS variable fallbacks - Components should work without theme context
- Document with JSDoc - Include
,@element
,@attr
,@fires@example - Run tests with
- Not plaindeno task testdeno test
Common Pitfalls to Avoid
- ❌ Forgetting to clean up Cell subscriptions (causes memory leaks)
- ❌ Mutating Cells without transactions (breaks reactivity)
- ❌ Using array index as key in
(breaks reactivity)repeat() - ❌ Missing box-sizing reset (causes layout issues)
- ❌ Not providing CSS variable fallbacks (breaks without theme)
- ❌ Using
for objects/arrays (serialization errors)attribute: true - ❌ Skipping
calls in lifecycle methods (breaks base functionality)super
Architecture Patterns to Study
Study these components to understand architectural patterns:
Basic patterns:
- Simple visual:
- Minimal component, CSS parts, ARIAct-separator - Layout:
- Flexbox abstraction, utility classes withct-vstackclassMap - Themed input:
- Theme consumption, event emission, variantsct-button
Advanced patterns:
- Context provider:
- Ambient configuration withct-theme
,@provide
, reactive Cell subscriptionsdisplay: contents - Cell integration:
- Array cell manipulation, finding by equality, transaction-based mutationsct-list - Runtime rendering:
- Recipe loading, UI extraction, lifecycle managementct-render - Third-party integration:
- CodeMirror lifecycle, Compartments, bidirectional sync, CellControllerct-code-editor - Tree operations:
- Path-based operations, diff-based rendering, keyboard commands, MentionControllerct-outliner
Each component reveals deeper patterns - study them not just for API but for architectural principles.