Skip to content
All essays
WebMarch 27, 202614 min

Component Libraries: Building Reusable UI Components

Build and maintain component libraries for consistent, scalable UIs

Ü
Ümit Uz
Mobile & Full Stack Developer

Component libraries are collections of reusable UI components that enable teams to build consistent user interfaces efficiently.

Benefits

  • Consistency: Unified look and feel
  • Speed: Build UIs faster
  • Quality: Well-tested components
  • Accessibility: Built-in a11y
  • Documentation: Clear usage guidelines

Building Blocks

Button Component

typescript
// Button.tsx
import React from 'react';
import { variants, sizes } from './button-types';

interface ButtonProps {
  children: React.ReactNode;
  variant?: keyof typeof variants;
  size?: keyof typeof sizes;
  disabled?: boolean;
  onClick?: () => void;
}

export const Button: React.FC<ButtonProps> = ({
  children,
  variant = 'primary',
  size = 'medium',
  disabled = false,
  onClick
}) => {
  const baseStyles = 'rounded font-medium transition-colors';
  const variantStyles = variants[variant];
  const sizeStyles = sizes[size];
  const disabledStyles = disabled ? 'opacity-50 cursor-not-allowed' : '';
  
  return (
    <button
      className={`${baseStyles} ${variantStyles} ${sizeStyles} ${disabledStyles}`}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
};
typescript
// button-types.ts
export const variants = {
  primary: 'bg-blue-600 text-white hover:bg-blue-700',
  secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
  outline: 'border-2 border-blue-600 text-blue-600 hover:bg-blue-50',
  danger: 'bg-red-600 text-white hover:bg-red-700'
};

export const sizes = {
  small: 'px-3 py-1 text-sm',
  medium: 'px-4 py-2 text-base',
  large: 'px-6 py-3 text-lg'
};

Documentation with Storybook

typescript
// stories/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'outline', 'danger']
    },
    size: {
      control: 'select',
      options: ['small', 'medium', 'large']
    }
  }
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    variant: 'primary',
    children: 'Primary Button'
  }
};

export const Secondary: Story = {
  args: {
    variant: 'secondary',
    children: 'Secondary Button'
  }
};

export const Sizes: Story = {
  args: {
    children: 'Button'
  },
  render: (args) => (
    <div className="flex gap-4">
      <Button {...args} size="small">Small</Button>
      <Button {...args} size="medium">Medium</Button>
      <Button {...args} size="large">Large</Button>
    </div>
  )
};

Testing Components

typescript
// Button.test.tsx
import { render, screen } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders children correctly', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });
  
  it('applies variant styles', () => {
    const { container } = render(
      <Button variant="primary">Primary</Button>
    );
    expect(container.firstChild).toHaveClass('bg-blue-600');
  });
  
  it('calls onClick when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click</Button>);
    
    screen.getByText('Click').click();
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
  
  it('is disabled when disabled prop is true', () => {
    render(<Button disabled>Click</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});

Accessibility

typescript
// Accessible Icon Button
interface IconButtonProps {
  icon: React.ReactNode;
  label: string;
  onClick?: () => void;
}

export const IconButton: React.FC<IconButtonProps> = ({
  icon,
  label,
  onClick
}) => {
  return (
    <button
      aria-label={label}
      onClick={onClick}
      className="p-2 hover:bg-gray-100 rounded"
    >
      {icon}
    </button>
  );
};

Distribution

json
// package.json
{
  "name": "@myorg/ui-library",
  "version": "1.0.0",
  "main": "dist/index.js",
  "module": "dist/index.esm.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "rollup -c",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  },
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}

Best Practices

  1. 1Keep components focused
  2. 2Use TypeScript for type safety
  3. 3Document with Storybook
  4. 4Test thoroughly
  5. 5Make accessible
  6. 6Version semantically
  7. 7Get feedback early

Conclusion

Component libraries accelerate development and ensure consistency. Start with core components and grow systematically.

Enjoyed this essay? Share it.

Related essays

Next essay
Design Tokens: Building Scalable Design Systems