重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
npx skills add https://github.com/commontoolsinc/labs --skill lit-component此技能为在 Common UI v2 组件库(packages/ui/src/v2)内开发 Lit Web 组件提供指导。
在以下情况下使用此技能:
ct- 为前缀的组件Common UI 的灵感来源于 SwiftUI,并强调:
确定组件属于哪个类别:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
复杂度谱系:组件范围从纯呈现(无运行时)到深度集成(Cell 操作、模式执行、反向链接解析)。选择满足要求的最简单模式。
有关每个类别的详细模式,请参阅 references/component-patterns.md;有关复杂集成模式,请参阅 references/advanced-patterns.md。
创建组件目录结构:
packages/ui/src/v2/components/ct-component-name/
├── ct-component-name.ts # 组件实现
├── index.ts # 导出和注册
└── styles.ts # 可选:用于复杂组件
基本模板:
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 = {
// 定义响应式属性
};
constructor() {
super();
// 设置默认值
}
override render() {
return html`<!-- component template -->`;
}
}
globalThis.customElements.define("ct-component-name", CTComponentName);
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 */ };
对于需要消费主题的组件(输入和复杂组件):
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);
}
}
然后使用带有回退值的主题 CSS 变量:
.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);
}
完整主题参考:有关所有可用的 CSS 变量和辅助函数,请参阅 references/theme-system.md。
对于处理响应式运行时数据的组件:
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")) {
// 清理之前的订阅
if (this._unsubscribe) {
this._unsubscribe();
this._unsubscribe = null;
}
// 订阅新的 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>`;
}
}
完整的 Cell 模式:请参阅 references/cell-integration.md 了解:
.key() 进行嵌套属性访问对于可重用的组件行为,请使用响应式控制器。示例:用于防抖/节流的 InputTimingController:
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 });
});
}
}
使用 BaseElement 中的 emit() 辅助函数:
private handleChange(newValue: string) {
this.emit("ct-change", { value: newValue });
}
事件自动设置为 bubbles: true 和 composed: true。
使用 classMap 处理条件类:
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>`;
使用带有稳定键的 repeat 指令:
import { repeat } from "lit/directives/repeat.js";
return html`
${repeat(
items,
(item) => item.id, // 稳定键
(item) => html`<div>${item.title}</div>`
)}
`;
将测试与组件放在一起:
// 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");
});
});
运行命令:deno task test(包含必需的标志)
组件从 @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";
根据需要加载这些参考资料以获取详细指导:
references/component-patterns.md - 每个组件类别的详细模式、文件结构、类型安全、样式约定、事件处理和生命周期方法references/theme-system.md - 主题理念、ct-theme 提供者、CTTheme 接口、CSS 变量和主题化模式references/cell-integration.md - 全面的 Cell 集成模式,包括订阅、变更、数组处理和常见陷阱references/advanced-patterns.md - 复杂组件揭示的高级架构模式:上下文提供、第三方集成、响应式控制器、基于路径的操作、基于差异的渲染和渐进增强BaseElement - 提供 emit() 辅助函数和基础 CSS 变量attribute: false - 防止序列化错误ct- 前缀 - 命名空间约定export type { ... }disconnectedCallback() 中取消订阅@element、@attr、@fires、@exampledeno task test 运行测试 - 而不是普通的 deno testrepeat() 中使用数组索引作为键(破坏响应性)attribute: true(序列化错误)super 调用(破坏基础功能)研究这些组件以理解架构模式:
基础模式:
ct-separator - 最小化组件、CSS 部件、ARIAct-vstack - Flexbox 抽象、使用 classMap 的实用类ct-button - 主题消费、事件发射、变体高级模式:
ct-theme - 使用 @provide 的环境配置、display: contents、响应式 Cell 订阅ct-render - 模式加载、UI 提取、生命周期管理ct-code-editor - CodeMirror 生命周期、Compartments、双向同步、CellControllerct-outliner - 基于路径的操作、基于差异的渲染、键盘命令、MentionController每个组件都揭示了更深层的模式 - 研究它们不仅是为了 API,也是为了架构原则。
每周安装次数
45
仓库
GitHub 星标数
30
首次出现
2026年1月21日
安全审计
安装于
opencode44
gemini-cli44
cursor44
github-copilot43
codex43
amp43
This skill provides guidance for developing Lit web components within the Common UI v2 component library (packages/ui/src/v2).
Use this skill when:
ct- prefixed components in the UI packageCommon UI is inspired by SwiftUI and emphasizes:
Identify which category the component falls into:
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.
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
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);
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 */ };
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.
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:
.key()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 });
});
}
}
Use the emit() helper from BaseElement:
private handleChange(newValue: string) {
this.emit("ct-change", { value: newValue });
}
Events are automatically bubbles: true and composed: true.
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>`;
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>`
)}
`;
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)
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";
Load these references as needed for detailed guidance:
references/component-patterns.md - Detailed patterns for each component category, file structure, type safety, styling conventions, event handling, and lifecycle methodsreferences/theme-system.md - Theme philosophy, ct-theme provider, CTTheme interface, CSS variables, and theming patternsreferences/cell-integration.md - Comprehensive Cell integration patterns including subscriptions, mutations, array handling, and common pitfallsreferences/advanced-patterns.md - Advanced architectural patterns revealed by complex components: context provision, third-party integration, reactive controllers, path-based operations, diff-based rendering, and progressive enhancementBaseElement - Provides emit() helper and base CSS variablesattribute: false for objects/arrays/Cells - Prevents serialization errorsct- - Namespace conventionexport type { ... }disconnectedCallback()@element, , , repeat() (breaks reactivity)attribute: true for objects/arrays (serialization errors)super calls in lifecycle methods (breaks base functionality)Study these components to understand architectural patterns:
Basic patterns:
ct-separator - Minimal component, CSS parts, ARIAct-vstack - Flexbox abstraction, utility classes with classMapct-button - Theme consumption, event emission, variantsAdvanced patterns:
ct-theme - Ambient configuration with @provide, display: contents, reactive Cell subscriptionsct-render - Pattern loading, UI extraction, lifecycle managementct-code-editor - CodeMirror lifecycle, Compartments, bidirectional sync, CellControllerct-outliner - Path-based operations, diff-based rendering, keyboard commands, MentionControllerEach component reveals deeper patterns - study them not just for API but for architectural principles.
Weekly Installs
45
Repository
GitHub Stars
30
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode44
gemini-cli44
cursor44
github-copilot43
codex43
amp43
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
125,600 周安装
@attr@fires@exampledeno task test - Not plain deno test