Create Component
Create Preact components for agentconfig.org following project conventions.
File Structure
Every component lives in its own folder under site/src/components/:
code
site/src/components/
└── {ComponentName}/
├── index.ts # Re-exports component and types
└── {ComponentName}.tsx # Main implementation
Step-by-Step Process
- •Create the component folder in
site/src/components/{ComponentName}/ - •Copy the template from
assets/component-template.tsx - •Rename and customize the component
- •Create index.ts with exports
- •Import in App.tsx or parent component
Template Files
index.ts
typescript
export { ComponentName } from './ComponentName'
export type { ComponentNameProps } from './ComponentName'
ComponentName.tsx
See assets/component-template.tsx for the full template.
TypeScript Rules
- •No semicolons - Omit semicolons at end of statements
- •Explicit return types - Always specify
: ReactNode - •Interface over type - Use
interfacefor props - •JSDoc comments - Document props with
/** */ - •No
any- Use proper types orunknown - •Readonly arrays - Use
readonlyfor array props
tsx
// Good
interface ListProps {
/** Items to display in the list */
readonly items: readonly string[]
}
// Bad
type ListProps = {
items: string[];
}
Styling Rules
- •Tailwind utilities - Use Tailwind classes for all styling
- •cn() helper - Use
cn()from@/lib/utilsfor conditional classes - •Mobile-first - Start with mobile, add
md:andlg:for larger screens - •Theme-aware - Use CSS variables for theme colors
tsx
// Good - mobile first, theme aware <div className="p-4 md:p-6 lg:p-8 bg-background text-foreground"> // Bad - desktop first <div className="p-8 sm:p-4">
Component Guidelines
Keep Components Focused
- •One component = one responsibility
- •Under 150 lines (split if longer)
- •Extract complex logic into custom hooks in
site/src/hooks/
Props Design
- •Always include
classNameprop for style overrides - •Use
childrenfor flexible content - •Use discriminated unions for variants
tsx
interface ButtonProps {
variant: 'primary' | 'secondary' | 'ghost'
className?: string
children?: ReactNode
}
Event Handlers
- •Prefix with
on:onClick,onToggle,onSelect - •Use specific types, not generic
Function
tsx
interface Props {
// Good
onSelect: (id: string) => void
// Bad
onSelect: Function
}
Accessibility
- •Semantic HTML - Use
buttonfor buttons,navfor navigation - •ARIA attributes - Add
aria-label,aria-expandedfor interactive elements - •Keyboard navigation - Ensure focusable and keyboard-usable
- •Focus visible - Use
focus-visible:for focus rings
tsx
<button
aria-expanded={isOpen}
aria-controls="menu-content"
className="focus-visible:ring-2 focus-visible:ring-primary"
>
Checklist
Before considering the component complete:
- • Component is in its own folder with
index.ts - • All props have TypeScript types with JSDoc comments
- • Uses
cn()for className merging - • Mobile-first responsive design
- • Theme-aware colors (CSS variables)
- • Accessible (semantic HTML, ARIA, keyboard)
- • Under 150 lines (or appropriately split)
- • Named export (not default)
- • No semicolons