UTA DevHub

UI Architecture Standards

Mandatory standards for UI folder organization, component development patterns, and quality requirements across all frontend projects.

UI Architecture Standards

Overview

This document outlines our recommended standards for UI folder organization, component development patterns, and quality requirements across frontend projects. Following these standards helps ensure consistency, maintainability, and scalability of our UI architecture.

Why UI Standards Matter

Through our development experience, we've found that consistent UI architecture significantly improves team collaboration, speeds up development, and creates a better foundation for AI collaboration. These standards have evolved through practical application and solving real-world challenges in our applications.

Purpose & Scope

This guide is designed to help all developers working on UI components:

  • UI folder structure best practices and organization
  • Component development patterns and their benefits
  • Integration approaches with our existing architecture
  • Testing and performance standards for quality
  • Quality assurance techniques for consistent UI

1. UI Folder Structure

Our three-tiered UI architecture (foundation, patterns, business) is designed to create clear separation between different types of components. This approach makes components more reusable, easier to test, and creates a natural progression from simple to complex UI elements.

We recommend following this folder structure for optimal organization and discoverability:

This structure provides several benefits:

  • Clear responsibility boundaries between different component types
  • Predictable imports that make component discovery easier
  • Logical progression from simple (foundation) to complex (business) components
  • Easier ownership with different teams potentially owning different layers

Component Placement Guidelines

Deciding where to place a component in our architecture is important for maintainability and reusability. The following guidelines can help you make these decisions:

Foundation Components

(Do ✅) Place basic UI building blocks in the foundation layer

Foundation components are the atomic building blocks of our UI system:

// ui/foundation/Button/Button.tsx
import { theme } from '@/core/shared/styles/theme';
 
export const Button = ({ 
  variant = 'primary',
  size = 'medium',
  children,
  ...props
}: ButtonProps) => {
  return (
    <TouchableOpacity
      style={[styles.base, styles[variant], styles[size]]}
      {...props}
    >
      <Text style={styles[`${variant}Text`]}>{children}</Text>
    </TouchableOpacity>
  );
};

Foundation components should be:

  • Single-purpose, atomic UI elements
  • Free from business logic or domain knowledge
  • Highly reusable across many contexts
  • Used by 3+ other components OR fundamental to the design system
  • Examples: Button, Input, Text, Card, Icon

(Consider 🤔) Creating a foundation component when you see the same UI pattern repeated

If you find yourself creating similar UI elements in multiple places, consider extracting them into foundation components.

(Don't ❌) Include business logic or domain-specific behavior in foundation components

// DON'T do this in foundation components
import { useProducts } from '@/core/domains/products';
 
// Wrong: Foundation components shouldn't fetch data or know about domains
export const Button = ({ productId, ...props }) => {
  const { data: product } = useProducts(productId);
  
  return (
    <TouchableOpacity {...props}>
      {product ? product.name : 'Loading...'} {/* Domain-specific logic */}
    </TouchableOpacity>
  );
};

Pattern Components

(Do ✅) Use pattern components for complex UI compositions

Pattern components combine multiple foundation components to create more complex, reusable UI patterns:

// ui/patterns/Form/Form.tsx
import { Button } from '@/ui/foundation/Button';
import { Input } from '@/ui/foundation/Input';
import { Text } from '@/ui/foundation/Text';
 
export const Form = ({ 
  fields, 
  onSubmit,
  submitLabel = 'Submit',
  ...props 
}: FormProps) => {
  const [values, setValues] = useState({});
  
  return (
    <View {...props}>
      {fields.map((field) => (
        <View key={field.name}>
          <Text>{field.label}</Text>
          <Input
            value={values[field.name]}
            onChangeText={(text) => setValues({
              ...values,
              [field.name]: text
            })}
          />
        </View>
      ))}
      <Button onPress={() => onSubmit(values)}>
        {submitLabel}
      </Button>
    </View>
  );
};

Pattern components should be:

  • Composed of multiple foundation components
  • Handling complex UI behavior but no domain-specific logic
  • Reusable across different features and contexts
  • Examples: Modal, Form, DataTable, Carousel, Tabs

(Consider 🤔) Extracting common UI interaction patterns into pattern components

When you notice the same combination of foundation components appearing together with similar behavior, consider creating a pattern component.

(Don't ❌) Mix domain-specific logic with pattern components

// DON'T do this in pattern components
import { useProducts } from '@/core/domains/products';
 
// Wrong: Pattern components shouldn't be tied to specific domains
export const ProductsTable = () => {
  const { data: products } = useProducts(); // Domain-specific data fetching
  
  return (
    <DataTable 
      data={products}
      columns={['name', 'price', 'category']} // Domain-specific fields
    />
  );
};

Instead, create a generic DataTable pattern component and let business components or features handle the domain-specific aspects.

Business Components

(Do ✅) Create business components for domain-specific UI needs

Business components integrate with specific domains and display domain-specific data:

// ui/business/ProductCard/ProductCard.tsx
import { Card } from '@/ui/foundation/Card';
import { Text } from '@/ui/foundation/Text';
import { Button } from '@/ui/foundation/Button';
import { useAddToCart } from '@/core/domains/cart';
 
export const ProductCard = ({ product }: ProductCardProps) => {
  const addToCart = useAddToCart();
  
  return (
    <Card>
      <Text variant="heading">{product.name}</Text>
      <Text variant="body">${product.price}</Text>
      <Button 
        onPress={() => addToCart.mutate({ productId: product.id })}
      >
        Add to Cart
      </Button>
    </Card>
  );
};

Business components should:

  • Integrate with specific domain logic and APIs
  • Use hooks from the domains layer
  • Display domain-specific data in a consistent way
  • Examples: ProductCard, UserAvatar, OrderSummary, PaymentForm

(Consider 🤔) Creating business components when the same domain entity UI appears in multiple features

When multiple features need to display the same type of domain entity in a consistent way, a business component helps maintain consistency and reduces duplication.

(Don't ❌) Make business components too specific to a single feature context

Business components should be reusable across different features that work with the same domain entity:

// DON'T do this - too feature-specific
export const ProductDetailPageCard = ({ /* specific to one page */ }) => {
  // Implementation tied to one specific feature's needs
};
 
// INSTEAD - create components that work across features
export const ProductCard = ({ product, variant = 'default' }) => {
  // Implementation that works in product listings, search results, favorites, etc.
};
  • Mixing component types within same folder

2. Component Development Standards

Well-structured components make code more maintainable, testable, and easier to understand. These standards represent patterns we've found effective across many projects.

2.1 Component File Organization

(Do ✅) Organize each component in a dedicated folder with consistent structure

A well-organized component structure makes it easier to find files and understand their purpose:

Button/
├── Button.tsx        # Main component implementation
├── Button.test.tsx  # Unit and integration tests
├── types.ts         # TypeScript interfaces and types
├── styles.ts        # Component styles (when separated)
└── index.ts         # Barrel export for clean imports

This structure provides several benefits:

  • Separation of concerns: Each file has a clear, single responsibility
  • Testing clarity: Tests are co-located with the component they test
  • Import simplicity: Barrel exports create clean import statements
  • Discoverability: Consistent structure makes finding things predictable

(Do ✅) Type Definitions:

// ui/foundation/Button/types.ts
import type { TouchableOpacityProps } from 'react-native';
 
export interface ButtonProps extends TouchableOpacityProps {
  variant?: 'primary' | 'secondary' | 'ghost';
  size?: 'small' | 'medium' | 'large';
  children: React.ReactNode;
}

(Do ✅) Barrel Export:

// ui/foundation/Button/index.ts
export { Button } from './Button';
export type { ButtonProps } from './types';

2.3 Design Token Integration

Design tokens are the foundation of a consistent visual language. They help maintain a cohesive look and feel across the application and make theme changes much simpler to implement.

(Do ✅) Always use design tokens for visual properties

Design tokens provide a single source of truth for visual styles and enable easier theming and maintenance:

import { theme } from '@/core/shared/styles/theme';
import { typography } from '@/core/shared/styles/typography';
import { spacing } from '@/core/shared/styles/spacing';
 
// Good: Using design tokens for all visual properties
const styles = StyleSheet.create({
  container: {
    backgroundColor: theme.colors.surface,
    padding: spacing.md,
    borderRadius: theme.radii.md,
    borderWidth: theme.borders.thin,
    borderColor: theme.colors.border,
  },
  text: {
    color: theme.colors.textPrimary,
    fontSize: typography.size.body,
    fontFamily: typography.families.regular,
    fontWeight: typography.weights.medium,
    lineHeight: typography.lineHeights.normal,
  },
});

This approach provides several benefits:

  • Consistency: All components use the same visual values
  • Themability: Changing theme is centralized in token files
  • Maintainability: Visual changes can be made in one place
  • Design alignment: Tokens match design system specifications

(Consider 🤔) Creating component-specific token extensions when needed

For specialized components, consider extending the token system in a structured way:

// ui/foundation/Card/tokens.ts
import { theme } from '@/core/shared/styles/theme';
 
export const cardTokens = {
  elevation: {
    low: theme.elevation.low,
    medium: theme.elevation.medium,
    high: theme.elevation.high,
  },
  padding: {
    compact: theme.spacing.sm,
    default: theme.spacing.md,
    spacious: theme.spacing.lg,
  }
};

(Don't ❌) Use arbitrary hardcoded values for visual properties

Hardcoded values create inconsistency and make maintenance difficult:

// DON'T do this: Hardcoded values with no connection to design system
const styles = StyleSheet.create({
  container: {
    backgroundColor: '#F5F5F5',  // Hardcoded color
    padding: 16,                // Arbitrary spacing
    borderRadius: 8,            // Arbitrary radius
    marginBottom: 24,           // Arbitrary margin
  },
  text: {
    color: '#333333',           // Hardcoded color
    fontSize: 16,               // Arbitrary size
    fontFamily: 'Roboto',       // Hardcoded font
    lineHeight: 24,             // Arbitrary line height
  },
});

This creates several problems:

  • Inconsistency: Different components use different values
  • Maintenance challenges: Changes require updating many files
  • Theme limitations: Dark mode and other themes become difficult to implement
  • Design drift: No guarantee that values match design specifications

Domain Integration Rules

(Do ✅) Business components can import from domains:

// ui/business/ProductCard/ProductCard.tsx
import { useProduct } from '@/core/domains/products/hooks';  // ✅ Allowed
import type { Product } from '@/core/domains/products/types'; // ✅ Allowed

(Don't ❌) Foundation/Pattern components cannot import domains:

// ui/foundation/Button/Button.tsx
import { useAuth } from '@/core/domains/auth/hooks';  // ❌ Not allowed

Feature Integration Rules

(Do ✅) Features can import any UI components:

// features/product-catalog/screens/ProductList.tsx
import { Button } from '@/ui/foundation/Button';        // ✅ Allowed
import { DataTable } from '@/ui/patterns/DataTable';    // ✅ Allowed
import { ProductCard } from '@/ui/business/ProductCard'; // ✅ Allowed

(Don't ❌) UI components cannot import from features:

// ui/foundation/Button/Button.tsx
import { ProductService } from '@/features/catalog/services'; // ❌ Not allowed

3. Integration with Existing Architecture

Domain Integration Rules

(Do ✅) Business components can import from domains:

// ui/business/ProductCard/ProductCard.tsx
import { useProduct } from '@/core/domains/products/hooks';  // ✅ Allowed
import type { Product } from '@/core/domains/products/types'; // ✅ Allowed

(Don't ❌) Foundation/Pattern components cannot import domains:

// ui/foundation/Button/Button.tsx
import { useAuth } from '@/core/domains/auth/hooks';  // ❌ Not allowed

Feature Integration Rules

(Do ✅) Features can import any UI components:

// features/product-catalog/screens/ProductList.tsx
import { Button } from '@/ui/foundation/Button';        // ✅ Allowed
import { DataTable } from '@/ui/patterns/DataTable';    // ✅ Allowed
import { ProductCard } from '@/ui/business/ProductCard'; // ✅ Allowed

(Don't ❌) UI components cannot import from features:

// ui/foundation/Button/Button.tsx
import { ProductService } from '@/features/catalog/services'; // ❌ Not allowed

4. Naming Conventions (Enforced)

Component Naming

  • Files: PascalCase (e.g., Button.tsx, ProductCard.tsx)
  • Folders: PascalCase (e.g., Button/, DataTable/)
  • Props Interfaces: ComponentName + Props (e.g., ButtonProps, ModalProps)

Consistent Export Patterns

// Always use named exports for components
export const Button = ...;           // ✅ Named export
export default Button;               // ❌ Avoid default exports
 
// Always export types
export type { ButtonProps } from './types';  // ✅ Type export

5. Accessibility Requirements

Required Accessibility Features

Every component must implement these accessibility features:

1. Semantic Roles

// Button component
<TouchableOpacity
  accessibilityRole="button"
  accessibilityLabel="Save changes"
  accessibilityHint="Saves your current changes to the profile"
>
 
// Text Input component
<TextInput
  accessibilityRole="text"
  accessibilityLabel="Email address"
  accessibilityHint="Enter your email address for account recovery"
>

2. State Communication

// Button states
<TouchableOpacity
  accessibilityState={{
    disabled: isDisabled,
    busy: isLoading,
    selected: isSelected,
  }}
>
 
// Input states
<TextInput
  accessibilityState={{
    disabled: isDisabled,
    invalid: hasError,
  }}
>

3. Dynamic Content

// Loading states
{isLoading && (
  <View
    accessibilityRole="progressbar"
    accessibilityLabel="Loading content"
    accessibilityValue={{ 
      now: progress, 
      min: 0, 
      max: 100 
    }}
  >
)}
 
// Error states
{error && (
  <Text
    accessibilityRole="alert"
    accessibilityLiveRegion="polite"
  >
    {error}
  </Text>
)}

6. Testing Requirements

Required Test Patterns

(Do ✅) Every component must have:

  • Render test
  • Props validation test
  • User interaction test (if applicable)
  • Accessibility test
// ComponentName.test.tsx template
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from './Button';
 
describe('Button', () => {
  it('renders correctly', () => {
    const { getByText } = render(<Button>Test</Button>);
    expect(getByText('Test')).toBeTruthy();
  });
 
  it('handles press events', () => {
    const onPress = jest.fn();
    const { getByText } = render(<Button onPress={onPress}>Test</Button>);
    fireEvent.press(getByText('Test'));
    expect(onPress).toHaveBeenCalled();
  });
 
  it('has correct accessibility role', () => {
    const { getByRole } = render(<Button>Test</Button>);
    expect(getByRole('button')).toBeTruthy();
  });
});

Testing Coverage Requirements

  • Unit Tests: 80%+ coverage required
  • Accessibility Tests: All accessibility features must be tested
  • Visual Regression Tests: Required for all visual states
  • Integration Tests: Required for complex business components

7. Performance Requirements

(Do ✅) Performance Standards:

  • Use React.memo for components with complex props
  • Implement proper prop comparison for expensive re-renders
  • Avoid inline styles in render methods
  • Use StyleSheet.create for static styles

(Don't ❌) Performance Anti-Patterns:

  • Creating style objects in render
  • Missing memoization for expensive computations
  • Unnecessary re-renders from prop drilling

8. Documentation Requirements

(Do ✅) Every component must have:

  • JSDoc comments for component and props
  • Usage examples in comments
  • Accessibility notes
/**
 * Primary button component for user actions
 * 
 * @example
 * <Button variant="primary" onPress={handleSave}>
 *   Save Changes
 * </Button>
 */
export const Button: React.FC<ButtonProps> = ({ ... }) => {
  // Implementation
};

9. Quality Control and Enforcement

Required Checks Before Merge

  • Component in correct folder (foundation/patterns/business)
  • Uses design tokens (no hardcoded values)
  • Proper TypeScript interfaces
  • Barrel exports implemented
  • Unit tests included
  • Accessibility features complete
  • Documentation complete
  • Performance optimizations applied

ESLint Rules Integration

// Recommended ESLint rules
{
  "no-restricted-imports": [
    "error",
    {
      "patterns": [
        {
          "group": ["../features/*"],
          "message": "UI components cannot import from features"
        }
      ]
    }
  ]
}

Code Review Checklist

Architecture Compliance

  • Component placed in correct folder
  • Follows naming conventions
  • Proper import/export patterns
  • No architecture rule violations

Design Token Usage

  • All colors use theme tokens
  • Typography follows token system
  • Spacing uses consistent tokens
  • No hardcoded values

Accessibility

  • Proper semantic roles
  • Screen reader support
  • Keyboard navigation
  • Focus management

Performance

  • Appropriate memoization
  • Efficient re-rendering
  • Bundle size impact
  • Runtime performance

10. Migration and Adoption

Project Migration Steps

  1. Create ui/ folder with foundation/patterns/business structure
  2. Move existing components to appropriate folders
  3. Update all imports across the project
  4. Add missing TypeScript interfaces
  5. Integrate design tokens
  6. Add required tests

Legacy Component Handling

  • Deprecation Period: 3 months for existing components
  • Migration Support: Provide migration guides
  • Backward Compatibility: Maintain during transition
  • Documentation: Clear migration paths

11. Enforcement and Compliance

Automated Checks

  • ESLint rules for import restrictions
  • TypeScript strict mode compliance
  • Test coverage requirements (80%+)
  • Design token usage validation

Manual Review Process

  • Architecture compliance review
  • Accessibility audit
  • Performance assessment
  • Documentation review

Non-Compliance Consequences

  • Warning Phase: First violation gets warning and guidance
  • Blocking Phase: Second violation blocks merge until fixed
  • Training Phase: Repeated violations require team training

These standards ensure consistent, maintainable, and high-quality UI components across all frontend projects.