Create Component
Create Preact components for agentconfig.org following project conventions.
File Structure
Every component lives in its own folder under site/src/components/:
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
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
// 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
// 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
interface ButtonProps {
variant: 'primary' | 'secondary' | 'ghost'
className?: string
children?: ReactNode
}
Event Handlers
- Prefix with
on:onClick,onToggle,onSelect - Use specific types, not generic
Function
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
<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