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