Skip to content
StackPractices
intermediate Por Mathias Paulenko

Plantilla de Documentación de Component API

Una plantilla para documentar APIs de componentes UI: props, events, slots, methods, accessibility y usage examples con TypeScript types.

Temas: testing

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Overview

Component API documentation le dice a developers cómo usar un component: qué props accept, qué events emite, qué slots provee y qué methods expone. Sin clear API docs, developers leen source code para entender components, wasteando time y leading a incorrect usage.

When to Use

  • For alternatives, see Browser Support Matrix Template.

  • Documentando design system components

  • Publicando un component library

  • Onboardéando new developers a un component system

  • Creando component usage guidelines

  • Manteniendo API consistency across components

Solution

# Component API — `<Button>`

## Component Overview

| Field | Value |
|-------|-------|
| Component Name | Button |
| Package | @example/ui-components |
| Version | 2.3.0 |
| Status | Stable |
| Last Updated | 2026-07-05 |
| Maintainer | UI Platform Team |
| Source | src/components/Button/Button.tsx |
| Bundle Size (gzipped) | 2.1 KB |
| Dependencies | none |

## Description

Un versatile button component con support para multiple variants, sizes, icons, loading states y full keyboard navigation. Renderea como un native `<button>` element por default, con polymorphic rendering via el `as` prop.

## Installation

```bash
npm install @example/ui-components

Basic Usage

import { Button } from '@example/ui-components';

function Example() {
  return <Button onClick={() => alert('Clicked!')}>Click me</Button>;
}

Props

PropTypeDefaultRequiredDescription
variant'primary' | 'secondary' | 'ghost' | 'danger' | 'link''primary'NoVisual style variant
size'sm' | 'md' | 'lg''md'NoButton size
disabledbooleanfalseNoDisablea interaction y apply disabled styles
loadingbooleanfalseNoMuestra loading spinner y disablea interaction
fullWidthbooleanfalseNoHace que button take full width de container
iconReactNodeundefinedNoIcon element rendereado before children
iconPosition'left' | 'right''left'NoPosition de icon relative a text
type'button' | 'submit' | 'reset''button'NoNative button type attribute
as'button' | 'a' | React.ElementType'button'NoPolymorphic rendering element
hrefstringundefinedNoURL cuando as="a"; required para link rendering
targetstringundefinedNoLink target attribute cuando as="a"
relstringundefinedNoLink rel attribute cuando as="a"
ariaLabelstringundefinedNoAccessible label para icon-only buttons
testIdstringundefinedNoData attribute para testing: data-testid
classNamestringundefinedNoAdditional CSS classes
styleCSSPropertiesundefinedNoInline styles
onClick(e: MouseEvent) => voidundefinedNoClick handler
onFocus(e: FocusEvent) => voidundefinedNoFocus handler
onBlur(e: FocusEvent) => voidundefinedNoBlur handler

TypeScript Types

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  loading?: boolean;
  fullWidth?: boolean;
  icon?: ReactNode;
  iconPosition?: 'left' | 'right';
  type?: 'button' | 'submit' | 'reset';
  as?: 'button' | 'a' | ElementType;
  href?: string;
  target?: string;
  rel?: string;
  ariaLabel?: string;
  testId?: string;
  className?: string;
  style?: CSSProperties;
  onClick?: (e: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
  onFocus?: (e: FocusEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
  onBlur?: (e: FocusEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
  children?: ReactNode;
}

Prop Details

variant

Control el visual style del button. Cada variant tiene distinct colors para default, hover, active y disabled states.

VariantUse CaseDefault BGText ColorBorder
primaryMain action en un pagebrand-600whitenone
secondaryAlternative actionwhiteslate-700slate-300
ghostTertiary actiontransparentslate-700none
dangerDestructive actionred-600whitenone
linkNavigation-style actiontransparentbrand-600none
<Button variant="primary">Save changes</Button>
<Button variant="secondary">Cancel</Button>
<Button variant="ghost">More options</Button>
<Button variant="danger">Delete account</Button>
<Button variant="link" as="a" href="/docs">Read documentation</Button>

size

Control el height, padding y font size del button.

SizeHeightPadding (x, y)Font SizeIcon Size
sm32px12px, 6px14px16px
md40px16px, 8px16px20px
lg48px24px, 12px18px24px
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

loading

Cuando true, el button muestra un spinner, disablea interaction y maintain su width para prevenir layout shift.

<Button loading={isSubmitting}>Submit</Button>

as (Polymorphic)

Renderea el button como un different element mientras maintain styling. Common use: renderear como un anchor para navigation.

<Button as="a" href="/dashboard" variant="primary">Go to Dashboard</Button>
<Button as="a" href="/report.pdf" target="_blank" rel="noopener noreferrer">
  Download Report
</Button>

Events

EventPayloadDescription
onClickMouseEventFire cuando button se clickea (no cuando disabled o loading)
onFocusFocusEventFire cuando button recibe focus
onBlurFocusEventFire cuando button pierde focus

Event Examples

function FormExample() {
  const [loading, setLoading] = useState(false);

  const handleClick = async (e: MouseEvent) => {
    e.preventDefault();
    setLoading(true);
    await submitForm();
    setLoading(false);
  };

  return (
    <Button onClick={handleClick} loading={loading}>
      Submit
    </Button>
  );
}

Slots

SlotDefaultDescription
childrenButton label text o content
iconundefinedIcon rendereado before o after children basado en iconPosition

Slot Examples

// Text only
<Button>Save</Button>

// Text con left icon
<Button icon={<SaveIcon />}>Save</Button>

// Text con right icon
<Button icon={<ArrowIcon />} iconPosition="right">Next</Button>

// Icon only (require ariaLabel)
<Button icon={<CloseIcon />} ariaLabel="Close dialog" />

// Custom content
<Button>
  <span className="flex items-center gap-2">
    <Badge>New</Badge>
    Try Pro
  </span>
</Button>

Methods

El Button component no expone imperative methods. All interaction se handlea through props y events.

Accessibility

AttributeValueNotes
Rolebutton (native)Usa native <button> element
KeyboardEnter/SpaceTriggerea onClick
FocusVisible outlinefocus-visible styles applied
Disabledaria-disabledSet cuando disabled o loading es true
Labelaria-labelRequired para icon-only buttons via ariaLabel prop

Accessibility Examples

// Icon-only button — ariaLabel es required
<Button icon={<SearchIcon />} ariaLabel="Search" />

// Loading state — aria-label updated
<Button loading={isLoading} ariaLabel={isLoading ? 'Submitting...' : 'Submit'}>
  Submit
</Button>

// Disabled state
<Button disabled ariaLabel="Save (disabled)">Save</Button>

Variants Showcase

// Primary actions
<Button variant="primary">Save changes</Button>
<Button variant="primary" icon={<PlusIcon />}>New project</Button>

// Secondary actions
<Button variant="secondary">Cancel</Button>
<Button variant="secondary" icon={<DownloadIcon />} iconPosition="right">Export</Button>

// Ghost actions
<Button variant="ghost">More options</Button>
<Button variant="ghost" size="sm">Filter</Button>

// Danger actions
<Button variant="danger">Delete</Button>
<Button variant="danger" loading={isDeleting}>Deleting...</Button>

// Link variant
<Button variant="link" as="a" href="/docs">Documentation</Button>
<Button variant="link" as="a" href="/docs" target="_blank" rel="noopener noreferrer">
  External link
</Button>

// Full width
<Button fullWidth>Submit form</Button>

// All sizes
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

Do’s and Don’ts

DoDon’t
Usá primary para el main action en un pageUsá multiple primary buttons en el same view
Proveé ariaLabel para icon-only buttonsUsá icon-only buttons sin accessible labels
Usá loading en vez de disablear + text changeCambiá button text a “Loading…” manualmente
Usá as="a" para navigationUsá onClick con window.location para navigation
Usá danger para destructive actionsUsá danger para non-destructive actions
Usá fullWidth en mobile layoutsUsá fullWidth en desktop layouts unnecessarily

Testing

import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '@example/ui-components';

describe('Button', () => {
  it('renders children', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
  });

  it('fires onClick when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click</Button>);
    fireEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('does not fire onClick when disabled', () => {
    const handleClick = jest.fn();
    render(<Button disabled onClick={handleClick}>Click</Button>);
    fireEvent.click(screen.getByRole('button'));
    expect(handleClick).not.toHaveBeenCalled();
  });

  it('does not fire onClick when loading', () => {
    const handleClick = jest.fn();
    render(<Button loading onClick={handleClick}>Click</Button>);
    fireEvent.click(screen.getByRole('button'));
    expect(handleClick).not.toHaveBeenCalled();
  });

  it('renders as anchor when as="a"', () => {
    render(<Button as="a" href="/home">Home</Button>);
    expect(screen.getByRole('link', { name: 'Home' })).toHaveAttribute('href', '/home');
  });

  it('applies aria-label for icon-only buttons', () => {
    render(<Button icon={<SearchIcon />} ariaLabel="Search" />);
    expect(screen.getByRole('button', { name: 'Search' })).toBeInTheDocument();
  });
});

Migration Guide

v1.x to v2.x

v1 Propv2 PropBreaking Change
colorvariantRenamed para clarity
blockfullWidthRenamed para consistency
isLoadingloadingSimplified naming
iconLefticon + iconPosition="left"Merged en single prop
iconRighticon + iconPosition="right"Merged en single prop

## Explanation

Component API documentation sirve a two audiences: developers que usan el component y developers que lo maintain. Users necesitan saber qué props existen, qué types accept y qué son los defaults. Maintainers necesitan trackear changes, deprecations y migration paths.

La props table es el core de la documentation. Cada prop debería tener un TypeScript type, un default value, si es required y un description. Para union types (como `variant`), listé todos los valid values. Para complex props, addeá un detailed subsection con examples.

Events documentean los callback props. Cada event debería specify el payload type y cuándo fire. Notá edge cases: ¿`onClick` firea cuando el button está disabled? (No debería.)

Slots documentean dónde content puede ser projected. En React, esto es `children` y render props. En Vue, esto es named slots. En Angular, esto es `ng-content` con selectors. Documentá qué content se espera y cómo se posiciona.

La accessibility section es non-negotiable. Cada component debería documentar su keyboard interactions, ARIA attributes y required labels. Icon-only buttons deben documentar el `ariaLabel` requirement.

La testing section muestra cómo testear el component. Esto helpa a consumers a escribir integration tests que incluyan el component. También sirve como executable documentation — los tests verify el documented behavior.

El migration guide helpa con version upgrades. Listé every breaking change con el old API y el new API. Esto reduce upgrade friction.

## Variants

| Context | Approach | Notes |
|---------|----------|-------|
| React component | Props, events as callbacks, children as slots | TypeScript interfaces |
| Vue component | Props, emits, slots | DefineProps, defineEmits |
| Angular component | @Input, @Output, ng-content | Angular decorators |
| Web component | Attributes, custom events, slots | Custom elements spec |
| Svelte component | Props, dispatch events, slots | Svelte stores |

## What Works

1. Documentá every prop — undocumented props se discovered leyendo source code
2. Incluí TypeScript types — types son el contract entre component y consumer
3. Mostrá real examples — no solo `<Button variant="primary">`, sino actual use cases
4. Documentá accessibility — keyboard, screen reader, ARIA requirements
5. Incluí un do's and don'ts table — previene common misuse
6. Proveé un migration guide — reduce friction en upgrades
7. Mantené docs next a code — co-located docs stay in sync better que separate wikis

## Common Mistakes

1. Missing default values — developers no saben qué get si omiten un prop
2. No examples para complex props — `variant` con 5 options necesita 5 examples
3. No accessibility section — components sin a11y docs lead a inaccessible UIs
4. No migration guide — breaking changes sin migration instructions causan frustration
5. Outdated docs — docs que no matchean el code son worse que no docs
6. No testing examples — consumers no saben cómo testear con el component
7. Vague descriptions — "setea el color" no explica qué colors son valid

## Frequently Asked Questions

### ¿Cómo mantenemos documentation in sync con code?

Co-locá docs con el component source. Usá un tool como Storybook que genere docs desde el component's TypeScript types y JSDoc comments. Corré un CI check que verify que documented props matcheen los actual component props. Revieweá docs en el same PR que cambia el component.

### ¿Deberíamos usar Storybook o Markdown docs?

Ambos. Storybook provee interactive examples y auto-generated prop tables. Markdown provee narrative documentation, migration guides y usage patterns. Usá Storybook para reference, Markdown para guides.

### ¿Qué tan detailed deberían ser prop descriptions?

Detailed enough que un developer pueda usar el prop sin leer el source code. Incluí valid values, default behavior y interaction con otros props. Por ejemplo, `loading` debería mention que disablea interaction y muestra un spinner.

### ¿Qué hay de internal props o methods?

No los documentes. Si un prop es internal (prefixed con `_` o marked `@internal`), excluílo de public docs. Documentar internal APIs crea un implicit contract que hace refactoring harder.

### ¿Cómo documentamos polymorphic components?

Documentá el `as` prop con all valid values. Para cada value, documentá los additional props que become available (e.g., `href` cuando `as="a"`). Mostrá examples para cada polymorphic variant. TypeScript discriminated unions help a enforce correct usage.