Sofondo Framework - API Design Standards
This document defines the API design standards for all Sofondo Framework components, hooks, and utilities.
Core Principles
- Consistency: Similar patterns across all components
- Predictability: Intuitive naming and behavior
- Type Safety: Full TypeScript support with proper types
- Documentation: Comprehensive JSDoc comments
- Flexibility: Support common use cases without overengineering
- Accessibility: ARIA attributes and keyboard support by default
Component Prop Patterns
Standard Props
All components should accept these standard React props where applicable:
interface StandardProps {
/** Custom CSS class name */
className?: string;
/** Inline styles */
style?: React.CSSProperties;
/** Test ID for testing */
'data-testid'?: string;
/** ARIA label */
'aria-label'?: string;
/** ARIA described by */
'aria-describedby'?: string;
}
Naming Conventions
Boolean Props
- Use
is,has,should, orenableprefixes - Examples:
isOpen,hasError,shouldAutoFocus,enableTooltips
Handler Props
- Use
onprefix followed by event name - Examples:
onClick,onChange,onClose,onSubmit - Type:
(event: EventType) => voidor() => void
Size Props
- Use standardized size values where possible
- Examples:
size?: 'sm' | 'md' | 'lg' - Or specific:
width?: number,height?: number
Variant Props
- Use
variantfor visual variations - Examples:
variant?: 'primary' | 'secondary' | 'outline'
Children
- Always type children props properly
- Use
ReactNodefor flexible content - Use specific types when children must be certain components
Prop Ordering
Order props in interfaces consistently:
- Required props (no optional marker)
- Content props (
children,title,label) - State props (
value,checked,selected) - Handler props (
onClick,onChange) - Visual props (
variant,size,color) - Behavior props (
disabled,readOnly,autoFocus) - Standard props (
className,style,data-testid)
Default Values
- Use default parameters over default props
- Document defaults in JSDoc
- Keep defaults consistent across similar components
Example:
/**
* Button component
* @param variant - Button style variant (default: 'primary')
* @param size - Button size (default: 'md')
*/
export function Button({
variant = 'primary',
size = 'md',
// ...rest
}: ButtonProps) {
// ...
}
JSDoc Standards
Component Documentation
Every component should have comprehensive JSDoc:
/**
* Brief one-line description of the component
*
* Longer description explaining:
* - What the component does
* - When to use it
* - Key features
* - Important notes or warnings
*
* @example
* ```tsx
* <ComponentName
* requiredProp="value"
* optionalProp={123}
* >
* Content
* </ComponentName>
* ```
*
* @see {@link RelatedComponent}
*/
export function ComponentName(props: ComponentNameProps) {
// ...
}
Prop Documentation
Every prop should be documented:
export interface ButtonProps {
/**
* Button text or content
*/
children: ReactNode;
/**
* Click handler
* @param event - Click event
*/
onClick?: (event: React.MouseEvent) => void;
/**
* Visual style variant
* @default 'primary'
*/
variant?: 'primary' | 'secondary' | 'outline';
/**
* Disable the button
* @default false
*/
disabled?: boolean;
}
Hook Documentation
Hooks should document:
- Purpose and use case
- Parameters
- Return value(s)
- Side effects
- Example usage
/**
* Hook to manage modal state
*
* Provides open/close state and handlers for modal components.
*
* @param defaultOpen - Initial open state (default: false)
* @returns Object with `isOpen`, `open`, `close`, `toggle` functions
*
* @example
* ```tsx
* function MyComponent() {
* const modal = useModal();
*
* return (
* <>
* <button onClick={modal.open}>Open</button>
* <Modal isOpen={modal.isOpen} onClose={modal.close}>
* Content
* </Modal>
* </>
* );
* }
* ```
*/
export function useModal(defaultOpen = false) {
// ...
}
Utility Documentation
Utilities should document:
- Purpose
- Parameters with types
- Return value with type
- Examples
- Edge cases
/**
* Format a file size in bytes to human-readable format
*
* @param bytes - File size in bytes
* @param decimals - Number of decimal places (default: 2)
* @returns Formatted string (e.g., "1.5 MB")
*
* @example
* ```ts
* formatFileSize(1024) // "1.00 KB"
* formatFileSize(1536, 0) // "2 KB"
* formatFileSize(0) // "0 Bytes"
* ```
*/
export function formatFileSize(bytes: number, decimals = 2): string {
// ...
}
Type Patterns
Component Props Interface
Always export the props interface:
export interface ComponentNameProps {
// props...
}
export function ComponentName(props: ComponentNameProps) {
// ...
}
Generic Components
Use generics for flexible, reusable components:
export interface DataGridProps<T> {
data: T[];
columns: Column<T>[];
keyField: keyof T;
}
export function DataGrid<T>({
data,
columns,
keyField,
}: DataGridProps<T>) {
// ...
}
Discriminated Unions
Use for components with different modes:
type ButtonProps =
| { variant: 'link'; href: string }
| { variant: 'button'; onClick: () => void };
Accessibility Standards
Required ARIA
Components should include:
aria-labeloraria-labelledbyfor non-text controlsrolewhen semantic HTML isn’t sufficientaria-expanded,aria-selected, etc. for statearia-describedbyfor additional context
Keyboard Support
Interactive components must support:
Tab/Shift+Tabfor focus navigationEnter/Spacefor activationEscapefor closing/canceling- Arrow keys for list/menu navigation
Focus Management
- Use
autoFocusprop sparingly - Trap focus in modals
- Return focus on close
- Visible focus indicators
Error Handling
Prop Validation
Use TypeScript for compile-time validation. For runtime:
if (typeof onClick !== 'function') {
console.error('onClick must be a function');
return;
}
Helpful Errors
Throw helpful errors with context:
if (!data || data.length === 0) {
throw new Error(
'DataGrid: data prop is required and must be a non-empty array. ' +
'Did you forget to pass data or is it loading?'
);
}
Context Errors
For hooks that require context:
const context = useContext(MyContext);
if (!context) {
throw new Error(
'useMyHook must be used within a MyProvider. ' +
'Wrap your component tree with <MyProvider> to use this hook.'
);
}
Performance Guidelines
Memoization
Use React optimization hooks appropriately:
useMemofor expensive calculationsuseCallbackfor stable function referencesReact.memofor components with expensive renders
Avoid Inline Objects
Don’t create objects in render:
// ❌ Bad
<Component style={{ margin: 10 }} />
// ✅ Good
const style = { margin: 10 };
<Component style={style} />
// ✅ Better (if dynamic)
const style = useMemo(() => ({ margin: spacing }), [spacing]);
<Component style={style} />
Testing Considerations
Test IDs
Support data-testid prop:
<div data-testid={props['data-testid'] || 'component-name'}>
Public API
Expose interfaces and types for testing:
// Export for testing
export interface ComponentMethods {
focus: () => void;
reset: () => void;
}
// Use with forwardRef
export const Component = forwardRef<ComponentMethods, ComponentProps>(
// ...
);
Version Compatibility
Breaking Changes
Avoid breaking changes when possible. When necessary:
- Deprecate old API first
- Provide codemod or migration guide
- Use major version bump
- Document in CHANGELOG
Deprecation
Mark deprecated APIs with JSDoc:
/**
* @deprecated Use `newFunction` instead. Will be removed in v2.0.
* @see {@link newFunction}
*/
export function oldFunction() {
console.warn('oldFunction is deprecated, use newFunction instead');
// ...
}
Examples
Complete Component Example
import { ReactNode, MouseEvent } from 'react';
import clsx from 'clsx';
import styles from './Button.module.css';
/**
* Button component for user interactions
*
* Provides a styled, accessible button with multiple variants and sizes.
* Supports both button and link modes.
*
* @example
* ```tsx
* <Button variant="primary" size="lg" onClick={handleClick}>
* Click me
* </Button>
* ```
*
* @example
* ```tsx
* <Button variant="outline" disabled>
* Disabled Button
* </Button>
* ```
*/
export interface ButtonProps {
/**
* Button content
*/
children: ReactNode;
/**
* Visual style variant
* @default 'primary'
*/
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
/**
* Button size
* @default 'md'
*/
size?: 'sm' | 'md' | 'lg';
/**
* Click handler
* @param event - Click event
*/
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
/**
* Disable the button
* @default false
*/
disabled?: boolean;
/**
* Button type attribute
* @default 'button'
*/
type?: 'button' | 'submit' | 'reset';
/**
* Auto focus on mount
* @default false
*/
autoFocus?: boolean;
/**
* Custom CSS class
*/
className?: string;
/**
* Inline styles
*/
style?: React.CSSProperties;
/**
* Test identifier
*/
'data-testid'?: string;
/**
* ARIA label
*/
'aria-label'?: string;
}
export function Button({
children,
variant = 'primary',
size = 'md',
onClick,
disabled = false,
type = 'button',
autoFocus = false,
className,
style,
'data-testid': testId,
'aria-label': ariaLabel,
}: ButtonProps) {
return (
<button
type={type}
onClick={onClick}
disabled={disabled}
autoFocus={autoFocus}
className={clsx(
styles.button,
styles[variant],
styles[size],
disabled && styles.disabled,
className
)}
style={style}
data-testid={testId}
aria-label={ariaLabel}
>
{children}
</button>
);
}
Checklist
Before marking a component as complete:
- Props interface exported
- Component documented with JSDoc
- All props documented
- Example usage in JSDoc
- TypeScript types are accurate
- Default values documented
- Accessibility attributes included
- Keyboard support implemented
- Error handling for edge cases
- Performance optimized (if needed)
- Test IDs supported
- Follows naming conventions
- Consistent with other components