Thank you for your interest in contributing! This guide will help you get started.
- Fork the repository on GitHub
- Clone your fork locally
- Install dependencies:
bun install - Start development:
bun run storybook - Make your changes
- Test thoroughly
- Submit a pull request
- Fix component functionality issues
- Resolve accessibility problems
- Correct styling inconsistencies
- Address TypeScript errors
- Improve existing component APIs
- Add missing component variants
- Enhance accessibility features
- Optimize performance
- Add components that fit our design system
- Must be generally useful (not app-specific)
- Should follow our established patterns
- Include comprehensive documentation
- Improve README and guides
- Add better Storybook stories
- Create usage examples
- Fix typos and clarity issues
- Accessibility First - WCAG 2.1 AA compliance
- Mobile-First - Responsive by default
- Composable - Components work well together
- Consistent - Follow established patterns
- Minimal - No unnecessary complexity
// ✅ Good - Fully typed with exported interfaces
interface ButtonProps {
variant?: "primary" | "secondary";
size?: "sm" | "md" | "lg";
children: React.ReactNode;
}
export const Button = ({ variant = "primary", ...props }: ButtonProps) => {
// Component implementation
};
export type { ButtonProps };// ✅ Good - Semantic HTML with ARIA
<button
type="button"
aria-label="Close dialog"
aria-expanded={isOpen}
className="..."
>
{children}
</button>
// ❌ Bad - Generic div without accessibility
<div onClick={handleClick} className="...">
{children}
</div>// ✅ Good - Mobile-first responsive classes
<div className="flex flex-col gap-2 md:flex-row md:gap-4">
{children}
</div>
// ❌ Bad - Desktop-first approach
<div className="flex-row gap-4 max-md:flex-col max-md:gap-2">
{children}
</div>import { cx } from "@/utils/cx";
// ✅ Good - Use cx utility for conditional classes
<button className={cx(
"base-classes",
variant === "primary" && "primary-classes",
size === "lg" && "large-classes",
disabled && "disabled-classes"
)}>
// ❌ Bad - String concatenation
<button className={`base-classes ${variant === "primary" ? "primary-classes" : ""}`}>components/category/component-name/
├── component-name.tsx # Main component
├── component-name.story.tsx # Storybook stories
├── component-name.demo.tsx # Usage demos
└── base-components/ # Sub-components (if needed)
├── sub-component.tsx
└── index.tsx
- Components:
kebab-case.tsx(e.g.,date-picker.tsx) - Stories:
component-name.story.tsx - Demos:
component-name.demo.tsx - Types: Export from main component file
-
Run tests
bun run build # TypeScript compilation bun run lint # ESLint checks bun run prettier # Code formatting
-
Test in Storybook
bun run storybook # Navigate to your component and test all variants -
Accessibility Check
- Test with keyboard navigation
- Verify screen reader compatibility
- Check color contrast ratios
- Ensure focus management
feat: add new component name
fix: resolve accessibility issue in component
docs: improve component documentation
style: update component styling
## Changes
- Brief description of what changed
- Why this change was needed
## Testing
- [ ] Tested in Storybook
- [ ] Keyboard navigation works
- [ ] Screen reader compatible
- [ ] Mobile responsive
- [ ] TypeScript compiles
- [ ] No lint errors
## Screenshots
[Include before/after screenshots for UI changes]
## Related issues
Closes #123- Functionality - Component works as expected
- Accessibility - Meets WCAG 2.1 AA standards
- Performance - No unnecessary re-renders or heavy computations
- API Design - Consistent with existing components
- Documentation - Clear stories and examples
- Code Quality - TypeScript, ESLint, and Prettier compliant
// ✅ Use semantic color tokens
className = "text-fg-primary bg-bg-secondary border-border-primary";
// ❌ Avoid hardcoded colors
className = "text-gray-900 bg-white border-gray-200";// ✅ Use consistent spacing scale
className = "p-4 gap-3 mt-6";
// ❌ Avoid arbitrary values
className = "p-[17px] gap-[13px] mt-[25px]";// ✅ Use design system typography
className = "text-lg font-semibold leading-7";
// ❌ Avoid custom font sizes
className = "text-[19px] font-[650] leading-[1.4]";- Component renders without errors
- All props work as expected
- Responsive across screen sizes
- Keyboard navigation functional
- Screen reader announces correctly
- Focus management works properly
- No console errors or warnings
- Use VoiceOver (Mac) or NVDA (Windows)
- Test with keyboard only navigation
- Check focus indicators are visible
- Verify color contrast meets standards
- Ensure semantic markup is used
// ✅ Correct - Use path aliases
import { Button } from "@/components/base/buttons/button";
import { cx } from "@/utils/cx";
// ❌ Wrong - Relative imports
import { Button } from "../../../base/buttons/button";
import { cx } from "../../../utils/cx";// ✅ Good - Extend HTML attributes
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary";
}
// ❌ Bad - Missing HTML attributes
interface ButtonProps {
variant?: "primary" | "secondary";
onClick?: () => void; // Missing other button attributes
}// ✅ Good - Default parameters
export const Button = ({
variant = "primary",
size = "md",
...props
}: ButtonProps) => {
// ❌ Bad - Default props (deprecated in TypeScript)
Button.defaultProps = {
variant: "primary",
size: "md"
};- GitHub Issues - For bug reports and feature requests
- GitHub Discussions - For questions and community help
- Discord - Real-time chat with maintainers and community
Thank you for contributing to Untitled UI! 🎉