UTA DevHub

Getting Started

Quick start guide to building your first UI component following our standardized architecture

Getting Started with UI Development

Overview

This guide will help you build your first UI component in our React Native application within 15 minutes. You'll create a working component that follows our UI Architecture Overview and all established standards.

Time to First Component: ~15 minutes

By the end of this guide, you'll have:

  • Created a foundation component
  • Applied design tokens
  • Added TypeScript types
  • Written basic tests
  • Integrated it into a feature

Prerequisites

Before starting, ensure you have:

  • Development environment set up (see development-setup)
  • Basic knowledge of React Native and TypeScript
  • Access to the project repository

Your First Component: IconButton

We'll create an IconButton component - a button that displays an icon with optional text. This example demonstrates all key concepts of our UI architecture.

Determine Component Category

First, decide where your component belongs using our decision tree:

Is IconButton specific to one feature?
└─ NO → Is it a basic UI building block?
    └─ YES → Place in ui/foundation/

Since IconButton is a reusable UI element with no business logic, it belongs in the foundation layer.

Create Component Structure

Create the folder and files following our File Naming Conventions:

# Create component directory
mkdir -p ui/foundation/IconButton
 
# Create component files
touch ui/foundation/IconButton/IconButton.tsx
touch ui/foundation/IconButton/types.ts
touch ui/foundation/IconButton/index.ts
touch ui/foundation/IconButton/IconButton.test.tsx

Define TypeScript Interface

Start with the types to establish the component's API:

// ui/foundation/IconButton/types.ts
import type { TouchableOpacityProps } from 'react-native';
import type { IconName } from '@/ui/foundation/Icon/types';
 
export interface IconButtonProps extends Omit<TouchableOpacityProps, 'style'> {
  /**
   * Icon to display in the button
   */
  icon: IconName;
  
  /**
   * Optional text to display alongside the icon
   */
  label?: string;
  
  /**
   * Visual style variant
   * @default 'primary'
   */
  variant?: 'primary' | 'secondary' | 'ghost';
  
  /**
   * Size of the button
   * @default 'medium'
   */
  size?: 'small' | 'medium' | 'large';
  
  /**
   * Whether the button is in a loading state
   */
  loading?: boolean;
  
  /**
   * Custom style overrides
   */
  style?: ViewStyle;
}

Implement the Component

Now implement the component using design tokens:

// ui/foundation/IconButton/IconButton.tsx
import React from 'react';
import { TouchableOpacity, Text, ActivityIndicator, View } from 'react-native';
import { Icon } from '@/ui/foundation/Icon';
import { theme } from '@/core/shared/styles/theme';
import { spacing } from '@/core/shared/styles/spacing';
import { typography } from '@/core/shared/styles/typography';
import type { IconButtonProps } from './types';
 
export function IconButton({
  icon,
  label,
  variant = 'primary',
  size = 'medium',
  loading = false,
  disabled = false,
  onPress,
  style,
  ...props
}: IconButtonProps) {
  // Size configurations using design tokens
  const sizeConfig = {
    small: {
      padding: spacing.xs,
      iconSize: 16,
      fontSize: typography.sizes.small,
    },
    medium: {
      padding: spacing.sm,
      iconSize: 20,
      fontSize: typography.sizes.body,
    },
    large: {
      padding: spacing.md,
      iconSize: 24,
      fontSize: typography.sizes.large,
    },
  };
 
  // Variant styles using design tokens
  const variantStyles = {
    primary: {
      backgroundColor: theme.colors.primary[500],
      color: theme.colors.white,
    },
    secondary: {
      backgroundColor: theme.colors.secondary[500],
      color: theme.colors.white,
    },
    ghost: {
      backgroundColor: 'transparent',
      color: theme.colors.primary[500],
    },
  };
 
  const config = sizeConfig[size];
  const colors = variantStyles[variant];
 
  return (
    <TouchableOpacity
      disabled={disabled || loading}
      onPress={onPress}
      style={[
        {
          flexDirection: 'row',
          alignItems: 'center',
          justifyContent: 'center',
          padding: config.padding,
          borderRadius: theme.radii.md,
          backgroundColor: colors.backgroundColor,
          opacity: disabled ? 0.5 : 1,
        },
        style,
      ]}
      accessibilityRole="button"
      accessibilityLabel={label || `${icon} button`}
      accessibilityState={{ disabled, busy: loading }}
      {...props}
    >
      {loading ? (
        <ActivityIndicator size="small" color={colors.color} />
      ) : (
        <>
          <Icon 
            name={icon} 
            size={config.iconSize} 
            color={colors.color} 
          />
          {label && (
            <Text
              style={{
                marginLeft: spacing.xs,
                color: colors.color,
                fontSize: config.fontSize,
                fontFamily: typography.families.medium,
              }}
            >
              {label}
            </Text>
          )}
        </>
      )}
    </TouchableOpacity>
  );
}

Add Barrel Export

Create clean exports for easy importing:

// ui/foundation/IconButton/index.ts
export { IconButton } from './IconButton';
export type { IconButtonProps } from './types';

Write Basic Tests

Add tests to ensure your component works correctly:

// ui/foundation/IconButton/IconButton.test.tsx
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { IconButton } from './IconButton';
 
describe('IconButton', () => {
  it('renders with icon', () => {
    const { getByLabelText } = render(
      <IconButton icon="home" />
    );
    
    expect(getByLabelText('home button')).toBeTruthy();
  });
 
  it('renders with icon and label', () => {
    const { getByText } = render(
      <IconButton icon="home" label="Home" />
    );
    
    expect(getByText('Home')).toBeTruthy();
  });
 
  it('calls onPress when pressed', () => {
    const onPress = jest.fn();
    const { getByLabelText } = render(
      <IconButton icon="home" onPress={onPress} />
    );
    
    fireEvent.press(getByLabelText('home button'));
    expect(onPress).toHaveBeenCalled();
  });
 
  it('does not call onPress when disabled', () => {
    const onPress = jest.fn();
    const { getByLabelText } = render(
      <IconButton icon="home" onPress={onPress} disabled />
    );
    
    fireEvent.press(getByLabelText('home button'));
    expect(onPress).not.toHaveBeenCalled();
  });
 
  it('shows loading indicator when loading', () => {
    const { getByTestId } = render(
      <IconButton icon="home" loading testID="button" />
    );
    
    expect(getByTestId('button').findByType('ActivityIndicator')).toBeTruthy();
  });
});

Use in a Feature

Now let's use the component in a feature:

// features/product-catalog/components/ProductActions/ProductActions.tsx
import React from 'react';
import { View } from 'react-native';
import { IconButton } from '@/ui/foundation/IconButton';
import { useAddToCart } from '@/core/domains/cart/hooks';
import { spacing } from '@/core/shared/styles/spacing';
 
interface ProductActionsProps {
  productId: string;
}
 
export function ProductActions({ productId }: ProductActionsProps) {
  const { mutate: addToCart, isLoading } = useAddToCart();
  
  const handleAddToCart = () => {
    addToCart({ productId, quantity: 1 });
  };
  
  return (
    <View style={{ flexDirection: 'row', gap: spacing.sm }}>
      <IconButton
        icon="heart"
        variant="ghost"
        size="medium"
        onPress={() => console.log('Add to favorites')}
      />
      
      <IconButton
        icon="shopping-cart"
        label="Add to Cart"
        variant="primary"
        size="medium"
        loading={isLoading}
        onPress={handleAddToCart}
      />
    </View>
  );
}

Component Checklist

Before considering your component complete, verify:

  • Correct folder placement (ui/foundation/, ui/patterns/, or ui/business/)
  • TypeScript interfaces with proper documentation
  • Design tokens only - no hardcoded colors, spacing, or sizes
  • Accessibility support with proper roles and labels
  • Basic tests covering main functionality
  • Barrel exports in index.ts
  • No business logic in foundation/pattern components

Common Patterns

Using Design Tokens

Always use design tokens for visual properties:

// ✅ GOOD - Using design tokens
const styles = {
  container: {
    backgroundColor: theme.colors.surface,
    padding: spacing.md,
    borderRadius: theme.radii.lg,
  }
};
 
// ❌ BAD - Hardcoded values
const styles = {
  container: {
    backgroundColor: '#FFFFFF',
    padding: 16,
    borderRadius: 8,
  }
};

Component Composition

Build complex components from simpler ones:

// ui/patterns/Card/Card.tsx
import { Surface } from '@/ui/foundation/Surface';
import { Text } from '@/ui/foundation/Text';
import { IconButton } from '@/ui/foundation/IconButton';
 
export function Card({ title, onClose, children }) {
  return (
    <Surface elevation="medium">
      <View style={styles.header}>
        <Text variant="heading">{title}</Text>
        <IconButton 
          icon="close" 
          variant="ghost" 
          size="small"
          onPress={onClose}
        />
      </View>
      {children}
    </Surface>
  );
}

For more Button component examples including all variants, states, and testing patterns, see the Button Component.

Next Steps

Now that you've created your first component:

  1. Explore existing components in ui/foundation/ for examples
  2. Read the UI Architecture Standards for detailed guidelines
  3. Study component-patterns for advanced techniques
  4. Learn about Theme Management for customization

Troubleshooting

Common Issues

Import errors: Ensure you're using the correct import paths:

  • @/ui/foundation/... for foundation components
  • @/core/shared/... for utilities and tokens
  • @/core/domains/... for business logic

TypeScript errors: Check that all props are properly typed and exported

Style issues: Verify you're using design tokens, not hardcoded values

Quick Reference

File Structure

ComponentName/
├── ComponentName.tsx      # Implementation
├── types.ts              # TypeScript interfaces
├── index.ts              # Exports
└── ComponentName.test.tsx # Tests

Import Order

// 1. React imports
import React from 'react';
import { View, Text } from 'react-native';
 
// 2. Internal imports
import { Icon } from '@/ui/foundation/Icon';
import { theme } from '@/core/shared/styles/theme';
 
// 3. Types
import type { ComponentProps } from './types';

Design Token Usage

// Colors
theme.colors.primary[500]
theme.colors.textPrimary
 
// Spacing  
spacing.xs // 4
spacing.sm // 8
spacing.md // 16
 
// Typography
typography.sizes.body
typography.families.regular
 
// Radii
theme.radii.sm // 4
theme.radii.md // 8

Congratulations! 🎉 You've successfully created your first component following our UI architecture. Continue exploring the documentation to master advanced patterns and best practices.