Component Patterns
Consistent patterns used across Matrix UI components for predictable and maintainable code.
Variant Pattern
Components use class-variance-authority for managing visual variants:
import { cva } from 'class-variance-authority' const componentVariants = cva( 'base-styles', { variants: { variant: { default: 'default-styles', secondary: 'secondary-styles', }, size: { sm: 'small-styles', md: 'medium-styles', lg: 'large-styles', }, }, defaultVariants: { variant: 'default', size: 'md', }, } )
ForwardRef Pattern
Every component forwards refs to enable direct DOM access:
const Component = React.forwardRef< HTMLElementType, ComponentProps >((props, ref) => { return <element ref={ref} {...props} /> }) Component.displayName = 'Component'
asChild Pattern
Components can render as different elements using Radix Slot:
import { Slot } from '@radix-ui/react-slot' interface ComponentProps { asChild?: boolean } const Component = ({ asChild, ...props }) => { const Comp = asChild ? Slot : 'button' return <Comp {...props} /> } // Usage <Component asChild> <Link href="/home">Home</Link> </Component>
Loading State Pattern
Components handle loading states consistently:
interface ComponentProps { loading?: boolean disabled?: boolean } const Component = ({ loading, disabled, ...props }) => { const isDisabled = disabled || loading return ( <button disabled={isDisabled} aria-busy={loading} {...props} > {loading && <Spinner className="mr-2" />} {children} </button> ) }
Icon Pattern
Components support left and right icon slots:
interface ButtonProps { leftIcon?: React.ReactNode rightIcon?: React.ReactNode } const Button = ({ leftIcon, rightIcon, children }) => ( <button> {leftIcon && <span className="mr-2">{leftIcon}</span>} {children} {rightIcon && <span className="ml-2">{rightIcon}</span>} </button> )
Controlled vs Uncontrolled Pattern
Form components support both controlled and uncontrolled usage:
// Controlled const [value, setValue] = useState('') <Input value={value} onChange={(e) => setValue(e.target.value)} /> // Uncontrolled const inputRef = useRef<HTMLInputElement>(null) <Input ref={inputRef} defaultValue="initial" /> // Component implementation const Input = React.forwardRef(({ value, defaultValue, onChange, ...props }, ref) => { // Internal state for uncontrolled mode const [internalValue, setInternalValue] = useState(defaultValue) const isControlled = value !== undefined const currentValue = isControlled ? value : internalValue const handleChange = (e) => { if (!isControlled) { setInternalValue(e.target.value) } onChange?.(e) } return <input value={currentValue} onChange={handleChange} ref={ref} {...props} /> })
Class Name Merging Pattern
User classes always override default classes:
const Component = ({ className, ...props }) => { return ( <div className={cn( 'default-styles', 'base-styles', className // User classes override defaults )} {...props} /> ) }
Event Handler Composition
Internal handlers compose with user-provided handlers:
const Component = ({ onClick, onKeyDown, ...props }) => { const handleClick = (e) => { // Internal logic console.log('Internal click') // Call user handler onClick?.(e) } const handleKeyDown = (e) => { // Internal keyboard handling if (e.key === 'Enter') { e.preventDefault() } // Call user handler onKeyDown?.(e) } return <button onClick={handleClick} onKeyDown={handleKeyDown} {...props} /> }
Size Prop Pattern
Consistent size naming across components:
type Size = 'sm' | 'md' | 'lg' interface ComponentProps { size?: Size } // Consistent across all components <Button size="sm" /> <Input size="sm" /> <Select size="sm" /> <Badge size="sm" />
Full Width Pattern
Components support full width mode:
interface ComponentProps { fullWidth?: boolean } const Component = ({ fullWidth, className, ...props }) => ( <div className={cn( 'inline-flex', fullWidth && 'w-full', className )} {...props} /> )
Data Attribute Pattern
Components expose state via data attributes for styling:
<button data-state={open ? 'open' : 'closed'} data-disabled={disabled} data-loading={loading} > /* CSS styling based on data attributes */ [data-state="open"] { } [data-disabled="true"] { } [data-loading="true"] { }
Error State Pattern
Form components handle error states:
interface InputProps { error?: boolean | string } const Input = ({ error, className, ...props }) => ( <input className={cn( 'base-styles', error && 'border-destructive focus:ring-destructive', className )} aria-invalid={!!error} aria-describedby={error ? 'error-message' : undefined} {...props} /> )
Best Practices
- Always use the same prop names across components
- Provide sensible defaults for all optional props
- Support both controlled and uncontrolled modes for forms
- Allow className override for all styling
- Forward all HTML attributes to the underlying element
- Compose event handlers rather than replacing them
- Use TypeScript for prop validation
- Keep patterns consistent across the library