AI Skill Report Card
Building Vanilla TypeScript UI Components
Quick Start15 / 15
TypeScript// components/Button/Button.ts export interface ButtonProps { text: string; variant?: 'primary' | 'secondary'; onClick?: () => void; } export class Button { private element: HTMLButtonElement; constructor(private props: ButtonProps) { this.element = this.createElement(); this.bindEvents(); } private createElement(): HTMLButtonElement { const button = document.createElement('button'); button.textContent = this.props.text; button.className = `btn btn--${this.props.variant || 'primary'}`; return button; } private bindEvents(): void { if (this.props.onClick) { this.element.addEventListener('click', this.props.onClick); } } public render(): HTMLButtonElement { return this.element; } public destroy(): void { this.element.removeEventListener('click', this.props.onClick!); } }
Recommendation▾
The workflow checklist could be more specific about testing and validation steps rather than just implementation steps
Workflow12 / 15
Progress:
- Define component interface and props
- Create component class with constructor
- Implement createElement() method
- Add event binding in bindEvents()
- Implement render() method
- Add destroy() method for cleanup
- Create accompanying CSS file
- Add barrel export in index.ts
- Write usage example
Folder Structure
src/
├── components/
│ ├── Button/
│ │ ├── Button.ts
│ │ ├── Button.css
│ │ └── index.ts
│ └── index.ts
├── types/
│ └── common.ts
├── utils/
│ └── dom.ts
└── styles/
└── variables.css
Recommendation▾
Add concrete pitfall examples showing 'bad vs good' code patterns rather than just listing warnings
Examples18 / 20
Example 1: Input Component Input:
TypeScriptinterface InputProps { placeholder?: string; type?: 'text' | 'email' | 'password'; onInput?: (value: string) => void; }
Output:
TypeScriptexport class Input { private element: HTMLInputElement; constructor(private props: InputProps) { this.element = this.createElement(); this.bindEvents(); } private createElement(): HTMLInputElement { const input = document.createElement('input'); input.type = this.props.type || 'text'; input.placeholder = this.props.placeholder || ''; input.className = 'input'; return input; } private bindEvents(): void { if (this.props.onInput) { this.element.addEventListener('input', (e) => { this.props.onInput!((e.target as HTMLInputElement).value); }); } } public render(): HTMLInputElement { return this.element; } public getValue(): string { return this.element.value; } public destroy(): void { this.element.removeEventListener('input', this.props.onInput!); } }
Example 2: Component Usage Input: Using components in application Output:
TypeScript// App.ts import { Button } from './components/Button'; import { Input } from './components/Input'; class App { private container: HTMLElement; constructor(containerId: string) { this.container = document.getElementById(containerId)!; this.render(); } private render(): void { const input = new Input({ placeholder: 'Enter your name', onInput: (value) => console.log(value) }); const button = new Button({ text: 'Submit', variant: 'primary', onClick: () => console.log('Clicked!') }); this.container.append(input.render(), button.render()); } } new App('app');
Recommendation▾
Consider adding a utilities section with common DOM helper functions that components typically need
Best Practices
- Use TypeScript interfaces for all component props
- Implement proper cleanup in destroy() methods to prevent memory leaks
- Keep components stateless when possible - pass data through props
- Use CSS custom properties for theming
- Create barrel exports (index.ts) for clean imports
- Separate concerns: one class per component, styles in separate CSS files
- Use semantic HTML elements as base (button, input, nav, etc.)
- Implement consistent naming: PascalCase for classes, camelCase for methods
- Add type guards for DOM element validation
Common Pitfalls
- Forgetting to remove event listeners in destroy() method
- Not validating DOM element existence before manipulation
- Mixing presentation logic with business logic in components
- Creating components that are too tightly coupled to specific data structures
- Not handling edge cases (null/undefined props)
- Overusing innerHTML instead of creating elements programmatically
- Not implementing proper TypeScript strict mode compliance
- Creating components without considering reusability
- Forgetting to export components from barrel files